Compare commits

..

95 Commits

Author SHA1 Message Date
Mira Weller
bdf6b8886b isort 2025-08-05 19:15:36 +02:00
Mira Weller
3aa7cdeb94 Improve error handling 2025-08-05 15:35:51 +02:00
Mira Weller
c18f1a8561 Allow sync_object_with_properties to return None (as is already done in pretix-hubspot) 2025-07-30 13:16:35 +02:00
Mira Weller
dcd9905b6c Rename sync_targets -> datasync_providers 2025-07-30 13:07:46 +02:00
Mira Weller
94b333b126 Rebase migrations 2025-07-29 17:24:24 +02:00
Mira Weller
56f6dcdcf2 Remove transaction.atomic around sync_order
Usually, sync targets won't allow an atomic transaction themselves,
so we should keep OrderSyncResults of partially successful sync
runs, as the objects will most likely exist in the target system
anyway, even if we fail halfway.
2025-07-29 17:24:24 +02:00
Mira Weller
29059e32e7 Umstellung mapped_objects von dict<list<sync_object_with_properties result dict>> auf dict<list<OrderSyncResult>> 2025-07-29 17:24:24 +02:00
Mira Weller
1705d1f4cf WIP: Umstellung mapped_objects von dict<list<sync_object_with_properties result dict>> auf dict<list<OrderSyncResult>> 2025-07-29 17:24:24 +02:00
Mira Weller
9fdfe16beb sourcefields: convert all TYPE_NUMBER to str 2025-07-29 17:24:24 +02:00
Mira Weller
4d8ecd34b8 sync_queued_orders: commit in_flight before starting actual sync 2025-07-29 17:24:24 +02:00
Mira Weller
2e0644921e OrderSyncResult: add mapping_id, don't delete before sync, use update_or_create 2025-07-29 17:24:24 +02:00
Mira Weller
dd55eb00cf line length 2025-07-29 17:24:24 +02:00
Mira Weller
441dd4fa73 add test_enqueue_order_twice 2025-07-29 17:24:24 +02:00
Mira Weller
4636e1b68e assign_properties: remove defaults for parameters, add doc string 2025-07-29 17:24:24 +02:00
Mira Weller
9e518eeccf assign_properties: remove defaults for parameters 2025-07-29 17:24:24 +02:00
Mira Weller
427837fe64 _add_to_list: wrap in list instead of casting to list 2025-07-29 17:24:24 +02:00
Mira Weller
ae2e747e46 disallow cancelling in_flight jobs 2025-07-29 17:24:24 +02:00
Mira Weller
a69fd08bde filter questions of TYPE_FILE 2025-07-29 17:24:24 +02:00
Mira Weller
db277ede16 actually include pagination view 2025-07-29 17:24:24 +02:00
Mira Weller
8cde94fad6 add info boxes to dashboard and hubspot settings if sync problems exist 2025-07-29 17:24:24 +02:00
Mira Weller
362b3f08e7 add Data sync problems to organizer nav 2025-07-29 17:24:24 +02:00
Mira Weller
46135e72a3 add EventFailedSyncJobsView 2025-07-29 17:24:24 +02:00
Mira Weller
7aca7ee89b get_data_fields: docstring, switch order_locale to TYPE_CHOICE 2025-07-29 17:24:24 +02:00
Mira Weller
ce346957f4 translate_property_mappings: docstring with example, move to utils, add reference to docs 2025-07-29 17:24:24 +02:00
Mira Weller
1c3bdf56f9 translate_property_mappings: docstring with example, move to utils, add reference to docs 2025-07-29 17:24:24 +02:00
Mira Weller
22b011594a translate_property_mappings: old_id not in checkin_list_map 2025-07-29 17:24:24 +02:00
Mira Weller
08a66e5e0b Docs, help texts, translations 2025-07-29 17:24:24 +02:00
Mira Weller
145babccee property_mappings_formset: add reordering buttons for new forms 2025-07-29 17:24:24 +02:00
Mira Weller
3cd596df61 add help text 2025-07-29 17:24:24 +02:00
Mira Weller
a56c83d78a move datasync tasks into services/datasync.py 2025-07-29 17:24:24 +02:00
Mira Weller
2cfe2a753d rebase migration 2025-07-29 17:24:24 +02:00
Mira Weller
78f5ee1935 formatting 2025-07-29 17:24:24 +02:00
Mira Weller
b2cc8294e4 switch to core question type labels instead of defining our own strings 2025-07-29 17:24:24 +02:00
Mira Weller
7591b57524 improve docs a bit 2025-07-29 17:24:24 +02:00
luelista
23565f20b6 Apply suggestions from code review
Co-authored-by: Raphael Michel <michel@rami.io>
2025-07-29 17:24:24 +02:00
Mira Weller
41fcc2a606 add protocol for object mapping type, declare types of sync_object_with_properties parameters 2025-07-29 17:24:24 +02:00
Mira Weller
9a2464db17 Keep namedtuples for data_fields dict values 2025-07-29 17:24:24 +02:00
Mira Weller
17c1d75780 Rename property_mapping -> property_mappings, association_mapping -> association_mappings 2025-07-29 17:24:24 +02:00
Mira Weller
c3833f8883 Make sure datasync tasks are always loaded 2025-07-29 17:24:24 +02:00
Mira Weller
a5d2d4d03b Use display_name for sync providers 2025-07-29 17:24:24 +02:00
Mira Weller
9e3a07d3c8 Add max_length 2025-07-29 17:24:23 +02:00
Mira Weller
5d7cb6372b Add in-flight state handling 2025-07-29 17:24:23 +02:00
Mira Weller
b57e1e5ffc Inline sync_event_to_target function 2025-07-29 17:24:23 +02:00
Mira Weller
4a39b7ab01 Do not fail when enqueueing an order that's already in queue 2025-07-29 17:24:23 +02:00
Mira Weller
c69c9d0119 Add user interface for manual retry 2025-07-29 17:24:23 +02:00
Mira Weller
ec64c0fc1b Store need_manual_retry flag for failed sync attempts 2025-07-29 17:24:23 +02:00
Mira Weller
87ee6fbce4 Suppress log message if order sync didn't perform any changes 2025-07-29 17:24:23 +02:00
Mira Weller
0f7ee1dc3b Add explanation for mapping.id 2025-07-29 17:24:23 +02:00
Mira Weller
63a273374c Rename mapping.pk -> mapping.id 2025-07-29 17:24:23 +02:00
Mira Weller
83f9e17251 Rename order_valid_for_sync -> should_sync_order 2025-07-29 17:24:23 +02:00
Mira Weller
1aa3c4e793 rebase datasync migration 2025-07-29 17:24:23 +02:00
Mira Weller
7f4908a5fd isort 2025-07-29 17:24:23 +02:00
Mira Weller
0da329be39 Update renamed fields in template 2025-07-29 17:24:23 +02:00
Mira Weller
8028879159 Add simple locking mechanism 2025-07-29 17:24:23 +02:00
Mira Weller
244cdb22b0 Distribute queue over events 2025-07-29 17:24:23 +02:00
Mira Weller
922d3cd629 Store event_id in OrderSyncQueue, always fill not_before, log unhandled exceptions 2025-07-29 17:24:23 +02:00
Mira Weller
114304855f Fix tests 2025-07-29 17:24:23 +02:00
Mira Weller
36981ab5c4 Unused import 2025-07-29 17:24:23 +02:00
Mira Weller
21250cda4f Move datasync view to views/datasync.py 2025-07-29 17:24:23 +02:00
Mira Weller
65d428edc9 Do not filter for admission products 2025-07-29 17:24:23 +02:00
Mira Weller
c0c9795407 Separate exception types 2025-07-29 17:24:23 +02:00
Mira Weller
03de7c1603 Docstring formatting 2025-07-29 17:24:23 +02:00
Mira Weller
20f6590b4d Describe triggered_by parameter ot enqueue_order 2025-07-29 17:24:23 +02:00
Mira Weller
01861f65ea Drop outstanding jobs for deactivated plugins 2025-07-29 17:24:23 +02:00
Mira Weller
cb98126223 Format transmitted timestamp 2025-07-29 17:24:23 +02:00
Mira Weller
2e515538d5 Revert to event_or_subevent 2025-07-29 17:24:23 +02:00
luelista
8283c0f300 Apply suggestions from code review
Co-authored-by: Raphael Michel <michel@rami.io>
2025-07-29 17:24:23 +02:00
Mira Weller
7ee0f24af5 Rename OrderSyncLink to OrderSyncResult 2025-07-29 17:24:23 +02:00
Mira Weller
b88d2c7783 Move datasync models into models package 2025-07-29 17:24:23 +02:00
Mira Weller
d84bbf636f Rename {pretix,external}_pk to {pretix,external}_id_field 2025-07-29 17:24:23 +02:00
Mira
398176bab5 Formatting 2025-07-29 17:24:23 +02:00
Mira
bba2a0e43d Improve docs 2025-07-29 17:24:23 +02:00
Mira Weller
58503d2a9d add tests, fix bug in legacy name splitting 2025-07-29 17:24:23 +02:00
Mira Weller
3b0d75d3a9 reformat 2025-07-29 17:24:23 +02:00
Mira Weller
63c06dc44a sourcefields: Add Order Position ID, rename Ticket ID 2025-07-29 17:24:23 +02:00
Mira Weller
2eb5e4cd25 fix punctuation, show message if no data was transmitted 2025-07-29 17:24:23 +02:00
Mira Weller
d455bb60f9 fix bug in sync task with multiple events 2025-07-29 17:24:23 +02:00
Mira Weller
e863ac23ca remove print statements 2025-07-29 17:24:23 +02:00
Mira Weller
a8acd976e1 get_data_fields: do not fail if invoice_address missing 2025-07-29 17:24:23 +02:00
Mira Weller
1a88db10ff switch to timezone aware now() 2025-07-29 17:24:23 +02:00
Mira Weller
bba84f76d3 tests: add test cases for OutboundSyncProvider and queue 2025-07-29 17:24:23 +02:00
Mira Weller
a0c69bb480 refactor: move some utils into core 2025-07-29 17:24:23 +02:00
Mira Weller
c06a5709da use 2021 in license header 2025-07-29 17:24:23 +02:00
Mira Weller
add5582f91 use list-group for backend UI 2025-07-29 17:24:23 +02:00
Mira Weller
00847b621d rebase migration 2025-07-29 17:24:23 +02:00
Mira Weller
d08dc7adc3 add license headers, formatting 2025-07-29 17:24:23 +02:00
Mira Weller
8933ddf90d Add documentation 2025-07-29 17:24:23 +02:00
Mira Weller
9d93d49c2c Add OrderSyncQueue migrations 2025-07-29 17:24:23 +02:00
Mira Weller
bea3f32251 Add deprecated fields 2025-07-29 17:24:23 +02:00
Mira Weller
e251f5c5b8 Improve error messages 2025-07-29 17:24:23 +02:00
Mira Weller
e051470e91 Store information about objects transferred in last successful sync 2025-07-29 17:24:23 +02:00
Mira Weller
87570df3f8 Fixups 2025-07-29 17:24:23 +02:00
Mira Weller
72bf4a2908 Fix imports 2025-07-29 17:24:23 +02:00
Mira Weller
6a0d316b82 Add control interface for pending data syncs 2025-07-29 17:24:23 +02:00
Mira Weller
79ea74ac6d wip 2025-07-29 17:24:23 +02:00
1092 changed files with 268559 additions and 410290 deletions

View File

@@ -1,7 +1,7 @@
This file is part of pretix (Community Edition).
Copyright (C) 2014-2020 Raphael Michel and contributors
Copyright (C) 2020-today pretix GmbH and contributors
Copyright (C) 2014-2020 Raphael Michel and contributors
Copyright (C) 2020-2021 rami.io GmbH and contributors
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -359,156 +359,3 @@ Performing a ticket search
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer or check-in list does not exist **or** you have no permission to view this resource.
:statuscode 404: The requested check-in list does not exist.
.. _`rest-checkin-annul`:
Annulment of a check-in
-----------------------
.. http:post:: /api/v1/organizers/(organizer)/checkinrpc/annul/
If a check-in was made in error and the person was not let in, it can be annulled. We do not recommend this to be used
in case of manual check-ins or user interfaces because it is too prone for human errors. It is mostly intended for
automated entry systems like a turnstile or automated door, where the check-in is first created, then the door is
opened, and then the check-in may be annulled if the system knows that the turnstile did not turn or was out of
order.
This endpoint supports passing multiple check-in lists for the context of a multi-event scan. However, each
check-in list passed needs to be from a distinct event.
Check-ins created by a device can only be annulled by the same device. The datetime of annulment may not be more than
15 minutes after the datetime of check-in (value subject to change).
A status code of 404 is returned if no check-in was found for the given nonce. A status code of 400 is returned when
multiple check-ins match the nonce, the input is invalid in another way, the annulment is made from the wrong device,
the check-in is already in an annulled or failed state, or the datetime constraint is not valid.
:<json string nonce: ``nonce`` value of the original check-in.
:<json array lists: List of check-in list IDs to search on. No two check-in lists may be from the same event.
:<json datetime datetime: Specifies the client-side datetime of the annulment. If not supplied, the current time will be used.
:<json string error_explanation: A human-readable description of why the check-in was annulled (optional).
:>json string status: ``"ok"``
**Example request**:
.. sourcecode:: http
POST /api/v1/organizers/bigevents/checkinrpc/annul/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
{
"lists": [1],
"nonce": "Pvrk50vUzQd0DhdpNRL4I4OcXsvg70uA",
"error_explanation": "Turnstile did not turn"
}
**Example successful response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
{
"status": "ok",
}
:param organizer: The ``slug`` field of the organizer to fetch
:statuscode 200: no error
:statuscode 400: Invalid or incomplete request, see above
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to view this resource.
:statuscode 404: The requested nonce does not exist.
Check-in history
----------------
.. rst-class:: rest-resource-table
===================================== ========================== =======================================================
Field Type Description
===================================== ========================== =======================================================
id integer Internal ID of the check-in
successful boolean Whether the check-in was successful
error_reason string Category of reason why the check-in was unsuccessful. Currently
``"canceled"``, ``"invalid"``, ``"unpaid"`` ``"product"``,
``"rules"``, ``"revoked"``, ``"incomplete"``, ``"already_redeemed"``,
``"ambiguous"``, ``"error"``, ``"blocked"``, ``"unapproved"``,
``"invalid_time"``, ``"annulled"`` or ``null``
error_explanation string Additional, human-readable reason for the check-in to be unsuccessful (or ``null``)
position integer Internal ID of the order position (or ``null`` for unknown scans)
datetime datetime Logical time when the check-in happened
created datetime Time when the check-in appeared on the server
list integer Internal ID of the check-in list
auto_checked_in boolean Whether the check-in was performed by the system automatically
gate integer Internal ID of the gate (or ``null``)
device integer Internal ID of the device (or ``null``)
device_id integer Organizer-internal ID of the device (or ``null``)
type string Type of check-in, currently ``"entry"`` or ``"exit"``
===================================== ========================== =======================================================
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/checkins/
Returns a list of all check-in events within a given event.
**Example request**:
.. sourcecode:: http
GET /api/v1/organizers/bigevents/events/sampleconf/checkins/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"id": 1,
"successful": true,
"error_reason": null,
"error_explanation": null,
"position": 1234,
"datetime": "2017-12-25T12:45:23Z",
"created": "2017-12-25T12:45:23Z",
"list": 2,
"auto_checked_in": false,
"gate": null,
"device": null,
"device_id": null,
"type": "entry",
}
]
}
:query integer page: The page number in case of a multi-page result set, default is 1
:query datetime created_since: Only return check-ins that have been created since the given date (inclusive).
:query datetime created_before: Only return check-ins that have been created before the given date (exclusive).
:query datetime datetime_since: Only return check-ins that have happened since the given date (inclusive).
:query datetime datetime_before: Only return check-ins that have happened before the given date (exclusive).
:query boolean successful: Only return check-ins that have (not) been successful.
:query boolean error_reason: Only return check-ins with a specific error reason.
:query integer list: Only return check-ins from a specific list.
:query string type: Only return check-ins of a specific type.
:query integer gate: Only return check-ins from a specific gate.
:query integer device: Only return check-ins from a specific device.
:query boolean auto_checked_in: Only return check-ins that are (not) auto-checked in.
:query string ordering: Manually set the ordering of results. Valid fields to be used are ``datetime``, ``created``,
and ``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.

View File

@@ -424,9 +424,9 @@ Endpoints
:param organizer: The ``slug`` field of the organizer of the event to create.
:param event: The ``slug`` field of the event to copy settings and items from.
:statuscode 201: no error
:statuscode 400: The event could not be updated due to invalid submitted data.
:statuscode 400: The event could not be created due to invalid submitted data.
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer does not exist **or** you have no permission to update this resource.
:statuscode 403: The requested organizer does not exist **or** you have no permission to create this resource.
.. http:patch:: /api/v1/organizers/(organizer)/events/(event)/

View File

@@ -19,7 +19,6 @@ at :ref:`plugin-docs`.
item_bundles
item_add-ons
item_meta_properties
item_program_times
questions
question_options
quotas

View File

@@ -22,13 +22,10 @@ invoice_from_name string Sender address:
invoice_from string Sender address: Address lines
invoice_from_zipcode string Sender address: ZIP code
invoice_from_city string Sender address: City
invoice_from_state string Sender address: State (only used in some countries)
invoice_from_country string Sender address: Country code
invoice_from_tax_id string Sender address: Local Tax ID
invoice_from_vat_id string Sender address: EU VAT ID
invoice_to string Full recipient address
invoice_to_is_business boolean Recipient address: Business vs individual (``null`` for
invoices created before pretix 2025.6).
invoice_to_company string Recipient address: Company name
invoice_to_name string Recipient address: Person name
invoice_to_street string Recipient address: Address lines
@@ -38,7 +35,6 @@ invoice_to_state string Recipient addre
invoice_to_country string Recipient address: Country code
invoice_to_vat_id string Recipient address: EU VAT ID
invoice_to_beneficiary string Invoice beneficiary
invoice_to_transmission_info object Additional transmission info (see :ref:`rest-transmission-types`)
custom_field string Custom invoice address field
date date Invoice date
refers string Invoice number of an invoice this invoice refers to
@@ -81,12 +77,17 @@ lines list of objects The actual invo
for all invoice lines
created before this field was introduced as well as for
all lines not created by a fee (e.g. a product).
period_start datetime Start date of the service or delivery period of the invoice line.
Can be ``null`` if not known.
├ period_end datetime End date of the service or delivery period of the invoice line.
Can be ``null`` if not known.
├ event_date_from datetime Deprecated alias of ``period_start``.
├ event_date_to datetime Deprecated alias of ``period_end``.
event_date_from datetime Start date of the (sub)event this line was created for as it
was set during invoice creation. Can be ``null`` for all invoice
lines created before this was introduced as well as for lines in
an event series not created by a product (e.g. shipping or
cancellation fees).
├ event_date_to datetime End date of the (sub)event this line was created for as it
was set during invoice creation. Can be ``null`` for all invoice
lines created before this was introduced as well as for lines in
an event series not created by a product (e.g. shipping or
cancellation fees) as well as whenever the respective (sub)event
has no end date set.
├ event_location string Location of the (sub)event this line was created for as it
was set during invoice creation. Can be ``null`` for all invoice
lines created before this was introduced as well as for lines in
@@ -109,12 +110,6 @@ foreign_currency_rate decimal (string) If ``foreign_cu
foreign_currency_rate_date date If ``foreign_currency_rate`` is set, this signifies the
date at which the currency rate was obtained.
internal_reference string Customer's reference to be printed on the invoice.
transmission_type string Requested transmission channel (see :ref:`rest-transmission-types`)
transmission_provider string Selected transmission provider (depends on installed
plugins). ``null`` if not yet chosen.
transmission_status string Transmission status, one of ``unknown`` (pre-2025.6),
``pending``, ``inflight``, ``failed``, and ``completed``.
transmission_date datetime Time of last change in transmission status (may be ``null``).
===================================== ========================== =======================================================
@@ -126,76 +121,6 @@ transmission_date datetime Time of last ch
The ``tax_code`` attribute has been added.
.. versionchanged:: 2025.6
The attributes ``invoice_to_is_business``, ``invoice_to_transmission_info``, ``transmission_type``,
``transmission_provider``, ``transmission_status``, and ``transmission_date`` have been added.
.. _`rest-transmission-types`:
Transmission types
------------------
pretix supports multiple ways to transmit an invoice from the organizer to the invoice recipient.
For each transmission type, different fields are supported in the ``transmission_info`` object of the
invoice address. Currently, pretix supports the following transmission types:
Email
"""""
The identifier ``"email"`` represents the transmission of PDF invoices through email.
This is the default transmission type in pretix and has some special behavior for backwards compatibility.
Transmission is always executed through the provider ``"email_pdf"``.
The ``transmission_info`` object may contain the following properties:
.. rst-class:: rest-resource-table
===================================== ========================== =======================================================
Field Type Description
===================================== ========================== =======================================================
transmission_email_address string Optional. An email address other than the order address
that the invoice should be sent to.
Business customers only.
===================================== ========================== =======================================================
Peppol
""""""
The identifier ``"peppol"`` represents the transmission of XML invoices through the `Peppol`_ network.
This is only available for business addresses.
This is not supported by pretix out of the box and requires the use of a suitable plugin.
The ``transmission_info`` object may contain the following properties:
.. rst-class:: rest-resource-table
===================================== ========================== =======================================================
Field Type Description
===================================== ========================== =======================================================
transmission_peppol_participant_id string Required. The Peppol participant ID of the recipient.
===================================== ========================== =======================================================
Italian Exchange System
"""""""""""""""""""""""
The identifier ``"it_sdi"`` represents the transmission of XML invoices through the `Sistema di Interscambio`_ network used in Italy.
This is only available for addresses with country ``"IT"``.
This is not supported by pretix out of the box and requires the use of a suitable plugin.
The ``transmission_info`` object may contain the following properties:
.. rst-class:: rest-resource-table
===================================== ========================== =======================================================
Field Type Description
===================================== ========================== =======================================================
transmission_it_sdi_codice_fiscale string Required for non-business address. Fiscal code of the
recipient.
transmission_it_sdi_pec string Required for business addresses. Address for certified
electronic mail.
transmission_it_sdi_recipient_code string Required for businesses. SdI recipient code.
===================================== ========================== =======================================================
If this type is selected, ``vat_id`` is required for business addresses.
List of all invoices
--------------------
@@ -234,13 +159,11 @@ List of all invoices
"invoice_from": "Demo street 12",
"invoice_from_zipcode":"",
"invoice_from_city":"Demo town",
"invoice_from_state":"CA",
"invoice_from_country":"US",
"invoice_from_tax_id":"",
"invoice_from_vat_id":"",
"invoice_to": "Sample company\nJohn Doe\nTest street 12\n12345 Testington\nTestikistan\nVAT-ID: EU123456789",
"invoice_to_company": "Sample company",
"invoice_to_is_business": true,
"invoice_to_name": "John Doe",
"invoice_to_street": "Test street 12",
"invoice_to_zipcode": "12345",
@@ -249,7 +172,6 @@ List of all invoices
"invoice_to_country": "TE",
"invoice_to_vat_id": "EU123456789",
"invoice_to_beneficiary": "",
"invoice_to_transmission_info": {},
"custom_field": null,
"date": "2017-12-01",
"refers": null,
@@ -271,8 +193,6 @@ List of all invoices
"fee_internal_type": null,
"event_date_from": "2017-12-27T10:00:00Z",
"event_date_to": null,
"period_start": "2017-12-27T10:00:00Z",
"period_end": "2017-12-27T10:00:00Z",
"event_location": "Heidelberg",
"attendee_name": null,
"gross_value": "23.00",
@@ -284,11 +204,7 @@ List of all invoices
],
"foreign_currency_display": "PLN",
"foreign_currency_rate": "4.2408",
"foreign_currency_rate_date": "2017-07-24",
"transmission_type": "email",
"transmission_provider": "email_pdf",
"transmission_status": "completed",
"transmission_date": "2017-07-24T10:00:00Z"
"foreign_currency_rate_date": "2017-07-24"
}
]
}
@@ -383,13 +299,11 @@ Fetching individual invoices
"invoice_from": "Demo street 12",
"invoice_from_zipcode":"",
"invoice_from_city":"Demo town",
"invoice_from_state":"CA",
"invoice_from_country":"US",
"invoice_from_tax_id":"",
"invoice_from_vat_id":"",
"invoice_to": "Sample company\nJohn Doe\nTest street 12\n12345 Testington\nTestikistan\nVAT-ID: EU123456789",
"invoice_to_company": "Sample company",
"invoice_to_is_business": true,
"invoice_to_name": "John Doe",
"invoice_to_street": "Test street 12",
"invoice_to_zipcode": "12345",
@@ -398,7 +312,6 @@ Fetching individual invoices
"invoice_to_country": "TE",
"invoice_to_vat_id": "EU123456789",
"invoice_to_beneficiary": "",
"invoice_to_transmission_info": {},
"custom_field": null,
"date": "2017-12-01",
"refers": null,
@@ -420,8 +333,6 @@ Fetching individual invoices
"fee_internal_type": null,
"event_date_from": "2017-12-27T10:00:00Z",
"event_date_to": null,
"period_start": "2017-12-27T10:00:00Z",
"period_end": "2017-12-27T10:00:00Z",
"event_location": "Heidelberg",
"attendee_name": null,
"gross_value": "23.00",
@@ -433,11 +344,7 @@ Fetching individual invoices
],
"foreign_currency_display": "PLN",
"foreign_currency_rate": "4.2408",
"foreign_currency_rate_date": "2017-07-24",
"transmission_type": "email",
"transmission_provider": "email_pdf",
"transmission_status": "completed",
"transmission_date": "2017-07-24T10:00:00Z"
"foreign_currency_rate_date": "2017-07-24"
}
:param organizer: The ``slug`` field of the organizer to fetch
@@ -542,70 +449,3 @@ Invoices cannot be edited directly, but the following actions can be triggered:
:statuscode 400: The invoice has already been canceled
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to change this resource.
Transmitting invoices
---------------------
Invoices are transmitted automatically when created during order creation or payment receipt,
but in other cases transmission may need to be triggered manually.
.. http:post:: /api/v1/organizers/(organizer)/events/(event)/invoices/(number)/transmit/
Transmits the invoice to the recipient, but only if it is in ``pending`` state.
**Example request**:
.. sourcecode:: http
GET /api/v1/organizers/bigevents/events/sampleconf/invoices/00001/transmit/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
**Example response**:
.. sourcecode:: http
HTTP/1.1 204 No Content
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 number: The ``number`` field of the invoice to transmit
:statuscode 200: no error
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to transmit this invoice **or** the invoice may not be transmitted
:statuscode 409: The invoice is currently in transmission
.. http:post:: /api/v1/organizers/(organizer)/events/(event)/invoices/(number)/retransmit/
Transmits the invoice to the recipient even if transmission was already attempted previously.
**Example request**:
.. sourcecode:: http
GET /api/v1/organizers/bigevents/events/sampleconf/invoices/00001/retransmit/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
**Example response**:
.. sourcecode:: http
HTTP/1.1 204 No Content
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 number: The ``number`` field of the invoice to transmit
:statuscode 200: no error
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to transmit this invoice **or** the invoice may not be transmitted
:statuscode 409: The invoice is currently in transmission
.. _Peppol: https://en.wikipedia.org/wiki/PEPPOL
.. _Sistema di Interscambio: https://it.wikipedia.org/wiki/Fattura_elettronica_in_Italia

View File

@@ -1,223 +0,0 @@
Item program times
==================
Resource description
--------------------
Program times for products (items) that can be set in addition to event times, e.g. to display seperate schedules within an event.
Note that ``program_times`` are not available for items inside event series.
The program times resource contains the following public fields:
.. rst-class:: rest-resource-table
===================================== ========================== =======================================================
Field Type Description
===================================== ========================== =======================================================
id integer Internal ID of the program time
start datetime The start date time for this program time slot.
end datetime The end date time for this program time slot.
===================================== ========================== =======================================================
.. versionchanged:: TODO
The resource has been added.
Endpoints
---------
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/items/(item)/program_times/
Returns a list of all program times for a given item.
**Example request**:
.. sourcecode:: http
GET /api/v1/organizers/bigevents/events/sampleconf/items/11/program_times/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
{
"count": 3,
"next": null,
"previous": null,
"results": [
{
"id": 2,
"start": "2025-08-14T22:00:00Z",
"end": "2025-08-15T00:00:00Z"
},
{
"id": 3,
"start": "2025-08-12T22:00:00Z",
"end": "2025-08-13T22:00:00Z"
},
{
"id": 14,
"start": "2025-08-15T22:00:00Z",
"end": "2025-08-17T22:00:00Z"
}
]
}
:param organizer: The ``slug`` field of the organizer to fetch
:param event: The ``slug`` field of the event to fetch
:param item: The ``id`` field of the item to fetch
:statuscode 200: no error
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer/event/item does not exist **or** you have no permission to view this resource.
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/items/(item)/program_times/(id)/
Returns information on one program time, identified by its ID.
**Example request**:
.. sourcecode:: http
GET /api/v1/organizers/bigevents/events/sampleconf/items/1/program_times/1/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
{
"id": 1,
"start": "2025-08-15T22:00:00Z",
"end": "2025-10-27T23:00:00Z"
}
:param organizer: The ``slug`` field of the organizer to fetch
:param event: The ``slug`` field of the event to fetch
:param item: The ``id`` field of the item to fetch
:param id: The ``id`` field of the program time to fetch
:statuscode 200: no error
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to view this resource.
.. http:post:: /api/v1/organizers/(organizer)/events/(event)/items/(item)/program_times/
Creates a new program time
**Example request**:
.. sourcecode:: http
POST /api/v1/organizers/bigevents/events/sampleconf/items/1/program_times/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
Content-Type: application/json
{
"start": "2025-08-15T10:00:00Z",
"end": "2025-08-15T22:00:00Z"
}
**Example response**:
.. sourcecode:: http
HTTP/1.1 201 Created
Vary: Accept
Content-Type: application/json
{
"id": 17,
"start": "2025-08-15T10:00:00Z",
"end": "2025-08-15T22:00:00Z"
}
:param organizer: The ``slug`` field of the organizer of the event/item to create a program time for
:param event: The ``slug`` field of the event to create a program time for
:param item: The ``id`` field of the item to create a program time for
:statuscode 201: no error
:statuscode 400: The program time could not be created due to invalid submitted data.
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to create this resource.
.. http:patch:: /api/v1/organizers/(organizer)/events/(event)/items/(item)/program_times/(id)/
Update a program time. You can also use ``PUT`` instead of ``PATCH``. With ``PUT``, you have to provide all fields of
the resource, other fields will be reset to default. With ``PATCH``, you only need to provide the fields that you
want to change.
You can change all fields of the resource except the ``id`` field.
**Example request**:
.. sourcecode:: http
PATCH /api/v1/organizers/bigevents/events/sampleconf/items/1/program_times/1/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
Content-Type: application/json
Content-Length: 94
{
"start": "2025-08-14T10:00:00Z"
}
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
{
"id": 1,
"start": "2025-08-14T10:00:00Z",
"end": "2025-08-15T12:00:00Z"
}
:param organizer: The ``slug`` field of the organizer to modify
:param event: The ``slug`` field of the event to modify
:param id: The ``id`` field of the item to modify
:param id: The ``id`` field of the program time to modify
:statuscode 200: no error
:statuscode 400: The program time could not be modified due to invalid submitted data
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to change this resource.
.. http:delete:: /api/v1/organizers/(organizer)/events/(event)/items/(id)/program_times/(id)/
Delete a program time.
**Example request**:
.. sourcecode:: http
DELETE /api/v1/organizers/bigevents/events/sampleconf/items/1/program_times/1/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
**Example response**:
.. sourcecode:: http
HTTP/1.1 204 No Content
Vary: Accept
:param organizer: The ``slug`` field of the organizer to modify
:param event: The ``slug`` field of the event to modify
:param id: The ``id`` field of the item to modify
:param id: The ``id`` field of the program time to delete
:statuscode 204: no error
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to delete this resource.

View File

@@ -139,10 +139,6 @@ has_variations boolean Shows whether
variations list of objects A list with one object for each variation of this item.
Can be empty. Only writable during creation,
use separate endpoint to modify this later.
program_times list of objects A list with one object for each program time of this item.
Can be empty. Only writable during creation,
use separate endpoint to modify this later.
Not available for items in event series.
├ id integer Internal ID of the variation
├ value multi-lingual string The "name" of the variation
├ default_price money (string) The price set directly for this variation or ``null``
@@ -229,10 +225,6 @@ meta_data object Values set fo
The ``hidden_if_item_available_mode`` attributes has been added.
.. versionchanged:: 2025.9
The ``program_times`` attribute has been added.
Notes
-----
@@ -240,11 +232,9 @@ Please note that an item either always has variations or never has. Once created
change to an item without and vice versa. To create an item with variations ensure that you POST an item with at least
one variation.
Also note that ``variations``, ``bundles``, ``addons`` and ``program_times`` are only supported on ``POST``. To update/delete variations,
bundles, add-ons and program times please use the dedicated nested endpoints. By design this endpoint does not support ``PATCH`` and ``PUT``
with nested ``variations``, ``bundles``, ``addons`` and/or ``program_times``.
``program_times`` is not available to items in event series.
Also note that ``variations``, ``bundles``, and ``addons`` are only supported on ``POST``. To update/delete variations,
bundles, and add-ons please use the dedicated nested endpoints. By design this endpoint does not support ``PATCH`` and ``PUT``
with nested ``variations``, ``bundles`` and/or ``addons``.
Endpoints
---------
@@ -383,8 +373,7 @@ Endpoints
}
],
"addons": [],
"bundles": [],
"program_times": []
"bundles": []
}
]
}
@@ -536,8 +525,7 @@ Endpoints
}
],
"addons": [],
"bundles": [],
"program_times": []
"bundles": []
}
:param organizer: The ``slug`` field of the organizer to fetch
@@ -665,13 +653,7 @@ Endpoints
}
],
"addons": [],
"bundles": [],
"program_times": [
{
"start": "2025-08-14T22:00:00Z",
"end": "2025-08-15T00:00:00Z"
}
]
"bundles": []
}
**Example response**:
@@ -791,13 +773,7 @@ Endpoints
}
],
"addons": [],
"bundles": [],
"program_times": [
{
"start": "2025-08-14T22:00:00Z",
"end": "2025-08-15T00:00:00Z"
}
]
"bundles": []
}
:param organizer: The ``slug`` field of the organizer of the event to create an item for
@@ -813,9 +789,8 @@ Endpoints
the resource, other fields will be reset to default. With ``PATCH``, you only need to provide the fields that you
want to change.
You can change all fields of the resource except the ``has_variations``, ``variations``, ``addon`` and the
``program_times`` field. If you need to update/delete variations, add-ons or program times, please use the nested
dedicated endpoints.
You can change all fields of the resource except the ``has_variations``, ``variations`` and the ``addon`` field. If
you need to update/delete variations or add-ons please use the nested dedicated endpoints.
**Example request**:
@@ -949,8 +924,7 @@ Endpoints
}
],
"addons": [],
"bundles": [],
"program_times": []
"bundles": []
}
:param organizer: The ``slug`` field of the organizer to modify

View File

@@ -41,7 +41,6 @@ expires datetime The order will
payment_date date **DEPRECATED AND INACCURATE** Date of payment receipt
payment_provider string **DEPRECATED AND INACCURATE** Payment provider used for this order
total money (string) Total value of this order
tax_rounding_mode string Tax rounding mode, see :ref:`algorithms-rounding`
comment string Internal comment on this order
api_meta object Meta data for that order. Only available through API, no guarantees
on the content structure. You can use this to save references to your system.
@@ -66,16 +65,11 @@ invoice_address object Invoice address
├ state string Customer state (ISO 3166-2 code). Only supported in
AU, BR, CA, CN, MY, MX, and US.
├ internal_reference string Customer's internal reference to be printed on the invoice
├ custom_field string Custom invoice address field
├ vat_id string Customer VAT ID
vat_id_validated string ``true``, if the VAT ID has been validated against the
vat_id_validated string ``true``, if the VAT ID has been validated against the
EU VAT service and validation was successful. This only
happens in rare cases.
├ transmission_type string Transmission channel for invoice (see also :ref:`rest-transmission-types`).
Defaults to ``email``.
└ transmission_info object Transmission-channel specific information (or ``null``).
See also :ref:`rest-transmission-types`.
positions list of objects List of order positions (see below). By default, only
non-canceled positions are included.
fees list of objects List of fees included in the order total. By default, only
@@ -148,14 +142,6 @@ plugin_data object Additional data
The ``plugin_data`` attribute has been added.
.. versionchanged:: 2025.6
The ``invoice_address.transmission_type`` and ``invoice_address.transmission_info`` attributes have been added.
.. versionchanged:: 2025.10
The ``tax_rounding_mode`` attribute has been added.
.. _order-position-resource:
Order position resource
@@ -363,7 +349,6 @@ List of all orders
"payment_provider": "banktransfer",
"fees": [],
"total": "23.00",
"tax_rounding_mode": "line",
"comment": "",
"custom_followup_at": null,
"checkin_attention": false,
@@ -383,9 +368,7 @@ List of all orders
"state": "",
"internal_reference": "",
"vat_id": "EU123456789",
"vat_id_validated": false,
"transmission_type": "email",
"transmission_info": {}
"vat_id_validated": false
},
"positions": [
{
@@ -424,7 +407,6 @@ List of all orders
"seat": null,
"checkins": [
{
"id": 1337,
"list": 44,
"type": "entry",
"gate": null,
@@ -608,7 +590,6 @@ Fetching individual orders
"payment_provider": "banktransfer",
"fees": [],
"total": "23.00",
"tax_rounding_mode": "line",
"comment": "",
"api_meta": {},
"custom_followup_at": null,
@@ -629,9 +610,7 @@ Fetching individual orders
"state": "",
"internal_reference": "",
"vat_id": "EU123456789",
"vat_id_validated": false,
"transmission_type": "email",
"transmission_info": {}
"vat_id_validated": false
},
"positions": [
{
@@ -670,7 +649,6 @@ Fetching individual orders
"seat": null,
"checkins": [
{
"id": 1337,
"list": 44,
"type": "entry",
"gate": null,
@@ -1018,7 +996,6 @@ Creating orders
provider will not be called to do anything about this (i.e. if you pass a bank account to a debit provider, *no*
charge will be created), this is just informative in case you *handled the payment already*.
* ``payment_date`` (optional) Date and time of the completion of the payment.
* ``tax_rounding_mode`` (optional)
* ``comment`` (optional)
* ``custom_followup_at`` (optional)
* ``checkin_attention`` (optional)
@@ -1038,10 +1015,8 @@ Creating orders
* ``internal_reference``
* ``vat_id``
* ``vat_id_validated`` (optional) If you need support for reverse charge (rarely the case), you need to check
yourself if the passed VAT ID is a valid EU VAT ID. In that case, set this to ``true``. Only valid VAT IDs will
trigger reverse charge taxation. Don't forget to set ``is_business`` as well!
* ``transmission_type`` (optional, defaults to ``email``)
* ``transmission_info`` (optional, see also :ref:`rest-transmission-types`)
yourself if the passed VAT ID is a valid EU VAT ID. In that case, set this to ``true``. Only valid VAT IDs will
trigger reverse charge taxation. Don't forget to set ``is_business`` as well!
* ``positions``
@@ -1066,7 +1041,6 @@ Creating orders
* ``valid_until`` (optional, if both ``valid_from`` and ``valid_until`` are **missing** (not ``null``) the availability will be computed from the given product)
* ``requested_valid_from`` (optional, can be set **instead** of ``valid_from`` and ``valid_until`` to signal a user choice for the start time that may or may not be respected)
* ``use_reusable_medium`` (optional, causes the new ticket to take over the given reusable medium, identified by its ID)
* ``discount`` (optional, only possible if ``price`` is set; attention: if this is set to not-``null`` on any position, automatic calculation of discounts will not run)
* ``answers``
* ``question``
@@ -1643,7 +1617,6 @@ List of all order positions
"blocked": null,
"checkins": [
{
"id": 1337,
"list": 44,
"type": "entry",
"gate": null,
@@ -1772,7 +1745,6 @@ Fetching individual positions
"seat": null,
"checkins": [
{
"id": 1337,
"list": 44,
"type": "entry",
"gate": null,
@@ -1954,7 +1926,6 @@ Manipulating individual positions
(Full order position resource, see above.)
:query boolean check_quotas: Whether to check quotas before committing item changes, default is ``true``
:param organizer: The ``slug`` field of the organizer of the event
:param event: The ``slug`` field of the event
:param id: The ``id`` field of the order position to update
@@ -2034,7 +2005,6 @@ Manipulating individual positions
(Full order position resource, see above.)
:query boolean check_quotas: Whether to check quotas before creating the new position, default is ``true``
:param organizer: The ``slug`` field of the organizer of the event
:param event: The ``slug`` field of the event
@@ -2321,7 +2291,6 @@ otherwise, such as splitting an order or changing fees.
(Full order position resource, see above.)
:query boolean check_quotas: Whether to check quotas before patching or creating positions, default is ``true``
:param organizer: The ``slug`` field of the organizer of the event
:param event: The ``slug`` field of the event
:param code: The ``code`` field of the order to update
@@ -2517,7 +2486,6 @@ Order payment endpoints
{
"amount": "23.00",
"comment": "Overpayment",
"mark_canceled": false
}

View File

@@ -19,11 +19,6 @@ name string The organizer's
slug string A short form of the name, used e.g. in URLs.
public_url string The public, customer-facing URL of the organizer, where
the list of all events can be found (read-only).
plugins list A list of package names of the enabled plugins for this
organizer. Note that most plugins are enabled on the
event level (or both levels). If you remove a plugin
that is also enabled on some events, it will
automatically be removed from all events as well.
===================================== ========================== =======================================================
@@ -58,10 +53,7 @@ Endpoints
{
"name": "Big Events LLC",
"slug": "Big Events",
"public_url": "https://pretix.eu/bigevents/",
"plugins": [
"pretix_datev"
]
"public_url": "https://pretix.eu/bigevents/"
}
]
}
@@ -95,10 +87,7 @@ Endpoints
{
"name": "Big Events LLC",
"slug": "Big Events",
"public_url": "https://pretix.eu/bigevents/",
"plugins": [
"pretix_datev"
]
"public_url": "https://pretix.eu/bigevents/"
}
:param organizer: The ``slug`` field of the organizer to fetch
@@ -106,50 +95,6 @@ Endpoints
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer does not exist **or** you have no permission to view it.
.. http:patch:: /api/v1/organizers/(organizer)/
Updates an organizer. Currently only the ``plugins`` field may be updated.
Permission required: "Can change organizer settings"
**Example request**:
.. sourcecode:: http
PATCH /api/v1/organizers/bigevents/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
Content-Type: application/json
{
"plugins": [
"pretix_seating"
]
}
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
{
"name": "Big Events LLC",
"slug": "Big Events",
"public_url": "https://pretix.eu/bigevents/",
"plugins": [
"pretix_seating"
]
}
:param organizer: The ``slug`` field of the organizer to update
:statuscode 200: no error
:statuscode 400: The organizer could not be updated due to invalid submitted data.
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer does not exist **or** you have no permission to update this resource.
Organizer settings
------------------

View File

@@ -38,10 +38,6 @@ available_number integer Number of avail
slightly out of date. ``null`` means unlimited.
===================================== ========================== =======================================================
.. versionchanged:: 2025.7
The attribute ``ignore_for_event_availability`` has been added.
Endpoints
---------

View File

@@ -14,7 +14,6 @@ The voucher resource contains the following public fields:
Field Type Description
===================================== ========================== =======================================================
id integer Internal ID of the voucher
created datetime The creation date of the voucher. For vouchers created before pretix 2025.7.0, this is guessed retroactively and might not be accurate.
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).
@@ -54,10 +53,6 @@ budget money (string) The budget a vo
budget_used money (string) The amount of budget the voucher has already used up.
===================================== ========================== =======================================================
.. versionchanged:: 2025.7
The attributes ``created``, ``budget``, and ``budget_used`` have been added.
Endpoints
---------
@@ -89,7 +84,6 @@ Endpoints
"results": [
{
"id": 1,
"created": "2020-09-18T14:17:40.971519Z",
"code": "43K6LKM37FBVR2YG",
"max_usages": 1,
"redeemed": 0,
@@ -162,7 +156,6 @@ Endpoints
{
"id": 1,
"created": "2020-09-18T14:17:40.971519Z",
"code": "43K6LKM37FBVR2YG",
"max_usages": 1,
"redeemed": 0,
@@ -235,7 +228,6 @@ Endpoints
{
"id": 1,
"created": "2020-09-18T14:17:40.971519Z",
"code": "43K6LKM37FBVR2YG",
"max_usages": 1,
"redeemed": 0,
@@ -329,7 +321,6 @@ Endpoints
[
{
"id": 1,
"created": "2020-09-18T14:17:40.971519Z",
"code": "43K6LKM37FBVR2YG",
}, …
@@ -376,7 +367,6 @@ Endpoints
{
"id": 1,
"created": "2020-09-18T14:17:40.971519Z",
"code": "43K6LKM37FBVR2YG",
"max_usages": 1,
"redeemed": 0,

View File

@@ -60,9 +60,6 @@ The following values for ``action_types`` are valid with pretix core:
* ``pretix.event.added``
* ``pretix.event.changed``
* ``pretix.event.deleted``
* ``pretix.voucher.added``
* ``pretix.voucher.changed``
* ``pretix.voucher.deleted``
* ``pretix.subevent.added``
* ``pretix.subevent.changed``
* ``pretix.subevent.deleted``

View File

@@ -178,124 +178,3 @@ Flowchart
---------
.. image:: /images/cart_pricing.png
.. _`algorithms-rounding`:
Rounding of taxes
-----------------
pretix internally always stores taxes on a per-line level, like this:
========== ========== =========== ======= =============
Product Tax rate Net price Tax Gross price
========== ========== =========== ======= =============
Ticket A 19 % 84.03 15.97 100.00
Ticket B 19 % 84.03 15.97 100.00
Ticket C 19 % 84.03 15.97 100.00
Ticket D 19 % 84.03 15.97 100.00
Ticket E 19 % 84.03 15.97 100.00
Sum 420.15 79.85 500.00
========== ========== =========== ======= =============
Whether the net price is computed from the gross price or vice versa is configured on the tax rule and may differ for every line.
The line-based computation has a few significant advantages:
- We can report both net and gross prices for every individual ticket.
- We can report both net and gross prices for every filter imaginable, such as the gross sum of all sales of Ticket A
or the net sum of all sales for a specific date in an event series. All numbers will be exact.
- When splitting the order into two, both net price and gross price are split without any changes in rounding.
The main disadvantage is that the tax looks "wrong" when computed from the sum. Taking the sum of net prices (420.15)
and multiplying it with the tax rate (19%) yields a tax amount of 79.83 (instead of 79.85) and a gross sum of 499.98
(instead of 500.00). This becomes a problem when juristictions, data formats, or external systems expect this calculation
to work on the level of the entire order. A prominent example is the EN 16931 standard for e-invoicing that
does not allow the computation as created by pretix.
However, calculating the tax rate from the net total has significant disadvantages:
- It is impossible to guarantee a stable gross price this way, i.e. if you advertise a price of €100 per ticket to
consumers, they will be confused when they only need to pay €499.98 for 5 tickets.
- Some prices are impossible, e.g. you cannot sell a ticket for a gross price of €99.99 at a 19% tax rate, since there
is no two-decimal net price that would be computed to a gross price of €99.99.
- When splitting an order into two, the combined of the new orders is not guaranteed to be the same as the total of the
original order. Therefore, additional payments or refunds of very small amounts might be necessary.
To allow organizers to make their own choices on this matter, pretix provides the following options:
Compute taxes for every line individually
"""""""""""""""""""""""""""""""""""""""""
Algorithm identifier: ``line``
This is our original algorithm where the tax value is rounded for every line individually.
**This is our current default algorithm and we recommend it whenever you do not have different requirements** (see below).
For the example above:
========== ========== =========== ======= =============
Product Tax rate Net price Tax Gross price
========== ========== =========== ======= =============
Ticket A 19 % 84.03 15.97 100.00
Ticket B 19 % 84.03 15.97 100.00
Ticket C 19 % 84.03 15.97 100.00
Ticket D 19 % 84.03 15.97 100.00
Ticket E 19 % 84.03 15.97 100.00
Sum 420.15 79.85 500.00
========== ========== =========== ======= =============
Compute taxes based on net total
""""""""""""""""""""""""""""""""
Algorithm identifier: ``sum_by_net``
In this algorithm, the tax value and gross total are computed from the sum of the net prices. To accomplish this within
our data model, the gross price and tax of some of the tickets will be changed by the minimum currency unit (e.g. €0.01).
The net price of the tickets always stay the same.
**This is the algorithm intended by EN 16931 invoices and our recommendation to use for e-invoicing when (primarily) business customers are involved.**
The main downside is that it might be confusing when selling to consumers, since the amounts to be paid change in unexpected ways.
For the example above, the customer expects to pay 5 times 100.00, but they are are in fact charged 499.98:
========== ========== =========== ============================== ==============================
Product Tax rate Net price Tax Gross price
========== ========== =========== ============================== ==============================
Ticket A 19 % 84.03 15.96 (incl. -0.01 rounding) 99.99 (incl. -0.01 rounding)
Ticket B 19 % 84.03 15.96 (incl. -0.01 rounding) 99.99 (incl. -0.01 rounding)
Ticket C 19 % 84.03 15.97 100.00
Ticket D 19 % 84.03 15.97 100.00
Ticket E 19 % 84.03 15.97 100.00
Sum 420.15 78.83 499.98
========== ========== =========== ============================== ==============================
Compute taxes based on net total with stable gross prices
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""
Algorithm identifier: ``sum_by_net_keep_gross``
In this algorithm, the tax value and gross total are computed from the sum of the net prices. However, the net prices
of some of the tickets will be changed automatically by the minimum currency unit (e.g. €0.01) such that the resulting
gross prices stay the same.
**This is less confusing to consumers and the end result is still compliant to EN 16931, so we recommend this for e-invoicing when (primarily) consumers are involved.**
The main downside is that it might be confusing when selling to business customers, since the prices of the identical tickets appear to be different.
Full computation for the example above:
========== ========== ============================= ============================== =============
Product Tax rate Net price Tax Gross price
========== ========== ============================= ============================== =============
Ticket A 19 % 84.04 (incl. 0.01 rounding) 15.96 (incl. -0.01 rounding) 100.00
Ticket B 19 % 84.04 (incl. 0.01 rounding) 15.96 (incl. -0.01 rounding) 100.00
Ticket C 19 % 84.03 15.97 100.00
Ticket D 19 % 84.03 15.97 100.00
Ticket E 19 % 84.03 15.97 100.00
Sum 420.17 79.83 500.00
========== ========== ============================= ============================== =============

View File

@@ -11,22 +11,19 @@ is unidirectionally sending (order, customer, ticket, ...) data into external sy
The transfer is usually triggered by signals provided by pretix core (e.g. :data:`order_placed`),
but performed asynchronously.
Such plugins should use the :class:`OutboundSyncProvider` API to utilize the queueing, retry and mapping
mechanisms as well as the user interface for configuration and monitoring. Sync providers are registered
in the :py:attr:`pretix.base.datasync.datasync.datasync_providers` :ref:`registry <registries>`.
Such plugins should use the :class:`OutboundSyncProvider` API to utilize the queueing, retry and mapping mechanisms as well as the user interface for configuration and monitoring.
An :class:`OutboundSyncProvider` for subscribing event participants to a mailing list could start
An :class:`OutboundSyncProvider` for registering event participants in a mailing list could start
like this, for example:
.. code-block:: python
from pretix.base.datasync.datasync import (OutboundSyncProvider, datasync_providers)
from pretix.base.datasync.datasync import OutboundSyncProvider
@datasync_providers.register
class MyListSyncProvider(OutboundSyncProvider):
identifier = "my_list"
display_name = "My Mailing List Service"
# ...
# ...c
The plugin must register listeners in `signals.py` for all signals that should to trigger a sync and
@@ -39,10 +36,7 @@ within it has to call :meth:`MyListSyncProvider.enqueue_order` to enqueue the or
MyListSyncProvider.enqueue_order(order, "order_placed")
Property mappings
-----------------
Most of these plugins need to translate data from some pretix objects (e.g. orders)
Furthermore, most of these plugins need to translate data from some pretix objects (e.g. orders)
into an external system's data structures. Sometimes, there is only one reasonable way or the
plugin author makes an opinionated decision what information from which objects should be
transferred into which data structures in the external system.
@@ -92,7 +86,6 @@ shown. Therein, a ``sync_object_with_properties`` method is defined as follows:
from pretix.base.datasync.utils import assign_properties
# class MyListSyncProvider, contd.
def sync_object_with_properties(
self, external_id_field, id_value, properties: list, inputs: dict,
mapping, mapped_objects: dict, **kwargs,

View File

@@ -23,21 +23,21 @@ There are multiple signals that will be sent out in the ordering cycle:
.. automodule:: pretix.base.signals
:no-index:
:members: validate_cart, validate_cart_addons, validate_order, order_valid_if_pending, order_fee_calculation, order_paid, order_placed, order_canceled, order_reactivated, order_expired, order_expiry_changed, order_modified, order_changed, order_approved, order_denied, order_fee_type_name, allow_ticket_download, order_split, order_gracefully_delete, build_invoice_data, invoice_line_text
:members: validate_cart, validate_cart_addons, validate_order, order_valid_if_pending, order_fee_calculation, order_paid, order_placed, order_canceled, order_reactivated, order_expired, order_expiry_changed, order_modified, order_changed, order_approved, order_denied, order_fee_type_name, allow_ticket_download, order_split, order_gracefully_delete, invoice_line_text
Check-ins
"""""""""
.. automodule:: pretix.base.signals
:no-index:
:members: checkin_created, checkin_annulled
:members: checkin_created
Frontend
--------
.. automodule:: pretix.presale.signals
:members: html_head, html_footer, footer_link, global_footer_link, front_page_top, front_page_bottom, front_page_bottom_widget, fee_calculation_for_cart, contact_form_fields, question_form_fields, contact_form_fields_overrides, question_form_fields_overrides, checkout_confirm_messages, checkout_confirm_page_content, checkout_all_optional, html_page_header, render_seating_plan, checkout_flow_steps, position_info, position_info_top, item_description, global_html_head, global_html_footer, global_html_page_header, seatingframe_html_head, filter_subevents
:members: html_head, html_footer, footer_link, global_footer_link, front_page_top, front_page_bottom, front_page_bottom_widget, fee_calculation_for_cart, contact_form_fields, question_form_fields, contact_form_fields_overrides, question_form_fields_overrides, checkout_confirm_messages, checkout_confirm_page_content, checkout_all_optional, html_page_header, render_seating_plan, checkout_flow_steps, position_info, position_info_top, item_description, global_html_head, global_html_footer, global_html_page_header, seatingframe_html_head
.. automodule:: pretix.presale.signals

View File

@@ -13,7 +13,6 @@ Contents:
email
placeholder
invoice
invoicetransmission
shredder
import
customview

View File

@@ -1,65 +0,0 @@
.. highlight:: python
:linenothreshold: 5
Writing an invoice transmission plugin
======================================
An invoice transmission provider transports an invoice from the sender to the recipient.
There are pre-defined types of invoice transmission in pretix, currently ``"email"``, ``"peppol"``, and ``"it_sdi"``.
You can find more information about them at :ref:`rest-transmission-types`.
New transmission types can not be added by plugins but need to be added to pretix itself.
However, plugins can provide implementations for the actual transmission.
Please read :ref:`Creating a plugin <pluginsetup>` first, if you haven't already.
Output registration
-------------------
New invoice transmission providers can be registered through the :ref:`registry <registries>` mechanism
.. code-block:: python
from pretix.base.invoicing.transmission import transmission_providers, TransmissionProvider
@transmission_providers.new()
class SdiTransmissionProvider(TransmissionProvider):
identifier = "fatturapa_providerabc"
type = "it_sdi"
verbose_name = _("FatturaPA through provider ABC")
...
The provider class
------------------
.. class:: pretix.base.invoicing.transmission.TransmissionProvider
.. autoattribute:: identifier
This is an abstract attribute, you **must** override this!
.. autoattribute:: type
This is an abstract attribute, you **must** override this!
.. autoattribute:: verbose_name
This is an abstract attribute, you **must** override this!
.. autoattribute:: priority
.. autoattribute:: testmode_supported
.. automethod:: is_ready
This is an abstract method, you **must** override this!
.. automethod:: is_available
This is an abstract method, you **must** override this!
.. automethod:: transmit
This is an abstract method, you **must** override this!
.. automethod:: settings_url

View File

@@ -56,20 +56,6 @@ restricted boolean (optional) ``False`` by default, restricts a plugin
for an event by system administrators / superusers.
experimental boolean (optional) ``False`` by default, marks a plugin as an experimental feature in the plugins list.
compatibility string Specifier for compatible pretix versions.
level string System level the plugin can be activated at.
Set to ``pretix.base.plugins.PLUGIN_LEVEL_EVENT`` for plugins that can be activated
at event level and then be active for that event only.
Set to ``pretix.base.plugins.PLUGIN_LEVEL_ORGANIZER`` for plugins that can be
activated only for the organizer as a whole and are active for any event within
that organizer.
Set to ``pretix.base.plugins.PLUGIN_LEVEL_EVENT_ORGANIZER_HYBRID`` for plugins that
can be activated at organizer level but are considered active only within events
for which they have also been specifically activated.
More levels, e.g. user-level plugins, might be invented in the future.
settings_links list List of ``((menu name, submenu name, …), urlname, url_kwargs)`` tuples that point
to the plugin's settings.
navigation_links list List of ``((menu name, submenu name, …), urlname, url_kwargs)`` tuples that point
to the plugin's system pages.
================== ==================== ===========================================================
A working example would be:
@@ -77,9 +63,9 @@ A working example would be:
.. code-block:: python
try:
from pretix.base.plugins import PluginConfig, PLUGIN_LEVEL_EVENT
from pretix.base.plugins import PluginConfig
except ImportError:
raise RuntimeError("Please use pretix 2025.7 or above to run this plugin!")
raise RuntimeError("Please use pretix 2.7 or above to run this plugin!")
from django.utils.translation import gettext_lazy as _
@@ -93,7 +79,6 @@ A working example would be:
version = '1.0.0'
category = 'PAYMENT'
picture = 'pretix_paypal/paypal_logo.svg'
level = PLUGIN_LEVEL_EVENT
visible = True
featured = False
restricted = False
@@ -157,14 +142,14 @@ method to make your receivers available:
from . import signals # NOQA
You can optionally specify code that is executed when your plugin is activated for an event
or organizer in the ``installed`` method:
in the ``installed`` method:
.. code-block:: python
class PaypalApp(AppConfig):
def installed(self, event_or_organizer):
def installed(self, event):
pass # Your code here

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 54 KiB

View File

@@ -23,7 +23,6 @@ partition "For every cart position" {
--> "Store as line_price (gross), tax_rate"
}
--> "Apply discount engine"
--> "Apply tax rounding"
--> "Store as price (gross)"
@enduml

View File

@@ -6,4 +6,4 @@ sphinxcontrib-images
sphinxcontrib-jquery
sphinxcontrib-spelling==8.*
sphinxemoji
pyenchant==3.3.*
pyenchant==3.2.*

View File

@@ -7,4 +7,4 @@ sphinxcontrib-images
sphinxcontrib-jquery
sphinxcontrib-spelling==8.*
sphinxemoji
pyenchant==3.3.*
pyenchant==3.2.*

View File

@@ -3,7 +3,7 @@ name = "pretix"
dynamic = ["version"]
description = "Reinventing presales, one ticket at a time"
readme = "README.rst"
requires-python = ">=3.10"
requires-python = ">=3.9"
license = {file = "LICENSE"}
keywords = ["tickets", "web", "shop", "ecommerce"]
authors = [
@@ -22,30 +22,29 @@ classifiers = [
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Framework :: Django :: 5.2",
"Framework :: Django :: 4.2",
]
dependencies = [
"arabic-reshaper==3.0.0", # Support for Arabic in reportlab
"babel",
"BeautifulSoup4==4.14.*",
"BeautifulSoup4==4.13.*",
"bleach==6.2.*",
"celery==5.6.*",
"celery==5.5.*",
"chardet==5.2.*",
"cryptography>=44.0.0",
"css-inline==0.18.*",
"css-inline==0.16.*",
"defusedcsv>=1.1.0",
"dnspython==2.*",
"Django[argon2]==5.2.*",
"django-bootstrap3==25.2",
"Django[argon2]==4.2.*,>=4.2.15",
"django-bootstrap3==25.1",
"django-compressor==4.5.1",
"django-countries==7.6.*",
"django-filter==25.1",
"django-formset-js-improved==0.5.0.4",
"django-formset-js-improved==0.5.0.3",
"django-formtools==2.5.1",
"django-hierarkey==2.0.*,>=2.0.1",
"django-hierarkey==1.2.*",
"django-hijack==3.7.*",
"django-i18nfield==1.11.*",
"django-i18nfield==1.10.*",
"django-libsass==0.9",
"django-localflavor==5.0",
"django-markup",
@@ -62,10 +61,10 @@ dependencies = [
"importlib_metadata==8.*", # Polyfill, we can probably drop this once we require Python 3.10+
"isoweek",
"jsonschema",
"kombu==5.6.*",
"kombu==5.5.*",
"libsass==0.23.*",
"lxml",
"markdown==3.9", # 3.3.5 requires importlib-metadata>=4.4, but django-bootstrap3 requires importlib-metadata<3.
"markdown==3.8.2", # 3.3.5 requires importlib-metadata>=4.4, but django-bootstrap3 requires importlib-metadata<3.
# We can upgrade markdown again once django-bootstrap3 upgrades or once we drop Python 3.6 and 3.7
"mt-940==4.30.*",
"oauthlib==3.3.*",
@@ -77,50 +76,51 @@ dependencies = [
"phonenumberslite==9.0.*",
"Pillow==11.3.*",
"pretix-plugin-build",
"protobuf==6.33.*",
"protobuf==6.31.*",
"psycopg2-binary",
"pycountry",
"pycparser==2.23",
"pycparser==2.22",
"pycryptodome==3.23.*",
"pypdf==6.4.*",
"pypdf==5.8.*",
"python-bidi==0.6.*", # Support for Arabic in reportlab
"python-dateutil==2.9.*",
"pytz",
"pytz-deprecation-shim==0.1.*",
"pyuca",
"qrcode==8.2",
"redis==6.4.*",
"redis==6.2.*",
"reportlab==4.4.*",
"requests==2.32.*",
"sentry-sdk==2.47.*",
"sepaxml==2.7.*",
"requests==2.31.*",
"sentry-sdk==2.31.*",
"sepaxml==2.6.*",
"stripe==7.9.*",
"text-unidecode==1.*",
"tlds>=2020041600",
"tqdm==4.*",
"ua-parser==1.0.*",
"vat_moss_forked==2020.3.20.0.11.0",
"vobject==0.9.*",
"webauthn==2.7.*",
"webauthn==2.6.*",
"zeep==4.3.*"
]
[project.optional-dependencies]
memcached = ["pylibmc"]
dev = [
"aiohttp==3.13.*",
"aiohttp==3.12.*",
"coverage",
"coveralls",
"fakeredis==2.32.*",
"fakeredis==2.30.*",
"flake8==7.3.*",
"freezegun",
"isort==6.1.*",
"isort==6.0.*",
"pep8-naming==0.15.*",
"potypo",
"pytest-asyncio>=0.24",
"pytest-cache",
"pytest-cov",
"pytest-django==4.*",
"pytest-mock==3.15.*",
"pytest-mock==3.14.*",
"pytest-sugar",
"pytest-xdist==3.8.*",
"pytest==8.4.*",

View File

@@ -25,8 +25,8 @@ coverage:
coverage run -m py.test
npminstall:
# keep this in sync with pretix/_build.py!
# keep this in sync with setup.py!
mkdir -p pretix/static.dist/node_prefix/
cp -r pretix/static/npm_dir/* pretix/static.dist/node_prefix/
npm ci --prefix=pretix/static.dist/node_prefix
npm install --prefix=pretix/static.dist/node_prefix

View File

@@ -2,8 +2,8 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.
@@ -19,4 +19,4 @@
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
# <https://www.gnu.org/licenses/>.
#
__version__ = "2025.11.0.dev0"
__version__ = "2025.7.0.dev0"

View File

@@ -1,8 +1,8 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.
@@ -39,7 +39,7 @@ def npm_install():
node_prefix = os.path.join(here, 'static.dist', 'node_prefix')
os.makedirs(node_prefix, exist_ok=True)
shutil.copytree(os.path.join(here, 'static', 'npm_dir'), node_prefix, dirs_exist_ok=True)
subprocess.check_call('npm ci', shell=True, cwd=node_prefix)
subprocess.check_call('npm install', shell=True, cwd=node_prefix)
npm_installed = True

View File

@@ -1,8 +1,8 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,23 +0,0 @@
# Generated by Django 4.2.24 on 2025-11-14 16:21
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("pretixapi", "0013_alter_webhookcallretry_retry_not_before"),
]
operations = [
migrations.AlterField(
model_name="webhook",
name="target_url",
field=models.URLField(max_length=1024),
),
migrations.AlterField(
model_name="webhookcall",
name="target_url",
field=models.URLField(max_length=1024),
),
]

View File

@@ -1,8 +1,8 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.
@@ -114,7 +114,7 @@ class OAuthRefreshToken(AbstractRefreshToken):
class WebHook(models.Model):
organizer = models.ForeignKey('pretixbase.Organizer', on_delete=models.CASCADE, related_name='webhooks')
enabled = models.BooleanField(default=True, verbose_name=_("Enable webhook"))
target_url = models.URLField(verbose_name=_("Target URL"), max_length=1024)
target_url = models.URLField(verbose_name=_("Target URL"), max_length=255)
all_events = models.BooleanField(default=True, verbose_name=_("All events (including newly created ones)"))
limit_events = models.ManyToManyField('pretixbase.Event', verbose_name=_("Limit to events"), blank=True)
comment = models.CharField(verbose_name=_("Comment"), max_length=255, null=True, blank=True)
@@ -140,7 +140,7 @@ class WebHookEventListener(models.Model):
class WebHookCall(models.Model):
webhook = models.ForeignKey('WebHook', on_delete=models.CASCADE, related_name='calls')
datetime = models.DateTimeField(auto_now_add=True)
target_url = models.URLField(max_length=1024)
target_url = models.URLField(max_length=255)
action_type = models.CharField(max_length=255)
is_retry = models.BooleanField(default=False)
execution_time = models.FloatField(null=True)

View File

@@ -1,8 +1,8 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.
@@ -104,14 +104,3 @@ class MiniCheckinListSerializer(I18nAwareModelSerializer):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
class CheckinRPCAnnulInputSerializer(serializers.Serializer):
lists = serializers.PrimaryKeyRelatedField(required=True, many=True, queryset=CheckinList.objects.none())
nonce = serializers.CharField(required=True, allow_null=False)
datetime = serializers.DateTimeField(required=False, allow_null=True)
error_explanation = serializers.CharField(required=False, allow_null=True)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['lists'].child_relation.queryset = CheckinList.objects.filter(event__in=self.context['events']).select_related('event')

View File

@@ -1,8 +1,8 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.
@@ -50,7 +50,6 @@ from rest_framework.relations import SlugRelatedField
from pretix.api.serializers import (
CompatibleJSONField, SalesChannelMigrationMixin,
)
from pretix.api.serializers.fields import PluginsField
from pretix.api.serializers.i18n import I18nAwareModelSerializer
from pretix.api.serializers.settings import SettingsSerializer
from pretix.base.models import (
@@ -62,9 +61,6 @@ from pretix.base.models.items import (
ItemMetaProperty, SubEventItem, SubEventItemVariation,
)
from pretix.base.models.tax import CustomRulesValidator
from pretix.base.plugins import (
PLUGIN_LEVEL_EVENT, PLUGIN_LEVEL_EVENT_ORGANIZER_HYBRID,
)
from pretix.base.services.seating import (
SeatProtected, generate_seats, validate_plan_change,
)
@@ -130,6 +126,22 @@ class SeatCategoryMappingField(Field):
}
class PluginsField(Field):
def to_representation(self, obj):
from pretix.base.plugins import get_all_plugins
return sorted([
p.module for p in get_all_plugins()
if not p.name.startswith('.') and getattr(p, 'visible', True) and p.module in obj.get_plugins()
])
def to_internal_value(self, data):
return {
'plugins': data
}
class TimeZoneField(ChoiceField):
def get_attribute(self, instance):
return instance.cache.get_or_set(
@@ -271,28 +283,17 @@ class EventSerializer(SalesChannelMigrationMixin, I18nAwareModelSerializer):
from pretix.base.plugins import get_all_plugins
plugins_available = {
p.module: p for p in get_all_plugins(event=self.instance)
p.module: p for p in get_all_plugins(self.instance)
if not p.name.startswith('.') and getattr(p, 'visible', True)
}
current_plugins = self.instance.get_plugins() if self.instance and self.instance.pk else []
settings_holder = self.instance if self.instance and self.instance.pk else self.context['organizer']
allowed_levels = (PLUGIN_LEVEL_EVENT, PLUGIN_LEVEL_EVENT_ORGANIZER_HYBRID)
for plugin in value.get('plugins'):
if plugin not in plugins_available:
raise ValidationError(_('Unknown plugin: \'{name}\'.').format(name=plugin))
if getattr(plugins_available[plugin], 'restricted', False):
if plugin not in settings_holder.settings.allowed_restricted_plugins:
raise ValidationError(_('Restricted plugin: \'{name}\'.').format(name=plugin))
level = getattr(plugins_available[plugin], 'level', PLUGIN_LEVEL_EVENT)
if level not in allowed_levels:
raise ValidationError('Plugin cannot be enabled on this level: \'{name}\'.'.format(name=plugin))
if level == PLUGIN_LEVEL_EVENT_ORGANIZER_HYBRID and plugin not in self.context['organizer'].get_plugins():
if plugin not in current_plugins:
# Technically, this is allowed, but consumers might be confused if the API call doesn't do anything
# so we prevent this change.
raise ValidationError('Plugin should be enabled on organizer level first: \'{name}\'.'.format(name=plugin))
return value
@@ -795,7 +796,6 @@ class EventSettingsSerializer(SettingsSerializer):
'invoice_address_asked',
'invoice_address_required',
'invoice_address_vatid',
'invoice_address_vatid_required_countries',
'invoice_address_company_required',
'invoice_address_beneficiary',
'invoice_address_custom_field',
@@ -806,7 +806,6 @@ class EventSettingsSerializer(SettingsSerializer):
'invoice_reissue_after_modify',
'invoice_include_free',
'invoice_generate',
'invoice_period',
'invoice_numbers_consecutive',
'invoice_numbers_prefix',
'invoice_numbers_prefix_cancellations',
@@ -821,7 +820,6 @@ class EventSettingsSerializer(SettingsSerializer):
'invoice_address_from',
'invoice_address_from_zipcode',
'invoice_address_from_city',
'invoice_address_from_state',
'invoice_address_from_country',
'invoice_address_from_tax_id',
'invoice_address_from_vat_id',
@@ -831,7 +829,6 @@ class EventSettingsSerializer(SettingsSerializer):
'invoice_eu_currencies',
'invoice_logo_image',
'invoice_renderer_highlight_order_code',
'tax_rounding',
'cancel_allow_user',
'cancel_allow_user_until',
'cancel_allow_user_unpaid_keep',
@@ -944,7 +941,6 @@ class DeviceEventSettingsSerializer(EventSettingsSerializer):
'invoice_address_asked',
'invoice_address_required',
'invoice_address_vatid',
'invoice_address_vatid_required_countries',
'invoice_address_company_required',
'invoice_address_beneficiary',
'invoice_address_custom_field',
@@ -955,7 +951,6 @@ class DeviceEventSettingsSerializer(EventSettingsSerializer):
'invoice_address_from',
'invoice_address_from_zipcode',
'invoice_address_from_city',
'invoice_address_from_state',
'invoice_address_from_country',
'invoice_address_from_tax_id',
'invoice_address_from_vat_id',

View File

@@ -1,8 +1,8 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.
@@ -19,16 +19,45 @@
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
# <https://www.gnu.org/licenses/>.
#
from django import forms
from django.conf import settings
from django.http import QueryDict
from pytz import common_timezones
from rest_framework import serializers
from rest_framework.exceptions import ValidationError
from pretix.api.serializers.forms import form_field_to_serializer_field
from pretix.base.exporter import OrganizerLevelExportMixin
from pretix.base.models import ScheduledEventExport, ScheduledOrganizerExport
from pretix.base.timeframes import SerializerDateFrameField
from pretix.base.timeframes import DateFrameField, SerializerDateFrameField
class FormFieldWrapperField(serializers.Field):
def __init__(self, *args, **kwargs):
self.form_field = kwargs.pop('form_field')
super().__init__(*args, **kwargs)
def to_representation(self, value):
return self.form_field.widget.format_value(value)
def to_internal_value(self, data):
d = self.form_field.widget.value_from_datadict({'name': data}, {}, 'name')
d = self.form_field.clean(d)
return d
simple_mappings = (
(forms.DateField, serializers.DateField, ()),
(forms.TimeField, serializers.TimeField, ()),
(forms.SplitDateTimeField, serializers.DateTimeField, ()),
(forms.DateTimeField, serializers.DateTimeField, ()),
(forms.DecimalField, serializers.DecimalField, ('max_digits', 'decimal_places', 'min_value', 'max_value')),
(forms.FloatField, serializers.FloatField, ()),
(forms.IntegerField, serializers.IntegerField, ()),
(forms.EmailField, serializers.EmailField, ()),
(forms.UUIDField, serializers.UUIDField, ()),
(forms.URLField, serializers.URLField, ()),
(forms.BooleanField, serializers.BooleanField, ()),
)
class SerializerDescriptionField(serializers.Field):
@@ -52,6 +81,13 @@ class ExporterSerializer(serializers.Serializer):
input_parameters = SerializerDescriptionField(source='_serializer')
class PrimaryKeyRelatedField(serializers.PrimaryKeyRelatedField):
def to_representation(self, value):
if isinstance(value, int):
return value
return super().to_representation(value)
class JobRunSerializer(serializers.Serializer):
def __init__(self, *args, **kwargs):
ex = kwargs.pop('exporter')
@@ -66,7 +102,59 @@ class JobRunSerializer(serializers.Serializer):
many=True
)
for k, v in ex.export_form_fields.items():
self.fields[k] = form_field_to_serializer_field(v)
for m_from, m_to, m_kwargs in simple_mappings:
if isinstance(v, m_from):
self.fields[k] = m_to(
required=v.required,
allow_null=not v.required,
validators=v.validators,
**{kwarg: getattr(v, kwargs, None) for kwarg in m_kwargs}
)
break
if isinstance(v, forms.NullBooleanField):
self.fields[k] = serializers.BooleanField(
required=v.required,
allow_null=True,
validators=v.validators,
)
if isinstance(v, forms.ModelMultipleChoiceField):
self.fields[k] = PrimaryKeyRelatedField(
queryset=v.queryset,
required=v.required,
allow_empty=not v.required,
validators=v.validators,
many=True
)
elif isinstance(v, forms.ModelChoiceField):
self.fields[k] = PrimaryKeyRelatedField(
queryset=v.queryset,
required=v.required,
allow_null=not v.required,
validators=v.validators,
)
elif isinstance(v, forms.MultipleChoiceField):
self.fields[k] = serializers.MultipleChoiceField(
choices=v.choices,
required=v.required,
allow_empty=not v.required,
validators=v.validators,
)
elif isinstance(v, forms.ChoiceField):
self.fields[k] = serializers.ChoiceField(
choices=v.choices,
required=v.required,
allow_null=not v.required,
validators=v.validators,
)
elif isinstance(v, DateFrameField):
self.fields[k] = SerializerDateFrameField(
required=v.required,
allow_null=not v.required,
validators=v.validators,
)
else:
self.fields[k] = FormFieldWrapperField(form_field=v, required=v.required, allow_null=not v.required)
def to_internal_value(self, data):
if isinstance(data, QueryDict):

View File

@@ -1,8 +1,8 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.
@@ -109,19 +109,3 @@ class UploadedFileField(serializers.Field):
return None
request = self.context['request']
return request.build_absolute_uri(url)
class PluginsField(serializers.Field):
def to_representation(self, obj):
from pretix.base.plugins import get_all_plugins
return sorted([
p.module for p in get_all_plugins()
if not p.name.startswith('.') and getattr(p, 'visible', True) and p.module in obj.get_plugins()
])
def to_internal_value(self, data):
return {
'plugins': data
}

View File

@@ -1,115 +0,0 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.
#
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
# this file, see <https://pretix.eu/about/en/license>.
#
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
# <https://www.gnu.org/licenses/>.
#
from django import forms
from rest_framework import serializers
from pretix.base.timeframes import DateFrameField, SerializerDateFrameField
simple_mappings = (
(forms.DateField, serializers.DateField, ()),
(forms.TimeField, serializers.TimeField, ()),
(forms.SplitDateTimeField, serializers.DateTimeField, ()),
(forms.DateTimeField, serializers.DateTimeField, ()),
(forms.DecimalField, serializers.DecimalField, ('max_digits', 'decimal_places', 'min_value', 'max_value')),
(forms.FloatField, serializers.FloatField, ()),
(forms.IntegerField, serializers.IntegerField, ()),
(forms.EmailField, serializers.EmailField, ()),
(forms.UUIDField, serializers.UUIDField, ()),
(forms.URLField, serializers.URLField, ()),
(forms.BooleanField, serializers.BooleanField, ()),
)
class PrimaryKeyRelatedField(serializers.PrimaryKeyRelatedField):
def to_representation(self, value):
if isinstance(value, int):
return value
return super().to_representation(value)
class FormFieldWrapperField(serializers.Field):
def __init__(self, *args, **kwargs):
self.form_field = kwargs.pop('form_field')
super().__init__(*args, **kwargs)
def to_representation(self, value):
return self.form_field.widget.format_value(value)
def to_internal_value(self, data):
d = self.form_field.widget.value_from_datadict({'name': data}, {}, 'name')
d = self.form_field.clean(d)
return d
def form_field_to_serializer_field(field):
for m_from, m_to, m_kwargs in simple_mappings:
if isinstance(field, m_from):
return m_to(
required=field.required,
allow_null=not field.required,
validators=field.validators,
**{kwarg: getattr(field, kwarg, None) for kwarg in m_kwargs}
)
if isinstance(field, forms.NullBooleanField):
return serializers.BooleanField(
required=field.required,
allow_null=True,
validators=field.validators,
)
if isinstance(field, forms.ModelMultipleChoiceField):
return PrimaryKeyRelatedField(
queryset=field.queryset,
required=field.required,
allow_empty=not field.required,
validators=field.validators,
many=True
)
elif isinstance(field, forms.ModelChoiceField):
return PrimaryKeyRelatedField(
queryset=field.queryset,
required=field.required,
allow_null=not field.required,
validators=field.validators,
)
elif isinstance(field, forms.MultipleChoiceField):
return serializers.MultipleChoiceField(
choices=field.choices,
required=field.required,
allow_empty=not field.required,
validators=field.validators,
)
elif isinstance(field, forms.ChoiceField):
return serializers.ChoiceField(
choices=field.choices,
required=field.required,
allow_null=not field.required,
validators=field.validators,
)
elif isinstance(field, DateFrameField):
return SerializerDateFrameField(
required=field.required,
allow_null=not field.required,
validators=field.validators,
)
else:
return FormFieldWrapperField(form_field=field, required=field.required, allow_null=not field.required)

View File

@@ -1,8 +1,8 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.
@@ -47,9 +47,8 @@ from pretix.api.serializers.event import MetaDataField
from pretix.api.serializers.fields import UploadedFileField
from pretix.api.serializers.i18n import I18nAwareModelSerializer
from pretix.base.models import (
Item, ItemAddOn, ItemBundle, ItemCategory, ItemMetaValue, ItemProgramTime,
ItemVariation, ItemVariationMetaValue, Question, QuestionOption, Quota,
SalesChannel,
Item, ItemAddOn, ItemBundle, ItemCategory, ItemMetaValue, ItemVariation,
ItemVariationMetaValue, Question, QuestionOption, Quota, SalesChannel,
)
@@ -188,12 +187,6 @@ class InlineItemAddOnSerializer(serializers.ModelSerializer):
'position', 'price_included', 'multi_allowed')
class InlineItemProgramTimeSerializer(serializers.ModelSerializer):
class Meta:
model = ItemProgramTime
fields = ('start', 'end')
class ItemBundleSerializer(serializers.ModelSerializer):
class Meta:
model = ItemBundle
@@ -219,37 +212,6 @@ class ItemBundleSerializer(serializers.ModelSerializer):
return data
class ItemProgramTimeSerializer(serializers.ModelSerializer):
class Meta:
model = ItemProgramTime
fields = ('id', 'start', 'end')
def validate(self, data):
data = super().validate(data)
full_data = self.to_internal_value(self.to_representation(self.instance)) if self.instance else {}
full_data.update(data)
start = full_data.get('start')
if not start:
raise ValidationError(_("The program start must not be empty."))
end = full_data.get('end')
if not end:
raise ValidationError(_("The program end must not be empty."))
if start > end:
raise ValidationError(_("The program end must not be before the program start."))
event = self.context['event']
if event.has_subevents:
raise ValidationError({
_("You cannot use program times on an event series.")
})
return data
class ItemAddOnSerializer(serializers.ModelSerializer):
class Meta:
model = ItemAddOn
@@ -288,7 +250,6 @@ class ItemSerializer(SalesChannelMigrationMixin, I18nAwareModelSerializer):
addons = InlineItemAddOnSerializer(many=True, required=False)
bundles = InlineItemBundleSerializer(many=True, required=False)
variations = InlineItemVariationSerializer(many=True, required=False)
program_times = InlineItemProgramTimeSerializer(many=True, required=False)
tax_rate = ItemTaxRateField(source='*', read_only=True)
meta_data = MetaDataField(required=False, source='*')
picture = UploadedFileField(required=False, allow_null=True, allowed_types=(
@@ -310,7 +271,7 @@ class ItemSerializer(SalesChannelMigrationMixin, I18nAwareModelSerializer):
'available_from', 'available_from_mode', 'available_until', 'available_until_mode',
'require_voucher', 'hide_without_voucher', 'allow_cancel', 'require_bundling',
'min_per_order', 'max_per_order', 'checkin_attention', 'checkin_text', 'has_variations', 'variations',
'addons', 'bundles', 'program_times', 'original_price', 'require_approval', 'generate_tickets',
'addons', 'bundles', 'original_price', 'require_approval', 'generate_tickets',
'show_quota_left', 'hidden_if_available', 'hidden_if_item_available', 'hidden_if_item_available_mode', 'allow_waitinglist',
'issue_giftcard', 'meta_data',
'require_membership', 'require_membership_types', 'require_membership_hidden', 'grant_membership_type',
@@ -333,9 +294,9 @@ class ItemSerializer(SalesChannelMigrationMixin, I18nAwareModelSerializer):
def validate(self, data):
data = super().validate(data)
if self.instance and ('addons' in data or 'variations' in data or 'bundles' in data or 'program_times' in data):
raise ValidationError(_('Updating add-ons, bundles, program times or variations via PATCH/PUT is not '
'supported. Please use the dedicated nested endpoint.'))
if self.instance and ('addons' in data or 'variations' in data or 'bundles' in data):
raise ValidationError(_('Updating add-ons, bundles, or variations via PATCH/PUT is not supported. Please use the '
'dedicated nested endpoint.'))
Item.clean_per_order(data.get('min_per_order'), data.get('max_per_order'))
Item.clean_available(data.get('available_from'), data.get('available_until'))
@@ -386,13 +347,6 @@ class ItemSerializer(SalesChannelMigrationMixin, I18nAwareModelSerializer):
ItemAddOn.clean_max_min_count(addon_data.get('max_count', 0), addon_data.get('min_count', 0))
return value
def validate_program_times(self, value):
if not self.instance:
for program_time_data in value:
ItemProgramTime.clean_start_end(self, start=program_time_data.get('start', None),
end=program_time_data.get('end', None))
return value
@cached_property
def item_meta_properties(self):
return {
@@ -410,7 +364,6 @@ class ItemSerializer(SalesChannelMigrationMixin, I18nAwareModelSerializer):
variations_data = validated_data.pop('variations') if 'variations' in validated_data else {}
addons_data = validated_data.pop('addons') if 'addons' in validated_data else {}
bundles_data = validated_data.pop('bundles') if 'bundles' in validated_data else {}
program_times_data = validated_data.pop('program_times') if 'program_times' in validated_data else {}
meta_data = validated_data.pop('meta_data', None)
picture = validated_data.pop('picture', None)
require_membership_types = validated_data.pop('require_membership_types', [])
@@ -445,8 +398,6 @@ class ItemSerializer(SalesChannelMigrationMixin, I18nAwareModelSerializer):
ItemAddOn.objects.create(base_item=item, **addon_data)
for bundle_data in bundles_data:
ItemBundle.objects.create(base_item=item, **bundle_data)
for program_time_data in program_times_data:
ItemProgramTime.objects.create(item=item, **program_time_data)
# Meta data
if meta_data is not None:
@@ -599,7 +550,7 @@ class QuestionSerializer(I18nAwareModelSerializer):
if full_data.get('show_during_checkin') and full_data.get('type') in Question.SHOW_DURING_CHECKIN_UNSUPPORTED:
raise ValidationError(_('This type of question cannot be shown during check-in.'))
Question.clean_items(event, full_data.get('items') or [])
Question.clean_items(event, full_data.get('items'))
return data
def validate_options(self, value):
@@ -615,7 +566,7 @@ class QuestionSerializer(I18nAwareModelSerializer):
@transaction.atomic
def create(self, validated_data):
options_data = validated_data.pop('options') if 'options' in validated_data else []
items = validated_data.pop('items', [])
items = validated_data.pop('items')
question = Question.objects.create(**validated_data)
question.items.set(items)

View File

@@ -1,8 +1,8 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.
@@ -42,7 +42,6 @@ from rest_framework.reverse import reverse
from pretix.api.serializers import CompatibleJSONField
from pretix.api.serializers.event import SubEventSerializer
from pretix.api.serializers.forms import form_field_to_serializer_field
from pretix.api.serializers.i18n import I18nAwareModelSerializer
from pretix.api.serializers.item import (
InlineItemVariationSerializer, ItemSerializer, QuestionSerializer,
@@ -50,12 +49,10 @@ from pretix.api.serializers.item import (
from pretix.api.signals import order_api_details, orderposition_api_details
from pretix.base.decimal import round_decimal
from pretix.base.i18n import language
from pretix.base.invoicing.transmission import get_transmission_types
from pretix.base.models import (
CachedFile, Checkin, Customer, Device, Invoice, InvoiceAddress,
InvoiceLine, Item, ItemVariation, Order, OrderPosition, Question,
QuestionAnswer, ReusableMedium, SalesChannel, Seat, SubEvent, TaxRule,
Voucher,
CachedFile, Checkin, Customer, Invoice, InvoiceAddress, InvoiceLine, Item,
ItemVariation, Order, OrderPosition, Question, QuestionAnswer,
ReusableMedium, SalesChannel, Seat, SubEvent, TaxRule, Voucher,
)
from pretix.base.models.orders import (
BlockedTicketSecret, CartPosition, OrderFee, OrderPayment, OrderRefund,
@@ -65,13 +62,10 @@ from pretix.base.pdf import get_images, get_variables
from pretix.base.services.cart import error_messages
from pretix.base.services.locking import LOCK_TRUST_WINDOW, lock_objects
from pretix.base.services.pricing import (
apply_discounts, apply_rounding, get_line_price, get_listed_price,
is_included_for_free,
apply_discounts, get_line_price, get_listed_price, is_included_for_free,
)
from pretix.base.services.quotas import QuotaAvailability
from pretix.base.settings import (
COUNTRIES_WITH_STATE_IN_ADDRESS, ROUNDING_MODES,
)
from pretix.base.settings import COUNTRIES_WITH_STATE_IN_ADDRESS
from pretix.base.signals import register_ticket_outputs
from pretix.helpers.countries import CachedCountries
from pretix.multidomain.urlreverse import build_absolute_uri
@@ -108,13 +102,6 @@ class CountryField(serializers.Field):
return str(src) if src else None
class TransmissionInfoSerializer(serializers.Serializer):
def __init__(self, *args, transmission_type, **kwargs):
super().__init__(*args, **kwargs)
for k, v in transmission_type.invoice_address_form_fields.items():
self.fields[k] = form_field_to_serializer_field(v)
class InvoiceAddressSerializer(I18nAwareModelSerializer):
country = CompatibleCountryField(source='*')
name = serializers.CharField(required=False)
@@ -122,8 +109,7 @@ class InvoiceAddressSerializer(I18nAwareModelSerializer):
class Meta:
model = InvoiceAddress
fields = ('last_modified', 'is_business', 'company', 'name', 'name_parts', 'street', 'zipcode', 'city', 'country',
'state', 'vat_id', 'vat_id_validated', 'custom_field', 'internal_reference', 'transmission_type',
'transmission_info')
'state', 'vat_id', 'vat_id_validated', 'custom_field', 'internal_reference')
read_only_fields = ('last_modified',)
def __init__(self, *args, **kwargs):
@@ -161,48 +147,6 @@ class InvoiceAddressSerializer(I18nAwareModelSerializer):
{'state': ['"{}" is not a known subdivision of the country "{}".'.format(data.get('state'), cc)]}
)
if data.get("transmission_type"):
for t in get_transmission_types():
if data.get("transmission_type") == t.identifier:
if not t.is_available(self.context["request"].event, data.get("country"), data.get("is_business")):
raise ValidationError({
"transmission_type": "The selected transmission type is not available for this country or address type."
})
ts = TransmissionInfoSerializer(transmission_type=t, data=data.get("transmission_info", {}))
try:
ts.is_valid(raise_exception=True)
except ValidationError as e:
raise ValidationError(
{"transmission_info": e.detail}
)
data["transmission_info"] = ts.validated_data
required_fields = t.invoice_address_form_fields_required(data.get("country"), data.get("is_business"))
for r in required_fields:
if r in self.fields:
if not data.get(r):
raise ValidationError(
{r: "This field is required for the selected type of invoice transmission."}
)
else:
if not ts.validated_data.get(r):
raise ValidationError(
{"transmission_info": {r: "This field is required for the selected type of invoice transmission."}}
)
break # do not call else branch of for loop
elif t.exclusive:
if t.is_available(self.context["request"].event, data.get("country"), data.get("is_business")):
raise ValidationError({
"transmission_type": "The transmission type '%s' must be used for this country or address type." % (
t.identifier,
)
})
else:
raise ValidationError(
{"transmission_type": "Unknown transmission type."}
)
return data
@@ -329,18 +273,6 @@ class AnswerSerializer(I18nAwareModelSerializer):
return data
class InlineCheckinSerializer(I18nAwareModelSerializer):
device_id = serializers.SlugRelatedField(
source='device',
slug_field='device_id',
read_only=True,
)
class Meta:
model = Checkin
fields = ('id', 'datetime', 'list', 'auto_checked_in', 'gate', 'device', 'device_id', 'type')
class CheckinSerializer(I18nAwareModelSerializer):
device_id = serializers.SlugRelatedField(
source='device',
@@ -350,10 +282,7 @@ class CheckinSerializer(I18nAwareModelSerializer):
class Meta:
model = Checkin
fields = (
'id', 'successful', 'error_reason', 'error_explanation', 'position', 'datetime', 'list', 'created',
'auto_checked_in', 'gate', 'device', 'device_id', 'type'
)
fields = ('id', 'datetime', 'list', 'auto_checked_in', 'gate', 'device', 'device_id', 'type')
class PrintLogSerializer(serializers.ModelSerializer):
@@ -579,7 +508,7 @@ class OrderPositionPluginDataField(serializers.Field):
class OrderPositionSerializer(I18nAwareModelSerializer):
checkins = InlineCheckinSerializer(many=True, read_only=True)
checkins = CheckinSerializer(many=True, read_only=True)
print_logs = PrintLogSerializer(many=True, read_only=True)
answers = AnswerSerializer(many=True)
downloads = PositionDownloadsField(source='*', read_only=True)
@@ -852,15 +781,14 @@ class OrderSerializer(I18nAwareModelSerializer):
list_serializer_class = OrderListSerializer
fields = (
'code', 'event', 'status', 'testmode', 'secret', 'email', 'phone', 'locale', 'datetime', 'expires', 'payment_date',
'payment_provider', 'fees', 'total', 'tax_rounding_mode', 'comment', 'custom_followup_at', 'invoice_address',
'positions', 'downloads', 'checkin_attention', 'checkin_text', 'last_modified', 'payments', 'refunds',
'require_approval', 'sales_channel', 'url', 'customer', 'valid_if_pending', 'api_meta', 'cancellation_date',
'plugin_data',
'payment_provider', 'fees', 'total', 'comment', 'custom_followup_at', 'invoice_address', 'positions', 'downloads',
'checkin_attention', 'checkin_text', 'last_modified', 'payments', 'refunds', 'require_approval', 'sales_channel',
'url', 'customer', 'valid_if_pending', 'api_meta', 'cancellation_date', 'plugin_data',
)
read_only_fields = (
'code', 'status', 'testmode', 'secret', 'datetime', 'expires', 'payment_date',
'payment_provider', 'fees', 'total', 'tax_rounding_mode', 'positions', 'downloads', 'customer',
'last_modified', 'payments', 'refunds', 'require_approval', 'sales_channel', 'cancellation_date',
'payment_provider', 'fees', 'total', 'positions', 'downloads', 'customer',
'last_modified', 'payments', 'refunds', 'require_approval', 'sales_channel', 'cancellation_date'
)
def __init__(self, *args, **kwargs):
@@ -1025,7 +953,7 @@ class OrderPositionCreateSerializer(I18nAwareModelSerializer):
fields = ('positionid', 'item', 'variation', 'price', 'attendee_name', 'attendee_name_parts', 'attendee_email',
'company', 'street', 'zipcode', 'city', 'country', 'state', 'is_bundled',
'secret', 'addon_to', 'subevent', 'answers', 'seat', 'voucher', 'valid_from', 'valid_until',
'requested_valid_from', 'use_reusable_medium', 'discount')
'requested_valid_from', 'use_reusable_medium')
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -1121,10 +1049,6 @@ class OrderPositionCreateSerializer(I18nAwareModelSerializer):
{'state': ['"{}" is not a known subdivision of the country "{}".'.format(data.get('state'), cc)]}
)
if data.get('price') is None and data.get('discount'):
raise ValidationError(
{'discount': ['You can only specify a discount if you do the price computation, but price is not set.']}
)
return data
@@ -1179,13 +1103,11 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
queryset=SalesChannel.objects.none(),
required=False,
)
tax_rounding_mode = serializers.ChoiceField(choices=ROUNDING_MODES, allow_null=True, required=False,)
locale = serializers.ChoiceField(choices=[], required=False, allow_null=True)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['positions'].child.fields['voucher'].queryset = self.context['event'].vouchers.all()
self.fields['positions'].child.fields['discount'].queryset = self.context['event'].discounts.all()
self.fields['customer'].queryset = self.context['event'].organizer.customers.all()
self.fields['expires'].required = False
self.fields["sales_channel"].queryset = self.context["event"].organizer.sales_channels.all()
@@ -1196,7 +1118,7 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
fields = ('code', 'status', 'testmode', 'email', 'phone', 'locale', 'payment_provider', 'fees', 'comment', 'sales_channel',
'invoice_address', 'positions', 'checkin_attention', 'checkin_text', 'payment_info', 'payment_date',
'consume_carts', 'force', 'send_email', 'simulate', 'customer', 'custom_followup_at',
'require_approval', 'valid_if_pending', 'expires', 'api_meta', 'tax_rounding_mode')
'require_approval', 'valid_if_pending', 'expires', 'api_meta')
def validate_payment_provider(self, pp):
if pp is None:
@@ -1593,22 +1515,19 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
pos.voucher_budget_use = max(listed_price - price_after_voucher, Decimal('0.00'))
order_positions = [pos_data['__instance'] for pos_data in positions_data]
if not any([p.get("discount") for p in positions_data]):
# If any discount is set by the client (i.e. pretixPOS), we do not recalculate but believe the client
# to avoid differences in end results.
discount_results = apply_discounts(
self.context['event'],
order.sales_channel,
[
(cp.item_id, cp.subevent_id, cp.subevent.date_from if cp.subevent_id else None, cp.price,
bool(cp.addon_to), cp.is_bundled, pos._voucher_discount)
for cp in order_positions
]
)
for cp, (new_price, discount) in zip(order_positions, discount_results):
if new_price != pos.price and pos._auto_generated_price:
pos.price = new_price
pos.discount = discount
discount_results = apply_discounts(
self.context['event'],
order.sales_channel,
[
(cp.item_id, cp.subevent_id, cp.subevent.date_from if cp.subevent_id else None, cp.price,
bool(cp.addon_to), cp.is_bundled, pos._voucher_discount)
for cp in order_positions
]
)
for cp, (new_price, discount) in zip(order_positions, discount_results):
if new_price != pos.price and pos._auto_generated_price:
pos.price = new_price
pos.discount = discount
# Save instances
for pos_data in positions_data:
@@ -1722,31 +1641,7 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
else:
f.save()
rounding_mode = validated_data.get("tax_rounding_mode")
if not rounding_mode:
if isinstance(self.context.get("auth"), Device):
# Safety fallback to avoid differences in tax reporting
brand = self.context.get("auth").software_brand or ""
if "pretixPOS" in brand or "pretixKIOSK" in brand:
rounding_mode = "line"
if not rounding_mode:
rounding_mode = self.context["event"].settings.tax_rounding
changed = apply_rounding(
rounding_mode,
self.context["event"].currency,
[*pos_map.values(), *fees]
)
for line in changed:
if isinstance(line, OrderPosition):
line.save(update_fields=[
"price", "price_includes_rounding_correction", "tax_value", "tax_value_includes_rounding_correction"
])
elif isinstance(line, OrderFee):
line.save(update_fields=[
"value", "value_includes_rounding_correction", "tax_value", "tax_value_includes_rounding_correction"
])
order.total = sum([c.price for c in pos_map.values()]) + sum([f.value for f in fees])
order.total += sum([f.value for f in fees])
if simulate:
order.fees = fees
order.positions = pos_map.values()
@@ -1810,14 +1705,12 @@ class LinePositionField(serializers.IntegerField):
class InlineInvoiceLineSerializer(I18nAwareModelSerializer):
position = LinePositionField(read_only=True)
event_date_from = serializers.DateTimeField(read_only=True, source="period_start")
event_date_to = serializers.DateTimeField(read_only=True, source="period_end")
class Meta:
model = InvoiceLine
fields = ('position', 'description', 'item', 'variation', 'subevent', 'attendee_name', 'event_date_from',
'event_date_to', 'period_start', 'period_end', 'gross_value', 'tax_value', 'tax_rate', 'tax_code',
'tax_name', 'fee_type', 'fee_internal_type', 'event_location')
'event_date_to', 'gross_value', 'tax_value', 'tax_rate', 'tax_code', 'tax_name', 'fee_type',
'fee_internal_type', 'event_location')
class InvoiceSerializer(I18nAwareModelSerializer):
@@ -1831,14 +1724,13 @@ class InvoiceSerializer(I18nAwareModelSerializer):
class Meta:
model = Invoice
fields = ('event', 'order', 'number', 'is_cancellation', 'invoice_from', 'invoice_from_name', 'invoice_from_zipcode',
'invoice_from_city', 'invoice_from_state', 'invoice_from_country', 'invoice_from_tax_id', 'invoice_from_vat_id',
'invoice_to', 'invoice_to_is_business', 'invoice_to_company', 'invoice_to_name', 'invoice_to_street',
'invoice_to_zipcode', 'invoice_to_city', 'invoice_to_state', 'invoice_to_country', 'invoice_to_vat_id',
'invoice_to_beneficiary', 'invoice_to_transmission_info', 'custom_field', 'date', 'refers', 'locale',
'invoice_from_city', 'invoice_from_country', 'invoice_from_tax_id', 'invoice_from_vat_id',
'invoice_to', 'invoice_to_company', 'invoice_to_name', 'invoice_to_street', 'invoice_to_zipcode',
'invoice_to_city', 'invoice_to_state', 'invoice_to_country', 'invoice_to_vat_id', 'invoice_to_beneficiary',
'custom_field', 'date', 'refers', 'locale',
'introductory_text', 'additional_text', 'payment_provider_text', 'payment_provider_stamp',
'footer_text', 'lines', 'foreign_currency_display', 'foreign_currency_rate',
'foreign_currency_rate_date', 'internal_reference', 'transmission_type', 'transmission_provider',
'transmission_status', 'transmission_date')
'foreign_currency_rate_date', 'internal_reference')
class OrderPaymentCreateSerializer(I18nAwareModelSerializer):

View File

@@ -1,8 +1,8 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.
@@ -83,7 +83,6 @@ class OrderPositionCreateForExistingOrderSerializer(OrderPositionCreateSerialize
def create(self, validated_data):
ocm = self.context['ocm']
check_quotas = self.context.get('check_quotas', True)
try:
ocm.add_position(
@@ -97,7 +96,7 @@ class OrderPositionCreateForExistingOrderSerializer(OrderPositionCreateSerialize
valid_until=validated_data.get('valid_until'),
)
if self.context.get('commit', True):
ocm.commit(check_quotas=check_quotas)
ocm.commit()
return validated_data['order'].positions.order_by('-positionid').first()
else:
return OrderPosition() # fake to appease DRF
@@ -311,7 +310,6 @@ class OrderPositionChangeSerializer(serializers.ModelSerializer):
def update(self, instance, validated_data):
ocm = self.context['ocm']
check_quotas = self.context.get('check_quotas', True)
current_seat = {'seat_guid': instance.seat.seat_guid} if instance.seat else None
item = validated_data.get('item', instance.item)
variation = validated_data.get('variation', instance.variation)
@@ -358,7 +356,7 @@ class OrderPositionChangeSerializer(serializers.ModelSerializer):
ocm.change_ticket_secret(instance, secret)
if self.context.get('commit', True):
ocm.commit(check_quotas=check_quotas)
ocm.commit()
instance.refresh_from_db()
except OrderError as e:
raise ValidationError(str(e))

View File

@@ -1,8 +1,8 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.
@@ -24,7 +24,6 @@ from decimal import Decimal
from django.conf import settings
from django.core.exceptions import ObjectDoesNotExist
from django.db import transaction
from django.db.models import Q
from django.utils.crypto import get_random_string
from django.utils.translation import gettext_lazy as _
@@ -33,7 +32,6 @@ from rest_framework.exceptions import ValidationError
from pretix.api.auth.devicesecurity import get_all_security_profiles
from pretix.api.serializers import AsymmetricField
from pretix.api.serializers.fields import PluginsField
from pretix.api.serializers.i18n import I18nAwareModelSerializer
from pretix.api.serializers.order import CompatibleJSONField
from pretix.api.serializers.settings import SettingsSerializer
@@ -45,10 +43,6 @@ from pretix.base.models import (
SalesChannel, SeatingPlan, Team, TeamAPIToken, TeamInvite, User,
)
from pretix.base.models.seating import SeatingPlanLayoutValidator
from pretix.base.plugins import (
PLUGIN_LEVEL_EVENT, PLUGIN_LEVEL_EVENT_ORGANIZER_HYBRID,
PLUGIN_LEVEL_ORGANIZER,
)
from pretix.base.services.mail import SendMailException, mail
from pretix.base.settings import validate_organizer_settings
from pretix.helpers.urls import build_absolute_uri as build_global_uri
@@ -59,47 +53,13 @@ logger = logging.getLogger(__name__)
class OrganizerSerializer(I18nAwareModelSerializer):
public_url = serializers.SerializerMethodField('get_organizer_url', read_only=True)
plugins = PluginsField(required=False, source='*')
name = serializers.CharField(read_only=True)
slug = serializers.CharField(read_only=True)
def get_organizer_url(self, organizer):
return build_absolute_uri(organizer, 'presale:organizer.index')
class Meta:
model = Organizer
fields = ('name', 'slug', 'public_url', 'plugins')
def validate_plugins(self, value):
from pretix.base.plugins import get_all_plugins
plugins_available = {
p.module: p for p in get_all_plugins(organizer=self.instance)
if not p.name.startswith('.') and getattr(p, 'visible', True)
}
settings_holder = self.instance
allowed_levels = (PLUGIN_LEVEL_ORGANIZER, PLUGIN_LEVEL_EVENT_ORGANIZER_HYBRID)
for plugin in value.get('plugins'):
if plugin not in plugins_available:
raise ValidationError(_('Unknown plugin: \'{name}\'.').format(name=plugin))
if getattr(plugins_available[plugin], 'restricted', False):
if plugin not in settings_holder.settings.allowed_restricted_plugins:
raise ValidationError(_('Restricted plugin: \'{name}\'.').format(name=plugin))
if getattr(plugins_available[plugin], 'level', PLUGIN_LEVEL_EVENT) not in allowed_levels:
raise ValidationError('Plugin cannot be enabled on this level: \'{name}\'.'.format(name=plugin))
return value
@transaction.atomic
def update(self, instance, validated_data):
plugins = validated_data.pop('plugins', None)
organizer = super().update(instance, validated_data)
# Plugins
if plugins is not None:
organizer.set_active_plugins(plugins)
organizer.save()
return organizer
fields = ('name', 'slug', 'public_url')
class SeatingPlanSerializer(I18nAwareModelSerializer):
@@ -484,7 +444,6 @@ class OrganizerSettingsSerializer(SettingsSerializer):
'reusable_media_type_nfc_mf0aes',
'reusable_media_type_nfc_mf0aes_autocreate_giftcard',
'reusable_media_type_nfc_mf0aes_autocreate_giftcard_currency',
'reusable_media_type_nfc_mf0aes_random_uid',
]
def __init__(self, *args, **kwargs):

View File

@@ -1,8 +1,8 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.
@@ -70,7 +70,7 @@ class VoucherSerializer(I18nAwareModelSerializer):
class Meta:
model = Voucher
fields = ('id', 'created', 'code', 'max_usages', 'redeemed', 'min_usages', 'valid_until', 'block_quota',
fields = ('id', 'code', 'max_usages', 'redeemed', 'min_usages', 'valid_until', 'block_quota',
'allow_ignore_quota', 'price_mode', 'value', 'item', 'variation', 'quota',
'tag', 'comment', 'subevent', 'show_hidden_items', 'seat', 'all_addons_included',
'all_bundles_included', 'budget', 'budget_used')

View File

@@ -1,8 +1,8 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.
@@ -21,22 +21,22 @@
#
from datetime import timedelta
from django.dispatch import receiver
from django.dispatch import Signal, receiver
from django.utils.timezone import now
from django_scopes import scopes_disabled
from pretix.api.models import ApiCall, WebHookCall
from pretix.base.signals import EventPluginSignal, GlobalSignal, periodic_task
from pretix.base.signals import EventPluginSignal, periodic_task
from pretix.helpers.periodic import minimum_interval
register_webhook_events = GlobalSignal()
register_webhook_events = Signal()
"""
This signal is sent out to get all known webhook events. Receivers should return an
instance of a subclass of ``pretix.api.webhooks.WebhookEvent`` or a list of such
instances.
"""
register_device_security_profile = GlobalSignal()
register_device_security_profile = Signal()
"""
This signal is sent out to get all known device security_profiles. Receivers should
return an instance of a subclass of ``pretix.api.auth.devicesecurity.BaseSecurityProfile``

View File

@@ -1,8 +1,8 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.
@@ -92,7 +92,6 @@ event_router.register(r'taxrules', event.TaxRuleViewSet)
event_router.register(r'seats', event.SeatViewSet)
event_router.register(r'waitinglistentries', waitinglist.WaitingListViewSet)
event_router.register(r'checkinlists', checkin.CheckinListViewSet)
event_router.register(r'checkins', checkin.CheckinViewSet)
event_router.register(r'cartpositions', cart.CartPositionViewSet)
event_router.register(r'scheduled_exports', exporters.ScheduledEventExportViewSet)
event_router.register(r'exporters', exporters.EventExportersViewSet, basename='exporters')
@@ -112,7 +111,6 @@ item_router = routers.DefaultRouter()
item_router.register(r'variations', item.ItemVariationViewSet)
item_router.register(r'addons', item.ItemAddOnViewSet)
item_router.register(r'bundles', item.ItemBundleViewSet)
item_router.register(r'program_times', item.ItemProgramTimeViewSet)
order_router = routers.DefaultRouter()
order_router.register(r'payments', order.PaymentViewSet)
@@ -134,8 +132,6 @@ urlpatterns = [
name="checkinrpc.redeem"),
re_path(r'^organizers/(?P<organizer>[^/]+)/checkinrpc/search/$', checkin.CheckinRPCSearchView.as_view(),
name="checkinrpc.search"),
re_path(r'^organizers/(?P<organizer>[^/]+)/checkinrpc/annul/$', checkin.CheckinRPCAnnulView.as_view(),
name="checkinrpc.annul"),
re_path(r'^organizers/(?P<organizer>[^/]+)/settings/$', organizer.OrganizerSettingsView.as_view(),
name="organizer.settings"),
re_path(r'^organizers/(?P<organizer>[^/]+)/giftcards/(?P<giftcard>[^/]+)/', include(giftcard_router.urls)),

View File

@@ -1,8 +1,8 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.
@@ -20,13 +20,12 @@
# <https://www.gnu.org/licenses/>.
#
import operator
from datetime import timedelta
from functools import reduce
import django_filters
from django.conf import settings
from django.core.exceptions import ValidationError as BaseValidationError
from django.db import connection, transaction
from django.db import transaction
from django.db.models import (
Count, Exists, F, Max, OrderBy, OuterRef, Prefetch, Q, Subquery,
prefetch_related_objects,
@@ -40,24 +39,21 @@ from django.utils.translation import gettext
from django_filters.rest_framework import DjangoFilterBackend, FilterSet
from django_scopes import scopes_disabled
from packaging.version import parse
from rest_framework import status, views, viewsets
from rest_framework import views, viewsets
from rest_framework.decorators import action
from rest_framework.exceptions import (
NotFound, PermissionDenied, ValidationError,
)
from rest_framework.exceptions import PermissionDenied, ValidationError
from rest_framework.fields import DateTimeField
from rest_framework.generics import ListAPIView
from rest_framework.permissions import SAFE_METHODS
from rest_framework.response import Response
from pretix.api.serializers.checkin import (
CheckinListSerializer, CheckinRPCAnnulInputSerializer,
CheckinRPCRedeemInputSerializer, MiniCheckinListSerializer,
CheckinListSerializer, CheckinRPCRedeemInputSerializer,
MiniCheckinListSerializer,
)
from pretix.api.serializers.item import QuestionSerializer
from pretix.api.serializers.order import (
CheckinListOrderPositionSerializer, CheckinSerializer,
FailedCheckinSerializer,
CheckinListOrderPositionSerializer, FailedCheckinSerializer,
)
from pretix.api.views import RichOrderingFilter
from pretix.api.views.order import OrderPositionFilter
@@ -70,8 +66,6 @@ from pretix.base.models.orders import PrintLog
from pretix.base.services.checkin import (
CheckInError, RequiredQuestionsError, SQLLogic, perform_checkin,
)
from pretix.base.signals import checkin_annulled
from pretix.helpers import OF_SELF
with scopes_disabled():
class CheckinListFilter(FilterSet):
@@ -97,16 +91,6 @@ with scopes_disabled():
)
return queryset.filter(expr)
class CheckinFilter(FilterSet):
created_since = django_filters.IsoDateTimeFilter(field_name='created', lookup_expr='gte')
created_before = django_filters.IsoDateTimeFilter(field_name='created', lookup_expr='lt')
datetime_since = django_filters.IsoDateTimeFilter(field_name='datetime', lookup_expr='gte')
datetime_before = django_filters.IsoDateTimeFilter(field_name='datetime', lookup_expr='lt')
class Meta:
model = Checkin
fields = ['successful', 'error_reason', 'list', 'type', 'gate', 'device', 'auto_checked_in']
class CheckinListViewSet(viewsets.ModelViewSet):
serializer_class = CheckinListSerializer
@@ -829,7 +813,7 @@ class CheckinListPositionViewSet(viewsets.ReadOnlyModelViewSet):
ctx = super().get_serializer_context()
ctx['event'] = self.request.event
ctx['expand'] = self.request.query_params.getlist('expand')
ctx['pdf_data'] = self.request.query_params.get('pdf_data', 'false').lower() == 'true'
ctx['pdf_data'] = self.request.query_params.get('pdf_data', 'false') == 'true'
return ctx
def get_filterset_kwargs(self):
@@ -848,9 +832,9 @@ class CheckinListPositionViewSet(viewsets.ReadOnlyModelViewSet):
def get_queryset(self, ignore_status=False, ignore_products=False):
qs = _checkin_list_position_queryset(
[self.checkinlist],
ignore_status=self.request.query_params.get('ignore_status', 'false').lower() == 'true' or ignore_status,
ignore_status=self.request.query_params.get('ignore_status', 'false') == 'true' or ignore_status,
ignore_products=ignore_products,
pdf_data=self.request.query_params.get('pdf_data', 'false').lower() == 'true',
pdf_data=self.request.query_params.get('pdf_data', 'false') == 'true',
expand=self.request.query_params.getlist('expand'),
)
@@ -892,7 +876,7 @@ class CheckinListPositionViewSet(viewsets.ReadOnlyModelViewSet):
user=self.request.user,
auth=self.request.auth,
expand=self.request.query_params.getlist('expand'),
pdf_data=self.request.query_params.get('pdf_data', 'false').lower() == 'true',
pdf_data=self.request.query_params.get('pdf_data', 'false') == 'true',
questions_supported=self.request.data.get('questions_supported', True),
canceled_supported=self.request.data.get('canceled_supported', False),
request=self.request, # this is not clean, but we need it in the serializers for URL generation
@@ -927,7 +911,7 @@ class CheckinRPCRedeemView(views.APIView):
user=self.request.user,
auth=self.request.auth,
expand=self.request.query_params.getlist('expand'),
pdf_data=self.request.query_params.get('pdf_data', 'false').lower() == 'true',
pdf_data=self.request.query_params.get('pdf_data', 'false') == 'true',
questions_supported=s.validated_data['questions_supported'],
use_order_locale=s.validated_data['use_order_locale'],
canceled_supported=True,
@@ -1005,9 +989,9 @@ class CheckinRPCSearchView(ListAPIView):
def get_queryset(self, ignore_status=False, ignore_products=False):
qs = _checkin_list_position_queryset(
self.lists,
ignore_status=self.request.query_params.get('ignore_status', 'false').lower() == 'true' or ignore_status,
ignore_status=self.request.query_params.get('ignore_status', 'false') == 'true' or ignore_status,
ignore_products=ignore_products,
pdf_data=self.request.query_params.get('pdf_data', 'false').lower() == 'true',
pdf_data=self.request.query_params.get('pdf_data', 'false') == 'true',
expand=self.request.query_params.getlist('expand'),
)
@@ -1015,101 +999,3 @@ class CheckinRPCSearchView(ListAPIView):
qs = qs.none()
return qs
class CheckinRPCAnnulView(views.APIView):
def post(self, request, *args, **kwargs):
if isinstance(self.request.auth, (TeamAPIToken, Device)):
events = self.request.auth.get_events_with_permission(('can_change_orders', 'can_checkin_orders'))
elif self.request.user.is_authenticated:
events = self.request.user.get_events_with_permission(('can_change_orders', 'can_checkin_orders'), self.request).filter(
organizer=self.request.organizer
)
else:
raise ValueError("unknown authentication method")
s = CheckinRPCAnnulInputSerializer(data=request.data, context={'events': events})
s.is_valid(raise_exception=True)
with transaction.atomic():
try:
qs = Checkin.all.all()
if isinstance(request.auth, Device):
qs = qs.filter(device=request.auth)
ci = qs.select_for_update(
of=OF_SELF,
).select_related("position", "position__order", "position__order__event").get(
list__in=s.validated_data['lists'],
nonce=s.validated_data['nonce'],
)
if connection.features.has_select_for_update_of and ci.position_id:
# Lock position as well, can't do it with of= above because relation is nullable
OrderPosition.objects.select_for_update(of=OF_SELF).get(pk=ci.position_id)
if not ci.successful or not ci.position:
raise ValidationError("Cannot annul an unsuccessful checkin")
except Checkin.DoesNotExist:
raise NotFound("No check-in found based on nonce")
except Checkin.MultipleObjectsReturned:
raise ValidationError("Multiple check-ins found based on nonce")
annulment_time = s.validated_data.get("datetime") or now()
if annulment_time - ci.datetime > timedelta(minutes=15):
# Compare to sent datetime, which makes this cheatable, but allows offline annulment of checkins
ci.position.order.log_action('pretix.event.checkin.annulment.ignored', data={
'checkin': ci.pk,
'position': ci.position.id,
'positionid': ci.position.positionid,
'datetime': annulment_time,
'error_explanation': s.validated_data.get("error_explanation"),
'type': ci.type,
'list': ci.list_id,
}, user=request.user, auth=request.auth)
return Response({
"non_field_errors": ["Annulment is not allowed more than 15 minutes after check-in"]
}, status=status.HTTP_400_BAD_REQUEST)
if ci.device and ci.device != request.auth:
return Response({
"non_field_errors": ["Annulment is only allowed from the same device"]
}, status=status.HTTP_400_BAD_REQUEST)
ci.successful = False
ci.error_reason = Checkin.REASON_ANNULLED
ci.error_explanation = s.validated_data.get("error_explanation")
ci.save(update_fields=["successful", "error_reason", "error_explanation"])
ci.position.order.log_action('pretix.event.checkin.annulled', data={
'checkin': ci.pk,
'position': ci.position.id,
'positionid': ci.position.positionid,
'datetime': annulment_time,
'error_explanation': s.validated_data.get("error_explanation"),
'type': ci.type,
'list': ci.list_id,
}, user=request.user, auth=request.auth)
checkin_annulled.send(ci.position.order.event, checkin=ci)
return Response({"status": "ok"}, status=status.HTTP_200_OK)
class CheckinViewSet(viewsets.ReadOnlyModelViewSet):
serializer_class = CheckinSerializer
queryset = Checkin.all.none()
filter_backends = (DjangoFilterBackend, RichOrderingFilter)
filterset_class = CheckinFilter
ordering = ('created', 'id')
ordering_fields = ('created', 'datetime', 'id',)
permission = 'can_view_orders'
def get_queryset(self):
qs = Checkin.all.filter().select_related(
"position",
"device",
)
return qs
def get_serializer_context(self):
ctx = super().get_serializer_context()
ctx['event'] = self.request.event
return ctx

View File

@@ -1,8 +1,8 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.
@@ -40,19 +40,19 @@ from django_filters.rest_framework import DjangoFilterBackend, FilterSet
from django_scopes import scopes_disabled
from rest_framework import viewsets
from rest_framework.decorators import action
from rest_framework.exceptions import PermissionDenied, ValidationError
from rest_framework.exceptions import PermissionDenied
from rest_framework.response import Response
from pretix.api.pagination import TotalOrderingFilter
from pretix.api.serializers.item import (
ItemAddOnSerializer, ItemBundleSerializer, ItemCategorySerializer,
ItemProgramTimeSerializer, ItemSerializer, ItemVariationSerializer,
QuestionOptionSerializer, QuestionSerializer, QuotaSerializer,
ItemSerializer, ItemVariationSerializer, QuestionOptionSerializer,
QuestionSerializer, QuotaSerializer,
)
from pretix.api.views import ConditionalListView
from pretix.base.models import (
CartPosition, Item, ItemAddOn, ItemBundle, ItemCategory, ItemProgramTime,
ItemVariation, Question, QuestionOption, Quota,
CartPosition, Item, ItemAddOn, ItemBundle, ItemCategory, ItemVariation,
Question, QuestionOption, Quota,
)
from pretix.base.services.quotas import QuotaAvailability
from pretix.helpers.dicts import merge_dicts
@@ -279,59 +279,6 @@ class ItemBundleViewSet(viewsets.ModelViewSet):
)
class ItemProgramTimeViewSet(viewsets.ModelViewSet):
serializer_class = ItemProgramTimeSerializer
queryset = ItemProgramTime.objects.none()
filter_backends = (DjangoFilterBackend, TotalOrderingFilter,)
ordering_fields = ('id',)
ordering = ('id',)
permission = None
write_permission = 'can_change_items'
@cached_property
def item(self):
return get_object_or_404(Item, pk=self.kwargs['item'], event=self.request.event)
def get_queryset(self):
if self.request.event.has_subevents:
raise ValidationError('You cannot use program times on an event series.')
return self.item.program_times.all()
def get_serializer_context(self):
ctx = super().get_serializer_context()
ctx['event'] = self.request.event
ctx['item'] = self.item
return ctx
def perform_create(self, serializer):
item = get_object_or_404(Item, pk=self.kwargs['item'], event=self.request.event)
serializer.save(item=item)
item.log_action(
'pretix.event.item.program_times.added',
user=self.request.user,
auth=self.request.auth,
data=merge_dicts(self.request.data, {'id': serializer.instance.pk})
)
def perform_update(self, serializer):
serializer.save(event=self.request.event)
serializer.instance.item.log_action(
'pretix.event.item.program_times.changed',
user=self.request.user,
auth=self.request.auth,
data=merge_dicts(self.request.data, {'id': serializer.instance.pk})
)
def perform_destroy(self, instance):
super().perform_destroy(instance)
instance.item.log_action(
'pretix.event.item.program_times.removed',
user=self.request.user,
auth=self.request.auth,
data={'start': instance.start, 'end': instance.end}
)
class ItemAddOnViewSet(viewsets.ModelViewSet):
serializer_class = ItemAddOnSerializer
queryset = ItemAddOn.objects.none()
@@ -567,7 +514,7 @@ class QuotaViewSet(ConditionalListView, viewsets.ModelViewSet):
write_permission = 'can_change_items'
def get_queryset(self):
return self.request.event.quotas.select_related('subevent').prefetch_related('items', 'variations').all()
return self.request.event.quotas.all()
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset()).distinct()

View File

@@ -1,8 +1,8 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.
@@ -88,7 +88,7 @@ from pretix.base.secrets import assign_ticket_secret
from pretix.base.services import tickets
from pretix.base.services.invoices import (
generate_cancellation, generate_invoice, invoice_pdf, invoice_qualified,
regenerate_invoice, transmit_invoice,
regenerate_invoice,
)
from pretix.base.services.mail import SendMailException
from pretix.base.services.orders import (
@@ -228,7 +228,7 @@ class OrderViewSetMixin:
def get_queryset(self):
qs = self.get_base_queryset()
if 'fees' not in self.request.GET.getlist('exclude'):
if self.request.query_params.get('include_canceled_fees', 'false').lower() == 'true':
if self.request.query_params.get('include_canceled_fees', 'false') == 'true':
fqs = OrderFee.all
else:
fqs = OrderFee.objects
@@ -246,11 +246,11 @@ class OrderViewSetMixin:
return qs
def _positions_prefetch(self, request):
if request.query_params.get('include_canceled_positions', 'false').lower() == 'true':
if request.query_params.get('include_canceled_positions', 'false') == 'true':
opq = OrderPosition.all
else:
opq = OrderPosition.objects
if request.query_params.get('pdf_data', 'false').lower() == 'true' and getattr(request, 'event', None):
if request.query_params.get('pdf_data', 'false') == 'true' and getattr(request, 'event', None):
prefetch_related_objects([request.organizer], 'meta_properties')
prefetch_related_objects(
[request.event],
@@ -344,8 +344,7 @@ class EventOrderViewSet(OrderViewSetMixin, viewsets.ModelViewSet):
def get_serializer_context(self):
ctx = super().get_serializer_context()
ctx['event'] = self.request.event
ctx['auth'] = self.request.auth
ctx['pdf_data'] = self.request.query_params.get('pdf_data', 'false').lower() == 'true'
ctx['pdf_data'] = self.request.query_params.get('pdf_data', 'false') == 'true'
return ctx
def get_base_queryset(self):
@@ -744,7 +743,7 @@ class EventOrderViewSet(OrderViewSetMixin, viewsets.ModelViewSet):
user=request.user if request.user.is_authenticated else None,
auth=request.auth,
)
order_placed.send(self.request.event, order=order, bulk=False)
order_placed.send(self.request.event, order=order)
if order.status == Order.STATUS_PAID:
order_paid.send(self.request.event, order=order)
order.log_action(
@@ -765,13 +764,7 @@ class EventOrderViewSet(OrderViewSetMixin, viewsets.ModelViewSet):
) and not order.invoices.last()
invoice = None
if gen_invoice:
try:
invoice = generate_invoice(order, trigger_pdf=True)
except Exception as e:
logger.exception("Could not generate invoice.")
order.log_action("pretix.event.order.invoice.failed", data={
"exception": str(e)
})
invoice = generate_invoice(order, trigger_pdf=True)
# Refresh serializer only after running signals
prefetch_related_objects([order], self._positions_prefetch(request))
@@ -950,7 +943,6 @@ class EventOrderViewSet(OrderViewSetMixin, viewsets.ModelViewSet):
@action(detail=True, methods=['POST'])
def change(self, request, **kwargs):
order = self.get_object()
check_quotas = self.request.query_params.get('check_quotas', 'true').lower() == 'true'
serializer = OrderChangeOperationSerializer(
context={'order': order, **self.get_serializer_context()},
@@ -1016,7 +1008,7 @@ class EventOrderViewSet(OrderViewSetMixin, viewsets.ModelViewSet):
elif serializer.validated_data.get('recalculate_taxes') == 'keep_gross':
ocm.recalculate_taxes(keep='gross')
ocm.commit(check_quotas=check_quotas)
ocm.commit()
except OrderError as e:
raise ValidationError(str(e))
@@ -1094,18 +1086,17 @@ class OrderPositionViewSet(viewsets.ModelViewSet):
def get_serializer_context(self):
ctx = super().get_serializer_context()
ctx['event'] = self.request.event
ctx['pdf_data'] = self.request.query_params.get('pdf_data', 'false').lower() == 'true'
ctx['check_quotas'] = self.request.query_params.get('check_quotas', 'true').lower() == 'true'
ctx['pdf_data'] = self.request.query_params.get('pdf_data', 'false') == 'true'
return ctx
def get_queryset(self):
if self.request.query_params.get('include_canceled_positions', 'false').lower() == 'true':
if self.request.query_params.get('include_canceled_positions', 'false') == 'true':
qs = OrderPosition.all
else:
qs = OrderPosition.objects
qs = qs.filter(order__event=self.request.event)
if self.request.query_params.get('pdf_data', 'false').lower() == 'true':
if self.request.query_params.get('pdf_data', 'false') == 'true':
prefetch_related_objects([self.request.organizer], 'meta_properties')
prefetch_related_objects(
[self.request.event],
@@ -1670,9 +1661,6 @@ class PaymentViewSet(CreateModelMixin, viewsets.ReadOnlyModelViewSet):
else:
mark_refunded = request.data.get('mark_canceled', False)
if not isinstance(request.data.get("comment", ""), str):
return Response({'comment': 'Invalid type.'}, status=status.HTTP_400_BAD_REQUEST)
if payment.state != OrderPayment.PAYMENT_STATE_CONFIRMED:
return Response({'detail': 'Invalid state of payment.'}, status=status.HTTP_400_BAD_REQUEST)
@@ -1699,7 +1687,6 @@ class PaymentViewSet(CreateModelMixin, viewsets.ReadOnlyModelViewSet):
amount=amount,
provider=payment.provider,
info='{}',
comment=request.data.get("comment"),
)
payment.order.log_action('pretix.event.order.refund.created', {
'local_id': r.local_id,
@@ -1902,12 +1889,6 @@ class RetryException(APIException):
default_code = 'retry_later'
class CurrentlyInflightException(APIException):
status_code = 409
default_detail = 'The requested action is already in progress.'
default_code = 'currently_inflight'
class InvoiceViewSet(viewsets.ReadOnlyModelViewSet):
serializer_class = InvoiceSerializer
queryset = Invoice.objects.none()
@@ -1956,52 +1937,13 @@ class InvoiceViewSet(viewsets.ReadOnlyModelViewSet):
resp['Content-Disposition'] = 'attachment; filename="{}.pdf"'.format(invoice.number)
return resp
@action(detail=True, methods=['POST'])
def transmit(self, request, **kwargs):
invoice = self.get_object()
if invoice.shredded:
raise PermissionDenied('The invoice file is no longer stored on the server.')
if invoice.transmission_status != Invoice.TRANSMISSION_STATUS_PENDING:
raise PermissionDenied('The invoice is not in pending state.')
transmit_invoice.apply_async(args=(self.request.event.pk, invoice.pk, False))
return Response(status=204)
@action(detail=True, methods=['POST'])
def retransmit(self, request, **kwargs):
invoice = self.get_object()
if invoice.shredded:
raise PermissionDenied('The invoice file is no longer stored on the server.')
with transaction.atomic(durable=True):
invoice = Invoice.objects.select_for_update(of=OF_SELF).get(pk=invoice.pk)
if invoice.transmission_status == Invoice.TRANSMISSION_STATUS_INFLIGHT:
raise CurrentlyInflightException()
invoice.transmission_status = Invoice.TRANSMISSION_STATUS_PENDING
invoice.transmission_date = now()
invoice.save(update_fields=["transmission_status", "transmission_date"])
invoice.order.log_action(
'pretix.event.order.invoice.retransmitted',
user=self.request.user,
auth=self.request.auth,
data={
'invoice': invoice.pk,
'full_invoice_no': invoice.full_invoice_no,
}
)
transmit_invoice.apply_async(args=(self.request.event.pk, invoice.pk, True))
return Response(status=204)
@action(detail=True, methods=['POST'])
def regenerate(self, request, **kwargs):
inv = self.get_object()
if inv.canceled:
raise ValidationError('The invoice has already been canceled.')
if not inv.regenerate_allowed:
raise PermissionDenied('Invoice may not be regenerated.')
if not inv.event.settings.invoice_regenerate_allowed:
raise PermissionDenied('Invoices may not be changed after they are created.')
elif inv.shredded:
raise PermissionDenied('The invoice file is no longer stored on the server.')
elif inv.sent_to_organizer:

View File

@@ -1,8 +1,8 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.
@@ -19,9 +19,7 @@
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
# <https://www.gnu.org/licenses/>.
#
import operator
from decimal import Decimal
from functools import reduce
import django_filters
from django.contrib.auth.hashers import make_password
@@ -50,18 +48,15 @@ from pretix.api.serializers.organizer import (
TeamInviteSerializer, TeamMemberSerializer, TeamSerializer,
)
from pretix.base.models import (
Customer, Device, Event, GiftCard, GiftCardTransaction, LogEntry,
Membership, MembershipType, Organizer, SalesChannel, SeatingPlan, Team,
TeamAPIToken, TeamInvite, User,
)
from pretix.base.plugins import (
PLUGIN_LEVEL_EVENT, PLUGIN_LEVEL_EVENT_ORGANIZER_HYBRID,
Customer, Device, GiftCard, GiftCardTransaction, Membership,
MembershipType, Organizer, SalesChannel, SeatingPlan, Team, TeamAPIToken,
TeamInvite, User,
)
from pretix.helpers import OF_SELF
from pretix.helpers.dicts import merge_dicts
class OrganizerViewSet(mixins.UpdateModelMixin, viewsets.ReadOnlyModelViewSet):
class OrganizerViewSet(viewsets.ReadOnlyModelViewSet):
serializer_class = OrganizerSerializer
queryset = Organizer.objects.none()
lookup_field = 'slug'
@@ -70,7 +65,6 @@ class OrganizerViewSet(mixins.UpdateModelMixin, viewsets.ReadOnlyModelViewSet):
filter_backends = (TotalOrderingFilter,)
ordering = ('slug',)
ordering_fields = ('name', 'slug')
write_permission = "can_change_organizer_settings"
def get_queryset(self):
if self.request.user.is_authenticated:
@@ -89,67 +83,6 @@ class OrganizerViewSet(mixins.UpdateModelMixin, viewsets.ReadOnlyModelViewSet):
else:
return Organizer.objects.filter(pk=self.request.auth.team.organizer_id)
@transaction.atomic()
def perform_update(self, serializer):
from pretix.base.plugins import get_all_plugins
original_data = self.get_serializer(instance=serializer.instance).data
current_plugins_value = serializer.instance.get_plugins()
updated_plugins_value = serializer.validated_data.get('plugins', None)
super().perform_update(serializer)
if serializer.data == original_data:
# Performance optimization: If nothing was changed, we do not need to save or log anything.
# This costs us a few cycles on save, but avoids thousands of lines in our log.
return
if updated_plugins_value is not None and set(updated_plugins_value) != set(current_plugins_value):
enabled = {m: 'enabled' for m in updated_plugins_value if m not in current_plugins_value}
disabled = {m: 'disabled' for m in current_plugins_value if m not in updated_plugins_value}
changed = merge_dicts(enabled, disabled)
plugins_available = {
p.module: p
for p in get_all_plugins(organizer=serializer.instance)
if not p.name.startswith('.') and getattr(p, 'visible', True)
}
qs = []
for module in disabled:
pluginmeta = plugins_available[module]
level = getattr(pluginmeta, 'level', PLUGIN_LEVEL_EVENT)
if level == PLUGIN_LEVEL_EVENT_ORGANIZER_HYBRID:
qs.append(Q(plugins__regex='(^|,)' + module + '(,|$)'))
if qs:
events_to_disable = set(self.request.organizer.events.filter(
reduce(operator.or_, qs)
).values_list("pk", flat=True))
logentries_to_save = []
events_to_save = []
for e in self.request.organizer.events.filter(pk__in=events_to_disable):
for module in disabled:
if module in e.get_plugins():
logentries_to_save.append(
e.log_action('pretix.event.plugins.disabled', user=self.request.user, auth=self.request.auth,
data={'plugin': module}, save=False)
)
e.disable_plugin(module)
events_to_save.append(e)
Event.objects.bulk_update(events_to_save, fields=["plugins"])
LogEntry.objects.bulk_create(logentries_to_save)
for module, operation in changed.items():
serializer.instance.log_action(
'pretix.organizer.plugins.' + operation,
user=self.request.user,
auth=self.request.auth,
data={'plugin': module}
)
class SeatingPlanViewSet(viewsets.ModelViewSet):
serializer_class = SeatingPlanSerializer
@@ -546,8 +479,7 @@ class DeviceViewSet(mixins.CreateModelMixin,
class OrganizerSettingsView(views.APIView):
permission = None
write_permission = 'can_change_organizer_settings'
permission = 'can_change_organizer_settings'
def get(self, request, *args, **kwargs):
s = OrganizerSettingsSerializer(instance=request.organizer.settings, organizer=request.organizer, context={
@@ -721,7 +653,7 @@ class MembershipViewSet(viewsets.ModelViewSet):
def get_queryset(self):
return Membership.objects.filter(
customer__organizer=self.request.organizer
).select_related('customer')
)
def get_serializer_context(self):
ctx = super().get_serializer_context()

View File

@@ -1,8 +1,8 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.
@@ -19,7 +19,6 @@
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
# <https://www.gnu.org/licenses/>.
#
from django.db import transaction
from django.db.models import F, Q
from django.utils.timezone import now
@@ -65,13 +64,8 @@ class VoucherViewSet(viewsets.ModelViewSet):
permission = 'can_view_vouchers'
write_permission = 'can_change_vouchers'
@scopes_disabled() # we have an event check here, and we can save some performance on subqueries
def get_queryset(self):
return Voucher.annotate_budget_used(
self.request.event.vouchers
).select_related(
'item', 'quota', 'seat', 'variation'
)
return self.request.event.vouchers.select_related('seat').all()
@transaction.atomic()
def create(self, request, *args, **kwargs):

View File

@@ -1,8 +1,8 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.
@@ -43,7 +43,6 @@ from pretix.base.services.tasks import ProfiledTask, TransactionAwareTask
from pretix.base.signals import periodic_task
from pretix.celery_app import app
from pretix.helpers import OF_SELF
from pretix.helpers.celery import get_task_priority
logger = logging.getLogger(__name__)
_ALL_EVENTS = None
@@ -79,13 +78,6 @@ class WebhookEvent:
"""
raise NotImplementedError() # NOQA
@property
def help_text(self) -> str:
"""
A human-readable description
"""
return ""
def get_all_webhook_events():
global _ALL_EVENTS
@@ -105,10 +97,9 @@ def get_all_webhook_events():
class ParametrizedWebhookEvent(WebhookEvent):
def __init__(self, action_type, verbose_name, help_text=""):
def __init__(self, action_type, verbose_name):
self._action_type = action_type
self._verbose_name = verbose_name
self._help_text = help_text
super().__init__()
@property
@@ -119,10 +110,6 @@ class ParametrizedWebhookEvent(WebhookEvent):
def verbose_name(self):
return self._verbose_name
@property
def help_text(self):
return self._help_text
class ParametrizedOrderWebhookEvent(ParametrizedWebhookEvent):
def build_payload(self, logentry: LogEntry):
@@ -174,19 +161,6 @@ class ParametrizedEventWebhookEvent(ParametrizedWebhookEvent):
}
class ParametrizedVoucherWebhookEvent(ParametrizedWebhookEvent):
def build_payload(self, logentry: LogEntry):
# do not use content_object, this is also called in deletion
return {
'notification_id': logentry.pk,
'organizer': logentry.event.organizer.slug,
'event': logentry.event.slug,
'voucher': logentry.object_id,
'action': logentry.action_type,
}
class ParametrizedSubEventWebhookEvent(ParametrizedWebhookEvent):
def build_payload(self, logentry: LogEntry):
@@ -372,9 +346,8 @@ def register_default_webhook_events(sender, **kwargs):
),
ParametrizedItemWebhookEvent(
'pretix.event.item.*',
_('Product changed'),
_('This includes product added or deleted and changes to nested objects like '
'variations or bundles.'),
_('Product changed (including product added or deleted and including changes to nested objects like '
'variations or bundles)'),
),
ParametrizedEventWebhookEvent(
'pretix.event.live.activated',
@@ -408,19 +381,6 @@ def register_default_webhook_events(sender, **kwargs):
'pretix.event.orders.waitinglist.voucher_assigned',
_('Waiting list entry received voucher'),
),
ParametrizedVoucherWebhookEvent(
'pretix.voucher.added',
_('Voucher added'),
),
ParametrizedVoucherWebhookEvent(
'pretix.voucher.changed',
_('Voucher changed'),
_('Only includes explicit changes to the voucher, not e.g. an increase of the number of redemptions.')
),
ParametrizedVoucherWebhookEvent(
'pretix.voucher.deleted',
_('Voucher deleted'),
),
ParametrizedCustomerWebhookEvent(
'pretix.customer.created',
_('Customer account created'),
@@ -440,12 +400,8 @@ def register_default_webhook_events(sender, **kwargs):
def notify_webhooks(logentry_ids: list):
if not isinstance(logentry_ids, list):
logentry_ids = [logentry_ids]
qs = LogEntry.all.select_related(
'event', 'event__organizer', 'organizer'
).order_by(
'action_type', 'organizer_id', 'event_id',
).filter(id__in=logentry_ids)
_org, _at, _ev, webhooks = None, None, None, None
qs = LogEntry.all.select_related('event', 'event__organizer', 'organizer').filter(id__in=logentry_ids)
_org, _at, webhooks = None, None, None
for logentry in qs:
if not logentry.organizer:
break # We need to know the organizer
@@ -455,7 +411,7 @@ def notify_webhooks(logentry_ids: list):
if not notification_type:
break # Ignore, no webhooks for this event type
if _org != logentry.organizer or _at != logentry.action_type or _ev != logentry.event_id or webhooks is None:
if _org != logentry.organizer or _at != logentry.action_type or webhooks is None:
_org = logentry.organizer
_at = logentry.action_type
@@ -475,10 +431,7 @@ def notify_webhooks(logentry_ids: list):
)
for wh in webhooks:
send_webhook.apply_async(
args=(logentry.id, notification_type.action_type, wh.pk),
priority=get_task_priority("notifications", logentry.organizer_id),
)
send_webhook.apply_async(args=(logentry.id, notification_type.action_type, wh.pk))
@app.task(base=ProfiledTask, bind=True, max_retries=5, default_retry_delay=60, acks_late=True, autoretry_for=(DatabaseError,),)

View File

@@ -1,8 +1,8 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.
@@ -43,7 +43,7 @@ class PretixBaseConfig(AppConfig):
from . import exporter # NOQA
from . import payment # NOQA
from . import exporters # NOQA
from .invoicing import pdf, transmission, email, peppol, national # NOQA
from . import invoice # NOQA
from . import notifications # NOQA
from . import email # NOQA
from .services import auth, checkin, currencies, datasync, export, mail, tickets, cart, modelimport, orders, invoices, cleanup, update_check, quotas, notifications, vouchers # NOQA

View File

@@ -1,8 +1,8 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.
@@ -112,6 +112,23 @@ def oidc_validate_and_complete_config(config):
scope="openid",
))
for scope in config["scope"].split(" "):
if scope not in provider_config.get("scopes_supported", []):
raise ValidationError(_('You are requesting scope "{scope}" but provider only supports these: {scopes}.').format(
scope=scope,
scopes=", ".join(provider_config.get("scopes_supported", []))
))
if "claims_supported" in provider_config:
claims_supported = provider_config.get("claims_supported", [])
for k, v in config.items():
if k.endswith('_field') and v:
if v not in claims_supported: # https://openid.net/specs/openid-connect-core-1_0.html#UserInfo
raise ValidationError(_('You are requesting field "{field}" but provider only supports these: {fields}.').format(
field=v,
fields=", ".join(provider_config.get("claims_supported", []))
))
if "token_endpoint_auth_methods_supported" in provider_config:
token_endpoint_auth_methods_supported = provider_config.get("token_endpoint_auth_methods_supported",
["client_secret_basic"])
@@ -182,7 +199,6 @@ def oidc_validate_authorization(provider, code, redirect_uri, pkce_code_verifier
params['client_id'] = provider.configuration['client_id']
params['client_secret'] = provider.configuration['client_secret']
resp = None
try:
resp = requests.post(
endpoint,
@@ -198,10 +214,7 @@ def oidc_validate_authorization(provider, code, redirect_uri, pkce_code_verifier
resp.raise_for_status()
data = resp.json()
except RequestException:
if resp:
logger.exception(f'Could not retrieve authorization token. Response: {resp.text}')
else:
logger.exception('Could not retrieve authorization token')
logger.exception('Could not retrieve authorization token')
raise ValidationError(
_('Login was not successful. Error message: "{error}".').format(
error='could not reach login provider',
@@ -209,7 +222,6 @@ def oidc_validate_authorization(provider, code, redirect_uri, pkce_code_verifier
)
if 'access_token' not in data:
logger.error(f'Could not find access token. Response: {data}')
raise ValidationError(
_('Login was not successful. Error message: "{error}".').format(
error='access token missing',
@@ -217,7 +229,6 @@ def oidc_validate_authorization(provider, code, redirect_uri, pkce_code_verifier
)
endpoint = provider.configuration['provider_config']['userinfo_endpoint']
resp = None
try:
# https://openid.net/specs/openid-connect-core-1_0.html#UserInfo
resp = requests.get(
@@ -229,10 +240,7 @@ def oidc_validate_authorization(provider, code, redirect_uri, pkce_code_verifier
resp.raise_for_status()
userinfo = resp.json()
except RequestException:
if resp:
logger.exception(f'Could not retrieve user info. Response: {resp.text}')
else:
logger.exception('Could not retrieve user info')
logger.exception('Could not retrieve user info')
raise ValidationError(
_('Login was not successful. Error message: "{error}".').format(
error='could not fetch user info',

View File

@@ -1,8 +1,8 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.
@@ -25,7 +25,7 @@ import logging
from collections import namedtuple
from datetime import timedelta
from functools import cached_property
from typing import List, Optional, Protocol
from typing import Optional, Protocol
import sentry_sdk
from django.db import DatabaseError, transaction
@@ -38,13 +38,13 @@ from pretix.base.datasync.sourcefields import (
from pretix.base.i18n import language
from pretix.base.logentrytype_registry import make_link
from pretix.base.models.datasync import OrderSyncQueue, OrderSyncResult
from pretix.base.signals import PluginAwareRegistry
from pretix.base.signals import EventPluginRegistry
from pretix.helpers import OF_SELF
logger = logging.getLogger(__name__)
datasync_providers = PluginAwareRegistry({"identifier": lambda o: o.identifier})
datasync_providers = EventPluginRegistry({"identifier": lambda o: o.identifier})
class BaseSyncError(Exception):
@@ -106,7 +106,7 @@ class OutboundSyncProvider:
return str(cls.identifier)
@classmethod
def enqueue_order(cls, order, triggered_by, not_before=None, immediate=False):
def enqueue_order(cls, order, triggered_by, not_before=None):
"""
Adds an order to the sync queue. May only be called on derived classes which define an ``identifier`` attribute.
@@ -119,14 +119,10 @@ class OutboundSyncProvider:
:param order: the Order that should be synced
:param triggered_by: the reason why the order should be synced, e.g. name of the signal
(currently only used internally for logging)
:param immediate: whether a new sync task should run immediately for this order, instead
of waiting for the next periodic_task interval
:return: Return a tuple (queue_item, created), where created is a boolean
specifying whether a new queue item was created.
"""
if not hasattr(cls, 'identifier'):
raise TypeError('Call this method on a derived class that defines an "identifier" attribute.')
queue_item, created = OrderSyncQueue.objects.update_or_create(
OrderSyncQueue.objects.update_or_create(
order=order,
sync_provider=cls.identifier,
in_flight=False,
@@ -137,10 +133,6 @@ class OutboundSyncProvider:
"need_manual_retry": None,
},
)
if immediate:
from pretix.base.services.datasync import sync_single
sync_single.apply_async(args=(queue_item.pk,))
return queue_item, created
@classmethod
def get_external_link_info(cls, event, external_link_href, external_link_display_name):
@@ -184,7 +176,7 @@ class OutboundSyncProvider:
- `pretix_id_field`: Which pretix data field should be used to identify the mapped object. Any ``DataFieldInfo.key``
returned by ``sourcefields.get_data_fields()`` for the combination of ``Event`` and ``pretix_model``.
- `external_id_field`: Destination identifier field in the target system.
- `property_mappings`: Mapping configuration as generated by ``PropertyMappingFormSet.to_property_mappings_list()``.
- `property_mappings`: Mapping configuration as generated by ``PropertyMappingFormSet.to_property_mappings_json()``.
"""
raise NotImplementedError
@@ -260,15 +252,9 @@ class OutboundSyncProvider:
except KeyError:
with language(self.event.settings.locale):
raise SyncConfigError([_(
'Field "{field_name}" does not exist. Please check your {provider_name} settings.'
).format(field_name=key, provider_name=self.display_name)])
try:
input = inputs[field.required_input]
except KeyError:
with language(self.event.settings.locale):
raise SyncConfigError([_(
'Field "{field_name}" requires {required_input}, but only got {available_inputs}. Please check your {provider_name} settings.'
).format(field_name=key, required_input=field.required_input, available_inputs=", ".join(inputs.keys()), provider_name=self.display_name)])
'Field "{field_name}" is not valid for {available_inputs}. Please check your {provider_name} settings.'
).format(key=key, available_inputs="/".join(inputs.keys()), provider_name=self.display_name)])
input = inputs[field.required_input]
val = field.getter(input)
if isinstance(val, list):
if field.enum_opts and mapping_entry.get("value_map"):
@@ -284,7 +270,8 @@ class OutboundSyncProvider:
val = ",".join(val)
return val
def get_properties(self, inputs: dict, property_mappings: List[dict]):
def get_properties(self, inputs: dict, property_mappings: str):
property_mappings = json.loads(property_mappings)
return [
(m["external_field"], self.get_field_value(inputs, m), m["overwrite"])
for m in property_mappings
@@ -391,7 +378,7 @@ class OutboundSyncProvider:
def sync_order(self, order):
if not self.should_sync_order(order):
logger.debug("Skipping order %r", order)
return {}
return
logger.debug("Syncing order %r", order)
positions = list(

View File

@@ -1,8 +1,8 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.
@@ -24,11 +24,10 @@ from collections import namedtuple
from functools import partial
from django.db.models import Max, Q
from django.utils.translation import gettext_lazy as _, pgettext_lazy
from django.utils.translation import gettext_lazy as _
from pretix.base.models import Checkin, InvoiceAddress, Order, Question
from pretix.base.settings import PERSON_NAME_SCHEMES
from pretix.multidomain.urlreverse import build_absolute_uri
def get_answer(op, question_identifier=None):
@@ -94,23 +93,6 @@ def split_name_on_last_space(name, part):
return name_parts[part] if len(name_parts) > part else ""
def normalize_email(email):
if email:
local, host = email.split("@")
host = host.encode("idna").decode()
return f"{local}@{host}"
else:
return None
def get_email_domain(email):
if email:
local, host = email.split("@")
return host
else:
return None
ORDER_POSITION = 'position'
ORDER = 'order'
EVENT = 'event'
@@ -120,23 +102,10 @@ AVAILABLE_MODELS = {
'Order': (ORDER, EVENT),
}
DataFieldCategory = namedtuple(
'DataFieldCategory',
field_names=('sort_index', 'label',),
)
CAT_ORDER_POSITION = DataFieldCategory(10, _('Order position details'))
CAT_ATTENDEE = DataFieldCategory(11, _('Attendee details'))
CAT_QUESTIONS = DataFieldCategory(12, _('Questions'))
CAT_PRODUCT = DataFieldCategory(20, _('Product details'))
CAT_ORDER = DataFieldCategory(21, _('Order details'))
CAT_INVOICE_ADDRESS = DataFieldCategory(22, _('Invoice address'))
CAT_EVENT = DataFieldCategory(30, _('Event information'))
CAT_EVENT_OR_SUBEVENT = DataFieldCategory(31, pgettext_lazy('subevent', 'Event or date information'))
DataFieldInfo = namedtuple(
'DataFieldInfo',
field_names=('required_input', 'category', 'key', 'label', 'type', 'enum_opts', 'getter', 'deprecated'),
field_names=('required_input', 'key', 'label', 'type', 'enum_opts', 'getter', 'deprecated'),
defaults=[False]
)
@@ -170,7 +139,6 @@ def get_data_fields(event, for_model=None):
[
DataFieldInfo(
ORDER_POSITION,
CAT_ATTENDEE,
"attendee_name",
_("Attendee name"),
Question.TYPE_STRING,
@@ -182,7 +150,6 @@ def get_data_fields(event, for_model=None):
+ [
DataFieldInfo(
ORDER_POSITION,
CAT_ATTENDEE,
"attendee_name_" + k,
_("Attendee") + ": " + label,
Question.TYPE_STRING,
@@ -202,32 +169,25 @@ def get_data_fields(event, for_model=None):
+ [
DataFieldInfo(
ORDER_POSITION,
CAT_ATTENDEE,
"attendee_email",
_("Attendee email"),
Question.TYPE_STRING,
None,
lambda position: normalize_email(
position.attendee_email
or (position.addon_to.attendee_email if position.addon_to else None)
),
lambda position: position.attendee_email
or (position.addon_to.attendee_email if position.addon_to else None),
),
DataFieldInfo(
ORDER_POSITION,
CAT_ATTENDEE,
"attendee_or_order_email",
_("Attendee or order email"),
Question.TYPE_STRING,
None,
lambda position: normalize_email(
position.attendee_email
or (position.addon_to.attendee_email if position.addon_to else None)
or position.order.email
),
lambda position: position.attendee_email
or (position.addon_to.attendee_email if position.addon_to else None)
or position.order.email,
),
DataFieldInfo(
ORDER_POSITION,
CAT_ATTENDEE,
"attendee_company",
_("Attendee company"),
Question.TYPE_STRING,
@@ -236,7 +196,6 @@ def get_data_fields(event, for_model=None):
),
DataFieldInfo(
ORDER_POSITION,
CAT_ATTENDEE,
"attendee_street",
_("Attendee address street"),
Question.TYPE_STRING,
@@ -245,7 +204,6 @@ def get_data_fields(event, for_model=None):
),
DataFieldInfo(
ORDER_POSITION,
CAT_ATTENDEE,
"attendee_zipcode",
_("Attendee address ZIP code"),
Question.TYPE_STRING,
@@ -254,7 +212,6 @@ def get_data_fields(event, for_model=None):
),
DataFieldInfo(
ORDER_POSITION,
CAT_ATTENDEE,
"attendee_city",
_("Attendee address city"),
Question.TYPE_STRING,
@@ -263,7 +220,6 @@ def get_data_fields(event, for_model=None):
),
DataFieldInfo(
ORDER_POSITION,
CAT_ATTENDEE,
"attendee_country",
_("Attendee address country"),
Question.TYPE_COUNTRYCODE,
@@ -274,7 +230,6 @@ def get_data_fields(event, for_model=None):
),
DataFieldInfo(
ORDER,
CAT_INVOICE_ADDRESS,
"invoice_address_company",
_("Invoice address company"),
Question.TYPE_STRING,
@@ -283,7 +238,6 @@ def get_data_fields(event, for_model=None):
),
DataFieldInfo(
ORDER,
CAT_INVOICE_ADDRESS,
"invoice_address_name",
_("Invoice address name"),
Question.TYPE_STRING,
@@ -294,7 +248,6 @@ def get_data_fields(event, for_model=None):
+ [
DataFieldInfo(
ORDER,
CAT_INVOICE_ADDRESS,
"invoice_address_name_" + k,
_("Invoice address") + ": " + label,
Question.TYPE_STRING,
@@ -312,7 +265,6 @@ def get_data_fields(event, for_model=None):
+ [
DataFieldInfo(
ORDER,
CAT_INVOICE_ADDRESS,
"invoice_address_street",
_("Invoice address street"),
Question.TYPE_STRING,
@@ -321,7 +273,6 @@ def get_data_fields(event, for_model=None):
),
DataFieldInfo(
ORDER,
CAT_INVOICE_ADDRESS,
"invoice_address_zipcode",
_("Invoice address ZIP code"),
Question.TYPE_STRING,
@@ -330,7 +281,6 @@ def get_data_fields(event, for_model=None):
),
DataFieldInfo(
ORDER,
CAT_INVOICE_ADDRESS,
"invoice_address_city",
_("Invoice address city"),
Question.TYPE_STRING,
@@ -339,7 +289,6 @@ def get_data_fields(event, for_model=None):
),
DataFieldInfo(
ORDER,
CAT_INVOICE_ADDRESS,
"invoice_address_country",
_("Invoice address country"),
Question.TYPE_COUNTRYCODE,
@@ -348,25 +297,14 @@ def get_data_fields(event, for_model=None):
),
DataFieldInfo(
ORDER,
CAT_ORDER,
"email",
_("Order email"),
Question.TYPE_STRING,
None,
lambda order: normalize_email(order.email),
lambda order: order.email,
),
DataFieldInfo(
ORDER,
CAT_ORDER,
"email_domain",
_("Order email domain"),
Question.TYPE_STRING,
None,
lambda order: get_email_domain(normalize_email(order.email)),
),
DataFieldInfo(
ORDER,
CAT_ORDER,
"order_code",
_("Order code"),
Question.TYPE_STRING,
@@ -375,7 +313,6 @@ def get_data_fields(event, for_model=None):
),
DataFieldInfo(
ORDER,
CAT_ORDER,
"event_order_code",
_("Event and order code"),
Question.TYPE_STRING,
@@ -384,7 +321,6 @@ def get_data_fields(event, for_model=None):
),
DataFieldInfo(
ORDER,
CAT_ORDER,
"order_total",
_("Order total"),
Question.TYPE_NUMBER,
@@ -393,7 +329,6 @@ def get_data_fields(event, for_model=None):
),
DataFieldInfo(
ORDER_POSITION,
CAT_PRODUCT,
"product",
_("Product and variation name"),
Question.TYPE_STRING,
@@ -405,25 +340,14 @@ def get_data_fields(event, for_model=None):
),
DataFieldInfo(
ORDER_POSITION,
CAT_PRODUCT,
"product_id",
_("Product ID"),
Question.TYPE_NUMBER,
None,
lambda position: str(position.item.pk),
),
DataFieldInfo(
ORDER_POSITION,
CAT_PRODUCT,
"product_is_admission",
_("Product is admission product"),
Question.TYPE_BOOLEAN,
None,
lambda position: bool(position.item.admission),
),
DataFieldInfo(
EVENT,
CAT_EVENT,
"event_slug",
_("Event short form"),
Question.TYPE_STRING,
@@ -432,7 +356,6 @@ def get_data_fields(event, for_model=None):
),
DataFieldInfo(
EVENT,
CAT_EVENT,
"event_name",
_("Event name"),
Question.TYPE_STRING,
@@ -441,7 +364,6 @@ def get_data_fields(event, for_model=None):
),
DataFieldInfo(
EVENT_OR_SUBEVENT,
CAT_EVENT_OR_SUBEVENT,
"event_date_from",
_("Event start date"),
Question.TYPE_DATETIME,
@@ -450,7 +372,6 @@ def get_data_fields(event, for_model=None):
),
DataFieldInfo(
EVENT_OR_SUBEVENT,
CAT_EVENT_OR_SUBEVENT,
"event_date_to",
_("Event end date"),
Question.TYPE_DATETIME,
@@ -459,7 +380,6 @@ def get_data_fields(event, for_model=None):
),
DataFieldInfo(
ORDER_POSITION,
CAT_ORDER_POSITION,
"voucher_code",
_("Voucher code"),
Question.TYPE_STRING,
@@ -468,7 +388,6 @@ def get_data_fields(event, for_model=None):
),
DataFieldInfo(
ORDER_POSITION,
CAT_ORDER_POSITION,
"ticket_id",
_("Order code and position number"),
Question.TYPE_STRING,
@@ -477,7 +396,6 @@ def get_data_fields(event, for_model=None):
),
DataFieldInfo(
ORDER_POSITION,
CAT_ORDER_POSITION,
"ticket_price",
_("Ticket price"),
Question.TYPE_NUMBER,
@@ -486,7 +404,6 @@ def get_data_fields(event, for_model=None):
),
DataFieldInfo(
ORDER,
CAT_ORDER,
"order_status",
_("Order status"),
Question.TYPE_CHOICE,
@@ -495,7 +412,6 @@ def get_data_fields(event, for_model=None):
),
DataFieldInfo(
ORDER_POSITION,
CAT_ORDER_POSITION,
"ticket_status",
_("Ticket status"),
Question.TYPE_CHOICE,
@@ -504,7 +420,6 @@ def get_data_fields(event, for_model=None):
),
DataFieldInfo(
ORDER,
CAT_ORDER,
"order_date",
_("Order date and time"),
Question.TYPE_DATETIME,
@@ -513,7 +428,6 @@ def get_data_fields(event, for_model=None):
),
DataFieldInfo(
ORDER,
CAT_ORDER,
"payment_date",
_("Payment date and time"),
Question.TYPE_DATETIME,
@@ -522,58 +436,24 @@ def get_data_fields(event, for_model=None):
),
DataFieldInfo(
ORDER,
CAT_ORDER,
"order_locale",
_("Order locale"),
_("Order language code"),
Question.TYPE_CHOICE,
[(lc, lc) for lc in event.settings.locales],
lambda order: [order.locale],
),
DataFieldInfo(
ORDER_POSITION,
CAT_ORDER_POSITION,
"position_id",
_("Order position ID"),
Question.TYPE_NUMBER,
None,
lambda op: str(op.pk),
),
DataFieldInfo(
ORDER,
CAT_ORDER,
"presale_order_url",
_("Order link"),
Question.TYPE_STRING,
None,
lambda order: build_absolute_uri(
event,
'presale:event.order', kwargs={
'order': order.code,
'secret': order.secret,
}
),
),
DataFieldInfo(
ORDER_POSITION,
CAT_ORDER_POSITION,
"presale_ticket_url",
_("Ticket link"),
Question.TYPE_STRING,
None,
lambda op: build_absolute_uri(
event,
'presale:event.order.position', kwargs={
'order': op.order.code,
'secret': op.web_secret,
'position': op.positionid
}
),
),
]
+ [
DataFieldInfo(
ORDER_POSITION,
CAT_ORDER_POSITION,
"checkin_date_" + str(cl.pk),
_("Check-in datetime on list {}").format(cl.name),
Question.TYPE_DATETIME,
@@ -585,7 +465,6 @@ def get_data_fields(event, for_model=None):
+ [
DataFieldInfo(
ORDER_POSITION,
CAT_QUESTIONS,
"question_" + q.identifier,
_("Question: {name}").format(name=str(q.question)),
q.type,
@@ -599,7 +478,6 @@ def get_data_fields(event, for_model=None):
src_fields += [
DataFieldInfo(
ORDER_POSITION,
CAT_ATTENDEE,
"attendee_name_given_name",
_("Attendee") + ": " + _("Given name") + " (⚠️ auto-generated, not recommended)",
Question.TYPE_STRING,
@@ -609,7 +487,6 @@ def get_data_fields(event, for_model=None):
),
DataFieldInfo(
ORDER,
CAT_INVOICE_ADDRESS,
"invoice_address_name_given_name",
_("Invoice address") + ": " + _("Given name") + " (⚠️ auto-generated, not recommended)",
Question.TYPE_STRING,
@@ -623,7 +500,6 @@ def get_data_fields(event, for_model=None):
src_fields += [
DataFieldInfo(
ORDER_POSITION,
CAT_ATTENDEE,
"attendee_name_family_name",
_("Attendee") + ": " + _("Family name") + " (⚠️ auto-generated, not recommended)",
Question.TYPE_STRING,
@@ -633,7 +509,6 @@ def get_data_fields(event, for_model=None):
),
DataFieldInfo(
ORDER,
CAT_INVOICE_ADDRESS,
"invoice_address_name_family_name",
_("Invoice address") + ": " + _("Family name") + " (⚠️ auto-generated, not recommended)",
Question.TYPE_STRING,

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