forked from CGM_Public/pretix_original
Merge branch 'master' into a11y-add-landmarks
This commit is contained in:
@@ -8,4 +8,5 @@ This part of the documentation contains how-to guides on some special use cases
|
|||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 2
|
:maxdepth: 2
|
||||||
|
|
||||||
|
order_lifecycle
|
||||||
custom_checkout
|
custom_checkout
|
||||||
|
|||||||
56
doc/api/guides/order_lifecycle.rst
Normal file
56
doc/api/guides/order_lifecycle.rst
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
Understanding the life cycle of orders
|
||||||
|
======================================
|
||||||
|
|
||||||
|
When integrating pretix with other systems, it is important that you understand how orders and related objects
|
||||||
|
such as order positions, fees, payments, refunds, and invoices work together, in order to react to their changes
|
||||||
|
properly and map them to processes in your system.
|
||||||
|
|
||||||
|
Order states
|
||||||
|
------------
|
||||||
|
|
||||||
|
Generally, an order can be in six states. For compatibility reasons, the ``status`` field only allows four values
|
||||||
|
and the two remaining states are modeled through the ``require_approval`` field and the number of positions within
|
||||||
|
an order. The states and their allowed changes are shown in the following graph:
|
||||||
|
|
||||||
|
.. image:: /images/order_states.png
|
||||||
|
|
||||||
|
|
||||||
|
Object types
|
||||||
|
------------
|
||||||
|
|
||||||
|
Order
|
||||||
|
One order represents one purchase. It's the main object you interact with and bundles all the other objects
|
||||||
|
together. Orders can change in many ways during their lifetime, but will never be deleted (unless ``testmode``
|
||||||
|
is set to ``true``).
|
||||||
|
|
||||||
|
Order position
|
||||||
|
An order position represents one product contained in the order. Orders can usually have multiple positions.
|
||||||
|
There might be a parent-child relation between order positions if one position is an add-on to another position.
|
||||||
|
Order positions can change in many ways during their lifetime, and can also be removed or added to an order.
|
||||||
|
|
||||||
|
Order fees
|
||||||
|
A fee represents a charge that is not related to a product. Examples include shipping fees, service fees, and
|
||||||
|
cancellation fees.
|
||||||
|
Order fees can change in many ways during their lifetime, and can also be removed or added to an order.
|
||||||
|
|
||||||
|
Order payment
|
||||||
|
An order payment represents one payment attempt with a specific payment method and amount. An order can have
|
||||||
|
multiple payments attached.
|
||||||
|
Order payments have their own state diagram. Apart from their state and their meta information (e.g. used
|
||||||
|
credit card, …) they usually don't change. They may be added at any time, but will never be deleted.
|
||||||
|
|
||||||
|
Order refund
|
||||||
|
An order payment represents one refund attempt with a specific payment method and amount. An order can have
|
||||||
|
multiple refunds attached.
|
||||||
|
Order refunds have their own state diagram. Apart from their state and their meta information (e.g. used
|
||||||
|
credit card, …) they usually don't change. They may be added at any time, but will never be deleted.
|
||||||
|
|
||||||
|
Invoice
|
||||||
|
An invoice represents a legal document stating the contents of an order. While the backend technically allows
|
||||||
|
to update an invoice in some situations, invoices are generally considered immutable. Once they are issued,
|
||||||
|
they no longer change. If the order changes substantially (e.g. prices change), an invoice is canceled through
|
||||||
|
creation of a new invoice with the opposite amount, plus the issuance of a new invoice.
|
||||||
|
|
||||||
|
Here's an example of how they all play together:
|
||||||
|
|
||||||
|
.. image:: /images/order_objects.png
|
||||||
@@ -42,10 +42,6 @@ seat objects The assigned se
|
|||||||
└ seat_guid string Identifier of the seat within the seating plan
|
└ seat_guid string Identifier of the seat within the seating plan
|
||||||
===================================== ========================== =======================================================
|
===================================== ========================== =======================================================
|
||||||
|
|
||||||
.. versionchanged:: 1.17
|
|
||||||
|
|
||||||
This resource has been added.
|
|
||||||
|
|
||||||
.. versionchanged:: 3.0
|
.. versionchanged:: 3.0
|
||||||
|
|
||||||
This ``seat`` attribute has been added.
|
This ``seat`` attribute has been added.
|
||||||
|
|||||||
@@ -25,14 +25,6 @@ is_addon boolean If ``true``, it
|
|||||||
defining add-ons for other products.
|
defining add-ons for other products.
|
||||||
===================================== ========================== =======================================================
|
===================================== ========================== =======================================================
|
||||||
|
|
||||||
.. versionchanged:: 1.14
|
|
||||||
|
|
||||||
The operations POST, PATCH, PUT and DELETE have been added.
|
|
||||||
|
|
||||||
.. versionchanged:: 1.16
|
|
||||||
|
|
||||||
The field ``internal_name`` has been added.
|
|
||||||
|
|
||||||
|
|
||||||
Endpoints
|
Endpoints
|
||||||
---------
|
---------
|
||||||
|
|||||||
@@ -36,22 +36,6 @@ rules object Custom check-in
|
|||||||
exit_all_at datetime Automatically check out (i.e. perform an exit scan) at this point in time. After this happened, this property will automatically be set exactly one day into the future. Note that this field is considered "internal configuration" and if you pull the list with ``If-Modified-Since``, the daily change in this field will not trigger a response.
|
exit_all_at datetime Automatically check out (i.e. perform an exit scan) at this point in time. After this happened, this property will automatically be set exactly one day into the future. Note that this field is considered "internal configuration" and if you pull the list with ``If-Modified-Since``, the daily change in this field will not trigger a response.
|
||||||
===================================== ========================== =======================================================
|
===================================== ========================== =======================================================
|
||||||
|
|
||||||
.. versionchanged:: 1.10
|
|
||||||
|
|
||||||
This resource has been added.
|
|
||||||
|
|
||||||
.. versionchanged:: 1.11
|
|
||||||
|
|
||||||
The ``positions`` endpoints have been added.
|
|
||||||
|
|
||||||
.. versionchanged:: 1.13
|
|
||||||
|
|
||||||
The ``include_pending`` field has been added.
|
|
||||||
|
|
||||||
.. versionchanged:: 3.2
|
|
||||||
|
|
||||||
The ``auto_checkin_sales_channels`` field has been added.
|
|
||||||
|
|
||||||
.. versionchanged:: 3.9
|
.. versionchanged:: 3.9
|
||||||
|
|
||||||
The ``subevent`` attribute may now be ``null`` inside event series. The ``allow_multiple_entries``,
|
The ``subevent`` attribute may now be ``null`` inside event series. The ``allow_multiple_entries``,
|
||||||
@@ -68,10 +52,6 @@ exit_all_at datetime Automatically c
|
|||||||
Endpoints
|
Endpoints
|
||||||
---------
|
---------
|
||||||
|
|
||||||
.. versionchanged:: 1.15
|
|
||||||
|
|
||||||
The ``../status/`` detail endpoint has been added.
|
|
||||||
|
|
||||||
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/checkinlists/
|
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/checkinlists/
|
||||||
|
|
||||||
Returns a list of all check-in lists within a given event.
|
Returns a list of all check-in lists within a given event.
|
||||||
@@ -380,29 +360,6 @@ Endpoints
|
|||||||
Order position endpoints
|
Order position endpoints
|
||||||
------------------------
|
------------------------
|
||||||
|
|
||||||
.. versionchanged:: 1.15
|
|
||||||
|
|
||||||
The order positions endpoint has been extended by the filter queries ``item__in``, ``variation__in``,
|
|
||||||
``order__status__in``, ``subevent__in``, ``addon_to__in``, and ``search``. The search for attendee names and order
|
|
||||||
codes is now case-insensitive.
|
|
||||||
|
|
||||||
The ``.../redeem/`` endpoint has been added.
|
|
||||||
|
|
||||||
.. versionchanged:: 2.0
|
|
||||||
|
|
||||||
The order positions endpoint has been extended by the filter queries ``voucher`` and ``voucher__code``.
|
|
||||||
|
|
||||||
.. versionchanged:: 2.7
|
|
||||||
|
|
||||||
The resource now contains the new attributes ``require_attention`` and ``order__status`` and accepts the new
|
|
||||||
``ignore_status`` filter. The ``attendee_name`` field is now "smart" (see below) and the redemption endpoint
|
|
||||||
returns ``400`` instead of ``404`` on tickets which are known but not paid.
|
|
||||||
|
|
||||||
.. versionchanged:: 3.2
|
|
||||||
|
|
||||||
The ``checkins`` dict now also contains a ``auto_checked_in`` value to indicate if the check-in has been performed
|
|
||||||
automatically by the system.
|
|
||||||
|
|
||||||
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/checkinlists/(list)/positions/
|
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/checkinlists/(list)/positions/
|
||||||
|
|
||||||
Returns a list of all order positions within a given event. The result is the same as
|
Returns a list of all order positions within a given event. The result is the same as
|
||||||
|
|||||||
@@ -52,31 +52,6 @@ sales_channels list A list of sales
|
|||||||
===================================== ========================== =======================================================
|
===================================== ========================== =======================================================
|
||||||
|
|
||||||
|
|
||||||
.. versionchanged:: 1.7
|
|
||||||
|
|
||||||
The ``meta_data`` field has been added.
|
|
||||||
|
|
||||||
.. versionchanged:: 1.15
|
|
||||||
|
|
||||||
The ``plugins`` field has been added.
|
|
||||||
The operations POST, PATCH, PUT and DELETE have been added.
|
|
||||||
|
|
||||||
.. versionchanged:: 2.1
|
|
||||||
|
|
||||||
Filters have been added to the list of events.
|
|
||||||
|
|
||||||
.. versionchanged:: 2.5
|
|
||||||
|
|
||||||
The ``testmode`` attribute has been added.
|
|
||||||
|
|
||||||
.. versionchanged:: 2.8
|
|
||||||
|
|
||||||
When cloning events, the ``testmode`` attribute will now be cloned, too.
|
|
||||||
|
|
||||||
.. versionchanged:: 3.0
|
|
||||||
|
|
||||||
The attributes ``seating_plan`` and ``seat_category_mapping`` have been added.
|
|
||||||
|
|
||||||
.. versionchanged:: 3.3
|
.. versionchanged:: 3.3
|
||||||
|
|
||||||
The attributes ``geo_lat`` and ``geo_lon`` have been added.
|
The attributes ``geo_lat`` and ``geo_lon`` have been added.
|
||||||
|
|||||||
@@ -46,24 +46,6 @@ internal_reference string Customer's refe
|
|||||||
===================================== ========================== =======================================================
|
===================================== ========================== =======================================================
|
||||||
|
|
||||||
|
|
||||||
.. versionchanged:: 1.6
|
|
||||||
|
|
||||||
The attribute ``invoice_no`` has been dropped in favor of ``number`` which includes the number including the prefix,
|
|
||||||
since the prefix can now vary. Also, invoices now need to be identified by their ``number`` instead of the raw
|
|
||||||
number.
|
|
||||||
|
|
||||||
|
|
||||||
.. versionchanged:: 1.7
|
|
||||||
|
|
||||||
The attributes ``lines.tax_name``, ``foreign_currency_display``, ``foreign_currency_rate``, and
|
|
||||||
``foreign_currency_rate_date`` have been added.
|
|
||||||
|
|
||||||
|
|
||||||
.. versionchanged:: 1.9
|
|
||||||
|
|
||||||
The attribute ``internal_reference`` has been added.
|
|
||||||
|
|
||||||
|
|
||||||
.. versionchanged:: 3.4
|
.. versionchanged:: 3.4
|
||||||
|
|
||||||
The attribute ``lines.number`` has been added.
|
The attribute ``lines.number`` has been added.
|
||||||
|
|||||||
@@ -28,10 +28,6 @@ multi_allowed boolean Adding the same
|
|||||||
price_included boolean Adding this add-on to the item is free
|
price_included boolean Adding this add-on to the item is free
|
||||||
===================================== ========================== =======================================================
|
===================================== ========================== =======================================================
|
||||||
|
|
||||||
.. versionchanged:: 1.12
|
|
||||||
|
|
||||||
This resource has been added.
|
|
||||||
|
|
||||||
Endpoints
|
Endpoints
|
||||||
---------
|
---------
|
||||||
|
|
||||||
|
|||||||
@@ -30,10 +30,6 @@ designated_price money (string) Designated pric
|
|||||||
taxation. This is not added to the price.
|
taxation. This is not added to the price.
|
||||||
===================================== ========================== =======================================================
|
===================================== ========================== =======================================================
|
||||||
|
|
||||||
.. versionchanged:: 2.6
|
|
||||||
|
|
||||||
This resource has been added.
|
|
||||||
|
|
||||||
Endpoints
|
Endpoints
|
||||||
---------
|
---------
|
||||||
|
|
||||||
|
|||||||
@@ -26,14 +26,6 @@ description multi-lingual string A public descri
|
|||||||
position integer An integer, used for sorting
|
position integer An integer, used for sorting
|
||||||
===================================== ========================== =======================================================
|
===================================== ========================== =======================================================
|
||||||
|
|
||||||
.. versionchanged:: 2.7
|
|
||||||
|
|
||||||
The attribute ``original_price`` has been added.
|
|
||||||
|
|
||||||
.. versionchanged:: 1.12
|
|
||||||
|
|
||||||
This resource has been added.
|
|
||||||
|
|
||||||
Endpoints
|
Endpoints
|
||||||
---------
|
---------
|
||||||
|
|
||||||
|
|||||||
@@ -118,44 +118,6 @@ bundles list of objects Definition of b
|
|||||||
meta_data object Values set for event-specific meta data parameters.
|
meta_data object Values set for event-specific meta data parameters.
|
||||||
===================================== ========================== =======================================================
|
===================================== ========================== =======================================================
|
||||||
|
|
||||||
.. versionchanged:: 2.7
|
|
||||||
|
|
||||||
The attribute ``original_price`` has been added for ``variations``.
|
|
||||||
|
|
||||||
.. versionchanged:: 1.7
|
|
||||||
|
|
||||||
The attribute ``tax_rule`` has been added. ``tax_rate`` is kept for compatibility. The attribute
|
|
||||||
``checkin_attention`` has been added.
|
|
||||||
|
|
||||||
.. versionchanged:: 1.12
|
|
||||||
|
|
||||||
The write operations ``POST``, ``PATCH``, ``PUT``, and ``DELETE`` have been added.
|
|
||||||
The attribute ``price_included`` has been added to ``addons``.
|
|
||||||
|
|
||||||
.. versionchanged:: 1.16
|
|
||||||
|
|
||||||
The ``internal_name`` and ``original_price`` fields have been added.
|
|
||||||
|
|
||||||
.. versionchanged:: 2.0
|
|
||||||
|
|
||||||
The field ``require_approval`` has been added.
|
|
||||||
|
|
||||||
.. versionchanged:: 2.3
|
|
||||||
|
|
||||||
The ``sales_channels`` attribute has been added.
|
|
||||||
|
|
||||||
.. versionchanged:: 2.4
|
|
||||||
|
|
||||||
The ``generate_tickets`` attribute has been added.
|
|
||||||
|
|
||||||
.. versionchanged:: 2.6
|
|
||||||
|
|
||||||
The ``bundles`` and ``require_bundling`` attributes have been added.
|
|
||||||
|
|
||||||
.. versionchanged:: 3.0
|
|
||||||
|
|
||||||
The ``show_quota_left``, ``allow_waitinglist``, and ``hidden_if_available`` attributes have been added.
|
|
||||||
|
|
||||||
.. versionchanged:: 3.7
|
.. versionchanged:: 3.7
|
||||||
|
|
||||||
The attribute ``meta_data`` has been added.
|
The attribute ``meta_data`` has been added.
|
||||||
|
|||||||
@@ -94,60 +94,6 @@ last_modified datetime Last modificati
|
|||||||
===================================== ========================== =======================================================
|
===================================== ========================== =======================================================
|
||||||
|
|
||||||
|
|
||||||
.. versionchanged:: 1.6
|
|
||||||
|
|
||||||
The ``invoice_address.country`` attribute contains a two-letter country code for all new orders. For old orders,
|
|
||||||
a custom text might still be returned.
|
|
||||||
|
|
||||||
.. versionchanged:: 1.7
|
|
||||||
|
|
||||||
The attributes ``invoice_address.vat_id_validated`` and ``invoice_address.is_business`` have been added.
|
|
||||||
The attributes ``order.payment_fee``, ``order.payment_fee_tax_rate`` and ``order.payment_fee_tax_value`` have been
|
|
||||||
deprecated in favor of the new ``fees`` attribute but will still be served and removed in 1.9.
|
|
||||||
|
|
||||||
.. versionchanged:: 1.9
|
|
||||||
|
|
||||||
First write operations (``…/mark_paid/``, ``…/mark_pending/``, ``…/mark_canceled/``, ``…/mark_expired/``) have been added.
|
|
||||||
The attribute ``invoice_address.internal_reference`` has been added.
|
|
||||||
|
|
||||||
.. versionchanged:: 1.13
|
|
||||||
|
|
||||||
The field ``checkin_attention`` has been added.
|
|
||||||
|
|
||||||
.. versionchanged:: 1.15
|
|
||||||
|
|
||||||
The attributes ``order.payment_fee``, ``order.payment_fee_tax_rate``, ``order.payment_fee_tax_value`` and
|
|
||||||
``order.payment_fee_tax_rule`` have finally been removed.
|
|
||||||
|
|
||||||
.. versionchanged:: 1.16
|
|
||||||
|
|
||||||
The attributes ``order.last_modified`` as well as the corresponding filters to the resource have been added.
|
|
||||||
An endpoint for order creation as well as ``…/mark_refunded/`` has been added.
|
|
||||||
|
|
||||||
.. versionchanged:: 2.0
|
|
||||||
|
|
||||||
The ``order.payment_date`` and ``order.payment_provider`` attributes have been deprecated in favor of the new
|
|
||||||
nested ``payments`` and ``refunds`` resources, but will still be served and removed in 2.2. The ``require_approval``
|
|
||||||
attribute has been added, as have been the ``…/approve/`` and ``…/deny/`` endpoints.
|
|
||||||
|
|
||||||
.. versionchanged:: 2.3
|
|
||||||
|
|
||||||
The ``sales_channel`` attribute has been added.
|
|
||||||
|
|
||||||
.. versionchanged:: 2.4
|
|
||||||
|
|
||||||
``order.status`` can no longer be ``r``, ``…/mark_canceled/`` now accepts a ``cancellation_fee`` parameter and
|
|
||||||
``…/mark_refunded/`` has been deprecated.
|
|
||||||
|
|
||||||
.. versionchanged:: 2.5
|
|
||||||
|
|
||||||
The ``testmode`` attribute has been added and ``DELETE`` has been implemented for orders.
|
|
||||||
|
|
||||||
.. versionchanged:: 3.1
|
|
||||||
|
|
||||||
The ``invoice_address.state`` and ``url`` attributes have been added. When creating orders through the API,
|
|
||||||
vouchers are now supported and many fields are now optional.
|
|
||||||
|
|
||||||
.. versionchanged:: 3.5
|
.. versionchanged:: 3.5
|
||||||
|
|
||||||
The ``order.fees.canceled`` attribute has been added.
|
The ``order.fees.canceled`` attribute has been added.
|
||||||
@@ -233,30 +179,6 @@ pdf_data object Data object req
|
|||||||
``pdf_data=true`` query parameter to your request.
|
``pdf_data=true`` query parameter to your request.
|
||||||
===================================== ========================== =======================================================
|
===================================== ========================== =======================================================
|
||||||
|
|
||||||
.. versionchanged:: 1.7
|
|
||||||
|
|
||||||
The attribute ``tax_rule`` has been added.
|
|
||||||
|
|
||||||
.. versionchanged:: 1.11
|
|
||||||
|
|
||||||
The attribute ``checkins.list`` has been added.
|
|
||||||
|
|
||||||
.. versionchanged:: 1.14
|
|
||||||
|
|
||||||
The attributes ``answers.question_identifier`` and ``answers.option_identifiers`` have been added.
|
|
||||||
|
|
||||||
.. versionchanged:: 1.16
|
|
||||||
|
|
||||||
The attributes ``pseudonymization_id`` and ``pdf_data`` have been added.
|
|
||||||
|
|
||||||
.. versionchanged:: 3.0
|
|
||||||
|
|
||||||
The attribute ``seat`` has been added.
|
|
||||||
|
|
||||||
.. versionchanged:: 3.2
|
|
||||||
|
|
||||||
The value ``auto_checked_in`` has been added to the ``checkins``-attribute.
|
|
||||||
|
|
||||||
.. versionchanged:: 3.3
|
.. versionchanged:: 3.3
|
||||||
|
|
||||||
The ``url`` of a ticket ``download`` can now also return a ``text/uri-list`` instead of a file. See
|
The ``url`` of a ticket ``download`` can now also return a ``text/uri-list`` instead of a file. See
|
||||||
@@ -306,14 +228,6 @@ details object Payment-specifi
|
|||||||
the object is empty.
|
the object is empty.
|
||||||
===================================== ========================== =======================================================
|
===================================== ========================== =======================================================
|
||||||
|
|
||||||
.. versionchanged:: 2.0
|
|
||||||
|
|
||||||
This resource has been added.
|
|
||||||
|
|
||||||
.. versionchanged:: 3.1
|
|
||||||
|
|
||||||
The attributes ``payment_url`` and ``details`` have been added.
|
|
||||||
|
|
||||||
.. _order-refund-resource:
|
.. _order-refund-resource:
|
||||||
|
|
||||||
Order refund resource
|
Order refund resource
|
||||||
@@ -334,17 +248,9 @@ execution_date datetime Date and time o
|
|||||||
provider string Identification string of the payment provider
|
provider string Identification string of the payment provider
|
||||||
===================================== ========================== =======================================================
|
===================================== ========================== =======================================================
|
||||||
|
|
||||||
.. versionchanged:: 2.0
|
|
||||||
|
|
||||||
This resource has been added.
|
|
||||||
|
|
||||||
List of all orders
|
List of all orders
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
.. versionchanged:: 1.15
|
|
||||||
|
|
||||||
Filtering for emails or order codes is now case-insensitive.
|
|
||||||
|
|
||||||
.. versionchanged:: 3.5
|
.. versionchanged:: 3.5
|
||||||
|
|
||||||
The ``include_canceled_positions`` and ``include_canceled_fees`` query parameters have been added.
|
The ``include_canceled_positions`` and ``include_canceled_fees`` query parameters have been added.
|
||||||
@@ -1450,21 +1356,6 @@ Sending e-mails
|
|||||||
List of all order positions
|
List of all order positions
|
||||||
---------------------------
|
---------------------------
|
||||||
|
|
||||||
.. versionchanged:: 1.15
|
|
||||||
|
|
||||||
The order positions endpoint has been extended by the filter queries ``item__in``, ``variation__in``,
|
|
||||||
``order__status__in``, ``subevent__in``, ``addon_to__in`` and ``search``. The search for attendee names and order
|
|
||||||
codes is now case-insensitive.
|
|
||||||
|
|
||||||
.. versionchanged:: 2.0
|
|
||||||
|
|
||||||
The order positions endpoint has been extended by the filter queries ``voucher``, ``voucher__code`` and
|
|
||||||
``pseudonymization_id``.
|
|
||||||
|
|
||||||
.. versionchanged:: 3.2
|
|
||||||
|
|
||||||
The value ``auto_checked_in`` has been added to the ``checkins``-attribute.
|
|
||||||
|
|
||||||
.. versionchanged:: 3.5
|
.. versionchanged:: 3.5
|
||||||
|
|
||||||
The ``include_canceled_positions`` and ``include_canceled_fees`` query parameters have been added.
|
The ``include_canceled_positions`` and ``include_canceled_fees`` query parameters have been added.
|
||||||
@@ -1804,10 +1695,6 @@ Manipulating individual positions
|
|||||||
Order payment endpoints
|
Order payment endpoints
|
||||||
-----------------------
|
-----------------------
|
||||||
|
|
||||||
.. versionchanged:: 2.0
|
|
||||||
|
|
||||||
These endpoints have been added.
|
|
||||||
|
|
||||||
.. versionchanged:: 3.6
|
.. versionchanged:: 3.6
|
||||||
|
|
||||||
Payments can now be created through the API.
|
Payments can now be created through the API.
|
||||||
@@ -2087,10 +1974,6 @@ Order payment endpoints
|
|||||||
Order refund endpoints
|
Order refund endpoints
|
||||||
----------------------
|
----------------------
|
||||||
|
|
||||||
.. versionchanged:: 2.0
|
|
||||||
|
|
||||||
These endpoints have been added.
|
|
||||||
|
|
||||||
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/orders/(code)/refunds/
|
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/orders/(code)/refunds/
|
||||||
|
|
||||||
Returns a list of all refunds for an order.
|
Returns a list of all refunds for an order.
|
||||||
|
|||||||
@@ -19,10 +19,6 @@ identifier string An arbitrary st
|
|||||||
answer multi-lingual string The displayed value of this option
|
answer multi-lingual string The displayed value of this option
|
||||||
===================================== ========================== =======================================================
|
===================================== ========================== =======================================================
|
||||||
|
|
||||||
.. versionchanged:: 1.12
|
|
||||||
|
|
||||||
This resource has been added.
|
|
||||||
|
|
||||||
Endpoints
|
Endpoints
|
||||||
---------
|
---------
|
||||||
|
|
||||||
|
|||||||
@@ -75,28 +75,6 @@ dependency_value string An old version
|
|||||||
for one value. **Deprecated.**
|
for one value. **Deprecated.**
|
||||||
===================================== ========================== =======================================================
|
===================================== ========================== =======================================================
|
||||||
|
|
||||||
.. versionchanged:: 1.12
|
|
||||||
|
|
||||||
The values ``D``, ``H``, and ``W`` for the field ``type`` are now allowed and the ``ask_during_checkin`` field has
|
|
||||||
been added.
|
|
||||||
|
|
||||||
.. versionchanged:: 1.14
|
|
||||||
|
|
||||||
Write methods have been added. The attribute ``identifier`` has been added to both the resource itself and the
|
|
||||||
options resource. The ``position`` attribute has been added to the options resource.
|
|
||||||
|
|
||||||
.. versionchanged:: 2.7
|
|
||||||
|
|
||||||
The attribute ``hidden`` and the question type ``CC`` have been added.
|
|
||||||
|
|
||||||
.. versionchanged:: 3.0
|
|
||||||
|
|
||||||
The attribute ``dependency_values`` has been added.
|
|
||||||
|
|
||||||
.. versionchanged:: 3.1
|
|
||||||
|
|
||||||
The attribute ``print_on_invoice`` has been added.
|
|
||||||
|
|
||||||
.. versionchanged:: 3.5
|
.. versionchanged:: 3.5
|
||||||
|
|
||||||
The attribute ``help_text`` has been added.
|
The attribute ``help_text`` has been added.
|
||||||
|
|||||||
@@ -30,14 +30,6 @@ release_after_exit boolean Whether the quo
|
|||||||
have been scanned at an exit.
|
have been scanned at an exit.
|
||||||
===================================== ========================== =======================================================
|
===================================== ========================== =======================================================
|
||||||
|
|
||||||
.. versionchanged:: 1.10
|
|
||||||
|
|
||||||
The write operations ``POST``, ``PATCH``, ``PUT``, and ``DELETE`` have been added.
|
|
||||||
|
|
||||||
.. versionchanged:: 3.0
|
|
||||||
|
|
||||||
The attributes ``close_when_sold_out`` and ``closed`` have been added.
|
|
||||||
|
|
||||||
.. versionchanged:: 3.10
|
.. versionchanged:: 3.10
|
||||||
|
|
||||||
The attribute ``release_after_exit`` has been added.
|
The attribute ``release_after_exit`` has been added.
|
||||||
|
|||||||
@@ -20,10 +20,6 @@ layout object JSON representa
|
|||||||
still evolves. The version in use can be found `here`_.
|
still evolves. The version in use can be found `here`_.
|
||||||
===================================== ========================== =======================================================
|
===================================== ========================== =======================================================
|
||||||
|
|
||||||
.. versionchanged:: 3.0
|
|
||||||
|
|
||||||
This endpoint has been added.
|
|
||||||
|
|
||||||
Endpoints
|
Endpoints
|
||||||
---------
|
---------
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ date_to datetime The sub-event's
|
|||||||
date_admission datetime The sub-event's admission date (or ``null``)
|
date_admission datetime The sub-event's admission date (or ``null``)
|
||||||
presale_start datetime The sub-date at which the ticket shop opens (or ``null``)
|
presale_start datetime The sub-date at which the ticket shop opens (or ``null``)
|
||||||
presale_end datetime The sub-date at which the ticket shop closes (or ``null``)
|
presale_end datetime The sub-date at which the ticket shop closes (or ``null``)
|
||||||
|
frontpage_text multi-lingual string The description of the event (or ``null``)
|
||||||
location multi-lingual string The sub-event location (or ``null``)
|
location multi-lingual string The sub-event location (or ``null``)
|
||||||
geo_lat float Latitude of the location (or ``null``)
|
geo_lat float Latitude of the location (or ``null``)
|
||||||
geo_lon float Longitude of the location (or ``null``)
|
geo_lon float Longitude of the location (or ``null``)
|
||||||
@@ -54,25 +55,6 @@ seat_category_mapping object An object mappi
|
|||||||
last_modified datetime Last modification of this object
|
last_modified datetime Last modification of this object
|
||||||
===================================== ========================== =======================================================
|
===================================== ========================== =======================================================
|
||||||
|
|
||||||
.. versionchanged:: 1.7
|
|
||||||
|
|
||||||
The ``meta_data`` field has been added.
|
|
||||||
|
|
||||||
.. versionchanged:: 2.1
|
|
||||||
|
|
||||||
The ``event`` field has been added, together with filters on the list of dates and an organizer-level list.
|
|
||||||
|
|
||||||
.. versionchanged:: 2.6
|
|
||||||
The write operations ``POST``, ``PATCH``, ``PUT``, and ``DELETE`` have been added.
|
|
||||||
|
|
||||||
.. versionchanged:: 2.7
|
|
||||||
|
|
||||||
The attribute ``is_public`` has been added.
|
|
||||||
|
|
||||||
.. versionchanged:: 3.0
|
|
||||||
|
|
||||||
The attributes ``seating_plan`` and ``seat_category_mapping`` have been added.
|
|
||||||
|
|
||||||
.. versionchanged:: 3.3
|
.. versionchanged:: 3.3
|
||||||
|
|
||||||
The attributes ``geo_lat`` and ``geo_lon`` have been added.
|
The attributes ``geo_lat`` and ``geo_lon`` have been added.
|
||||||
|
|||||||
@@ -24,14 +24,6 @@ home_country string Merchant countr
|
|||||||
``null`` or empty string
|
``null`` or empty string
|
||||||
===================================== ========================== =======================================================
|
===================================== ========================== =======================================================
|
||||||
|
|
||||||
.. versionchanged:: 1.7
|
|
||||||
|
|
||||||
This resource has been added.
|
|
||||||
|
|
||||||
.. versionchanged:: 1.9
|
|
||||||
|
|
||||||
The write operations ``POST``, ``PATCH``, ``PUT``, and ``DELETE`` have been added.
|
|
||||||
|
|
||||||
|
|
||||||
Endpoints
|
Endpoints
|
||||||
---------
|
---------
|
||||||
|
|||||||
@@ -46,14 +46,6 @@ show_hidden_items boolean Only if set to
|
|||||||
===================================== ========================== =======================================================
|
===================================== ========================== =======================================================
|
||||||
|
|
||||||
|
|
||||||
.. versionchanged:: 1.9
|
|
||||||
|
|
||||||
The write operations ``POST``, ``PATCH``, ``PUT``, and ``DELETE`` have been added.
|
|
||||||
|
|
||||||
.. versionchanged:: 3.0
|
|
||||||
|
|
||||||
The attribute ``show_hidden_items`` has been added.
|
|
||||||
|
|
||||||
.. versionchanged:: 3.4
|
.. versionchanged:: 3.4
|
||||||
|
|
||||||
The attribute ``seat`` has been added.
|
The attribute ``seat`` has been added.
|
||||||
|
|||||||
@@ -82,11 +82,15 @@ Orders
|
|||||||
^^^^^^
|
^^^^^^
|
||||||
|
|
||||||
If a customer completes the checkout process, an **Order** will be created containing all the entered information.
|
If a customer completes the checkout process, an **Order** will be created containing all the entered information.
|
||||||
An order can be in one of currently four states that are listed in the diagram below:
|
An order can be in one of currently six states that are listed in the diagram below:
|
||||||
|
|
||||||
.. image:: /images/order_states.png
|
.. image:: /images/order_states.png
|
||||||
|
|
||||||
There are additional "fake" states that are displayed like states but not represented as states in the system:
|
The dotted lines represent status changes that usually do not happen as part of the regular process, but can be
|
||||||
|
performed manually in the admin backend.
|
||||||
|
|
||||||
|
For historical reasons, there are only four valid values of the ``status`` field, and the two additional states are
|
||||||
|
represented differently:
|
||||||
|
|
||||||
* An order is considered **canceled (with paid fee)** if it is in **paid** status but does not include any non-cancelled positions.
|
* An order is considered **canceled (with paid fee)** if it is in **paid** status but does not include any non-cancelled positions.
|
||||||
|
|
||||||
|
|||||||
BIN
doc/images/order_objects.png
Normal file
BIN
doc/images/order_objects.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 67 KiB |
34
doc/images/order_objects.puml
Normal file
34
doc/images/order_objects.puml
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
@startuml
|
||||||
|
|
||||||
|
participant User
|
||||||
|
collections "OrderPayment\nOrderRefund" as P
|
||||||
|
collections "Order\nOrderPosition" as O
|
||||||
|
collections "Invoice\nInvoiceLine" as I
|
||||||
|
|
||||||
|
User -> O: Order placed (€100)
|
||||||
|
rnote over O #6DD96D: Order A1B2C\nstatus = **n**\ntotal = €100
|
||||||
|
O -> P: Payment created
|
||||||
|
O -> I: Invoice created\n(can also happen later)
|
||||||
|
rnote over I #6DD96D: Invoice 00001\n€100
|
||||||
|
rnote over P #6DD96D: OrderPayment A1B2C-P-1\nstate = **created**
|
||||||
|
P -> User: Payment details (web, email)
|
||||||
|
User -> P: Payment performed
|
||||||
|
rnote over P #EFF46B: OrderPayment A1B2C-P-1\nstate = **confirmed**
|
||||||
|
P -> O: Order marked as paid
|
||||||
|
rnote over O #EFF46B: Order A1B2C\nstatus = **p**\ntotal = €100
|
||||||
|
User -> O: Data change (e.g. invoice address)
|
||||||
|
O -> I: Invoice reissued
|
||||||
|
rnote over I #6DD96D: Invoice 00002\n€-100
|
||||||
|
rnote over I #6DD96D: Invoice 00003\n€100
|
||||||
|
rnote over O #EFF46B: Order A1B2C\nstatus = **p**\ntotal = €100
|
||||||
|
User -> O: Order canceled
|
||||||
|
rnote over O #EFF46B: Order A1B2C\nstatus = **c**
|
||||||
|
O -> I: Invoice canceled
|
||||||
|
rnote over I #6DD96D: Invoice 00004\n€-100
|
||||||
|
O -> P: Refund started
|
||||||
|
rnote over P #6DD96D: OrderRefund\nA1B2C-R-1\nstate = **created**
|
||||||
|
P -> User: Money sent
|
||||||
|
rnote over P #EFF46B: OrderRefund\nA1B2C-R-1\nstate = **done**
|
||||||
|
|
||||||
|
@enduml
|
||||||
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 93 KiB |
@@ -1,19 +1,39 @@
|
|||||||
@startuml
|
@startuml
|
||||||
|
|
||||||
Pending: Order is expecting payment\nOrder reduces quotas
|
state "Approval Pending" as AP
|
||||||
Expired: Payment period is over\nOrder does not affect quotas
|
state "Canceled (with paid fee)" as CP
|
||||||
Paid: Order was successful\nOrder reduces quotas
|
AP: status = "n"
|
||||||
Canceled: Order has been canceled\nOrder does not affect quotas
|
AP: require_approval = true
|
||||||
|
Pending: status = "n"
|
||||||
|
Pending: require_approval = false
|
||||||
|
Pending: Tickets reserved: yes
|
||||||
|
Expired: status = "e"
|
||||||
|
Expired: Tickets reserved: no
|
||||||
|
Paid: status = "p"
|
||||||
|
Paid: count(positions | !canceled) > 0
|
||||||
|
Paid: Tickets reserved: yes
|
||||||
|
CP: status = "p"
|
||||||
|
CP: count(positions | !canceled) = 0
|
||||||
|
Canceled: status = "c"
|
||||||
|
Canceled: Tickets reserved: no
|
||||||
|
|
||||||
[*] --> Pending: customer\nplaces order
|
|
||||||
Pending --> Paid: successful payment
|
[*] -> Pending: order placed\ntotal > 0
|
||||||
Pending --> Expired: automatically\nor manually\non admin action
|
[*] -> Paid: order placed\ntotal = 0
|
||||||
Expired --> Paid: if payment is received\nonly if quota left
|
[*] -> AP: order placed\napproval required
|
||||||
Expired --> Canceled
|
Pending --> Paid: order paid
|
||||||
Expired --> Pending: manually\non admin action
|
Pending --> Expired: after payment\ndeadline
|
||||||
Paid --> Canceled: manually on\nadmin action\nor if an external\npayment provider\nnotifies about a\npayment refund
|
Expired --> Paid: order paid\n(only if quota left)
|
||||||
Pending --> Canceled: on admin or\ncustomer action
|
Expired -[dashed]-> Canceled
|
||||||
Paid -> Pending: manually on admin action
|
Expired -[dashed]-> Pending: order extended
|
||||||
[*] --> Paid: customer\nplaces free order
|
Paid --> Canceled: order canceled
|
||||||
|
Pending --> Canceled: order canceled
|
||||||
|
Paid -[dashed]-> Pending: refund
|
||||||
|
AP --> Pending: order approved
|
||||||
|
AP --> Canceled: order denied
|
||||||
|
Paid --> CP: order canceled\n(with cancellation fee)
|
||||||
|
Canceled -[dashed]-> Pending: order reactivated
|
||||||
|
Canceled -[dashed]-> Paid: order reactivated
|
||||||
|
CP -[dashed]-> Canceled: fee canceled
|
||||||
|
|
||||||
@enduml
|
@enduml
|
||||||
|
|||||||
@@ -22,10 +22,6 @@ item_assignments list of objects Products this l
|
|||||||
└ item integer Item ID
|
└ item integer Item ID
|
||||||
===================================== ========================== =======================================================
|
===================================== ========================== =======================================================
|
||||||
|
|
||||||
.. versionchanged:: 1.16
|
|
||||||
|
|
||||||
This resource has been added.
|
|
||||||
|
|
||||||
|
|
||||||
Endpoints
|
Endpoints
|
||||||
---------
|
---------
|
||||||
|
|||||||
@@ -24,14 +24,6 @@ item_assignments list of objects Products this l
|
|||||||
└ item integer Item ID
|
└ item integer Item ID
|
||||||
===================================== ========================== =======================================================
|
===================================== ========================== =======================================================
|
||||||
|
|
||||||
.. versionchanged:: 1.16
|
|
||||||
|
|
||||||
This resource has been added.
|
|
||||||
|
|
||||||
.. versionchanged:: 2.3
|
|
||||||
|
|
||||||
The ``item_assignments.sales_channel`` field has been added.
|
|
||||||
|
|
||||||
|
|
||||||
Endpoints
|
Endpoints
|
||||||
---------
|
---------
|
||||||
|
|||||||
@@ -437,11 +437,6 @@ Hosted or pretix Enterprise are active, you can pass the following fields:
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
.. versionchanged:: 2.3
|
|
||||||
|
|
||||||
Data passing options have been added in pretix 2.3. If you use a self-hosted version of pretix, they only work
|
|
||||||
fully if you configured a redis server.
|
|
||||||
|
|
||||||
.. versionchanged:: 3.6
|
.. versionchanged:: 3.6
|
||||||
|
|
||||||
Dynamically opening the widget has been added in pretix 3.6.
|
Dynamically opening the widget has been added in pretix 3.6.
|
||||||
|
|||||||
@@ -409,8 +409,8 @@ class SubEventSerializer(I18nAwareModelSerializer):
|
|||||||
model = SubEvent
|
model = SubEvent
|
||||||
fields = ('id', 'name', 'date_from', 'date_to', 'active', 'date_admission',
|
fields = ('id', 'name', 'date_from', 'date_to', 'active', 'date_admission',
|
||||||
'presale_start', 'presale_end', 'location', 'geo_lat', 'geo_lon', 'event', 'is_public',
|
'presale_start', 'presale_end', 'location', 'geo_lat', 'geo_lon', 'event', 'is_public',
|
||||||
'seating_plan', 'item_price_overrides', 'variation_price_overrides', 'meta_data',
|
'frontpage_text', 'seating_plan', 'item_price_overrides', 'variation_price_overrides',
|
||||||
'seat_category_mapping', 'last_modified')
|
'meta_data', 'seat_category_mapping', 'last_modified')
|
||||||
|
|
||||||
def validate(self, data):
|
def validate(self, data):
|
||||||
data = super().validate(data)
|
data = super().validate(data)
|
||||||
|
|||||||
@@ -468,7 +468,8 @@ def base_placeholders(sender, **kwargs):
|
|||||||
'68CYU2H6ZTP3WLK5'
|
'68CYU2H6ZTP3WLK5'
|
||||||
),
|
),
|
||||||
SimpleFunctionalMailTextPlaceholder(
|
SimpleFunctionalMailTextPlaceholder(
|
||||||
'voucher_list', ['voucher_list'], lambda voucher_list: '\n'.join(voucher_list),
|
# join vouchers with two spaces at end of line so markdown-parser inserts a <br>
|
||||||
|
'voucher_list', ['voucher_list'], lambda voucher_list: ' \n'.join(voucher_list),
|
||||||
' 68CYU2H6ZTP3WLK5\n 7MB94KKPVEPSMVF2'
|
' 68CYU2H6ZTP3WLK5\n 7MB94KKPVEPSMVF2'
|
||||||
),
|
),
|
||||||
SimpleFunctionalMailTextPlaceholder(
|
SimpleFunctionalMailTextPlaceholder(
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import io
|
import io
|
||||||
|
import re
|
||||||
import tempfile
|
import tempfile
|
||||||
from collections import OrderedDict, namedtuple
|
from collections import OrderedDict, namedtuple
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
@@ -10,11 +11,21 @@ from django.db.models import QuerySet
|
|||||||
from django.utils.formats import localize
|
from django.utils.formats import localize
|
||||||
from django.utils.translation import gettext, gettext_lazy as _
|
from django.utils.translation import gettext, gettext_lazy as _
|
||||||
from openpyxl import Workbook
|
from openpyxl import Workbook
|
||||||
from openpyxl.cell.cell import KNOWN_TYPES
|
from openpyxl.cell.cell import ILLEGAL_CHARACTERS_RE, KNOWN_TYPES
|
||||||
|
|
||||||
from pretix.base.models import Event
|
from pretix.base.models import Event
|
||||||
|
|
||||||
|
|
||||||
|
def excel_safe(val):
|
||||||
|
if not isinstance(val, KNOWN_TYPES):
|
||||||
|
val = str(val)
|
||||||
|
|
||||||
|
if isinstance(val, str):
|
||||||
|
val = re.sub(ILLEGAL_CHARACTERS_RE, '', val)
|
||||||
|
|
||||||
|
return val
|
||||||
|
|
||||||
|
|
||||||
class BaseExporter:
|
class BaseExporter:
|
||||||
"""
|
"""
|
||||||
This is the base class for all data exporters
|
This is the base class for all data exporters
|
||||||
@@ -181,7 +192,7 @@ class ListExporter(BaseExporter):
|
|||||||
total = line.total
|
total = line.total
|
||||||
continue
|
continue
|
||||||
ws.append([
|
ws.append([
|
||||||
str(val) if not isinstance(val, KNOWN_TYPES) else val
|
excel_safe(val) if not isinstance(val, KNOWN_TYPES) else val
|
||||||
for val in line
|
for val in line
|
||||||
])
|
])
|
||||||
if total:
|
if total:
|
||||||
@@ -301,7 +312,7 @@ class MultiSheetListExporter(ListExporter):
|
|||||||
total = line.total
|
total = line.total
|
||||||
continue
|
continue
|
||||||
ws.append([
|
ws.append([
|
||||||
str(val) if not isinstance(val, KNOWN_TYPES) else val
|
excel_safe(val)
|
||||||
for val in line
|
for val in line
|
||||||
])
|
])
|
||||||
if total:
|
if total:
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ from datetime import date, datetime, time
|
|||||||
from django.core.validators import MinLengthValidator, RegexValidator
|
from django.core.validators import MinLengthValidator, RegexValidator
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import Exists, OuterRef, Q
|
from django.db.models import Exists, OuterRef, Q
|
||||||
|
from django.urls import reverse
|
||||||
from django.utils.crypto import get_random_string
|
from django.utils.crypto import get_random_string
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
from django.utils.timezone import get_current_timezone, make_aware, now
|
from django.utils.timezone import get_current_timezone, make_aware, now
|
||||||
@@ -88,6 +89,15 @@ class Organizer(LoggedModel):
|
|||||||
|
|
||||||
return ObjectRelatedCache(self)
|
return ObjectRelatedCache(self)
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def all_logentries_link(self):
|
||||||
|
return reverse(
|
||||||
|
'control:organizer.log',
|
||||||
|
kwargs={
|
||||||
|
'organizer': self.slug,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def has_gift_cards(self):
|
def has_gift_cards(self):
|
||||||
return self.cache.get_or_set(
|
return self.cache.get_or_set(
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from datetime import datetime, time
|
from datetime import datetime, time, timedelta
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from urllib.parse import urlencode
|
from urllib.parse import urlencode
|
||||||
|
|
||||||
@@ -766,10 +766,15 @@ class SubEventFilterForm(FilterForm):
|
|||||||
),
|
),
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
date = forms.DateField(
|
date_from = forms.DateField(
|
||||||
label=_('Date'),
|
label=_('Date from'),
|
||||||
required=False,
|
required=False,
|
||||||
widget=DatePickerWidget
|
widget=DatePickerWidget,
|
||||||
|
)
|
||||||
|
date_until = forms.DateField(
|
||||||
|
label=_('Date until'),
|
||||||
|
required=False,
|
||||||
|
widget=DatePickerWidget,
|
||||||
)
|
)
|
||||||
weekday = forms.ChoiceField(
|
weekday = forms.ChoiceField(
|
||||||
label=_('Weekday'),
|
label=_('Weekday'),
|
||||||
@@ -796,7 +801,8 @@ class SubEventFilterForm(FilterForm):
|
|||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.fields['date'].widget = DatePickerWidget()
|
self.fields['date_from'].widget = DatePickerWidget()
|
||||||
|
self.fields['date_until'].widget = DatePickerWidget()
|
||||||
|
|
||||||
def filter_qs(self, qs):
|
def filter_qs(self, qs):
|
||||||
fdata = self.cleaned_data
|
fdata = self.cleaned_data
|
||||||
@@ -838,19 +844,21 @@ class SubEventFilterForm(FilterForm):
|
|||||||
Q(name__icontains=i18ncomp(query)) | Q(location__icontains=query)
|
Q(name__icontains=i18ncomp(query)) | Q(location__icontains=query)
|
||||||
)
|
)
|
||||||
|
|
||||||
if fdata.get('date'):
|
if fdata.get('date_until'):
|
||||||
date_start = make_aware(datetime.combine(
|
date_end = make_aware(datetime.combine(
|
||||||
fdata.get('date'),
|
fdata.get('date_until') + timedelta(days=1),
|
||||||
time(hour=0, minute=0, second=0, microsecond=0)
|
time(hour=0, minute=0, second=0, microsecond=0)
|
||||||
), get_current_timezone())
|
), get_current_timezone())
|
||||||
date_end = make_aware(datetime.combine(
|
|
||||||
fdata.get('date'),
|
|
||||||
time(hour=23, minute=59, second=59, microsecond=999999)
|
|
||||||
), get_current_timezone())
|
|
||||||
qs = qs.filter(
|
qs = qs.filter(
|
||||||
Q(date_to__isnull=True, date_from__gte=date_start, date_from__lte=date_end) |
|
Q(date_to__isnull=True, date_from__lt=date_end) |
|
||||||
Q(date_to__isnull=False, date_from__lte=date_end, date_to__gte=date_start)
|
Q(date_to__isnull=False, date_to__lt=date_end)
|
||||||
)
|
)
|
||||||
|
if fdata.get('date_from'):
|
||||||
|
date_start = make_aware(datetime.combine(
|
||||||
|
fdata.get('date_from'),
|
||||||
|
time(hour=0, minute=0, second=0, microsecond=0)
|
||||||
|
), get_current_timezone())
|
||||||
|
qs = qs.filter(date_from__gte=date_start)
|
||||||
|
|
||||||
if fdata.get('ordering'):
|
if fdata.get('ordering'):
|
||||||
qs = qs.order_by(self.get_order_by())
|
qs = qs.order_by(self.get_order_by())
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ class ConfirmPaymentForm(forms.Form):
|
|||||||
class CancelForm(ConfirmPaymentForm):
|
class CancelForm(ConfirmPaymentForm):
|
||||||
send_email = forms.BooleanField(
|
send_email = forms.BooleanField(
|
||||||
required=False,
|
required=False,
|
||||||
label=_('Notify user by e-mail'),
|
label=_('Notify customer by email'),
|
||||||
initial=True
|
initial=True
|
||||||
)
|
)
|
||||||
cancellation_fee = forms.DecimalField(
|
cancellation_fee = forms.DecimalField(
|
||||||
@@ -139,6 +139,11 @@ class CancelForm(ConfirmPaymentForm):
|
|||||||
|
|
||||||
|
|
||||||
class MarkPaidForm(ConfirmPaymentForm):
|
class MarkPaidForm(ConfirmPaymentForm):
|
||||||
|
send_email = forms.BooleanField(
|
||||||
|
required=False,
|
||||||
|
label=_('Notify customer by email'),
|
||||||
|
initial=True
|
||||||
|
)
|
||||||
amount = forms.DecimalField(
|
amount = forms.DecimalField(
|
||||||
required=True,
|
required=True,
|
||||||
max_digits=10, decimal_places=2,
|
max_digits=10, decimal_places=2,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from bootstrap3.renderers import FieldRenderer
|
from bootstrap3.renderers import FieldRenderer, InlineFieldRenderer
|
||||||
from bootstrap3.text import text_value
|
from bootstrap3.text import text_value
|
||||||
from django.forms import CheckboxInput
|
from django.forms import CheckboxInput
|
||||||
from django.forms.utils import flatatt
|
from django.forms.utils import flatatt
|
||||||
@@ -58,3 +58,40 @@ class ControlFieldRenderer(FieldRenderer):
|
|||||||
optional=not required and not isinstance(self.widget, CheckboxInput)
|
optional=not required and not isinstance(self.widget, CheckboxInput)
|
||||||
) + html
|
) + html
|
||||||
return html
|
return html
|
||||||
|
|
||||||
|
|
||||||
|
class BulkEditMixin:
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
kwargs['layout'] = self.layout
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
def wrap_field(self, html):
|
||||||
|
field_class = self.get_field_class()
|
||||||
|
name = '{}{}'.format(self.field.form.prefix, self.field.name)
|
||||||
|
checked = self.field.form.data and name in self.field.form.data.getlist('_bulk')
|
||||||
|
html = (
|
||||||
|
'<div class="{klass} bulk-edit-field-group">'
|
||||||
|
'<label class="field-toggle">'
|
||||||
|
'<input type="checkbox" name="_bulk" value="{name}" {checked}> {label}'
|
||||||
|
'</label>'
|
||||||
|
'<div class="field-content">'
|
||||||
|
'{html}'
|
||||||
|
'</div>'
|
||||||
|
'</div>'
|
||||||
|
).format(
|
||||||
|
klass=field_class or '',
|
||||||
|
name=name,
|
||||||
|
label=pgettext('form_bulk', 'change'),
|
||||||
|
checked='checked' if checked else '',
|
||||||
|
html=html
|
||||||
|
)
|
||||||
|
return html
|
||||||
|
|
||||||
|
|
||||||
|
class BulkEditFieldRenderer(BulkEditMixin, FieldRenderer):
|
||||||
|
layout = 'horizontal'
|
||||||
|
|
||||||
|
|
||||||
|
class InlineBulkEditFieldRenderer(BulkEditMixin, InlineFieldRenderer):
|
||||||
|
layout = 'inline'
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
from datetime import timedelta
|
from datetime import datetime, timedelta
|
||||||
from urllib.parse import urlencode
|
from urllib.parse import urlencode
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.forms import formset_factory
|
from django.forms import formset_factory
|
||||||
|
from django.forms.utils import ErrorDict
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.dates import MONTHS, WEEKDAYS
|
from django.utils.dates import MONTHS, WEEKDAYS
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
@@ -11,6 +12,7 @@ from django.utils.translation import gettext_lazy as _, pgettext_lazy
|
|||||||
from i18nfield.forms import I18nInlineFormSet
|
from i18nfield.forms import I18nInlineFormSet
|
||||||
|
|
||||||
from pretix.base.forms import I18nModelForm
|
from pretix.base.forms import I18nModelForm
|
||||||
|
from pretix.base.forms.widgets import DatePickerWidget, TimePickerWidget
|
||||||
from pretix.base.models.event import SubEvent, SubEventMetaValue
|
from pretix.base.models.event import SubEvent, SubEventMetaValue
|
||||||
from pretix.base.models.items import SubEventItem
|
from pretix.base.models.items import SubEventItem
|
||||||
from pretix.base.reldate import RelativeDateTimeField
|
from pretix.base.reldate import RelativeDateTimeField
|
||||||
@@ -88,6 +90,142 @@ class SubEventBulkForm(SubEventForm):
|
|||||||
del self.fields['date_admission']
|
del self.fields['date_admission']
|
||||||
|
|
||||||
|
|
||||||
|
class NullBooleanSelect(forms.NullBooleanSelect):
|
||||||
|
def __init__(self, attrs=None):
|
||||||
|
choices = (
|
||||||
|
('unknown', _('Keep the current values')),
|
||||||
|
('true', _('Yes')),
|
||||||
|
('false', _('No')),
|
||||||
|
)
|
||||||
|
super(forms.NullBooleanSelect, self).__init__(attrs, choices)
|
||||||
|
|
||||||
|
|
||||||
|
class SubEventBulkEditForm(I18nModelForm):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
self.mixed_values = kwargs.pop('mixed_values')
|
||||||
|
self.queryset = kwargs.pop('queryset')
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.fields['location'].widget.attrs['rows'] = '3'
|
||||||
|
|
||||||
|
for k in ('name', 'location', 'frontpage_text'):
|
||||||
|
# i18n fields
|
||||||
|
if k in self.mixed_values:
|
||||||
|
self.fields[k].widget.attrs['placeholder'] = '[{}]'.format(_('Selection contains various values'))
|
||||||
|
else:
|
||||||
|
self.fields[k].widget.attrs['placeholder'] = ''
|
||||||
|
self.fields[k].one_required = False
|
||||||
|
|
||||||
|
for k in ('geo_lat', 'geo_lon'):
|
||||||
|
# scalar fields
|
||||||
|
if k in self.mixed_values:
|
||||||
|
self.fields[k].widget.attrs['placeholder'] = '[{}]'.format(_('Selection contains various values'))
|
||||||
|
else:
|
||||||
|
self.fields[k].widget.attrs['placeholder'] = ''
|
||||||
|
self.fields[k].widget.is_required = False
|
||||||
|
self.fields[k].required = False
|
||||||
|
|
||||||
|
for k in ('date_from', 'date_to', 'date_admission', 'presale_start', 'presale_end'):
|
||||||
|
self.fields[k + '_day'] = forms.DateField(
|
||||||
|
label=self._meta.model._meta.get_field(k).verbose_name,
|
||||||
|
help_text=self._meta.model._meta.get_field(k).help_text,
|
||||||
|
widget=DatePickerWidget(),
|
||||||
|
required=False,
|
||||||
|
)
|
||||||
|
self.fields[k + '_time'] = forms.TimeField(
|
||||||
|
label=self._meta.model._meta.get_field(k).verbose_name,
|
||||||
|
help_text=self._meta.model._meta.get_field(k).help_text,
|
||||||
|
widget=TimePickerWidget(),
|
||||||
|
required=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = SubEvent
|
||||||
|
localized_fields = '__all__'
|
||||||
|
fields = [
|
||||||
|
'name',
|
||||||
|
'location',
|
||||||
|
'frontpage_text',
|
||||||
|
'geo_lat',
|
||||||
|
'geo_lon',
|
||||||
|
'is_public',
|
||||||
|
'active',
|
||||||
|
]
|
||||||
|
field_classes = {
|
||||||
|
}
|
||||||
|
widgets = {
|
||||||
|
}
|
||||||
|
|
||||||
|
def save(self, commit=True):
|
||||||
|
objs = list(self.queryset)
|
||||||
|
fields = set()
|
||||||
|
|
||||||
|
check_map = {
|
||||||
|
'geo_lat': '__geo',
|
||||||
|
'geo_lon': '__geo',
|
||||||
|
}
|
||||||
|
for k in self.fields:
|
||||||
|
cb_val = self.prefix + check_map.get(k, k)
|
||||||
|
if cb_val not in self.data.getlist('_bulk'):
|
||||||
|
continue
|
||||||
|
|
||||||
|
if k.endswith('_day'):
|
||||||
|
for obj in objs:
|
||||||
|
oldval = getattr(obj, k.replace('_day', ''))
|
||||||
|
cval = self.cleaned_data[k]
|
||||||
|
if cval is None:
|
||||||
|
newval = None
|
||||||
|
if not self._meta.model._meta.get_field(k.replace('_day', '')).null:
|
||||||
|
continue
|
||||||
|
elif oldval:
|
||||||
|
oldval = oldval.astimezone(self.event.timezone)
|
||||||
|
newval = oldval.replace(
|
||||||
|
year=cval.year,
|
||||||
|
month=cval.month,
|
||||||
|
day=cval.day,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# If there is no previous date/time set, we'll just set to midnight
|
||||||
|
# If the user also selected a time, this will be overridden anyways
|
||||||
|
newval = datetime(
|
||||||
|
year=cval.year,
|
||||||
|
month=cval.month,
|
||||||
|
day=cval.day,
|
||||||
|
tzinfo=self.event.timezone
|
||||||
|
)
|
||||||
|
setattr(obj, k.replace('_day', ''), newval)
|
||||||
|
fields.add(k.replace('_day', ''))
|
||||||
|
elif k.endswith('_time'):
|
||||||
|
for obj in objs:
|
||||||
|
# If there is no previous date/time set and only a time is changed not the
|
||||||
|
# date, we instead use the date of the event
|
||||||
|
oldval = getattr(obj, k.replace('_time', '')) or obj.date_from
|
||||||
|
cval = self.cleaned_data[k]
|
||||||
|
if cval is None:
|
||||||
|
continue
|
||||||
|
oldval = oldval.astimezone(self.event.timezone)
|
||||||
|
newval = oldval.replace(
|
||||||
|
hour=cval.hour,
|
||||||
|
minute=cval.minute,
|
||||||
|
second=cval.second,
|
||||||
|
)
|
||||||
|
setattr(obj, k.replace('_time', ''), newval)
|
||||||
|
fields.add(k.replace('_time', ''))
|
||||||
|
else:
|
||||||
|
fields.add(k)
|
||||||
|
for obj in objs:
|
||||||
|
setattr(obj, k, self.cleaned_data[k])
|
||||||
|
|
||||||
|
if fields:
|
||||||
|
SubEvent.objects.bulk_update(objs, fields, 200)
|
||||||
|
|
||||||
|
def full_clean(self):
|
||||||
|
if len(self.data) == 0:
|
||||||
|
# form wasn't submitted
|
||||||
|
self._errors = ErrorDict()
|
||||||
|
return
|
||||||
|
super().full_clean()
|
||||||
|
|
||||||
|
|
||||||
class SubEventItemOrVariationFormMixin:
|
class SubEventItemOrVariationFormMixin:
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
self.item = kwargs.pop('item')
|
self.item = kwargs.pop('item')
|
||||||
@@ -162,7 +300,7 @@ class SubEventMetaValueForm(forms.ModelForm):
|
|||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
self.property = kwargs.pop('property')
|
self.property = kwargs.pop('property')
|
||||||
self.default = kwargs.pop('default', None)
|
self.default = kwargs.pop('default', None)
|
||||||
self.disabled = kwargs.pop('disabled')
|
self.disabled = kwargs.pop('disabled', False)
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
if self.property.allowed_values:
|
if self.property.allowed_values:
|
||||||
self.fields['value'] = forms.ChoiceField(
|
self.fields['value'] = forms.ChoiceField(
|
||||||
|
|||||||
@@ -273,8 +273,15 @@ def _display_checkin(event, logentry):
|
|||||||
def pretixcontrol_logentry_display(sender: Event, logentry: LogEntry, **kwargs):
|
def pretixcontrol_logentry_display(sender: Event, logentry: LogEntry, **kwargs):
|
||||||
plains = {
|
plains = {
|
||||||
'pretix.object.cloned': _('This object has been created by cloning.'),
|
'pretix.object.cloned': _('This object has been created by cloning.'),
|
||||||
|
'pretix.organizer.changed': _('The organizer has been changed.'),
|
||||||
|
'pretix.organizer.settings': _('The organizer settings have been changed.'),
|
||||||
|
'pretix.giftcards.acceptance.added': _('Gift card acceptance for another organizer has been added.'),
|
||||||
|
'pretix.giftcards.acceptance.removed': _('Gift card acceptance for another organizer has been removed.'),
|
||||||
|
'pretix.webhook.created': _('The webhook has been created.'),
|
||||||
|
'pretix.webhook.changed': _('The webhook has been changed.'),
|
||||||
'pretix.event.comment': _('The event\'s internal comment has been updated.'),
|
'pretix.event.comment': _('The event\'s internal comment has been updated.'),
|
||||||
'pretix.event.canceled': _('The event has been canceled.'),
|
'pretix.event.canceled': _('The event has been canceled.'),
|
||||||
|
'pretix.event.deleted': _('An event has been deleted.'),
|
||||||
'pretix.event.order.modified': _('The order details have been changed.'),
|
'pretix.event.order.modified': _('The order details have been changed.'),
|
||||||
'pretix.event.order.unpaid': _('The order has been marked as unpaid.'),
|
'pretix.event.order.unpaid': _('The order has been marked as unpaid.'),
|
||||||
'pretix.event.order.secret.changed': _('The order\'s secret has been changed.'),
|
'pretix.event.order.secret.changed': _('The order\'s secret has been changed.'),
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
{% bootstrap_form_errors form %}
|
{% bootstrap_form_errors form %}
|
||||||
{% bootstrap_field form.amount layout='horizontal' %}
|
{% bootstrap_field form.amount layout='horizontal' %}
|
||||||
{% bootstrap_field form.payment_date layout='horizontal' %}
|
{% bootstrap_field form.payment_date layout='horizontal' %}
|
||||||
|
{% bootstrap_field form.send_email layout='horizontal' %}
|
||||||
{% if form.force %}
|
{% if form.force %}
|
||||||
{% bootstrap_field form.force layout='horizontal' horizontal_label_class='sr-only' horizontal_field_class='col-md-12' %}
|
{% bootstrap_field form.force layout='horizontal' horizontal_label_class='sr-only' horizontal_field_class='col-md-12' %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -15,7 +15,8 @@
|
|||||||
{% endblocktrans %}
|
{% endblocktrans %}
|
||||||
</a>
|
</a>
|
||||||
</h1>
|
</h1>
|
||||||
<form method="post" href="">
|
<form method="post" href=""
|
||||||
|
>
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<fieldset class="form-inline form-refund-choose">
|
<fieldset class="form-inline form-refund-choose">
|
||||||
<legend>{% trans "How should the refund be sent?" %}</legend>
|
<legend>{% trans "How should the refund be sent?" %}</legend>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</h1>
|
</h1>
|
||||||
{% for e in exporters %}
|
{% for e in exporters %}
|
||||||
<details class="panel panel-default" {% if "identifier" in request.GET %}open{% endif %}>
|
<details class="panel panel-default" {% if "identifier" in request.GET or "exporter" in request.POST %}open{% endif %}>
|
||||||
<summary class="panel-heading">
|
<summary class="panel-heading">
|
||||||
<h3 class="panel-title">
|
<h3 class="panel-title">
|
||||||
{{ e.verbose_name }}
|
{{ e.verbose_name }}
|
||||||
|
|||||||
@@ -22,54 +22,68 @@
|
|||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{% bootstrap_form_errors sform %}
|
{% bootstrap_form_errors sform %}
|
||||||
{% bootstrap_form_errors form %}
|
{% bootstrap_form_errors form %}
|
||||||
<div class="tabbed-form">
|
<div class="row">
|
||||||
<fieldset>
|
<div class="col-xs-12 col-lg-10">
|
||||||
<legend>{% trans "General" %}</legend>
|
<div class="tabbed-form">
|
||||||
{% bootstrap_field form.name layout="control" %}
|
<fieldset>
|
||||||
{% bootstrap_field form.slug layout="control" %}
|
<legend>{% trans "General" %}</legend>
|
||||||
{% if form.domain %}
|
{% bootstrap_field form.name layout="control" %}
|
||||||
{% bootstrap_field form.domain layout="control" %}
|
{% bootstrap_field form.slug layout="control" %}
|
||||||
{% endif %}
|
{% if form.domain %}
|
||||||
{% bootstrap_field sform.imprint_url layout="control" %}
|
{% bootstrap_field form.domain layout="control" %}
|
||||||
{% bootstrap_field sform.contact_mail layout="control" %}
|
{% endif %}
|
||||||
{% bootstrap_field sform.organizer_info_text layout="control" %}
|
{% bootstrap_field sform.imprint_url layout="control" %}
|
||||||
{% bootstrap_field sform.event_team_provisioning layout="control" %}
|
{% bootstrap_field sform.contact_mail layout="control" %}
|
||||||
</fieldset>
|
{% bootstrap_field sform.organizer_info_text layout="control" %}
|
||||||
<fieldset>
|
{% bootstrap_field sform.event_team_provisioning layout="control" %}
|
||||||
<legend>{% trans "Organizer page" %}</legend>
|
</fieldset>
|
||||||
{% bootstrap_field sform.organizer_logo_image layout="control" %}
|
<fieldset>
|
||||||
{% bootstrap_field sform.organizer_logo_image_large layout="control" %}
|
<legend>{% trans "Organizer page" %}</legend>
|
||||||
{% bootstrap_field sform.organizer_homepage_text layout="control" %}
|
{% bootstrap_field sform.organizer_logo_image layout="control" %}
|
||||||
{% bootstrap_field sform.event_list_type layout="control" %}
|
{% bootstrap_field sform.organizer_logo_image_large layout="control" %}
|
||||||
{% bootstrap_field sform.event_list_availability layout="control" %}
|
{% bootstrap_field sform.organizer_homepage_text layout="control" %}
|
||||||
{% bootstrap_field sform.organizer_link_back layout="control" %}
|
{% bootstrap_field sform.event_list_type layout="control" %}
|
||||||
</fieldset>
|
{% bootstrap_field sform.event_list_availability layout="control" %}
|
||||||
<fieldset>
|
{% bootstrap_field sform.organizer_link_back layout="control" %}
|
||||||
<legend>{% trans "Localization" %}</legend>
|
</fieldset>
|
||||||
{% bootstrap_field sform.locales layout="control" %}
|
<fieldset>
|
||||||
{% bootstrap_field sform.region layout="control" %}
|
<legend>{% trans "Localization" %}</legend>
|
||||||
</fieldset>
|
{% bootstrap_field sform.locales layout="control" %}
|
||||||
<fieldset>
|
{% bootstrap_field sform.region layout="control" %}
|
||||||
<legend>{% trans "Shop design" %}</legend>
|
</fieldset>
|
||||||
<p class="help-block">
|
<fieldset>
|
||||||
{% blocktrans trimmed %}
|
<legend>{% trans "Shop design" %}</legend>
|
||||||
These settings will be used for the organizer page as well as for the default settings
|
<p class="help-block">
|
||||||
for all events in this account that do not have their own design settings.
|
{% blocktrans trimmed %}
|
||||||
{% endblocktrans %}
|
These settings will be used for the organizer page as well as for the default settings
|
||||||
</p>
|
for all events in this account that do not have their own design settings.
|
||||||
{% bootstrap_field sform.primary_color layout="control" %}
|
{% endblocktrans %}
|
||||||
{% bootstrap_field sform.theme_color_success layout="control" %}
|
</p>
|
||||||
{% bootstrap_field sform.theme_color_danger layout="control" %}
|
{% bootstrap_field sform.primary_color layout="control" %}
|
||||||
{% bootstrap_field sform.theme_color_background layout="control" %}
|
{% bootstrap_field sform.theme_color_success layout="control" %}
|
||||||
{% bootstrap_field sform.theme_round_borders layout="control" %}
|
{% bootstrap_field sform.theme_color_danger layout="control" %}
|
||||||
{% bootstrap_field sform.primary_font layout="control" %}
|
{% bootstrap_field sform.theme_color_background layout="control" %}
|
||||||
{% bootstrap_field sform.favicon layout="control" %}
|
{% bootstrap_field sform.theme_round_borders layout="control" %}
|
||||||
</fieldset>
|
{% bootstrap_field sform.primary_font layout="control" %}
|
||||||
<fieldset>
|
{% bootstrap_field sform.favicon layout="control" %}
|
||||||
<legend>{% trans "Gift cards" %}</legend>
|
</fieldset>
|
||||||
{% bootstrap_field sform.giftcard_expiry_years layout="control" %}
|
<fieldset>
|
||||||
{% bootstrap_field sform.giftcard_length layout="control" %}
|
<legend>{% trans "Gift cards" %}</legend>
|
||||||
</fieldset>
|
{% bootstrap_field sform.giftcard_expiry_years layout="control" %}
|
||||||
|
{% bootstrap_field sform.giftcard_length layout="control" %}
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-12 col-lg-2">
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-heading">
|
||||||
|
<h3 class="panel-title">
|
||||||
|
{% trans "Change history" %}
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
{% include "pretixcontrol/includes/logs.html" with obj=organizer %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group submit-group">
|
<div class="form-group submit-group">
|
||||||
<button type="submit" class="btn btn-primary btn-save">
|
<button type="submit" class="btn btn-primary btn-save">
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</h1>
|
</h1>
|
||||||
{% for e in exporters %}
|
{% for e in exporters %}
|
||||||
<details class="panel panel-default" {% if "identifier" in request.GET %}open{% endif %}>
|
<details class="panel panel-default" {% if "identifier" in request.GET or "exporter" in request.POST %}open{% endif %}>
|
||||||
<summary class="panel-heading">
|
<summary class="panel-heading">
|
||||||
<h3 class="panel-title">
|
<h3 class="panel-title">
|
||||||
{{ e.verbose_name }}
|
{{ e.verbose_name }}
|
||||||
|
|||||||
@@ -0,0 +1,85 @@
|
|||||||
|
{% extends "pretixcontrol/items/base.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% load static %}
|
||||||
|
{% block title %}{% trans "Organizer logs" %}{% endblock %}
|
||||||
|
{% block inside %}
|
||||||
|
<h1>{% trans "Organizer logs" %}</h1>
|
||||||
|
<form class="form-inline helper-display-inline" action="" method="get">
|
||||||
|
<input type="hidden" name="content_type" value="{{ request.GET.content_type }}">
|
||||||
|
<input type="hidden" name="object" value="{{ request.GET.object }}">
|
||||||
|
<p>
|
||||||
|
<select name="user" class="form-control">
|
||||||
|
<option value="">{% trans "All actions" %}</option>
|
||||||
|
{% for up in userlist %}
|
||||||
|
{% if up.user__id %}
|
||||||
|
<option value="{{ up.user__id }}"
|
||||||
|
{% if request.GET.user == up.user__id %}selected="selected"{% endif %}>
|
||||||
|
{{ up.user__email }}
|
||||||
|
</option>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
<button class="btn btn-primary" type="submit">{% trans "Filter" %}</button>
|
||||||
|
</p>
|
||||||
|
</form>
|
||||||
|
<ul class="list-group">
|
||||||
|
{% for log in logs %}
|
||||||
|
<li class="list-group-item logentry">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-2 col-sm-6 col-xs-12">
|
||||||
|
<span class="fa fa-clock-o"></span>
|
||||||
|
{{ log.datetime|date:"SHORT_DATETIME_FORMAT" }}
|
||||||
|
{% if log.shredded %}
|
||||||
|
<span class="fa fa-eraser fa-danger fa-fw"
|
||||||
|
data-toggle="tooltip"
|
||||||
|
title="{% trans "Personal data was cleared from this log entry." %}">
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-2 col-sm-6 col-xs-12">
|
||||||
|
{% if log.user %}
|
||||||
|
{% if log.user.is_staff %}
|
||||||
|
<span class="fa fa-id-card fa-danger fa-fw"
|
||||||
|
data-toggle="tooltip"
|
||||||
|
title="{% trans "This change was performed by a pretix administrator." %}">
|
||||||
|
</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="fa fa-user fa-fw"></span>
|
||||||
|
{% endif %}
|
||||||
|
{{ log.user.get_full_name }}
|
||||||
|
{% if log.oauth_application %}
|
||||||
|
<br><span class="fa fa-plug fa-fw"></span>
|
||||||
|
{{ log.oauth_application.name }}
|
||||||
|
{% endif %}
|
||||||
|
{% elif log.device %}
|
||||||
|
<span class="fa fa-mobile fa-fw"></span>
|
||||||
|
{{ log.device.name }}
|
||||||
|
{% elif log.api_token %}
|
||||||
|
<span class="fa fa-key fa-fw"></span>
|
||||||
|
{{ log.api_token.name }}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-2 col-sm-12 col-xs-12">
|
||||||
|
{% if log.display_object %}
|
||||||
|
<span class="fa fa-flag"></span> {{ log.display_object|safe }}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-6 col-sm-12 col-xs-12">
|
||||||
|
{{ log.display }}
|
||||||
|
{% if staff_session %}
|
||||||
|
<a href="" class="btn btn-default btn-xs" data-expandlogs data-id="{{ log.pk }}">
|
||||||
|
<span class="fa-eye fa fa-fw"></span>
|
||||||
|
{% trans "Inspect" %}
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
{% empty %}
|
||||||
|
<div class="list-group-item">
|
||||||
|
<em>{% trans "No results" %}</em>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% include "pretixcontrol/pagination.html" %}
|
||||||
|
{% endblock %}
|
||||||
@@ -0,0 +1,351 @@
|
|||||||
|
{% extends "pretixcontrol/event/base.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% load bootstrap3 %}
|
||||||
|
{% load formset_tags %}
|
||||||
|
{% load captureas %}
|
||||||
|
{% load static %}
|
||||||
|
{% load eventsignal %}
|
||||||
|
{% block title %}{% trans "Change multiple dates" context "subevent" %}{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
<h1>
|
||||||
|
{% trans "Change multiple dates" context "subevent" %}
|
||||||
|
<small>
|
||||||
|
{% blocktrans trimmed with number=subevents.count %}
|
||||||
|
{{ number }} selected
|
||||||
|
{% endblocktrans %}
|
||||||
|
</small>
|
||||||
|
</h1>
|
||||||
|
<form action="" method="post" class="form-horizontal" id="subevent-bulk-create-form">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% bootstrap_form_errors form %}
|
||||||
|
{% for f in itemvar_forms %}
|
||||||
|
{% bootstrap_form_errors f %}
|
||||||
|
{% endfor %}
|
||||||
|
<div class="hidden">
|
||||||
|
{% for se in subevents %}
|
||||||
|
<input type="hidden" name="subevent" value="{{ se.pk }}">
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
<fieldset>
|
||||||
|
<legend>{% trans "General information" %}</legend>
|
||||||
|
{% bootstrap_field form.name layout="bulkedit" %}
|
||||||
|
{% bootstrap_field form.active layout="bulkedit" %}
|
||||||
|
<div class="geodata-section">
|
||||||
|
{% bootstrap_field form.location layout="bulkedit" %}
|
||||||
|
<div class="form-group geodata-group"
|
||||||
|
data-tiles="{{ global_settings.leaflet_tiles|default_if_none:"" }}"
|
||||||
|
data-attrib="{{ global_settings.leaflet_tiles_attribution }}"
|
||||||
|
data-icon="{% static "leaflet/images/marker-icon.png" %}"
|
||||||
|
data-shadow="{% static "leaflet/images/marker-shadow.png" %}">
|
||||||
|
<label class="col-md-3 control-label">
|
||||||
|
{% trans "Geo coordinates" %}
|
||||||
|
</label>
|
||||||
|
<div class="col-md-9">
|
||||||
|
<div class="bulk-edit-field-group">
|
||||||
|
<label class="field-toggle">
|
||||||
|
<input type="checkbox" name="_bulk" value="{{ form.prefix }}__geo" {% if form.prefix|add:"__geo" in bulk_selected %}checked{% endif %}>
|
||||||
|
{% trans "change" context "form_bulk" %}
|
||||||
|
</label>
|
||||||
|
<div class="field-content">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
{% bootstrap_field form.geo_lat layout="inline" %}
|
||||||
|
{% if global_settings.opencagedata_apikey %}
|
||||||
|
<p class="attrib">
|
||||||
|
<a href="https://openstreetmap.org/" target="_blank" tabindex="-1">
|
||||||
|
{% trans "Geocoding data © OpenStreetMap" %}
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
{% bootstrap_field form.geo_lon layout="inline" %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% bootstrap_field form.frontpage_text layout="bulkedit" %}
|
||||||
|
{% bootstrap_field form.is_public layout="bulkedit" %}
|
||||||
|
{% if meta_forms %}
|
||||||
|
<div class="form-group metadata-group">
|
||||||
|
<label class="col-md-3 control-label">{% trans "Meta data" %}</label>
|
||||||
|
<div class="col-md-9">
|
||||||
|
{% for form in meta_forms %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label for="{{ form.value.id_for_label }}">
|
||||||
|
{{ form.property.name }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-8">
|
||||||
|
{% bootstrap_form form layout="bulkedit_inline" %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</fieldset>
|
||||||
|
<fieldset>
|
||||||
|
<legend>{% trans "Timeline" %}</legend>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="col-md-3 control-label" for="{{ form.date_from_day.id_for_label }}">
|
||||||
|
{{ form.date_from_day.label }}
|
||||||
|
</label>
|
||||||
|
<div class="col-md-5">
|
||||||
|
{% bootstrap_field form.date_from_day layout="bulkedit_inline" form_group_class="" %}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
{% bootstrap_field form.date_from_time layout="bulkedit_inline" form_group_class="" %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="col-md-3 control-label" for="{{ form.date_to_day.id_for_label }}">
|
||||||
|
{{ form.date_to_day.label }}
|
||||||
|
</label>
|
||||||
|
<div class="col-md-5">
|
||||||
|
{% bootstrap_field form.date_to_day layout="bulkedit_inline" form_group_class="" %}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
{% bootstrap_field form.date_to_time layout="bulkedit_inline" form_group_class="" %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="col-md-3 control-label" for="{{ form.date_admission_day.id_for_label }}">
|
||||||
|
{{ form.date_admission_day.label }}
|
||||||
|
</label>
|
||||||
|
<div class="col-md-5">
|
||||||
|
{% bootstrap_field form.date_admission_day layout="bulkedit_inline" form_group_class="" %}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
{% bootstrap_field form.date_admission_time layout="bulkedit_inline" form_group_class="" %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="col-md-3 control-label" for="{{ form.presale_start_day.id_for_label }}">
|
||||||
|
{{ form.presale_start_day.label }}
|
||||||
|
</label>
|
||||||
|
<div class="col-md-5">
|
||||||
|
{% bootstrap_field form.presale_start_day layout="bulkedit_inline" form_group_class="" %}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
{% bootstrap_field form.presale_start_time layout="bulkedit_inline" form_group_class="" %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="col-md-3 control-label" for="{{ form.presale_end_day.id_for_label }}">
|
||||||
|
{{ form.presale_end_day.label }}
|
||||||
|
</label>
|
||||||
|
<div class="col-md-5">
|
||||||
|
{% bootstrap_field form.presale_end_day layout="bulkedit_inline" form_group_class="" %}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
{% bootstrap_field form.presale_end_time layout="bulkedit_inline" form_group_class="" %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
<fieldset>
|
||||||
|
<legend>{% trans "Item prices" %}</legend>
|
||||||
|
{% for f in itemvar_forms %}
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="col-md-3 control-label" for="id_{{ f.prefix }}-price">
|
||||||
|
{% if f.variation %}{{ f.item }} – {{ f.variation }}{% else %}{{ f.item }}{% endif %}
|
||||||
|
</label>
|
||||||
|
<div class="col-md-6">
|
||||||
|
{% bootstrap_field f.price addon_after=request.event.currency form_group_class="" layout="bulkedit_inline" %}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
{% bootstrap_field f.disabled layout="bulkedit_inline" form_group_class="" %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</fieldset>
|
||||||
|
<fieldset>
|
||||||
|
<legend>{% trans "Quotas" %}</legend>
|
||||||
|
{% if sampled_quotas|default_if_none:"NONE" == "NONE" %}
|
||||||
|
<div class="alert alert-warning">
|
||||||
|
{% blocktrans trimmed %}
|
||||||
|
You selected a set of dates that currently have different quota setups. You can therefore
|
||||||
|
not change their quotas in bulk. If you want, you can set up a new set of quotas to
|
||||||
|
<strong>replace</strong> the quota setup of all selected dates.
|
||||||
|
{% endblocktrans %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<div class="bulk-edit-field-group">
|
||||||
|
<label class="field-toggle">
|
||||||
|
<input type="checkbox" name="_bulk" value="__quotas" {% if "__quotas" in bulk_selected %}checked{% endif %}>
|
||||||
|
{% trans "change" context "form_bulk" %}
|
||||||
|
</label>
|
||||||
|
<div class="field-content">
|
||||||
|
<div class="formset" data-formset data-formset-prefix="{{ formset.prefix }}">
|
||||||
|
{{ formset.management_form }}
|
||||||
|
{% bootstrap_formset_errors formset %}
|
||||||
|
<div data-formset-body>
|
||||||
|
{% for form in formset %}
|
||||||
|
<div class="panel panel-default" data-formset-form>
|
||||||
|
<div class="sr-only">
|
||||||
|
{{ form.id }}
|
||||||
|
{% bootstrap_field form.DELETE form_group_class="" layout="inline" %}
|
||||||
|
</div>
|
||||||
|
<div class="panel-heading">
|
||||||
|
<h4 class="panel-title">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-10">
|
||||||
|
{% bootstrap_field form.name layout='inline' form_group_class="" %}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2 text-right flip">
|
||||||
|
<button type="button" class="btn btn-danger" data-formset-delete-button>
|
||||||
|
<i class="fa fa-trash"></i></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
<div class="panel-body form-horizontal">
|
||||||
|
{% bootstrap_form_errors form %}
|
||||||
|
{% bootstrap_field form.size layout="control" %}
|
||||||
|
{% bootstrap_field form.itemvars layout="control" %}
|
||||||
|
{% bootstrap_field form.release_after_exit layout="control" %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
<script type="form-template" data-formset-empty-form>
|
||||||
|
{% escapescript %}
|
||||||
|
<div class="panel panel-default" data-formset-form>
|
||||||
|
<div class="sr-only">
|
||||||
|
{{ formset.empty_form.id }}
|
||||||
|
{% bootstrap_field formset.empty_form.DELETE form_group_class="" layout="inline" %}
|
||||||
|
</div>
|
||||||
|
<div class="panel-heading">
|
||||||
|
<h4 class="panel-title">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-10">
|
||||||
|
{% bootstrap_field formset.empty_form.name layout='inline' form_group_class="" %}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2 text-right flip">
|
||||||
|
<button type="button" class="btn btn-danger" data-formset-delete-button>
|
||||||
|
<i class="fa fa-trash"></i></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
<div class="panel-body form-horizontal">
|
||||||
|
{% bootstrap_field formset.empty_form.size layout="control" %}
|
||||||
|
{% bootstrap_field formset.empty_form.itemvars layout="control" %}
|
||||||
|
{% bootstrap_field formset.empty_form.release_after_exit layout="control" %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endescapescript %}
|
||||||
|
</script>
|
||||||
|
<p>
|
||||||
|
<button type="button" class="btn btn-default" data-formset-add>
|
||||||
|
<i class="fa fa-plus"></i> {% trans "Add a new quota" %}</button>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
<p> </p>
|
||||||
|
<fieldset>
|
||||||
|
<legend>{% trans "Check-in lists" %}</legend>
|
||||||
|
{% if sampled_lists|default_if_none:"NONE" == "NONE" %}
|
||||||
|
<div class="alert alert-warning">
|
||||||
|
{% blocktrans trimmed %}
|
||||||
|
You selected a set of dates that currently have different check-in list setups. You can
|
||||||
|
therefore not change their check-in lists in bulk.
|
||||||
|
{% endblocktrans %}
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="bulk-edit-field-group">
|
||||||
|
<label class="field-toggle">
|
||||||
|
<input type="checkbox" name="_bulk" value="__checkinlists" {% if "__checkinlists" in bulk_selected %}checked{% endif %}>
|
||||||
|
{% trans "change" context "form_bulk" %}
|
||||||
|
</label>
|
||||||
|
<div class="field-content">
|
||||||
|
<div class="formset" data-formset data-formset-prefix="{{ cl_formset.prefix }}">
|
||||||
|
{{ cl_formset.management_form }}
|
||||||
|
{% bootstrap_formset_errors cl_formset %}
|
||||||
|
<div data-formset-body>
|
||||||
|
{% for form in cl_formset %}
|
||||||
|
<div class="panel panel-default" data-formset-form>
|
||||||
|
<div class="sr-only">
|
||||||
|
{{ form.id }}
|
||||||
|
{% bootstrap_field form.DELETE form_group_class="" layout="inline" %}
|
||||||
|
</div>
|
||||||
|
<div class="panel-heading">
|
||||||
|
<h4 class="panel-title">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-10">
|
||||||
|
{% bootstrap_field form.name layout='inline' form_group_class="" %}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2 text-right flip">
|
||||||
|
<button type="button" class="btn btn-danger" data-formset-delete-button>
|
||||||
|
<i class="fa fa-trash"></i></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
<div class="panel-body form-horizontal">
|
||||||
|
{% bootstrap_form_errors form %}
|
||||||
|
{% bootstrap_field form.include_pending layout="control" %}
|
||||||
|
{% bootstrap_field form.all_products layout="control" %}
|
||||||
|
{% bootstrap_field form.limit_products layout="control" %}
|
||||||
|
{% bootstrap_field form.allow_entry_after_exit layout="control" %}
|
||||||
|
{% if form.gates %}
|
||||||
|
{% bootstrap_field form.gates layout="control" %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
<script type="form-template" data-formset-empty-form>
|
||||||
|
{% escapescript %}
|
||||||
|
<div class="panel panel-default" data-formset-form>
|
||||||
|
<div class="sr-only">
|
||||||
|
{{ cl_formset.empty_form.id }}
|
||||||
|
{% bootstrap_field cl_formset.empty_form.DELETE form_group_class="" layout="inline" %}
|
||||||
|
</div>
|
||||||
|
<div class="panel-heading">
|
||||||
|
<h4 class="panel-title">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-10">
|
||||||
|
{% bootstrap_field cl_formset.empty_form.name layout='inline' form_group_class="" %}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2 text-right flip">
|
||||||
|
<button type="button" class="btn btn-danger" data-formset-delete-button>
|
||||||
|
<i class="fa fa-trash"></i></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
<div class="panel-body form-horizontal">
|
||||||
|
{% bootstrap_field cl_formset.empty_form.include_pending layout="control" %}
|
||||||
|
{% bootstrap_field cl_formset.empty_form.all_products layout="control" %}
|
||||||
|
{% bootstrap_field cl_formset.empty_form.limit_products layout="control" %}
|
||||||
|
{% bootstrap_field cl_formset.empty_form.allow_entry_after_exit layout="control" %}
|
||||||
|
{% if cl_formset.empty_form.gates %}
|
||||||
|
{% bootstrap_field cl_formset.empty_form.gates layout="control" %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endescapescript %}
|
||||||
|
</script>
|
||||||
|
<p>
|
||||||
|
<button type="button" class="btn btn-default" data-formset-add>
|
||||||
|
<i class="fa fa-plus"></i> {% trans "Add a new check-in list" %}
|
||||||
|
</button>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</fieldset>
|
||||||
|
<div class="form-group submit-group">
|
||||||
|
<button type="submit" class="btn btn-primary btn-save">
|
||||||
|
{% trans "Save" %}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
@@ -22,14 +22,17 @@
|
|||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<form class="row filter-form" action="" method="get">
|
<form class="row filter-form" action="" method="get">
|
||||||
<div class="col-md-3 col-sm-6 col-xs-12">
|
<div class="col-md-2 col-sm-6 col-xs-12">
|
||||||
{% bootstrap_field filter_form.query layout='inline' %}
|
{% bootstrap_field filter_form.query layout='inline' %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-3 col-sm-6 col-xs-12">
|
<div class="col-md-2 col-sm-6 col-xs-12">
|
||||||
{% bootstrap_field filter_form.status layout='inline' %}
|
{% bootstrap_field filter_form.status layout='inline' %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-2 col-sm-6 col-xs-12">
|
<div class="col-md-2 col-sm-6 col-xs-12">
|
||||||
{% bootstrap_field filter_form.date layout='inline' %}
|
{% bootstrap_field filter_form.date_from layout='inline' %}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2 col-sm-6 col-xs-12">
|
||||||
|
{% bootstrap_field filter_form.date_until layout='inline' %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-2 col-sm-6 col-xs-12">
|
<div class="col-md-2 col-sm-6 col-xs-12">
|
||||||
{% bootstrap_field filter_form.weekday layout='inline' %}
|
{% bootstrap_field filter_form.weekday layout='inline' %}
|
||||||
@@ -43,23 +46,28 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<p>
|
{% if "can_change_event_settings" in request.eventpermset %}
|
||||||
<a href="{% url "control:event.subevents.add" organizer=request.event.organizer.slug event=request.event.slug %}"
|
<p>
|
||||||
class="btn btn-default"><i class="fa fa-plus"></i>
|
<a href="{% url "control:event.subevents.add" organizer=request.event.organizer.slug event=request.event.slug %}"
|
||||||
{% trans "Create a new date" context "subevent" %}</a>
|
class="btn btn-default"><i class="fa fa-plus"></i>
|
||||||
<a href="{% url "control:event.subevents.bulk" organizer=request.event.organizer.slug event=request.event.slug %}"
|
{% trans "Create a new date" context "subevent" %}</a>
|
||||||
class="btn btn-default"><i class="fa fa-plus"></i>
|
<a href="{% url "control:event.subevents.bulk" organizer=request.event.organizer.slug event=request.event.slug %}"
|
||||||
{% trans "Create many new dates" context "subevent" %}</a>
|
class="btn btn-default"><i class="fa fa-plus"></i>
|
||||||
</p>
|
{% trans "Create many new dates" context "subevent" %}</a>
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
<form action="{% url "control:event.subevents.bulkaction" organizer=request.event.organizer.slug event=request.event.slug %}" method="post">
|
<form action="{% url "control:event.subevents.bulkaction" organizer=request.event.organizer.slug event=request.event.slug %}" method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
<div class="hidden">
|
||||||
|
{{ filter_form.as_p }}
|
||||||
|
</div>
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table table-hover table-quotas">
|
<table class="table table-hover table-quotas">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>
|
<th>
|
||||||
{% if "can_change_event_settings" in request.eventpermset %}
|
{% if "can_change_event_settings" in request.eventpermset %}
|
||||||
<input type="checkbox" data-toggle-table/>
|
<label aria-label="{% trans "select all rows for batch-operation" %}" class="batch-select-label"><input type="checkbox" data-toggle-table/></label>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</th>
|
</th>
|
||||||
<th>
|
<th>
|
||||||
@@ -67,28 +75,40 @@
|
|||||||
</th>
|
</th>
|
||||||
<th>
|
<th>
|
||||||
{% trans "Begin" %}
|
{% trans "Begin" %}
|
||||||
<a href="?{% url_replace request 'ordering' '-date_from' %}"><i class="fa fa-caret-down"></i></a>
|
<a href="?{% url_replace request 'filter-ordering' '-date_from' %}"><i class="fa fa-caret-down"></i></a>
|
||||||
<a href="?{% url_replace request 'ordering' 'date_from' %}"><i class="fa fa-caret-up"></i></a>
|
<a href="?{% url_replace request 'filter-ordering' 'date_from' %}"><i class="fa fa-caret-up"></i></a>
|
||||||
</th>
|
</th>
|
||||||
<th>
|
<th>
|
||||||
{% trans "Paid tickets per quota" %}
|
{% trans "Paid tickets per quota" %}
|
||||||
<a href="?{% url_replace request 'ordering' '-sum_tickets_paid' %}"><i class="fa fa-caret-down"></i></a>
|
<a href="?{% url_replace request 'filter-ordering' '-sum_tickets_paid' %}"><i class="fa fa-caret-down"></i></a>
|
||||||
<a href="?{% url_replace request 'ordering' 'sum_tickets_paid' %}"><i class="fa fa-caret-up"></i></a>
|
<a href="?{% url_replace request 'filter-ordering' 'sum_tickets_paid' %}"><i class="fa fa-caret-up"></i></a>
|
||||||
</th>
|
</th>
|
||||||
<th>
|
<th>
|
||||||
{% trans "Status" %}
|
{% trans "Status" %}
|
||||||
<a href="?{% url_replace request 'ordering' '-active' %}"><i class="fa fa-caret-down"></i></a>
|
<a href="?{% url_replace request 'filter-ordering' '-active' %}"><i class="fa fa-caret-down"></i></a>
|
||||||
<a href="?{% url_replace request 'ordering' 'active' %}"><i class="fa fa-caret-up"></i></a>
|
<a href="?{% url_replace request 'filter-ordering' 'active' %}"><i class="fa fa-caret-up"></i></a>
|
||||||
</th>
|
</th>
|
||||||
<th></th>
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
|
{% if "can_change_event_settings" in request.eventpermset %}
|
||||||
|
<tr class="table-select-all warning hidden">
|
||||||
|
<td>
|
||||||
|
<input type="checkbox" name="__ALL" id="__all">
|
||||||
|
</td>
|
||||||
|
<td colspan="5">
|
||||||
|
<label for="__all">
|
||||||
|
{% trans "Select all results on other pages as well" %}
|
||||||
|
</label>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for s in subevents %}
|
{% for s in subevents %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
{% if "can_change_event_settings" in request.eventpermset %}
|
{% if "can_change_event_settings" in request.eventpermset %}
|
||||||
<input type="checkbox" name="subevent" class="" value="{{ s.pk }}"/>
|
<label aria-label="{% trans "select row for batch-operation" %}" class="batch-select-label"><input type="checkbox" name="subevent" class="" value="{{ s.pk }}"/></label>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
@@ -150,6 +170,10 @@
|
|||||||
<button type="submit" class="btn btn-default btn-save" name="action" value="delete">
|
<button type="submit" class="btn btn-default btn-save" name="action" value="delete">
|
||||||
{% trans "Delete selected" %}
|
{% trans "Delete selected" %}
|
||||||
</button>
|
</button>
|
||||||
|
<button type="submit" class="btn btn-default btn-save" name="action" value="disable"
|
||||||
|
formaction="{% url "control:event.subevents.bulkedit" organizer=request.event.organizer.slug event=request.event.slug %}">
|
||||||
|
{% trans "Change selected" %}
|
||||||
|
</button>
|
||||||
<button type="submit" class="btn btn-default btn-save" name="action" value="enable">
|
<button type="submit" class="btn btn-default btn-save" name="action" value="enable">
|
||||||
{% trans "Enable selected" %}
|
{% trans "Enable selected" %}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -122,6 +122,7 @@ urlpatterns = [
|
|||||||
url(r'^organizer/(?P<organizer>[^/]+)/team/(?P<team>[^/]+)/delete$', organizer.TeamDeleteView.as_view(),
|
url(r'^organizer/(?P<organizer>[^/]+)/team/(?P<team>[^/]+)/delete$', organizer.TeamDeleteView.as_view(),
|
||||||
name='organizer.team.delete'),
|
name='organizer.team.delete'),
|
||||||
url(r'^organizer/(?P<organizer>[^/]+)/slugrng', main.SlugRNG.as_view(), name='events.add.slugrng'),
|
url(r'^organizer/(?P<organizer>[^/]+)/slugrng', main.SlugRNG.as_view(), name='events.add.slugrng'),
|
||||||
|
url(r'^organizer/(?P<organizer>[^/]+)/logs', organizer.LogView.as_view(), name='organizer.log'),
|
||||||
url(r'^organizer/(?P<organizer>[^/]+)/export/$', organizer.ExportView.as_view(), name='organizer.export'),
|
url(r'^organizer/(?P<organizer>[^/]+)/export/$', organizer.ExportView.as_view(), name='organizer.export'),
|
||||||
url(r'^organizer/(?P<organizer>[^/]+)/export/do$', organizer.ExportDoView.as_view(), name='organizer.export.do'),
|
url(r'^organizer/(?P<organizer>[^/]+)/export/do$', organizer.ExportDoView.as_view(), name='organizer.export.do'),
|
||||||
url(r'^nav/typeahead/$', typeahead.nav_context_list, name='nav.typeahead'),
|
url(r'^nav/typeahead/$', typeahead.nav_context_list, name='nav.typeahead'),
|
||||||
@@ -173,6 +174,7 @@ urlpatterns = [
|
|||||||
url(r'^subevents/add$', subevents.SubEventCreate.as_view(), name='event.subevents.add'),
|
url(r'^subevents/add$', subevents.SubEventCreate.as_view(), name='event.subevents.add'),
|
||||||
url(r'^subevents/bulk_add$', subevents.SubEventBulkCreate.as_view(), name='event.subevents.bulk'),
|
url(r'^subevents/bulk_add$', subevents.SubEventBulkCreate.as_view(), name='event.subevents.bulk'),
|
||||||
url(r'^subevents/bulk_action$', subevents.SubEventBulkAction.as_view(), name='event.subevents.bulkaction'),
|
url(r'^subevents/bulk_action$', subevents.SubEventBulkAction.as_view(), name='event.subevents.bulkaction'),
|
||||||
|
url(r'^subevents/bulk_edit$', subevents.SubEventBulkEdit.as_view(), name='event.subevents.bulkedit'),
|
||||||
url(r'^items/$', item.ItemList.as_view(), name='event.items'),
|
url(r'^items/$', item.ItemList.as_view(), name='event.items'),
|
||||||
url(r'^items/add$', item.ItemCreate.as_view(), name='event.items.add'),
|
url(r'^items/add$', item.ItemCreate.as_view(), name='event.items.add'),
|
||||||
url(r'^items/(?P<item>\d+)/$', item.ItemUpdateGeneral.as_view(), name='event.item'),
|
url(r'^items/(?P<item>\d+)/$', item.ItemUpdateGeneral.as_view(), name='event.item'),
|
||||||
|
|||||||
@@ -1105,6 +1105,7 @@ class OrderTransition(OrderView):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
p.confirm(user=self.request.user, count_waitinglist=False, payment_date=payment_date,
|
p.confirm(user=self.request.user, count_waitinglist=False, payment_date=payment_date,
|
||||||
|
send_mail=self.mark_paid_form.cleaned_data['send_email'],
|
||||||
force=self.mark_paid_form.cleaned_data.get('force', False))
|
force=self.mark_paid_form.cleaned_data.get('force', False))
|
||||||
except Quota.QuotaExceededException as e:
|
except Quota.QuotaExceededException as e:
|
||||||
p.state = OrderPayment.PAYMENT_STATE_FAILED
|
p.state = OrderPayment.PAYMENT_STATE_FAILED
|
||||||
@@ -2079,11 +2080,17 @@ class ExportMixin:
|
|||||||
exporters.append(ex)
|
exporters.append(ex)
|
||||||
return exporters
|
return exporters
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
ctx = super().get_context_data(**kwargs)
|
||||||
|
ctx['exporters'] = self.exporters
|
||||||
|
return ctx
|
||||||
|
|
||||||
class ExportDoView(EventPermissionRequiredMixin, ExportMixin, AsyncAction, View):
|
|
||||||
|
class ExportDoView(EventPermissionRequiredMixin, ExportMixin, AsyncAction, TemplateView):
|
||||||
permission = 'can_view_orders'
|
permission = 'can_view_orders'
|
||||||
known_errortypes = ['ExportError']
|
known_errortypes = ['ExportError']
|
||||||
task = export
|
task = export
|
||||||
|
template_name = 'pretixcontrol/orders/export.html'
|
||||||
|
|
||||||
def get_success_message(self, value):
|
def get_success_message(self, value):
|
||||||
return None
|
return None
|
||||||
@@ -2103,6 +2110,11 @@ class ExportDoView(EventPermissionRequiredMixin, ExportMixin, AsyncAction, View)
|
|||||||
if ex.identifier == self.request.POST.get("exporter"):
|
if ex.identifier == self.request.POST.get("exporter"):
|
||||||
return ex
|
return ex
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
if 'async_id' in request.GET and settings.HAS_CELERY:
|
||||||
|
return self.get_result(request)
|
||||||
|
return TemplateView.get(self, request, *args, **kwargs)
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
if not self.exporter:
|
if not self.exporter:
|
||||||
messages.error(self.request, _('The selected exporter was not found.'))
|
messages.error(self.request, _('The selected exporter was not found.'))
|
||||||
@@ -2112,16 +2124,8 @@ class ExportDoView(EventPermissionRequiredMixin, ExportMixin, AsyncAction, View)
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
if not self.exporter.form.is_valid():
|
if not self.exporter.form.is_valid():
|
||||||
messages.error(
|
messages.error(self.request, _('There was a problem processing your input. See below for error details.'))
|
||||||
self.request,
|
return self.get(request, *args, **kwargs)
|
||||||
str(_('There was a problem processing your input:')) + ' ' + ', '.join(
|
|
||||||
', '.join(line) for line in self.exporter.form.errors.values()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return redirect(reverse('control:event.orders.export', kwargs={
|
|
||||||
'event': self.request.event.slug,
|
|
||||||
'organizer': self.request.event.organizer.slug
|
|
||||||
}) + '?identifier=' + self.exporter.identifier)
|
|
||||||
|
|
||||||
cf = CachedFile(web_download=True, session_key=request.session.session_key)
|
cf = CachedFile(web_download=True, session_key=request.session.session_key)
|
||||||
cf.date = now()
|
cf.date = now()
|
||||||
@@ -2134,11 +2138,6 @@ class ExportView(EventPermissionRequiredMixin, ExportMixin, TemplateView):
|
|||||||
permission = 'can_view_orders'
|
permission = 'can_view_orders'
|
||||||
template_name = 'pretixcontrol/orders/export.html'
|
template_name = 'pretixcontrol/orders/export.html'
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
|
||||||
ctx = super().get_context_data(**kwargs)
|
|
||||||
ctx['exporters'] = self.exporters
|
|
||||||
return ctx
|
|
||||||
|
|
||||||
|
|
||||||
class RefundList(EventPermissionRequiredMixin, PaginationMixin, ListView):
|
class RefundList(EventPermissionRequiredMixin, PaginationMixin, ListView):
|
||||||
model = OrderRefund
|
model = OrderRefund
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ from pretix.control.forms.organizer import (
|
|||||||
GiftCardUpdateForm, OrganizerDeleteForm, OrganizerForm,
|
GiftCardUpdateForm, OrganizerDeleteForm, OrganizerForm,
|
||||||
OrganizerSettingsForm, OrganizerUpdateForm, TeamForm, WebHookForm,
|
OrganizerSettingsForm, OrganizerUpdateForm, TeamForm, WebHookForm,
|
||||||
)
|
)
|
||||||
|
from pretix.control.logdisplay import OVERVIEW_BANLIST
|
||||||
from pretix.control.permissions import (
|
from pretix.control.permissions import (
|
||||||
AdministratorPermissionRequiredMixin, OrganizerPermissionRequiredMixin,
|
AdministratorPermissionRequiredMixin, OrganizerPermissionRequiredMixin,
|
||||||
)
|
)
|
||||||
@@ -1147,8 +1148,9 @@ class ExportMixin:
|
|||||||
organizer=self.request.organizer
|
organizer=self.request.organizer
|
||||||
)
|
)
|
||||||
responses = register_multievent_data_exporters.send(self.request.organizer)
|
responses = register_multievent_data_exporters.send(self.request.organizer)
|
||||||
|
id = self.request.GET.get("identifier") or self.request.POST.get("exporter")
|
||||||
for ex in sorted([response(events) for r, response in responses if response], key=lambda ex: str(ex.verbose_name)):
|
for ex in sorted([response(events) for r, response in responses if response], key=lambda ex: str(ex.verbose_name)):
|
||||||
if self.request.GET.get("identifier") and ex.identifier != self.request.GET.get("identifier"):
|
if id and ex.identifier != id:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Use form parse cycle to generate useful defaults
|
# Use form parse cycle to generate useful defaults
|
||||||
@@ -1180,10 +1182,16 @@ class ExportMixin:
|
|||||||
exporters.append(ex)
|
exporters.append(ex)
|
||||||
return exporters
|
return exporters
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
ctx = super().get_context_data(**kwargs)
|
||||||
|
ctx['exporters'] = self.exporters
|
||||||
|
return ctx
|
||||||
|
|
||||||
class ExportDoView(OrganizerPermissionRequiredMixin, ExportMixin, AsyncAction, View):
|
|
||||||
|
class ExportDoView(OrganizerPermissionRequiredMixin, ExportMixin, AsyncAction, TemplateView):
|
||||||
known_errortypes = ['ExportError']
|
known_errortypes = ['ExportError']
|
||||||
task = multiexport
|
task = multiexport
|
||||||
|
template_name = 'pretixcontrol/organizers/export.html'
|
||||||
|
|
||||||
def get_success_message(self, value):
|
def get_success_message(self, value):
|
||||||
return None
|
return None
|
||||||
@@ -1202,6 +1210,11 @@ class ExportDoView(OrganizerPermissionRequiredMixin, ExportMixin, AsyncAction, V
|
|||||||
if ex.identifier == self.request.POST.get("exporter"):
|
if ex.identifier == self.request.POST.get("exporter"):
|
||||||
return ex
|
return ex
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
if 'async_id' in request.GET and settings.HAS_CELERY:
|
||||||
|
return self.get_result(request)
|
||||||
|
return TemplateView.get(self, request, *args, **kwargs)
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
if not self.exporter:
|
if not self.exporter:
|
||||||
messages.error(self.request, _('The selected exporter was not found.'))
|
messages.error(self.request, _('The selected exporter was not found.'))
|
||||||
@@ -1231,11 +1244,6 @@ class ExportDoView(OrganizerPermissionRequiredMixin, ExportMixin, AsyncAction, V
|
|||||||
class ExportView(OrganizerPermissionRequiredMixin, ExportMixin, TemplateView):
|
class ExportView(OrganizerPermissionRequiredMixin, ExportMixin, TemplateView):
|
||||||
template_name = 'pretixcontrol/organizers/export.html'
|
template_name = 'pretixcontrol/organizers/export.html'
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
|
||||||
ctx = super().get_context_data(**kwargs)
|
|
||||||
ctx['exporters'] = self.exporters
|
|
||||||
return ctx
|
|
||||||
|
|
||||||
|
|
||||||
class GateListView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, ListView):
|
class GateListView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, ListView):
|
||||||
model = Gate
|
model = Gate
|
||||||
@@ -1427,3 +1435,24 @@ class EventMetaPropertyDeleteView(OrganizerDetailViewMixin, OrganizerPermissionR
|
|||||||
self.object.delete()
|
self.object.delete()
|
||||||
messages.success(request, _('The selected property has been deleted.'))
|
messages.success(request, _('The selected property has been deleted.'))
|
||||||
return redirect(success_url)
|
return redirect(success_url)
|
||||||
|
|
||||||
|
|
||||||
|
class LogView(OrganizerPermissionRequiredMixin, ListView):
|
||||||
|
template_name = 'pretixcontrol/organizers/logs.html'
|
||||||
|
permission = 'can_change_organizer_settings'
|
||||||
|
model = LogEntry
|
||||||
|
context_object_name = 'logs'
|
||||||
|
paginate_by = 20
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
qs = self.request.organizer.all_logentries().select_related(
|
||||||
|
'user', 'content_type', 'api_token', 'oauth_application', 'device'
|
||||||
|
).order_by('-datetime')
|
||||||
|
qs = qs.exclude(action_type__in=OVERVIEW_BANLIST)
|
||||||
|
if self.request.GET.get('user'):
|
||||||
|
qs = qs.filter(user_id=self.request.GET.get('user'))
|
||||||
|
return qs
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
ctx = super().get_context_data()
|
||||||
|
return ctx
|
||||||
|
|||||||
@@ -1,14 +1,17 @@
|
|||||||
import copy
|
import copy
|
||||||
|
from collections import defaultdict
|
||||||
from datetime import datetime, time, timedelta
|
from datetime import datetime, time, timedelta
|
||||||
|
|
||||||
from dateutil.rrule import DAILY, MONTHLY, WEEKLY, YEARLY, rrule, rruleset
|
from dateutil.rrule import DAILY, MONTHLY, WEEKLY, YEARLY, rrule, rruleset
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.core.files import File
|
from django.core.files import File
|
||||||
from django.db import connections, transaction
|
from django.db import connections, transaction
|
||||||
from django.db.models import F, IntegerField, OuterRef, Prefetch, Subquery, Sum
|
from django.db.models import (
|
||||||
from django.db.models.functions import Coalesce
|
Count, F, IntegerField, OuterRef, Prefetch, Subquery, Sum,
|
||||||
|
)
|
||||||
|
from django.db.models.functions import Coalesce, TruncDate, TruncTime
|
||||||
from django.forms import inlineformset_factory
|
from django.forms import inlineformset_factory
|
||||||
from django.http import Http404, HttpResponseRedirect
|
from django.http import Http404, HttpResponse, HttpResponseRedirect
|
||||||
from django.shortcuts import redirect, render
|
from django.shortcuts import redirect, render
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.formats import get_format
|
from django.utils.formats import get_format
|
||||||
@@ -16,7 +19,9 @@ from django.utils.functional import cached_property
|
|||||||
from django.utils.timezone import make_aware
|
from django.utils.timezone import make_aware
|
||||||
from django.utils.translation import gettext_lazy as _, pgettext_lazy
|
from django.utils.translation import gettext_lazy as _, pgettext_lazy
|
||||||
from django.views import View
|
from django.views import View
|
||||||
from django.views.generic import CreateView, DeleteView, ListView, UpdateView
|
from django.views.generic import (
|
||||||
|
CreateView, DeleteView, FormView, ListView, UpdateView,
|
||||||
|
)
|
||||||
|
|
||||||
from pretix.base.models import CartPosition, LogEntry
|
from pretix.base.models import CartPosition, LogEntry
|
||||||
from pretix.base.models.checkin import CheckinList
|
from pretix.base.models.checkin import CheckinList
|
||||||
@@ -31,24 +36,27 @@ from pretix.control.forms.checkin import SimpleCheckinListForm
|
|||||||
from pretix.control.forms.filter import SubEventFilterForm
|
from pretix.control.forms.filter import SubEventFilterForm
|
||||||
from pretix.control.forms.item import QuotaForm
|
from pretix.control.forms.item import QuotaForm
|
||||||
from pretix.control.forms.subevents import (
|
from pretix.control.forms.subevents import (
|
||||||
CheckinListFormSet, QuotaFormSet, RRuleFormSet, SubEventBulkForm,
|
CheckinListFormSet, QuotaFormSet, RRuleFormSet, SubEventBulkEditForm,
|
||||||
SubEventForm, SubEventItemForm, SubEventItemVariationForm,
|
SubEventBulkForm, SubEventForm, SubEventItemForm,
|
||||||
SubEventMetaValueForm, TimeFormSet,
|
SubEventItemVariationForm, SubEventMetaValueForm, TimeFormSet,
|
||||||
)
|
)
|
||||||
from pretix.control.permissions import EventPermissionRequiredMixin
|
from pretix.control.permissions import EventPermissionRequiredMixin
|
||||||
from pretix.control.signals import subevent_forms
|
from pretix.control.signals import subevent_forms
|
||||||
from pretix.control.views import PaginationMixin
|
from pretix.control.views import PaginationMixin
|
||||||
from pretix.control.views.event import MetaDataEditorMixin
|
from pretix.control.views.event import MetaDataEditorMixin
|
||||||
|
from pretix.helpers import GroupConcat
|
||||||
from pretix.helpers.models import modelcopy
|
from pretix.helpers.models import modelcopy
|
||||||
|
|
||||||
|
|
||||||
class SubEventList(EventPermissionRequiredMixin, PaginationMixin, ListView):
|
class SubEventQueryMixin:
|
||||||
model = SubEvent
|
|
||||||
context_object_name = 'subevents'
|
|
||||||
template_name = 'pretixcontrol/subevents/index.html'
|
|
||||||
permission = 'can_change_settings'
|
|
||||||
|
|
||||||
def get_queryset(self):
|
@cached_property
|
||||||
|
def request_data(self):
|
||||||
|
if self.request.method == "POST":
|
||||||
|
return self.request.POST
|
||||||
|
return self.request.GET
|
||||||
|
|
||||||
|
def get_queryset(self, list=False):
|
||||||
sum_tickets_paid = Quota.objects.filter(
|
sum_tickets_paid = Quota.objects.filter(
|
||||||
subevent=OuterRef('pk')
|
subevent=OuterRef('pk')
|
||||||
).order_by().values('subevent').annotate(
|
).order_by().values('subevent').annotate(
|
||||||
@@ -56,18 +64,39 @@ class SubEventList(EventPermissionRequiredMixin, PaginationMixin, ListView):
|
|||||||
).values(
|
).values(
|
||||||
's'
|
's'
|
||||||
)
|
)
|
||||||
|
qs = self.request.event.subevents
|
||||||
qs = self.request.event.subevents.annotate(
|
if list:
|
||||||
sum_tickets_paid=Subquery(sum_tickets_paid, output_field=IntegerField())
|
qs = qs.annotate(
|
||||||
).prefetch_related(
|
sum_tickets_paid=Subquery(sum_tickets_paid, output_field=IntegerField())
|
||||||
Prefetch('quotas',
|
).prefetch_related(
|
||||||
queryset=Quota.objects.annotate(s=Coalesce(F('size'), 0)).order_by('-s'),
|
Prefetch('quotas',
|
||||||
to_attr='first_quotas')
|
queryset=Quota.objects.annotate(s=Coalesce(F('size'), 0)).order_by('-s'),
|
||||||
)
|
to_attr='first_quotas')
|
||||||
|
)
|
||||||
if self.filter_form.is_valid():
|
if self.filter_form.is_valid():
|
||||||
qs = self.filter_form.filter_qs(qs)
|
qs = self.filter_form.filter_qs(qs)
|
||||||
|
|
||||||
|
if 'subevent' in self.request_data and '__ALL' not in self.request_data:
|
||||||
|
qs = qs.filter(
|
||||||
|
id__in=self.request_data.getlist('subevent')
|
||||||
|
)
|
||||||
|
|
||||||
return qs
|
return qs
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def filter_form(self):
|
||||||
|
return SubEventFilterForm(data=self.request_data, prefix='filter')
|
||||||
|
|
||||||
|
|
||||||
|
class SubEventList(EventPermissionRequiredMixin, PaginationMixin, SubEventQueryMixin, ListView):
|
||||||
|
model = SubEvent
|
||||||
|
context_object_name = 'subevents'
|
||||||
|
template_name = 'pretixcontrol/subevents/index.html'
|
||||||
|
permission = 'can_change_settings'
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return super().get_queryset(True)
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
ctx = super().get_context_data(**kwargs)
|
ctx = super().get_context_data(**kwargs)
|
||||||
ctx['filter_form'] = self.filter_form
|
ctx['filter_form'] = self.filter_form
|
||||||
@@ -95,10 +124,6 @@ class SubEventList(EventPermissionRequiredMixin, PaginationMixin, ListView):
|
|||||||
)
|
)
|
||||||
return ctx
|
return ctx
|
||||||
|
|
||||||
@cached_property
|
|
||||||
def filter_form(self):
|
|
||||||
return SubEventFilterForm(data=self.request.GET)
|
|
||||||
|
|
||||||
|
|
||||||
class SubEventDelete(EventPermissionRequiredMixin, DeleteView):
|
class SubEventDelete(EventPermissionRequiredMixin, DeleteView):
|
||||||
model = SubEvent
|
model = SubEvent
|
||||||
@@ -535,19 +560,13 @@ class SubEventCreate(SubEventEditorMixin, EventPermissionRequiredMixin, CreateVi
|
|||||||
return formlist
|
return formlist
|
||||||
|
|
||||||
|
|
||||||
class SubEventBulkAction(EventPermissionRequiredMixin, View):
|
class SubEventBulkAction(SubEventQueryMixin, EventPermissionRequiredMixin, View):
|
||||||
permission = 'can_change_settings'
|
permission = 'can_change_settings'
|
||||||
|
|
||||||
@cached_property
|
|
||||||
def objects(self):
|
|
||||||
return self.request.event.subevents.filter(
|
|
||||||
id__in=self.request.POST.getlist('subevent')
|
|
||||||
)
|
|
||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
if request.POST.get('action') == 'disable':
|
if request.POST.get('action') == 'disable':
|
||||||
for obj in self.objects:
|
for obj in self.get_queryset():
|
||||||
obj.log_action(
|
obj.log_action(
|
||||||
'pretix.subevent.changed', user=self.request.user, data={
|
'pretix.subevent.changed', user=self.request.user, data={
|
||||||
'active': False
|
'active': False
|
||||||
@@ -557,7 +576,7 @@ class SubEventBulkAction(EventPermissionRequiredMixin, View):
|
|||||||
obj.save(update_fields=['active'])
|
obj.save(update_fields=['active'])
|
||||||
messages.success(request, pgettext_lazy('subevent', 'The selected dates have been disabled.'))
|
messages.success(request, pgettext_lazy('subevent', 'The selected dates have been disabled.'))
|
||||||
elif request.POST.get('action') == 'enable':
|
elif request.POST.get('action') == 'enable':
|
||||||
for obj in self.objects:
|
for obj in self.get_queryset():
|
||||||
obj.log_action(
|
obj.log_action(
|
||||||
'pretix.subevent.changed', user=self.request.user, data={
|
'pretix.subevent.changed', user=self.request.user, data={
|
||||||
'active': True
|
'active': True
|
||||||
@@ -568,11 +587,11 @@ class SubEventBulkAction(EventPermissionRequiredMixin, View):
|
|||||||
messages.success(request, pgettext_lazy('subevent', 'The selected dates have been enabled.'))
|
messages.success(request, pgettext_lazy('subevent', 'The selected dates have been enabled.'))
|
||||||
elif request.POST.get('action') == 'delete':
|
elif request.POST.get('action') == 'delete':
|
||||||
return render(request, 'pretixcontrol/subevents/delete_bulk.html', {
|
return render(request, 'pretixcontrol/subevents/delete_bulk.html', {
|
||||||
'allowed': self.objects.filter(orderposition__isnull=True),
|
'allowed': self.get_queryset().filter(orderposition__isnull=True),
|
||||||
'forbidden': self.objects.filter(orderposition__isnull=False),
|
'forbidden': self.get_queryset().filter(orderposition__isnull=False).distinct(),
|
||||||
})
|
})
|
||||||
elif request.POST.get('action') == 'delete_confirm':
|
elif request.POST.get('action') == 'delete_confirm':
|
||||||
for obj in self.objects:
|
for obj in self.get_queryset():
|
||||||
if obj.allow_delete():
|
if obj.allow_delete():
|
||||||
CartPosition.objects.filter(addon_to__subevent=obj).delete()
|
CartPosition.objects.filter(addon_to__subevent=obj).delete()
|
||||||
obj.cartposition_set.all().delete()
|
obj.cartposition_set.all().delete()
|
||||||
@@ -899,3 +918,537 @@ class SubEventBulkCreate(SubEventEditorMixin, EventPermissionRequiredMixin, Crea
|
|||||||
|
|
||||||
messages.error(self.request, _('We could not save your changes. See below for details.'))
|
messages.error(self.request, _('We could not save your changes. See below for details.'))
|
||||||
return self.form_invalid(form)
|
return self.form_invalid(form)
|
||||||
|
|
||||||
|
|
||||||
|
class SubEventBulkEdit(SubEventQueryMixin, EventPermissionRequiredMixin, FormView):
|
||||||
|
permission = 'can_change_settings'
|
||||||
|
form_class = SubEventBulkEditForm
|
||||||
|
template_name = 'pretixcontrol/subevents/bulk_edit.html'
|
||||||
|
context_object_name = 'subevent'
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return super().get_queryset().prefetch_related(None).order_by()
|
||||||
|
|
||||||
|
def get_success_url(self) -> str:
|
||||||
|
return reverse('control:event.subevents', kwargs={
|
||||||
|
'organizer': self.request.event.organizer.slug,
|
||||||
|
'event': self.request.event.slug,
|
||||||
|
})
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
return HttpResponse(status=405)
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def cached_num(self):
|
||||||
|
return self.get_queryset().count()
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def itemvar_forms(self):
|
||||||
|
matches = defaultdict(list)
|
||||||
|
for sei in SubEventItem.objects.filter(
|
||||||
|
subevent__in=self.get_queryset()
|
||||||
|
).order_by().values('item', 'price', 'disabled').annotate(c=Count('*')):
|
||||||
|
matches['item', sei['item']].append(sei)
|
||||||
|
for sei in SubEventItemVariation.objects.filter(
|
||||||
|
subevent__in=self.get_queryset()
|
||||||
|
).order_by().values('variation', 'price', 'disabled').annotate(c=Count('*')):
|
||||||
|
matches['variation', sei['variation']].append(sei)
|
||||||
|
total = self.cached_num
|
||||||
|
|
||||||
|
formlist = []
|
||||||
|
for i in self.request.event.items.filter(active=True).prefetch_related('variations'):
|
||||||
|
if i.has_variations:
|
||||||
|
for v in i.variations.all():
|
||||||
|
m = matches['variation', v.pk]
|
||||||
|
if m and len(m) == 1 and m[0]['c'] == total:
|
||||||
|
inst = SubEventItemVariation(variation=v, disabled=m[0]['disabled'], price=m[0]['price'])
|
||||||
|
else:
|
||||||
|
inst = SubEventItemVariation(variation=v)
|
||||||
|
formlist.append(SubEventItemVariationForm(
|
||||||
|
prefix='itemvar-{}'.format(v.pk),
|
||||||
|
item=i, variation=v,
|
||||||
|
instance=inst,
|
||||||
|
data=(self.request.POST if self.is_submitted else None)
|
||||||
|
))
|
||||||
|
else:
|
||||||
|
m = matches['item', i.pk]
|
||||||
|
if m and len(m) == 1 and m[0]['c'] == total:
|
||||||
|
inst = SubEventItem(item=i, disabled=m[0]['disabled'], price=m[0]['price'])
|
||||||
|
else:
|
||||||
|
inst = SubEventItem(item=i)
|
||||||
|
formlist.append(SubEventItemForm(
|
||||||
|
prefix='item-{}'.format(i.pk),
|
||||||
|
item=i,
|
||||||
|
instance=inst,
|
||||||
|
data=(self.request.POST if self.is_submitted else None)
|
||||||
|
))
|
||||||
|
return formlist
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def meta_forms(self):
|
||||||
|
matches = defaultdict(list)
|
||||||
|
for smv in SubEventMetaValue.objects.filter(
|
||||||
|
subevent__in=self.get_queryset()
|
||||||
|
).order_by().values('property', 'value').annotate(c=Count('*')):
|
||||||
|
matches[smv['property']].append(smv)
|
||||||
|
total = self.cached_num
|
||||||
|
|
||||||
|
formlist = []
|
||||||
|
|
||||||
|
if not hasattr(self, '_default_meta'):
|
||||||
|
self._default_meta = self.request.event.meta_data
|
||||||
|
|
||||||
|
for p in self.request.organizer.meta_properties.all():
|
||||||
|
inst = SubEventMetaValue(property=p)
|
||||||
|
if len(matches[p.id]) == 1 and matches[p.id][0]['c'] == total:
|
||||||
|
inst.value = matches[p.id][0]['value']
|
||||||
|
formlist.append(SubEventMetaValueForm(
|
||||||
|
prefix='prop-{}'.format(p.pk),
|
||||||
|
property=p,
|
||||||
|
default=self._default_meta.get(p.name, ''),
|
||||||
|
instance=inst,
|
||||||
|
data=(self.request.POST if self.is_submitted else None)
|
||||||
|
))
|
||||||
|
return formlist
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def quota_formset(self):
|
||||||
|
extra = 0
|
||||||
|
kwargs = {}
|
||||||
|
|
||||||
|
if self.sampled_quotas is not None:
|
||||||
|
kwargs['instance'] = self.get_queryset()[0]
|
||||||
|
|
||||||
|
formsetclass = inlineformset_factory(
|
||||||
|
SubEvent, Quota,
|
||||||
|
form=QuotaForm, formset=QuotaFormSet, min_num=0, validate_min=False,
|
||||||
|
can_order=False, can_delete=True, extra=extra,
|
||||||
|
)
|
||||||
|
return formsetclass(
|
||||||
|
self.request.POST if self.is_submitted else None,
|
||||||
|
event=self.request.event, **kwargs
|
||||||
|
)
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def list_formset(self):
|
||||||
|
extra = 0
|
||||||
|
kwargs = {}
|
||||||
|
|
||||||
|
if self.sampled_lists is not None:
|
||||||
|
kwargs['instance'] = self.get_queryset()[0]
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
formsetclass = inlineformset_factory(
|
||||||
|
SubEvent, CheckinList,
|
||||||
|
form=SimpleCheckinListForm, formset=CheckinListFormSet, min_num=0, validate_min=False,
|
||||||
|
can_order=False, can_delete=True, extra=extra,
|
||||||
|
)
|
||||||
|
return formsetclass(
|
||||||
|
self.request.POST if self.is_submitted else None,
|
||||||
|
event=self.request.event, **kwargs
|
||||||
|
)
|
||||||
|
|
||||||
|
def save_list_formset(self, log_entries):
|
||||||
|
if not self.list_formset.has_changed() or self.sampled_lists is None:
|
||||||
|
return
|
||||||
|
qidx = 0
|
||||||
|
subevents = list(self.get_queryset().prefetch_related('checkinlist_set'))
|
||||||
|
to_save_products = []
|
||||||
|
to_save_gates = []
|
||||||
|
|
||||||
|
for f in self.list_formset.forms:
|
||||||
|
if self.list_formset._should_delete_form(f) and f in self.list_formset.extra_forms:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if self.list_formset._should_delete_form(f):
|
||||||
|
for se in subevents:
|
||||||
|
q = list(se.checkinlist_set.all())[qidx]
|
||||||
|
log_entries += [
|
||||||
|
q.log_action(action='pretix.event.checkinlist.deleted', user=self.request.user, save=False),
|
||||||
|
]
|
||||||
|
q.delete()
|
||||||
|
elif f in self.list_formset.extra_forms:
|
||||||
|
change_data = {k: f.cleaned_data.get(k) for k in f.changed_data}
|
||||||
|
for se in subevents:
|
||||||
|
q = copy.copy(f.instance)
|
||||||
|
q.pk = None
|
||||||
|
q.subevent = se
|
||||||
|
q.event = self.request.event
|
||||||
|
q.save()
|
||||||
|
for _i in f.cleaned_data.get('limit_products', []):
|
||||||
|
to_save_products.append(CheckinList.limit_products.through(checkinlist_id=q.pk, item_id=_i.pk))
|
||||||
|
for _i in f.cleaned_data.get('gates', []):
|
||||||
|
to_save_gates.append(CheckinList.gates.through(checkinlist_id=q.pk, gate_id=_i.pk))
|
||||||
|
change_data['id'] = q.pk
|
||||||
|
log_entries.append(
|
||||||
|
q.log_action(action='pretix.event.checkinlist.added', user=self.request.user,
|
||||||
|
data=change_data, save=False)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
if f.changed_data:
|
||||||
|
change_data = {k: f.cleaned_data.get(k) for k in f.changed_data}
|
||||||
|
for se in subevents:
|
||||||
|
q = list(se.checkinlist_set.all())[qidx]
|
||||||
|
for fname in ('name', 'all_products', 'include_pending', 'allow_entry_after_exit'):
|
||||||
|
setattr(q, fname, f.cleaned_data.get(fname))
|
||||||
|
q.save()
|
||||||
|
if 'limit_products' in f.changed_data:
|
||||||
|
q.limit_products.set(f.cleaned_data.get('limit_products', []))
|
||||||
|
if 'gates' in f.changed_data:
|
||||||
|
q.gates.set(f.cleaned_data.get('limit_products', []))
|
||||||
|
log_entries.append(
|
||||||
|
q.log_action(action='pretix.event.checkinlist.changed', user=self.request.user,
|
||||||
|
data=change_data, save=False)
|
||||||
|
)
|
||||||
|
qidx += 1
|
||||||
|
if to_save_products:
|
||||||
|
CheckinList.limit_products.through.objects.bulk_create(to_save_products)
|
||||||
|
if to_save_gates:
|
||||||
|
CheckinList.gates.through.objects.bulk_create(to_save_gates)
|
||||||
|
|
||||||
|
def save_quota_formset(self, log_entries):
|
||||||
|
if not self.quota_formset.has_changed():
|
||||||
|
return
|
||||||
|
qidx = 0
|
||||||
|
subevents = list(self.get_queryset().prefetch_related('quotas'))
|
||||||
|
to_save_items = []
|
||||||
|
to_save_variations = []
|
||||||
|
to_delete_quota_ids = []
|
||||||
|
|
||||||
|
if self.sampled_quotas is None:
|
||||||
|
if len(self.quota_formset.forms) == 0:
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
for se in subevents:
|
||||||
|
for q in se.quotas.all():
|
||||||
|
to_delete_quota_ids.append(q.pk)
|
||||||
|
log_entries += [
|
||||||
|
q.log_action(action='pretix.event.quota.deleted', user=self.request.user, save=False),
|
||||||
|
se.log_action('pretix.subevent.quota.deleted', user=self.request.user, data={
|
||||||
|
'id': q.pk
|
||||||
|
}, save=False)
|
||||||
|
]
|
||||||
|
|
||||||
|
if to_delete_quota_ids:
|
||||||
|
Quota.objects.filter(id__in=to_delete_quota_ids).delete()
|
||||||
|
|
||||||
|
for f in self.quota_formset.forms:
|
||||||
|
if self.quota_formset._should_delete_form(f) and f in self.quota_formset.extra_forms:
|
||||||
|
continue
|
||||||
|
|
||||||
|
selected_items = set(list(self.request.event.items.filter(id__in=[
|
||||||
|
i.split('-')[0] for i in f.cleaned_data.get('itemvars', [])
|
||||||
|
])))
|
||||||
|
selected_variations = list(ItemVariation.objects.filter(item__event=self.request.event, id__in=[
|
||||||
|
i.split('-')[1] for i in f.cleaned_data.get('itemvars', []) if '-' in i
|
||||||
|
]))
|
||||||
|
|
||||||
|
if self.quota_formset._should_delete_form(f):
|
||||||
|
for se in subevents:
|
||||||
|
q = list(se.quotas.all())[qidx]
|
||||||
|
log_entries += [
|
||||||
|
q.log_action(action='pretix.event.quota.deleted', user=self.request.user, save=False),
|
||||||
|
se.log_action('pretix.subevent.quota.deleted', user=self.request.user, data={
|
||||||
|
'id': q.pk
|
||||||
|
}, save=False)
|
||||||
|
]
|
||||||
|
q.delete()
|
||||||
|
elif f in self.quota_formset.extra_forms:
|
||||||
|
change_data = {k: f.cleaned_data.get(k) for k in f.changed_data}
|
||||||
|
for se in subevents:
|
||||||
|
q = copy.copy(f.instance)
|
||||||
|
q.pk = None
|
||||||
|
q.subevent = se
|
||||||
|
q.event = self.request.event
|
||||||
|
q.save(clear_cache=False)
|
||||||
|
for _i in selected_items:
|
||||||
|
to_save_items.append(Quota.items.through(quota_id=q.pk, item_id=_i.pk))
|
||||||
|
for _i in selected_variations:
|
||||||
|
to_save_variations.append(Quota.variations.through(quota_id=q.pk, itemvariation_id=_i.pk))
|
||||||
|
|
||||||
|
change_data['id'] = q.pk
|
||||||
|
log_entries.append(
|
||||||
|
q.log_action(action='pretix.event.quota.added', user=self.request.user,
|
||||||
|
data=change_data, save=False)
|
||||||
|
)
|
||||||
|
log_entries.append(
|
||||||
|
se.log_action('pretix.subevent.quota.added', user=self.request.user, data=change_data,
|
||||||
|
save=False)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
if f.changed_data:
|
||||||
|
change_data = {k: f.cleaned_data.get(k) for k in f.changed_data}
|
||||||
|
for se in subevents:
|
||||||
|
q = list(se.quotas.all())[qidx]
|
||||||
|
for fname in ('size', 'name', 'release_after_exit'):
|
||||||
|
setattr(q, fname, f.cleaned_data.get(fname))
|
||||||
|
q.save(clear_cache=False)
|
||||||
|
if 'itemvar' in f.changed_data:
|
||||||
|
q.items.set(selected_items)
|
||||||
|
q.variations.set(selected_variations)
|
||||||
|
log_entries.append(
|
||||||
|
q.log_action(action='pretix.event.quota.added', user=self.request.user,
|
||||||
|
data=change_data, save=False)
|
||||||
|
)
|
||||||
|
qidx += 1
|
||||||
|
if to_save_items:
|
||||||
|
Quota.items.through.objects.bulk_create(to_save_items)
|
||||||
|
if to_save_variations:
|
||||||
|
Quota.variations.through.objects.bulk_create(to_save_variations)
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
ctx = super().get_context_data(**kwargs)
|
||||||
|
ctx['subevents'] = self.get_queryset()
|
||||||
|
ctx['filter_form'] = self.filter_form
|
||||||
|
ctx['sampled_quotas'] = self.sampled_quotas
|
||||||
|
ctx['sampled_lists'] = self.sampled_lists
|
||||||
|
ctx['formset'] = self.quota_formset
|
||||||
|
ctx['cl_formset'] = self.list_formset
|
||||||
|
ctx['itemvar_forms'] = self.itemvar_forms
|
||||||
|
ctx['bulk_selected'] = self.request.POST.getlist("_bulk")
|
||||||
|
ctx['meta_forms'] = self.meta_forms
|
||||||
|
return ctx
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def sampled_quotas(self):
|
||||||
|
all_quotas = Quota.objects.filter(
|
||||||
|
subevent__in=self.get_queryset()
|
||||||
|
).annotate(
|
||||||
|
item_list=GroupConcat('items__id'),
|
||||||
|
var_list=GroupConcat('variations__id'),
|
||||||
|
).values(
|
||||||
|
'item_list', 'var_list',
|
||||||
|
*(f.name for f in Quota._meta.fields if f.name not in (
|
||||||
|
'id', 'event', 'items', 'variations', 'cached_availability_state', 'cached_availability_number',
|
||||||
|
'cached_availability_paid_orders', 'cached_availability_time', 'closed',
|
||||||
|
))
|
||||||
|
).order_by('subevent_id')
|
||||||
|
|
||||||
|
if not all_quotas:
|
||||||
|
return Quota.objects.none()
|
||||||
|
|
||||||
|
quotas_by_subevent = defaultdict(list)
|
||||||
|
for q in all_quotas:
|
||||||
|
quotas_by_subevent[q.pop('subevent')].append(q)
|
||||||
|
|
||||||
|
prev = None
|
||||||
|
for se in self.get_queryset():
|
||||||
|
if se.pk not in quotas_by_subevent:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if prev is None:
|
||||||
|
prev = quotas_by_subevent[se.pk]
|
||||||
|
|
||||||
|
if quotas_by_subevent[se.pk] != prev:
|
||||||
|
return None
|
||||||
|
return se.quotas.all()
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def sampled_lists(self):
|
||||||
|
all_lists = CheckinList.objects.filter(
|
||||||
|
subevent__in=self.get_queryset()
|
||||||
|
).annotate(
|
||||||
|
item_list=GroupConcat('limit_products__id'),
|
||||||
|
gates_list=GroupConcat('gates__id'),
|
||||||
|
).values(
|
||||||
|
'item_list', 'gates_list',
|
||||||
|
*(f.name for f in CheckinList._meta.fields if f.name not in (
|
||||||
|
'id', 'event', 'limit_products', 'gates',
|
||||||
|
))
|
||||||
|
).order_by('subevent_id')
|
||||||
|
|
||||||
|
if not all_lists:
|
||||||
|
return SubEvent.objects.none()
|
||||||
|
|
||||||
|
lists_by_subevent = defaultdict(list)
|
||||||
|
for cl in all_lists:
|
||||||
|
lists_by_subevent[cl.pop('subevent')].append(cl)
|
||||||
|
|
||||||
|
prev = None
|
||||||
|
for se in self.get_queryset():
|
||||||
|
if se.pk not in lists_by_subevent:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if prev is None:
|
||||||
|
prev = lists_by_subevent[se.pk]
|
||||||
|
|
||||||
|
if lists_by_subevent[se.pk] != prev:
|
||||||
|
return None
|
||||||
|
return se.checkinlist_set.all()
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def is_submitted(self):
|
||||||
|
# Usually, django considers a form "bound" / "submitted" on every POST request. However, this view is always
|
||||||
|
# called with POST method, even if just to pass the selection of objects to work on, so we want to modify
|
||||||
|
# that behaviour
|
||||||
|
return '_bulk' in self.request.POST
|
||||||
|
|
||||||
|
def get_form_kwargs(self):
|
||||||
|
initial = {}
|
||||||
|
mixed_values = set()
|
||||||
|
qs = self.get_queryset()
|
||||||
|
|
||||||
|
qs = qs.annotate(
|
||||||
|
**{
|
||||||
|
# TODO: Once we're on Django 3.2, pass a tzinfo parameter
|
||||||
|
# Before Django 3.2, it uses the current timezone, which is hopefully fine
|
||||||
|
# as well in all cases we are concerned about
|
||||||
|
# See also: https://code.djangoproject.com/ticket/31948
|
||||||
|
k + '_day': TruncDate(k)
|
||||||
|
for k in ('date_from', 'date_to', 'date_admission', 'presale_start', 'presale_end')
|
||||||
|
},
|
||||||
|
**{
|
||||||
|
k + '_time': TruncTime(k)
|
||||||
|
for k in ('date_from', 'date_to', 'date_admission', 'presale_start', 'presale_end')
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
fields = {
|
||||||
|
'name',
|
||||||
|
'location',
|
||||||
|
'frontpage_text',
|
||||||
|
'geo_lat',
|
||||||
|
'geo_lon',
|
||||||
|
'is_public',
|
||||||
|
'active',
|
||||||
|
'date_from_day',
|
||||||
|
'date_from_time',
|
||||||
|
'date_to_day',
|
||||||
|
'date_to_time',
|
||||||
|
'date_admission_day',
|
||||||
|
'date_admission_time',
|
||||||
|
'presale_start_day',
|
||||||
|
'presale_start_time',
|
||||||
|
'presale_end_day',
|
||||||
|
'presale_end_time',
|
||||||
|
}
|
||||||
|
for k in fields:
|
||||||
|
existing_values = list(qs.order_by(k).values(k).annotate(c=Count('*')))
|
||||||
|
if len(existing_values) == 1:
|
||||||
|
initial[k] = existing_values[0][k]
|
||||||
|
elif len(existing_values) > 1:
|
||||||
|
mixed_values.add(k)
|
||||||
|
initial[k] = None
|
||||||
|
|
||||||
|
kwargs = super().get_form_kwargs()
|
||||||
|
kwargs['event'] = self.request.event
|
||||||
|
kwargs['prefix'] = 'bulkedit'
|
||||||
|
kwargs['initial'] = initial
|
||||||
|
kwargs['queryset'] = self.get_queryset()
|
||||||
|
kwargs['mixed_values'] = mixed_values
|
||||||
|
if not self.is_submitted:
|
||||||
|
kwargs['data'] = None
|
||||||
|
kwargs['files'] = None
|
||||||
|
return kwargs
|
||||||
|
|
||||||
|
def post(self, request, *args, **kwargs):
|
||||||
|
form = self.get_form()
|
||||||
|
is_valid = (
|
||||||
|
self.is_submitted and
|
||||||
|
form.is_valid() and
|
||||||
|
self.quota_formset.is_valid() and
|
||||||
|
(not self.list_formset or self.list_formset.is_valid()) and
|
||||||
|
all(f.is_valid() for f in self.itemvar_forms)and
|
||||||
|
all(f.is_valid() for f in self.meta_forms)
|
||||||
|
)
|
||||||
|
if is_valid:
|
||||||
|
return self.form_valid(form)
|
||||||
|
else:
|
||||||
|
if self.is_submitted:
|
||||||
|
messages.error(self.request, _('We could not save your changes. See below for details.'))
|
||||||
|
return self.form_invalid(form)
|
||||||
|
|
||||||
|
def save_meta(self):
|
||||||
|
for f in self.meta_forms:
|
||||||
|
if f.prefix + 'value' not in self.request.POST.getlist('_bulk'):
|
||||||
|
continue
|
||||||
|
|
||||||
|
if f.cleaned_data.get('value'):
|
||||||
|
for obj in self.get_queryset():
|
||||||
|
SubEventMetaValue.objects.update_or_create(
|
||||||
|
property=f.instance.property,
|
||||||
|
subevent=obj,
|
||||||
|
defaults={
|
||||||
|
'value': f.cleaned_data['value']
|
||||||
|
}
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
SubEventMetaValue.objects.filter(
|
||||||
|
property=f.instance.property,
|
||||||
|
subevent__in=self.get_queryset()
|
||||||
|
).delete()
|
||||||
|
|
||||||
|
def save_itemvars(self):
|
||||||
|
for f in self.itemvar_forms:
|
||||||
|
u = {}
|
||||||
|
if f.prefix + 'price' in self.request.POST.getlist('_bulk'):
|
||||||
|
u['price'] = f.cleaned_data.get('price')
|
||||||
|
if f.prefix + 'disabled' in self.request.POST.getlist('_bulk'):
|
||||||
|
u['disabled'] = f.cleaned_data.get('disabled')
|
||||||
|
|
||||||
|
if not u:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if isinstance(f, SubEventItemForm):
|
||||||
|
if u.get('price') is None and not u.get('disabled'):
|
||||||
|
SubEventItem.objects.filter(
|
||||||
|
subevent__in=self.get_queryset(),
|
||||||
|
item=f.instance.item,
|
||||||
|
).delete()
|
||||||
|
else:
|
||||||
|
for obj in self.get_queryset():
|
||||||
|
SubEventItem.objects.update_or_create(
|
||||||
|
subevent=obj,
|
||||||
|
item=f.instance.item,
|
||||||
|
defaults=u
|
||||||
|
)
|
||||||
|
elif isinstance(f, SubEventItemVariationForm):
|
||||||
|
if u.get('price') is None and not u.get('disabled'):
|
||||||
|
SubEventItemVariation.objects.filter(
|
||||||
|
subevent__in=self.get_queryset(),
|
||||||
|
variation=f.instance.variation,
|
||||||
|
).delete()
|
||||||
|
else:
|
||||||
|
for obj in self.get_queryset():
|
||||||
|
SubEventItemVariation.objects.update_or_create(
|
||||||
|
subevent=obj,
|
||||||
|
variation=f.instance.variation,
|
||||||
|
defaults=u
|
||||||
|
)
|
||||||
|
|
||||||
|
@transaction.atomic()
|
||||||
|
def form_valid(self, form):
|
||||||
|
log_entries = []
|
||||||
|
|
||||||
|
# Main form
|
||||||
|
form.save()
|
||||||
|
data = {
|
||||||
|
k: v for k, v in form.cleaned_data.items() if k in form.changed_data
|
||||||
|
}
|
||||||
|
data['_raw_bulk_data'] = self.request.POST.dict()
|
||||||
|
for obj in self.get_queryset():
|
||||||
|
log_entries.append(
|
||||||
|
obj.log_action('pretix.subevent.changed', data=data, user=self.request.user, save=False)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Formsets
|
||||||
|
if '__quotas' in self.request.POST.getlist('_bulk'):
|
||||||
|
self.save_quota_formset(log_entries)
|
||||||
|
if '__checkinlists' in self.request.POST.getlist('_bulk'):
|
||||||
|
self.save_list_formset(log_entries)
|
||||||
|
|
||||||
|
self.save_itemvars()
|
||||||
|
self.save_meta()
|
||||||
|
|
||||||
|
if connections['default'].features.can_return_rows_from_bulk_insert:
|
||||||
|
LogEntry.objects.bulk_create(log_entries, batch_size=200)
|
||||||
|
LogEntry.bulk_postprocess(log_entries)
|
||||||
|
else:
|
||||||
|
for le in log_entries:
|
||||||
|
le.save()
|
||||||
|
LogEntry.bulk_postprocess(log_entries)
|
||||||
|
|
||||||
|
self.request.event.cache.clear()
|
||||||
|
messages.success(self.request, _('Your changes have been saved.'))
|
||||||
|
return super().form_valid(form)
|
||||||
|
|||||||
@@ -67,7 +67,8 @@ class VoucherList(PaginationMixin, EventPermissionRequiredMixin, ListView):
|
|||||||
|
|
||||||
headers = [
|
headers = [
|
||||||
_('Voucher code'), _('Valid until'), _('Product'), _('Reserve quota'), _('Bypass quota'),
|
_('Voucher code'), _('Valid until'), _('Product'), _('Reserve quota'), _('Bypass quota'),
|
||||||
_('Price effect'), _('Value'), _('Tag'), _('Redeemed'), _('Maximum usages'), _('Seat')
|
_('Price effect'), _('Value'), _('Tag'), _('Redeemed'), _('Maximum usages'), _('Seat'),
|
||||||
|
_('Comment')
|
||||||
]
|
]
|
||||||
writer.writerow(headers)
|
writer.writerow(headers)
|
||||||
|
|
||||||
@@ -92,7 +93,8 @@ class VoucherList(PaginationMixin, EventPermissionRequiredMixin, ListView):
|
|||||||
v.tag,
|
v.tag,
|
||||||
str(v.redeemed),
|
str(v.redeemed),
|
||||||
str(v.max_usages),
|
str(v.max_usages),
|
||||||
str(v.seat) if v.seat else ""
|
str(v.seat) if v.seat else "",
|
||||||
|
str(v.comment) if v.comment else ""
|
||||||
]
|
]
|
||||||
writer.writerow(row)
|
writer.writerow(row)
|
||||||
|
|
||||||
|
|||||||
@@ -8,16 +8,16 @@ msgstr ""
|
|||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2021-01-27 17:45+0000\n"
|
"POT-Creation-Date: 2021-01-27 17:45+0000\n"
|
||||||
"PO-Revision-Date: 2020-12-14 10:00+0000\n"
|
"PO-Revision-Date: 2021-02-16 06:00+0000\n"
|
||||||
"Last-Translator: Ondřej Sokol <osokol@treesoft.cz>\n"
|
"Last-Translator: Ondřej Sokol <osokol@treesoft.cz>\n"
|
||||||
"Language-Team: Czech <https://translate.pretix.eu/projects/pretix/pretix/cs/"
|
"Language-Team: Czech <https://translate.pretix.eu/projects/pretix/pretix/cs/>"
|
||||||
">\n"
|
"\n"
|
||||||
"Language: cs\n"
|
"Language: cs\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n"
|
"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n"
|
||||||
"X-Generator: Weblate 3.10.3\n"
|
"X-Generator: Weblate 4.4.2\n"
|
||||||
|
|
||||||
#: htmlcov/pretix_control_views_dashboards_py.html:898
|
#: htmlcov/pretix_control_views_dashboards_py.html:898
|
||||||
#: pretix/control/templates/pretixcontrol/events/index.html:144
|
#: pretix/control/templates/pretixcontrol/events/index.html:144
|
||||||
@@ -218,17 +218,17 @@ msgstr ""
|
|||||||
#: pretix/api/serializers/organizer.py:142
|
#: pretix/api/serializers/organizer.py:142
|
||||||
#: pretix/control/views/organizer.py:539
|
#: pretix/control/views/organizer.py:539
|
||||||
msgid "pretix account invitation"
|
msgid "pretix account invitation"
|
||||||
msgstr ""
|
msgstr "pozvánka k pretix účtu"
|
||||||
|
|
||||||
#: pretix/api/serializers/organizer.py:164
|
#: pretix/api/serializers/organizer.py:164
|
||||||
#: pretix/control/views/organizer.py:638
|
#: pretix/control/views/organizer.py:638
|
||||||
msgid "This user already has been invited for this team."
|
msgid "This user already has been invited for this team."
|
||||||
msgstr "Tento uživatel byl již pozván do této skupiny."
|
msgstr "Tento uživatel byl již pozván do tohoto týmu."
|
||||||
|
|
||||||
#: pretix/api/serializers/organizer.py:180
|
#: pretix/api/serializers/organizer.py:180
|
||||||
#: pretix/control/views/organizer.py:655
|
#: pretix/control/views/organizer.py:655
|
||||||
msgid "This user already has permissions for this team."
|
msgid "This user already has permissions for this team."
|
||||||
msgstr ""
|
msgstr "Tento uživatel již má nastavena práva pro tento tým."
|
||||||
|
|
||||||
#: pretix/api/views/oauth.py:85 pretix/control/logdisplay.py:356
|
#: pretix/api/views/oauth.py:85 pretix/control/logdisplay.py:356
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
@@ -240,7 +240,7 @@ msgstr ""
|
|||||||
#: pretix/api/views/order.py:460 pretix/control/views/orders.py:1186
|
#: pretix/api/views/order.py:460 pretix/control/views/orders.py:1186
|
||||||
#: pretix/presale/views/order.py:663 pretix/presale/views/order.py:728
|
#: pretix/presale/views/order.py:663 pretix/presale/views/order.py:728
|
||||||
msgid "You cannot generate an invoice for this order."
|
msgid "You cannot generate an invoice for this order."
|
||||||
msgstr ""
|
msgstr "Nemůžete vygenerovat fakturu pro tuto objednávku."
|
||||||
|
|
||||||
#: pretix/api/views/order.py:465 pretix/control/views/orders.py:1188
|
#: pretix/api/views/order.py:465 pretix/control/views/orders.py:1188
|
||||||
#: pretix/presale/views/order.py:665 pretix/presale/views/order.py:730
|
#: pretix/presale/views/order.py:665 pretix/presale/views/order.py:730
|
||||||
@@ -1868,7 +1868,7 @@ msgstr "Časové pásmo"
|
|||||||
|
|
||||||
#: pretix/base/models/auth.py:108
|
#: pretix/base/models/auth.py:108
|
||||||
msgid "Two-factor authentication is required to log in"
|
msgid "Two-factor authentication is required to log in"
|
||||||
msgstr ""
|
msgstr "Pro přihlášení je vyžadována dvoufaktorová autentizace"
|
||||||
|
|
||||||
#: pretix/base/models/auth.py:112
|
#: pretix/base/models/auth.py:112
|
||||||
msgid "Receive notifications according to my settings below"
|
msgid "Receive notifications according to my settings below"
|
||||||
@@ -1991,7 +1991,7 @@ msgstr ""
|
|||||||
#: pretix/base/models/devices.py:91
|
#: pretix/base/models/devices.py:91
|
||||||
#: pretix/control/templates/pretixcontrol/organizers/gates.html:16
|
#: pretix/control/templates/pretixcontrol/organizers/gates.html:16
|
||||||
msgid "Gate"
|
msgid "Gate"
|
||||||
msgstr ""
|
msgstr "Brána"
|
||||||
|
|
||||||
#: pretix/base/models/devices.py:109
|
#: pretix/base/models/devices.py:109
|
||||||
#: pretix/control/templates/pretixcontrol/organizers/devices.html:38
|
#: pretix/control/templates/pretixcontrol/organizers/devices.html:38
|
||||||
@@ -2008,7 +2008,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: pretix/base/models/event.py:46
|
#: pretix/base/models/event.py:46
|
||||||
msgid "The end of the event has to be later than its start."
|
msgid "The end of the event has to be later than its start."
|
||||||
msgstr ""
|
msgstr "Konec události musí být pozdější než začátek."
|
||||||
|
|
||||||
#: pretix/base/models/event.py:353
|
#: pretix/base/models/event.py:353
|
||||||
msgid ""
|
msgid ""
|
||||||
@@ -2030,7 +2030,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: pretix/base/models/event.py:369
|
#: pretix/base/models/event.py:369
|
||||||
msgid "Shop is live"
|
msgid "Shop is live"
|
||||||
msgstr ""
|
msgstr "Obchod je spuštěný"
|
||||||
|
|
||||||
#: pretix/base/models/event.py:371
|
#: pretix/base/models/event.py:371
|
||||||
msgid "Event currency"
|
msgid "Event currency"
|
||||||
@@ -2057,7 +2057,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: pretix/base/models/event.py:380 pretix/base/models/event.py:1106
|
#: pretix/base/models/event.py:380 pretix/base/models/event.py:1106
|
||||||
msgid "Show in lists"
|
msgid "Show in lists"
|
||||||
msgstr ""
|
msgstr "Zobrazit v seznamu"
|
||||||
|
|
||||||
#: pretix/base/models/event.py:381
|
#: pretix/base/models/event.py:381
|
||||||
msgid ""
|
msgid ""
|
||||||
@@ -2068,7 +2068,7 @@ msgstr ""
|
|||||||
#: pretix/base/models/event.py:384 pretix/base/models/event.py:1120
|
#: pretix/base/models/event.py:384 pretix/base/models/event.py:1120
|
||||||
#: pretix/control/forms/subevents.py:75
|
#: pretix/control/forms/subevents.py:75
|
||||||
msgid "End of presale"
|
msgid "End of presale"
|
||||||
msgstr ""
|
msgstr "Konec předprodeje"
|
||||||
|
|
||||||
#: pretix/base/models/event.py:385 pretix/base/models/event.py:1121
|
#: pretix/base/models/event.py:385 pretix/base/models/event.py:1121
|
||||||
#: pretix/control/forms/subevents.py:76
|
#: pretix/control/forms/subevents.py:76
|
||||||
@@ -2080,7 +2080,7 @@ msgstr ""
|
|||||||
#: pretix/base/models/event.py:390 pretix/base/models/event.py:1126
|
#: pretix/base/models/event.py:390 pretix/base/models/event.py:1126
|
||||||
#: pretix/control/forms/subevents.py:69
|
#: pretix/control/forms/subevents.py:69
|
||||||
msgid "Start of presale"
|
msgid "Start of presale"
|
||||||
msgstr ""
|
msgstr "Začátek předprodeje"
|
||||||
|
|
||||||
#: pretix/base/models/event.py:391 pretix/base/models/event.py:1127
|
#: pretix/base/models/event.py:391 pretix/base/models/event.py:1127
|
||||||
#: pretix/control/forms/subevents.py:70
|
#: pretix/control/forms/subevents.py:70
|
||||||
@@ -2101,13 +2101,13 @@ msgstr ""
|
|||||||
|
|
||||||
#: pretix/base/models/event.py:416 pretix/control/navigation.py:44
|
#: pretix/base/models/event.py:416 pretix/control/navigation.py:44
|
||||||
msgid "Plugins"
|
msgid "Plugins"
|
||||||
msgstr ""
|
msgstr "Zásuvné moduly"
|
||||||
|
|
||||||
#: pretix/base/models/event.py:419
|
#: pretix/base/models/event.py:419
|
||||||
#: pretix/control/templates/pretixcontrol/event/index.html:143
|
#: pretix/control/templates/pretixcontrol/event/index.html:143
|
||||||
#: pretix/control/templates/pretixcontrol/order/index.html:865
|
#: pretix/control/templates/pretixcontrol/order/index.html:865
|
||||||
msgid "Internal comment"
|
msgid "Internal comment"
|
||||||
msgstr ""
|
msgstr "Interní poznámka"
|
||||||
|
|
||||||
#: pretix/base/models/event.py:423 pretix/control/forms/event.py:211
|
#: pretix/base/models/event.py:423 pretix/control/forms/event.py:211
|
||||||
#: pretix/control/forms/filter.py:988
|
#: pretix/control/forms/filter.py:988
|
||||||
@@ -2133,7 +2133,7 @@ msgstr ""
|
|||||||
#: pretix/control/templates/pretixcontrol/search/orders.html:44
|
#: pretix/control/templates/pretixcontrol/search/orders.html:44
|
||||||
#: pretix/presale/templates/pretixpresale/event/waitinglist.html:18
|
#: pretix/presale/templates/pretixpresale/event/waitinglist.html:18
|
||||||
msgid "Event"
|
msgid "Event"
|
||||||
msgstr ""
|
msgstr "Událost"
|
||||||
|
|
||||||
#: pretix/base/models/event.py:437 pretix/control/navigation.py:305
|
#: pretix/base/models/event.py:437 pretix/control/navigation.py:305
|
||||||
#: pretix/control/navigation.py:407
|
#: pretix/control/navigation.py:407
|
||||||
@@ -2144,7 +2144,7 @@ msgstr ""
|
|||||||
#: pretix/control/templates/pretixcontrol/organizers/webhooks.html:37
|
#: pretix/control/templates/pretixcontrol/organizers/webhooks.html:37
|
||||||
#: pretix/control/views/organizer.py:1205
|
#: pretix/control/views/organizer.py:1205
|
||||||
msgid "Events"
|
msgid "Events"
|
||||||
msgstr ""
|
msgstr "Události"
|
||||||
|
|
||||||
#: pretix/base/models/event.py:951
|
#: pretix/base/models/event.py:951
|
||||||
msgid ""
|
msgid ""
|
||||||
@@ -2182,7 +2182,7 @@ msgstr ""
|
|||||||
#: pretix/control/forms/filter.py:1250
|
#: pretix/control/forms/filter.py:1250
|
||||||
#: pretix/control/templates/pretixcontrol/users/index.html:46
|
#: pretix/control/templates/pretixcontrol/users/index.html:46
|
||||||
msgid "Active"
|
msgid "Active"
|
||||||
msgstr ""
|
msgstr "Aktivní"
|
||||||
|
|
||||||
#: pretix/base/models/event.py:1103
|
#: pretix/base/models/event.py:1103
|
||||||
msgid ""
|
msgid ""
|
||||||
@@ -6399,7 +6399,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: pretix/base/settings.py:1694
|
#: pretix/base/settings.py:1694
|
||||||
msgid "Primary color"
|
msgid "Primary color"
|
||||||
msgstr ""
|
msgstr "Hlavní barva"
|
||||||
|
|
||||||
#: pretix/base/settings.py:1714
|
#: pretix/base/settings.py:1714
|
||||||
msgid "Accent color for success"
|
msgid "Accent color for success"
|
||||||
@@ -9737,7 +9737,7 @@ msgstr ""
|
|||||||
#: pretix/control/templates/pretixcontrol/dashboard.html:3
|
#: pretix/control/templates/pretixcontrol/dashboard.html:3
|
||||||
#: pretix/control/templates/pretixcontrol/dashboard.html:5
|
#: pretix/control/templates/pretixcontrol/dashboard.html:5
|
||||||
msgid "Dashboard"
|
msgid "Dashboard"
|
||||||
msgstr ""
|
msgstr "Nástěnka"
|
||||||
|
|
||||||
#: pretix/control/navigation.py:28 pretix/control/navigation.py:329
|
#: pretix/control/navigation.py:28 pretix/control/navigation.py:329
|
||||||
#: pretix/control/navigation.py:424
|
#: pretix/control/navigation.py:424
|
||||||
@@ -10328,7 +10328,7 @@ msgstr ""
|
|||||||
#: pretix/control/templates/pretixcontrol/waitinglist/index.html:123
|
#: pretix/control/templates/pretixcontrol/waitinglist/index.html:123
|
||||||
#: pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/import_form.html:80
|
#: pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/import_form.html:80
|
||||||
msgid "Filter"
|
msgid "Filter"
|
||||||
msgstr ""
|
msgstr "Filtrovat"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/checkin/index.html:52
|
#: pretix/control/templates/pretixcontrol/checkin/index.html:52
|
||||||
msgid "No attendee record was found."
|
msgid "No attendee record was found."
|
||||||
@@ -10558,11 +10558,11 @@ msgstr ""
|
|||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/dashboard.html:9
|
#: pretix/control/templates/pretixcontrol/dashboard.html:9
|
||||||
msgid "Go to event"
|
msgid "Go to event"
|
||||||
msgstr ""
|
msgstr "Jít na událost"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/dashboard.html:15
|
#: pretix/control/templates/pretixcontrol/dashboard.html:15
|
||||||
msgid "Your upcoming events"
|
msgid "Your upcoming events"
|
||||||
msgstr ""
|
msgstr "Vaše nadcházející události"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/dashboard.html:20
|
#: pretix/control/templates/pretixcontrol/dashboard.html:20
|
||||||
#: pretix/control/templates/pretixcontrol/events/create_base.html:4
|
#: pretix/control/templates/pretixcontrol/events/create_base.html:4
|
||||||
@@ -10571,11 +10571,11 @@ msgstr ""
|
|||||||
#: pretix/control/templates/pretixcontrol/events/index.html:57
|
#: pretix/control/templates/pretixcontrol/events/index.html:57
|
||||||
#: pretix/control/templates/pretixcontrol/organizers/detail.html:12
|
#: pretix/control/templates/pretixcontrol/organizers/detail.html:12
|
||||||
msgid "Create a new event"
|
msgid "Create a new event"
|
||||||
msgstr ""
|
msgstr "Vytvořit novou událost"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/dashboard.html:39
|
#: pretix/control/templates/pretixcontrol/dashboard.html:39
|
||||||
msgid "View all upcoming events"
|
msgid "View all upcoming events"
|
||||||
msgstr ""
|
msgstr "Zobrazit všechny nadcházející události"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/dashboard.html:44
|
#: pretix/control/templates/pretixcontrol/dashboard.html:44
|
||||||
msgid "Your most recent events"
|
msgid "Your most recent events"
|
||||||
@@ -13546,7 +13546,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/orders/index.html:98
|
#: pretix/control/templates/pretixcontrol/orders/index.html:98
|
||||||
msgid "Remove filter"
|
msgid "Remove filter"
|
||||||
msgstr ""
|
msgstr "Odstranit filtr"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/orders/index.html:116
|
#: pretix/control/templates/pretixcontrol/orders/index.html:116
|
||||||
msgid "Order paid / total"
|
msgid "Order paid / total"
|
||||||
@@ -13618,7 +13618,7 @@ msgstr ""
|
|||||||
#: pretix/control/templates/pretixcontrol/orders/overview.html:69
|
#: pretix/control/templates/pretixcontrol/orders/overview.html:69
|
||||||
#: pretix/plugins/reports/exporters.py:259
|
#: pretix/plugins/reports/exporters.py:259
|
||||||
msgid "Purchased"
|
msgid "Purchased"
|
||||||
msgstr ""
|
msgstr "Zakoupeno"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/orders/overview.html:178
|
#: pretix/control/templates/pretixcontrol/orders/overview.html:178
|
||||||
msgid ""
|
msgid ""
|
||||||
@@ -13679,7 +13679,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/organizers/device_connect.html:14
|
#: pretix/control/templates/pretixcontrol/organizers/device_connect.html:14
|
||||||
msgid "Download pretixSCAN"
|
msgid "Download pretixSCAN"
|
||||||
msgstr ""
|
msgstr "Stáhnout pretixSCAN"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/organizers/device_connect.html:18
|
#: pretix/control/templates/pretixcontrol/organizers/device_connect.html:18
|
||||||
msgid ""
|
msgid ""
|
||||||
@@ -17735,12 +17735,12 @@ msgstr ""
|
|||||||
#: pretix/plugins/reports/exporters.py:120
|
#: pretix/plugins/reports/exporters.py:120
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Page %d"
|
msgid "Page %d"
|
||||||
msgstr ""
|
msgstr "Strana %d"
|
||||||
|
|
||||||
#: pretix/plugins/reports/exporters.py:122
|
#: pretix/plugins/reports/exporters.py:122
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Created: %s"
|
msgid "Created: %s"
|
||||||
msgstr ""
|
msgstr "Vytvořeno: %s"
|
||||||
|
|
||||||
#: pretix/plugins/reports/exporters.py:162
|
#: pretix/plugins/reports/exporters.py:162
|
||||||
msgid "Order overview (PDF)"
|
msgid "Order overview (PDF)"
|
||||||
@@ -17810,10 +17810,8 @@ msgstr ""
|
|||||||
|
|
||||||
#: pretix/plugins/reports/exporters.py:668
|
#: pretix/plugins/reports/exporters.py:668
|
||||||
#: pretix/plugins/reports/exporters.py:713
|
#: pretix/plugins/reports/exporters.py:713
|
||||||
#, fuzzy
|
|
||||||
#| msgid "Country"
|
|
||||||
msgid "Country code"
|
msgid "Country code"
|
||||||
msgstr "Stát"
|
msgstr "Kód země"
|
||||||
|
|
||||||
#: pretix/plugins/returnurl/__init__.py:9
|
#: pretix/plugins/returnurl/__init__.py:9
|
||||||
#: pretix/plugins/returnurl/__init__.py:12
|
#: pretix/plugins/returnurl/__init__.py:12
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2020-12-22 11:05+0000\n"
|
"POT-Creation-Date: 2020-12-22 11:05+0000\n"
|
||||||
"PO-Revision-Date: 2021-01-26 03:00+0000\n"
|
"PO-Revision-Date: 2021-02-15 05:00+0000\n"
|
||||||
"Last-Translator: Jaakko Rinta-Filppula <jaakko@r-f.fi>\n"
|
"Last-Translator: Jaakko Rinta-Filppula <jaakko@r-f.fi>\n"
|
||||||
"Language-Team: Finnish <https://translate.pretix.eu/projects/pretix/pretix/"
|
"Language-Team: Finnish <https://translate.pretix.eu/projects/pretix/pretix/"
|
||||||
"fi/>\n"
|
"fi/>\n"
|
||||||
@@ -17,7 +17,7 @@ msgstr ""
|
|||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||||
"X-Generator: Weblate 3.10.3\n"
|
"X-Generator: Weblate 4.4.2\n"
|
||||||
|
|
||||||
#: htmlcov/pretix_control_views_dashboards_py.html:898
|
#: htmlcov/pretix_control_views_dashboards_py.html:898
|
||||||
#: pretix/control/templates/pretixcontrol/events/index.html:144
|
#: pretix/control/templates/pretixcontrol/events/index.html:144
|
||||||
@@ -3136,10 +3136,8 @@ msgstr "Ulkoinen"
|
|||||||
|
|
||||||
#: pretix/base/models/orders.py:1720
|
#: pretix/base/models/orders.py:1720
|
||||||
#: pretix/control/templates/pretixcontrol/order/refund_choose.html:191
|
#: pretix/control/templates/pretixcontrol/order/refund_choose.html:191
|
||||||
#, fuzzy
|
|
||||||
#| msgid "Refund order"
|
|
||||||
msgid "Refund reason"
|
msgid "Refund reason"
|
||||||
msgstr "Hyvitä tilaus"
|
msgstr "Hyvityksen syy"
|
||||||
|
|
||||||
#: pretix/base/models/orders.py:1721
|
#: pretix/base/models/orders.py:1721
|
||||||
#: pretix/control/templates/pretixcontrol/order/refund_choose.html:192
|
#: pretix/control/templates/pretixcontrol/order/refund_choose.html:192
|
||||||
@@ -4537,10 +4535,8 @@ msgstr ""
|
|||||||
|
|
||||||
#: pretix/base/services/cancelevent.py:200
|
#: pretix/base/services/cancelevent.py:200
|
||||||
#: pretix/base/services/cancelevent.py:258
|
#: pretix/base/services/cancelevent.py:258
|
||||||
#, fuzzy
|
|
||||||
#| msgid "Event created"
|
|
||||||
msgid "Event canceled"
|
msgid "Event canceled"
|
||||||
msgstr "Tapahtuma luotu"
|
msgstr "Tapahtuma peruttu"
|
||||||
|
|
||||||
#: pretix/base/services/cart.py:52 pretix/base/services/orders.py:73
|
#: pretix/base/services/cart.py:52 pretix/base/services/orders.py:73
|
||||||
msgid ""
|
msgid ""
|
||||||
@@ -7817,16 +7813,12 @@ msgid "Order placed before"
|
|||||||
msgstr "Tilattu jälkeen"
|
msgstr "Tilattu jälkeen"
|
||||||
|
|
||||||
#: pretix/control/forms/filter.py:468
|
#: pretix/control/forms/filter.py:468
|
||||||
#, fuzzy
|
|
||||||
#| msgid "Order payments and refunds"
|
|
||||||
msgid "Minimal sum of payments and refunds"
|
msgid "Minimal sum of payments and refunds"
|
||||||
msgstr "Tilauksen maksut ja palautukset"
|
msgstr "Maksujen ja palautusten vähimmäissumma"
|
||||||
|
|
||||||
#: pretix/control/forms/filter.py:473
|
#: pretix/control/forms/filter.py:473
|
||||||
#, fuzzy
|
|
||||||
#| msgid "Order payments and refunds"
|
|
||||||
msgid "Maximal sum of payments and refunds"
|
msgid "Maximal sum of payments and refunds"
|
||||||
msgstr "Tilauksen maksut ja palautukset"
|
msgstr "Tilausten ja maksujen enimmäissumma"
|
||||||
|
|
||||||
#: pretix/control/forms/filter.py:515 pretix/control/forms/filter.py:520
|
#: pretix/control/forms/filter.py:515 pretix/control/forms/filter.py:520
|
||||||
#: pretix/control/forms/filter.py:546 pretix/control/forms/filter.py:551
|
#: pretix/control/forms/filter.py:546 pretix/control/forms/filter.py:551
|
||||||
@@ -8487,11 +8479,9 @@ msgid "Automatically refund money if possible"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: pretix/control/forms/orders.py:619
|
#: pretix/control/forms/orders.py:619
|
||||||
#, fuzzy
|
|
||||||
#| msgid "This payment method does not support automatic refunds."
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"Create manual refund if the payment method does not support automatic refunds"
|
"Create manual refund if the payment method does not support automatic refunds"
|
||||||
msgstr "Tätä maksutapa ei tue automaattisia hyvityksiä."
|
msgstr "Luo manuaalinen hyvitys, jos maksutapa ei tue automaattista hyvitystä"
|
||||||
|
|
||||||
#: pretix/control/forms/orders.py:623
|
#: pretix/control/forms/orders.py:623
|
||||||
msgid ""
|
msgid ""
|
||||||
@@ -12876,10 +12866,8 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/order/index.html:104
|
#: pretix/control/templates/pretixcontrol/order/index.html:104
|
||||||
#, fuzzy
|
|
||||||
#| msgid "Refund order"
|
|
||||||
msgid "Refund for overpayment"
|
msgid "Refund for overpayment"
|
||||||
msgstr "Hyvitä tilaus"
|
msgstr ""
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/order/index.html:106
|
#: pretix/control/templates/pretixcontrol/order/index.html:106
|
||||||
#, python-format
|
#, python-format
|
||||||
@@ -13262,16 +13250,12 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/order/refund_choose.html:30
|
#: pretix/control/templates/pretixcontrol/order/refund_choose.html:30
|
||||||
#, fuzzy
|
|
||||||
#| msgid "Refund order"
|
|
||||||
msgid "Refund to original payment method"
|
msgid "Refund to original payment method"
|
||||||
msgstr "Hyvitä tilaus"
|
msgstr "Hyvitä alkuperäiseen maksutapaan"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/order/refund_choose.html:36
|
#: pretix/control/templates/pretixcontrol/order/refund_choose.html:36
|
||||||
#, fuzzy
|
|
||||||
#| msgid "Payment method"
|
|
||||||
msgid "Payment details"
|
msgid "Payment details"
|
||||||
msgstr "Maksutapa"
|
msgstr "Maksun tiedot"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/order/refund_choose.html:37
|
#: pretix/control/templates/pretixcontrol/order/refund_choose.html:37
|
||||||
msgid "Amount not refunded"
|
msgid "Amount not refunded"
|
||||||
@@ -13279,10 +13263,8 @@ msgstr ""
|
|||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/order/refund_choose.html:38
|
#: pretix/control/templates/pretixcontrol/order/refund_choose.html:38
|
||||||
#: pretix/control/templates/pretixcontrol/order/refund_choose.html:85
|
#: pretix/control/templates/pretixcontrol/order/refund_choose.html:85
|
||||||
#, fuzzy
|
|
||||||
#| msgid "Refund order"
|
|
||||||
msgid "Refund amount"
|
msgid "Refund amount"
|
||||||
msgstr "Hyvitä tilaus"
|
msgstr "Hyvitettävä summa"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/order/refund_choose.html:66
|
#: pretix/control/templates/pretixcontrol/order/refund_choose.html:66
|
||||||
msgid "Full amount"
|
msgid "Full amount"
|
||||||
@@ -13293,16 +13275,12 @@ msgid "This payment method does not support automatic refunds."
|
|||||||
msgstr "Tätä maksutapa ei tue automaattisia hyvityksiä."
|
msgstr "Tätä maksutapa ei tue automaattisia hyvityksiä."
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/order/refund_choose.html:78
|
#: pretix/control/templates/pretixcontrol/order/refund_choose.html:78
|
||||||
#, fuzzy
|
|
||||||
#| msgid "Refund order"
|
|
||||||
msgid "Refund to a different payment method"
|
msgid "Refund to a different payment method"
|
||||||
msgstr "Hyvitä tilaus"
|
msgstr "Hyvitä eri maksutavalle"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/order/refund_choose.html:84
|
#: pretix/control/templates/pretixcontrol/order/refund_choose.html:84
|
||||||
#, fuzzy
|
|
||||||
#| msgid "Question options"
|
|
||||||
msgid "Recipient / options"
|
msgid "Recipient / options"
|
||||||
msgstr "Kysymysvaihtoehdot"
|
msgstr "Vastaanottaja / vaihtoehdot"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/order/refund_choose.html:112
|
#: pretix/control/templates/pretixcontrol/order/refund_choose.html:112
|
||||||
msgid "Transfer to other order"
|
msgid "Transfer to other order"
|
||||||
@@ -14500,16 +14478,12 @@ msgid "Times"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/subevents/bulk.html:339
|
#: pretix/control/templates/pretixcontrol/subevents/bulk.html:339
|
||||||
#, fuzzy
|
|
||||||
#| msgid "Start of presale"
|
|
||||||
msgid "Start of first slot"
|
msgid "Start of first slot"
|
||||||
msgstr "Ennakkomyynnin alku"
|
msgstr ""
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/subevents/bulk.html:345
|
#: pretix/control/templates/pretixcontrol/subevents/bulk.html:345
|
||||||
#, fuzzy
|
|
||||||
#| msgid "End of presale"
|
|
||||||
msgid "End of time slots"
|
msgid "End of time slots"
|
||||||
msgstr "Ennakkomyynnin loppu"
|
msgstr ""
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/subevents/bulk.html:351
|
#: pretix/control/templates/pretixcontrol/subevents/bulk.html:351
|
||||||
msgid "Length of slots"
|
msgid "Length of slots"
|
||||||
@@ -14525,11 +14499,8 @@ msgid "Break between slots"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/subevents/bulk.html:370
|
#: pretix/control/templates/pretixcontrol/subevents/bulk.html:370
|
||||||
#, fuzzy
|
|
||||||
#| msgctxt "payment_state"
|
|
||||||
#| msgid "created"
|
|
||||||
msgid "Create"
|
msgid "Create"
|
||||||
msgstr "luotu"
|
msgstr "Luo"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/subevents/bulk.html:377
|
#: pretix/control/templates/pretixcontrol/subevents/bulk.html:377
|
||||||
msgid "Add a single time slot"
|
msgid "Add a single time slot"
|
||||||
@@ -16187,10 +16158,8 @@ msgid "The selected exporter was not found."
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: pretix/control/views/orders.py:2117
|
#: pretix/control/views/orders.py:2117
|
||||||
#, fuzzy
|
|
||||||
#| msgid "We are processing your request …"
|
|
||||||
msgid "There was a problem processing your input:"
|
msgid "There was a problem processing your input:"
|
||||||
msgstr "Pyyntöäsi käsitellään …"
|
msgstr "Ongelma syötteen käsittelyssä:"
|
||||||
|
|
||||||
#: pretix/control/views/orders.py:2213
|
#: pretix/control/views/orders.py:2213
|
||||||
msgid "All orders have been canceled."
|
msgid "All orders have been canceled."
|
||||||
@@ -16949,17 +16918,12 @@ msgid "Can only create a bank transfer refund from an existing payment."
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: pretix/plugins/banktransfer/payment.py:349
|
#: pretix/plugins/banktransfer/payment.py:349
|
||||||
#, fuzzy
|
|
||||||
#| msgid "Price (optional)"
|
|
||||||
msgid "BIC (optional)"
|
msgid "BIC (optional)"
|
||||||
msgstr "Hinta (valinnainen)"
|
msgstr "BIC (valinnainen)"
|
||||||
|
|
||||||
#: pretix/plugins/banktransfer/payment.py:388
|
#: pretix/plugins/banktransfer/payment.py:388
|
||||||
#, fuzzy
|
|
||||||
#| msgid "Your order has been placed successfully. See below for details."
|
|
||||||
msgid "Your input was invalid, please see below for details."
|
msgid "Your input was invalid, please see below for details."
|
||||||
msgstr ""
|
msgstr "Epäkelpo syöte, katso tiedot alta."
|
||||||
"Tilauksesi on vastaanotettu onnistuneesti. Katso tilauksen tiedot alta."
|
|
||||||
|
|
||||||
#: pretix/plugins/banktransfer/refund_export.py:25
|
#: pretix/plugins/banktransfer/refund_export.py:25
|
||||||
#: pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/control.html:5
|
#: pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/control.html:5
|
||||||
@@ -20213,10 +20177,8 @@ msgid "You chose an invalid cancellation fee."
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: pretix/presale/views/order.py:869
|
#: pretix/presale/views/order.py:869
|
||||||
#, fuzzy
|
|
||||||
#| msgid "Any customer"
|
|
||||||
msgid "Canceled by customer"
|
msgid "Canceled by customer"
|
||||||
msgstr "Kaikki asiakkaat"
|
msgstr "Asiakkaan peruuttama"
|
||||||
|
|
||||||
#: pretix/presale/views/order.py:880
|
#: pretix/presale/views/order.py:880
|
||||||
msgid "The cancellation has been requested."
|
msgid "The cancellation has been requested."
|
||||||
|
|||||||
@@ -720,6 +720,8 @@ BOOTSTRAP3 = {
|
|||||||
'default': 'bootstrap3.renderers.FieldRenderer',
|
'default': 'bootstrap3.renderers.FieldRenderer',
|
||||||
'inline': 'bootstrap3.renderers.InlineFieldRenderer',
|
'inline': 'bootstrap3.renderers.InlineFieldRenderer',
|
||||||
'control': 'pretix.control.forms.renderers.ControlFieldRenderer',
|
'control': 'pretix.control.forms.renderers.ControlFieldRenderer',
|
||||||
|
'bulkedit': 'pretix.control.forms.renderers.BulkEditFieldRenderer',
|
||||||
|
'bulkedit_inline': 'pretix.control.forms.renderers.InlineBulkEditFieldRenderer',
|
||||||
'checkout': 'pretix.presale.forms.renderers.CheckoutFieldRenderer',
|
'checkout': 'pretix.presale.forms.renderers.CheckoutFieldRenderer',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -758,3 +760,6 @@ OAUTH2_PROVIDER = {
|
|||||||
COUNTRIES_OVERRIDE = {
|
COUNTRIES_OVERRIDE = {
|
||||||
'XK': _('Kosovo'),
|
'XK': _('Kosovo'),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DATA_UPLOAD_MAX_NUMBER_FIELDS = 25000
|
||||||
|
DATA_UPLOAD_MAX_MEMORY_SIZE = 10 * 1024 * 1024 # 10 MB
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ function async_task_check_error(jqXHR, textStatus, errorThrown) {
|
|||||||
jqXHR.responseText.indexOf("</body")
|
jqXHR.responseText.indexOf("</body")
|
||||||
));
|
));
|
||||||
form_handlers($("body"));
|
form_handlers($("body"));
|
||||||
|
setup_collapsible_details($("body"));
|
||||||
} else if (c.length > 0) {
|
} else if (c.length > 0) {
|
||||||
// This is some kind of 500/404/403 page, show it in an overlay
|
// This is some kind of 500/404/403 page, show it in an overlay
|
||||||
$("body").data('ajaxing', false);
|
$("body").data('ajaxing', false);
|
||||||
@@ -146,11 +147,20 @@ function async_task_error(jqXHR, textStatus, errorThrown) {
|
|||||||
if (respdom.filter('form') && (respdom.filter('.has-error') || respdom.filter('.alert-danger'))) {
|
if (respdom.filter('form') && (respdom.filter('.has-error') || respdom.filter('.alert-danger'))) {
|
||||||
// This is a failed form validation, let's just use it
|
// This is a failed form validation, let's just use it
|
||||||
waitingDialog.hide();
|
waitingDialog.hide();
|
||||||
$("body").html(jqXHR.responseText.substring(
|
|
||||||
jqXHR.responseText.indexOf("<body"),
|
if (respdom.filter('#page-wrapper') && $('#page-wrapper').length) {
|
||||||
jqXHR.responseText.indexOf("</body")
|
$("#page-wrapper").html(respdom.find("#page-wrapper").html());
|
||||||
));
|
form_handlers($("#page-wrapper"));
|
||||||
form_handlers($("body"));
|
setup_collapsible_details($("#page-wrapper"));
|
||||||
|
} else {
|
||||||
|
$("body").html(jqXHR.responseText.substring(
|
||||||
|
jqXHR.responseText.indexOf("<body"),
|
||||||
|
jqXHR.responseText.indexOf("</body")
|
||||||
|
));
|
||||||
|
form_handlers($("body"));
|
||||||
|
setup_collapsible_details($("body"));
|
||||||
|
}
|
||||||
|
|
||||||
} else if (c.length > 0) {
|
} else if (c.length > 0) {
|
||||||
waitingDialog.hide();
|
waitingDialog.hide();
|
||||||
ajaxErrDialog.show(c.first().html());
|
ajaxErrDialog.show(c.first().html());
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
/*global $ */
|
/*global $ */
|
||||||
|
|
||||||
$(function () {
|
setup_collapsible_details = function (el) {
|
||||||
"use strict";
|
|
||||||
|
|
||||||
var isOpera = Object.prototype.toString.call(window.opera) == '[object Opera]';
|
var isOpera = Object.prototype.toString.call(window.opera) == '[object Opera]';
|
||||||
|
el.find("details summary, details summary a[data-toggle=variations]").click(function (e) {
|
||||||
$("details summary, details summary a[data-toggle=variations]").click(function (e) {
|
|
||||||
if (this.tagName !== "A" && $(e.target).closest("a").length > 0) {
|
if (this.tagName !== "A" && $(e.target).closest("a").length > 0) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -44,7 +41,12 @@ $(function () {
|
|||||||
$detailsNotSummary = $details.children(':not(summary)');
|
$detailsNotSummary = $details.children(':not(summary)');
|
||||||
$details.prop('open', typeof $details.attr('open') == 'string');
|
$details.prop('open', typeof $details.attr('open') == 'string');
|
||||||
if (!$details.prop('open')) {
|
if (!$details.prop('open')) {
|
||||||
$detailsNotSummary.hide();
|
if ($details.find(".has-error, .alert-danger").length) {
|
||||||
|
$details.addClass("details-open");
|
||||||
|
$details.prop('open', true);
|
||||||
|
} else {
|
||||||
|
$detailsNotSummary.hide();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
$details.addClass("details-open");
|
$details.addClass("details-open");
|
||||||
}
|
}
|
||||||
@@ -55,4 +57,10 @@ $(function () {
|
|||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$(function () {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
setup_collapsible_details($("body"));
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -362,6 +362,9 @@ $(document).ready(function () {
|
|||||||
} else {
|
} else {
|
||||||
this.$set(this.rule[this.operator], 1, time);
|
this.$set(this.rule[this.operator], 1, time);
|
||||||
}
|
}
|
||||||
|
if (event.target.value === "custom") {
|
||||||
|
this.$set(this.rule[this.operator], 2, 0);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
setTimeValue: function (val) {
|
setTimeValue: function (val) {
|
||||||
console.log(val);
|
console.log(val);
|
||||||
|
|||||||
@@ -546,6 +546,25 @@ var form_handlers = function (el) {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
el.find(".bulk-edit-field-group").each(function () {
|
||||||
|
var $checkbox = $(this).find("input[type=checkbox][name=_bulk]");
|
||||||
|
var $content = $(this).find(".field-content");
|
||||||
|
var $fields = $content.find("input, select, textarea, button");
|
||||||
|
|
||||||
|
var update = function () {
|
||||||
|
var isChecked = $checkbox.prop("checked");
|
||||||
|
$content.toggleClass("enabled", isChecked);
|
||||||
|
$fields.attr("tabIndex", isChecked ? 0 : -1);
|
||||||
|
}
|
||||||
|
$content.on("focusin change click", function () {
|
||||||
|
if ($checkbox.prop("checked")) return;
|
||||||
|
$checkbox.prop("checked", true);
|
||||||
|
update();
|
||||||
|
});
|
||||||
|
$checkbox.on('change', update)
|
||||||
|
update();
|
||||||
|
});
|
||||||
|
|
||||||
el.find("input[name*=question], select[name*=question]").change(questions_toggle_dependent);
|
el.find("input[name*=question], select[name*=question]").change(questions_toggle_dependent);
|
||||||
questions_toggle_dependent();
|
questions_toggle_dependent();
|
||||||
};
|
};
|
||||||
@@ -563,7 +582,6 @@ $(function () {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
$("[data-formset]").on("formAdded", "div", function (event) {
|
$("[data-formset]").on("formAdded", "div", function (event) {
|
||||||
console.log("formAdded")
|
|
||||||
form_handlers($(event.target));
|
form_handlers($(event.target));
|
||||||
});
|
});
|
||||||
$(document).on("click", ".variations .variations-select-all", function (e) {
|
$(document).on("click", ".variations .variations-select-all", function (e) {
|
||||||
@@ -672,29 +690,104 @@ $(function () {
|
|||||||
// Tables with bulk selection, e.g. subevent list
|
// Tables with bulk selection, e.g. subevent list
|
||||||
$("input[data-toggle-table]").each(function (ev) {
|
$("input[data-toggle-table]").each(function (ev) {
|
||||||
var $toggle = $(this);
|
var $toggle = $(this);
|
||||||
|
var $table = $toggle.closest("table");
|
||||||
var update = function () {
|
var $selectAll = $table.find(".table-select-all");
|
||||||
var all_true = true;
|
var $rows = $table.find("tbody tr");
|
||||||
var all_false = true;
|
var $checkboxes = $rows.find("td:first-child input[type=checkbox]");
|
||||||
$toggle.closest("table").find("td:first-child input[type=checkbox]").each(function () {
|
var firstIndex, lastIndex, selectionChecked, onChangeSelectionHappened = false;
|
||||||
if ($(this).prop("checked")) {
|
var updateSelection = function(a, b, checked) {
|
||||||
all_false = false;
|
if (a > b) {
|
||||||
} else {
|
//[a, b] = [b, a];// ES6 not ready yet for pretix
|
||||||
all_true = false;
|
var tmp = a;
|
||||||
}
|
a = b;
|
||||||
});
|
b = tmp;
|
||||||
if (all_true) {
|
}
|
||||||
$toggle.prop("checked", true).prop("indeterminate", false);
|
for (var i = a; i <= b; i++) {
|
||||||
} else if (all_false) {
|
var checkbox = $checkboxes.get(i);
|
||||||
$toggle.prop("checked", false).prop("indeterminate", false);
|
if (!checkbox.hasAttribute("data-inital")) checkbox.setAttribute("data-inital", checkbox.checked);
|
||||||
} else {
|
if (checked === undefined || checked === null) checkbox.checked = checkbox.getAttribute("data-inital") === "true";
|
||||||
$toggle.prop("checked", false).prop("indeterminate", true);
|
else checkbox.checked = checked;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
var onChangeSelection = function(ev) {
|
||||||
|
onChangeSelectionHappened = true;
|
||||||
|
|
||||||
$(this).closest("table").find("td:first-child input[type=checkbox]").change(update);
|
var row = ev.target.closest("tr");
|
||||||
$(this).change(function (ev) {
|
var currentIndex = 0;
|
||||||
$(this).closest("table").find("td:first-child input[type=checkbox]").prop("checked", $(this).prop("checked"));
|
while(row = row.previousSibling) {
|
||||||
|
if (row.tagName) currentIndex++;
|
||||||
|
}
|
||||||
|
var dCurrent = currentIndex - firstIndex;
|
||||||
|
var dLast = lastIndex - firstIndex;
|
||||||
|
if (dCurrent*dLast < 0) {
|
||||||
|
// direction of selection changed => reset all previously selected
|
||||||
|
updateSelection(lastIndex, firstIndex);
|
||||||
|
}
|
||||||
|
else if (Math.abs(dCurrent) < Math.abs(dLast)) {
|
||||||
|
// selection distance decreased => reset unselected
|
||||||
|
updateSelection(currentIndex, lastIndex);
|
||||||
|
}
|
||||||
|
lastIndex = currentIndex;
|
||||||
|
updateSelection(firstIndex, currentIndex, selectionChecked);
|
||||||
|
|
||||||
|
ev.preventDefault();
|
||||||
|
};
|
||||||
|
$table.on("pointerdown", function(ev) {
|
||||||
|
if (!ev.target.closest("td:first-child")) return;
|
||||||
|
var row = ev.target.closest("tr");
|
||||||
|
selectionChecked = !row.querySelector("td:first-child input").checked;
|
||||||
|
|
||||||
|
firstIndex = 0;
|
||||||
|
while(row = row.previousSibling) {
|
||||||
|
if (row.tagName) firstIndex++;
|
||||||
|
}
|
||||||
|
lastIndex = firstIndex;
|
||||||
|
|
||||||
|
ev.preventDefault();
|
||||||
|
$rows.on("pointerenter", onChangeSelection);
|
||||||
|
|
||||||
|
$(document).one("pointerup", function(ev) {
|
||||||
|
if (onChangeSelectionHappened) {
|
||||||
|
ev.preventDefault();
|
||||||
|
onChangeSelectionHappened = false;
|
||||||
|
$checkboxes.removeAttr("data-inital");
|
||||||
|
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
$rows.off("pointerenter", onChangeSelection);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
var update = function () {
|
||||||
|
var all_same;
|
||||||
|
var checkboxes = $checkboxes.toArray();
|
||||||
|
var i = checkboxes.length;
|
||||||
|
while (i--) {
|
||||||
|
if (all_same === undefined) {
|
||||||
|
all_same = checkboxes[i].checked;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (all_same != checkboxes[i].checked) {
|
||||||
|
$toggle.prop("checked", false).prop("indeterminate", true).trigger("change");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$toggle.prop("checked", all_same).prop("indeterminate", false).trigger("change");
|
||||||
|
};
|
||||||
|
|
||||||
|
var debounceUpdate;
|
||||||
|
$checkboxes.change(function() {
|
||||||
|
//$(this).closest("tr").toggleClass("warning", this.checked);
|
||||||
|
// when changing the $toggle’s checked-property, lots of change events
|
||||||
|
// get triggered => debounce
|
||||||
|
if (debounceUpdate) window.clearTimeout(debounceUpdate);
|
||||||
|
debounceUpdate = window.setTimeout(update, 10);
|
||||||
|
});
|
||||||
|
$toggle.change(function (ev) {
|
||||||
|
if (!this.indeterminate) $checkboxes.prop("checked", this.checked);//.trigger("change");
|
||||||
|
$selectAll.toggleClass("hidden", !this.checked).prop("hidden", !this.checked);
|
||||||
|
if (!this.checked) $selectAll.find("input").prop("checked", false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -613,3 +613,41 @@ table td > .checkbox input[type="checkbox"] {
|
|||||||
border-bottom: 1px solid $input-border;
|
border-bottom: 1px solid $input-border;
|
||||||
padding-bottom: 5px;
|
padding-bottom: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.batch-select-label {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: 1.5em;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bulk-edit-field-group {
|
||||||
|
.field-toggle {
|
||||||
|
font-weight: normal;
|
||||||
|
display: inline-block;
|
||||||
|
background: $gray-lighter;
|
||||||
|
padding: 2px 8px 4px;
|
||||||
|
border-top-left-radius: $border-radius-base;
|
||||||
|
border-top-right-radius: $border-radius-base;
|
||||||
|
margin-bottom: 0;
|
||||||
|
input {
|
||||||
|
position: relative;
|
||||||
|
top: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.field-content {
|
||||||
|
border: 2px solid $gray-lighter;
|
||||||
|
padding: 15px;
|
||||||
|
opacity: 0.5;
|
||||||
|
.datepickerfield::placeholder, .timepickerfield::placeholder {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
&.enabled {
|
||||||
|
opacity: 1;
|
||||||
|
.datepickerfield::placeholder, .timepickerfield::placeholder {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ TEST_SUBEVENT_RES = {
|
|||||||
'date_to': None,
|
'date_to': None,
|
||||||
'date_admission': None,
|
'date_admission': None,
|
||||||
'name': {'en': 'Foobar'},
|
'name': {'en': 'Foobar'},
|
||||||
|
'frontpage_text': None,
|
||||||
'date_from': '2017-12-27T10:00:00Z',
|
'date_from': '2017-12-27T10:00:00Z',
|
||||||
'presale_end': None,
|
'presale_end': None,
|
||||||
'seating_plan': None,
|
'seating_plan': None,
|
||||||
|
|||||||
@@ -34,16 +34,32 @@ def extract_form_fields(soup):
|
|||||||
|
|
||||||
if field['type'] in ('checkbox', 'radio'):
|
if field['type'] in ('checkbox', 'radio'):
|
||||||
if field.has_attr('checked') and field.has_attr('name'):
|
if field.has_attr('checked') and field.has_attr('name'):
|
||||||
data[field['name']] = field.get('value', 'on')
|
if field['name'] in data:
|
||||||
|
if not isinstance(data[field['name']], list):
|
||||||
|
data[field['name']] = [data[field['name']]]
|
||||||
|
data[field['name']].append(field.get('value', 'on'))
|
||||||
|
else:
|
||||||
|
data[field['name']] = field.get('value', 'on')
|
||||||
continue
|
continue
|
||||||
elif field.has_attr('name'):
|
elif field.has_attr('name'):
|
||||||
# single element name/value fields
|
# single element name/value fields
|
||||||
data[field['name']] = field.get('value', '')
|
value = field.get('value', '')
|
||||||
|
if field['name'] in data:
|
||||||
|
if not isinstance(data[field['name']], list):
|
||||||
|
data[field['name']] = [data[field['name']]]
|
||||||
|
data[field['name']].append(value)
|
||||||
|
else:
|
||||||
|
data[field['name']] = value
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# textareas
|
# textareas
|
||||||
for textarea in soup.findAll('textarea'):
|
for textarea in soup.findAll('textarea'):
|
||||||
data[textarea['name']] = textarea.text or ''
|
if textarea['name'] in data:
|
||||||
|
if not isinstance(data[textarea['name']], list):
|
||||||
|
data[textarea['name']] = [data[textarea['name']]]
|
||||||
|
data[textarea['name']].append(textarea.text or '')
|
||||||
|
else:
|
||||||
|
data[textarea['name']] = textarea.text or ''
|
||||||
|
|
||||||
# select fields
|
# select fields
|
||||||
for select in soup.find_all('select'):
|
for select in soup.find_all('select'):
|
||||||
@@ -66,6 +82,11 @@ def extract_form_fields(soup):
|
|||||||
else:
|
else:
|
||||||
value = [option['value'] for option in selected_options]
|
value = [option['value'] for option in selected_options]
|
||||||
|
|
||||||
data[select['name']] = value
|
if select['name'] in data:
|
||||||
|
if not isinstance(data[select['name']], list):
|
||||||
|
data[select['name']] = [data[select['name']]]
|
||||||
|
data[select['name']].append(value)
|
||||||
|
else:
|
||||||
|
data[select['name']] = value
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|||||||
@@ -9,10 +9,7 @@ from i18nfield.strings import LazyI18nString
|
|||||||
from pytz import timezone
|
from pytz import timezone
|
||||||
from tests.base import SoupTest, extract_form_fields
|
from tests.base import SoupTest, extract_form_fields
|
||||||
|
|
||||||
from pretix.base.models import (
|
from pretix.base.models import Event, Order, Organizer, Team, User
|
||||||
Event, Order, OrderPosition, Organizer, SubEvent, Team, User,
|
|
||||||
)
|
|
||||||
from pretix.base.models.items import SubEventItem
|
|
||||||
from pretix.testutils.mock import mocker_context
|
from pretix.testutils.mock import mocker_context
|
||||||
|
|
||||||
|
|
||||||
@@ -996,646 +993,6 @@ class EventsTest(SoupTest):
|
|||||||
assert doc.select(".has-error")
|
assert doc.select(".has-error")
|
||||||
|
|
||||||
|
|
||||||
class SubEventsTest(SoupTest):
|
|
||||||
@scopes_disabled()
|
|
||||||
def setUp(self):
|
|
||||||
super().setUp()
|
|
||||||
self.user = User.objects.create_user('dummy@dummy.dummy', 'dummy')
|
|
||||||
self.orga1 = Organizer.objects.create(name='CCC', slug='ccc')
|
|
||||||
self.event1 = Event.objects.create(
|
|
||||||
organizer=self.orga1, name='30C3', slug='30c3',
|
|
||||||
date_from=datetime.datetime(2013, 12, 26, tzinfo=datetime.timezone.utc),
|
|
||||||
plugins='pretix.plugins.banktransfer,tests.testdummy',
|
|
||||||
has_subevents=True
|
|
||||||
)
|
|
||||||
|
|
||||||
t = Team.objects.create(organizer=self.orga1, can_create_events=True, can_change_event_settings=True,
|
|
||||||
can_change_items=True)
|
|
||||||
t.members.add(self.user)
|
|
||||||
t.limit_events.add(self.event1)
|
|
||||||
self.ticket = self.event1.items.create(name='Early-bird ticket',
|
|
||||||
category=None, default_price=23,
|
|
||||||
admission=True)
|
|
||||||
|
|
||||||
self.client.login(email='dummy@dummy.dummy', password='dummy')
|
|
||||||
|
|
||||||
self.subevent1 = self.event1.subevents.create(name='SE1', date_from=now())
|
|
||||||
self.subevent2 = self.event1.subevents.create(name='SE2', date_from=now())
|
|
||||||
|
|
||||||
def test_list(self):
|
|
||||||
doc = self.get_doc('/control/event/ccc/30c3/subevents/')
|
|
||||||
tabletext = doc.select("#page-wrapper .table")[0].text
|
|
||||||
self.assertIn("SE1", tabletext)
|
|
||||||
|
|
||||||
def test_create(self):
|
|
||||||
doc = self.get_doc('/control/event/ccc/30c3/subevents/add')
|
|
||||||
assert doc.select("input[name=quotas-TOTAL_FORMS]")
|
|
||||||
doc = self.post_doc('/control/event/ccc/30c3/subevents/add', {
|
|
||||||
'name_0': 'SE2',
|
|
||||||
'active': 'on',
|
|
||||||
'date_from_0': '2017-07-01',
|
|
||||||
'date_from_1': '10:00:00',
|
|
||||||
'date_to_0': '2017-07-01',
|
|
||||||
'date_to_1': '12:00:00',
|
|
||||||
'location_0': 'Hamburg',
|
|
||||||
'presale_start_0': '2017-06-20',
|
|
||||||
'presale_start_1': '10:00:00',
|
|
||||||
'checkinlist_set-TOTAL_FORMS': '1',
|
|
||||||
'checkinlist_set-INITIAL_FORMS': '0',
|
|
||||||
'checkinlist_set-MIN_NUM_FORMS': '0',
|
|
||||||
'checkinlist_set-MAX_NUM_FORMS': '1000',
|
|
||||||
'checkinlist_set-0-name': 'Default',
|
|
||||||
'checkinlist_set-0-all_products': 'on',
|
|
||||||
'quotas-TOTAL_FORMS': '1',
|
|
||||||
'quotas-INITIAL_FORMS': '0',
|
|
||||||
'quotas-MIN_NUM_FORMS': '0',
|
|
||||||
'quotas-MAX_NUM_FORMS': '1000',
|
|
||||||
'quotas-0-name': 'Q1',
|
|
||||||
'quotas-0-size': '50',
|
|
||||||
'quotas-0-itemvars': str(self.ticket.pk),
|
|
||||||
'item-%d-price' % self.ticket.pk: '12'
|
|
||||||
})
|
|
||||||
assert doc.select(".alert-success")
|
|
||||||
with scopes_disabled():
|
|
||||||
se = self.event1.subevents.first()
|
|
||||||
assert str(se.name) == "SE2"
|
|
||||||
assert se.active
|
|
||||||
assert se.date_from.isoformat() == "2017-07-01T10:00:00+00:00"
|
|
||||||
assert se.date_to.isoformat() == "2017-07-01T12:00:00+00:00"
|
|
||||||
assert str(se.location) == "Hamburg"
|
|
||||||
assert se.presale_start.isoformat() == "2017-06-20T10:00:00+00:00"
|
|
||||||
assert not se.presale_end
|
|
||||||
assert se.quotas.count() == 1
|
|
||||||
q = se.quotas.last()
|
|
||||||
assert q.name == "Q1"
|
|
||||||
assert q.size == 50
|
|
||||||
assert list(q.items.all()) == [self.ticket]
|
|
||||||
sei = SubEventItem.objects.get(subevent=se, item=self.ticket)
|
|
||||||
assert sei.price == 12
|
|
||||||
assert se.checkinlist_set.count() == 1
|
|
||||||
|
|
||||||
def test_modify(self):
|
|
||||||
doc = self.get_doc('/control/event/ccc/30c3/subevents/%d/' % self.subevent1.pk)
|
|
||||||
assert doc.select("input[name=quotas-TOTAL_FORMS]")
|
|
||||||
doc = self.post_doc('/control/event/ccc/30c3/subevents/%d/' % self.subevent1.pk, {
|
|
||||||
'name_0': 'SE2',
|
|
||||||
'active': 'on',
|
|
||||||
'date_from_0': '2017-07-01',
|
|
||||||
'date_from_1': '10:00:00',
|
|
||||||
'date_to_0': '2017-07-01',
|
|
||||||
'date_to_1': '12:00:00',
|
|
||||||
'location_0': 'Hamburg',
|
|
||||||
'presale_start_0': '2017-06-20',
|
|
||||||
'presale_start_1': '10:00:00',
|
|
||||||
'quotas-TOTAL_FORMS': '1',
|
|
||||||
'quotas-INITIAL_FORMS': '0',
|
|
||||||
'quotas-MIN_NUM_FORMS': '0',
|
|
||||||
'quotas-MAX_NUM_FORMS': '1000',
|
|
||||||
'quotas-0-name': 'Q1',
|
|
||||||
'quotas-0-size': '50',
|
|
||||||
'quotas-0-itemvars': str(self.ticket.pk),
|
|
||||||
'checkinlist_set-TOTAL_FORMS': '1',
|
|
||||||
'checkinlist_set-INITIAL_FORMS': '0',
|
|
||||||
'checkinlist_set-MIN_NUM_FORMS': '0',
|
|
||||||
'checkinlist_set-MAX_NUM_FORMS': '1000',
|
|
||||||
'checkinlist_set-0-name': 'Default',
|
|
||||||
'checkinlist_set-0-all_products': 'on',
|
|
||||||
'item-%d-price' % self.ticket.pk: '12'
|
|
||||||
})
|
|
||||||
assert doc.select(".alert-success")
|
|
||||||
self.subevent1.refresh_from_db()
|
|
||||||
se = self.subevent1
|
|
||||||
assert str(se.name) == "SE2"
|
|
||||||
assert se.active
|
|
||||||
assert se.date_from.isoformat() == "2017-07-01T10:00:00+00:00"
|
|
||||||
assert se.date_to.isoformat() == "2017-07-01T12:00:00+00:00"
|
|
||||||
assert str(se.location) == "Hamburg"
|
|
||||||
assert se.presale_start.isoformat() == "2017-06-20T10:00:00+00:00"
|
|
||||||
assert not se.presale_end
|
|
||||||
with scopes_disabled():
|
|
||||||
assert se.quotas.count() == 1
|
|
||||||
q = se.quotas.last()
|
|
||||||
assert q.name == "Q1"
|
|
||||||
assert q.size == 50
|
|
||||||
assert list(q.items.all()) == [self.ticket]
|
|
||||||
sei = SubEventItem.objects.get(subevent=se, item=self.ticket)
|
|
||||||
assert sei.price == 12
|
|
||||||
assert se.checkinlist_set.count() == 1
|
|
||||||
|
|
||||||
def test_delete(self):
|
|
||||||
doc = self.get_doc('/control/event/ccc/30c3/subevents/%d/delete' % self.subevent1.pk)
|
|
||||||
assert doc.select("button")
|
|
||||||
doc = self.post_doc('/control/event/ccc/30c3/subevents/%d/delete' % self.subevent1.pk, {})
|
|
||||||
assert doc.select(".alert-success")
|
|
||||||
# deleting the second event
|
|
||||||
doc = self.post_doc('/control/event/ccc/30c3/subevents/%d/delete' % self.subevent2.pk, {})
|
|
||||||
assert doc.select(".alert-success")
|
|
||||||
with scopes_disabled():
|
|
||||||
assert not SubEvent.objects.filter(pk=self.subevent2.pk).exists()
|
|
||||||
assert not SubEvent.objects.filter(pk=self.subevent1.pk).exists()
|
|
||||||
|
|
||||||
def test_delete_with_orders(self):
|
|
||||||
with scopes_disabled():
|
|
||||||
o = Order.objects.create(
|
|
||||||
code='FOO', event=self.event1, email='dummy@dummy.test',
|
|
||||||
status=Order.STATUS_PENDING,
|
|
||||||
datetime=now(), expires=now() + datetime.timedelta(days=10),
|
|
||||||
total=14, locale='en'
|
|
||||||
)
|
|
||||||
OrderPosition.objects.create(
|
|
||||||
order=o,
|
|
||||||
item=self.ticket,
|
|
||||||
subevent=self.subevent1,
|
|
||||||
price=Decimal("14"),
|
|
||||||
)
|
|
||||||
doc = self.get_doc('/control/event/ccc/30c3/subevents/%d/delete' % self.subevent1.pk, follow=True)
|
|
||||||
assert doc.select(".alert-danger")
|
|
||||||
doc = self.post_doc('/control/event/ccc/30c3/subevents/%d/delete' % self.subevent1.pk, {}, follow=True)
|
|
||||||
assert doc.select(".alert-danger")
|
|
||||||
with scopes_disabled():
|
|
||||||
assert self.event1.subevents.filter(pk=self.subevent1.pk).exists()
|
|
||||||
|
|
||||||
def test_create_bulk(self):
|
|
||||||
with scopes_disabled():
|
|
||||||
self.event1.subevents.all().delete()
|
|
||||||
self.event1.settings.timezone = 'Europe/Berlin'
|
|
||||||
|
|
||||||
doc = self.get_doc('/control/event/ccc/30c3/subevents/bulk_add')
|
|
||||||
assert doc.select("input[name=rruleformset-TOTAL_FORMS]")
|
|
||||||
doc = self.post_doc('/control/event/ccc/30c3/subevents/bulk_add', {
|
|
||||||
'rruleformset-TOTAL_FORMS': '1',
|
|
||||||
'rruleformset-INITIAL_FORMS': '0',
|
|
||||||
'rruleformset-MIN_NUM_FORMS': '0',
|
|
||||||
'rruleformset-MAX_NUM_FORMS': '1000',
|
|
||||||
'rruleformset-0-interval': '1',
|
|
||||||
'rruleformset-0-freq': 'yearly',
|
|
||||||
'rruleformset-0-dtstart': '2018-04-03',
|
|
||||||
'rruleformset-0-yearly_same': 'on',
|
|
||||||
'rruleformset-0-yearly_bysetpos': '1',
|
|
||||||
'rruleformset-0-yearly_byweekday': 'MO',
|
|
||||||
'rruleformset-0-yearly_bymonth': '1',
|
|
||||||
'rruleformset-0-monthly_same': 'on',
|
|
||||||
'rruleformset-0-monthly_bysetpos': '1',
|
|
||||||
'rruleformset-0-monthly_byweekday': 'MO',
|
|
||||||
'rruleformset-0-end': 'count',
|
|
||||||
'rruleformset-0-count': '10',
|
|
||||||
'rruleformset-0-until': '2019-04-03',
|
|
||||||
'timeformset-TOTAL_FORMS': '1',
|
|
||||||
'timeformset-INITIAL_FORMS': '0',
|
|
||||||
'timeformset-MIN_NUM_FORMS': '1',
|
|
||||||
'timeformset-MAX_NUM_FORMS': '1000',
|
|
||||||
'timeformset-0-time_from': '13:29:31',
|
|
||||||
'timeformset-0-time_to': '15:29:31',
|
|
||||||
'name_0': 'Foo',
|
|
||||||
'active': 'on',
|
|
||||||
'location_0': 'Loc',
|
|
||||||
'time_admission': '',
|
|
||||||
'frontpage_text_0': '',
|
|
||||||
'rel_presale_start_0': 'unset',
|
|
||||||
'rel_presale_start_1': '',
|
|
||||||
'rel_presale_start_2': '1',
|
|
||||||
'rel_presale_start_3': 'date_from',
|
|
||||||
'rel_presale_start_4': '',
|
|
||||||
'rel_presale_end_1': '',
|
|
||||||
'rel_presale_end_0': 'relative',
|
|
||||||
'rel_presale_end_2': '1',
|
|
||||||
'rel_presale_end_3': 'date_from',
|
|
||||||
'rel_presale_end_4': '13:29:31',
|
|
||||||
'quotas-TOTAL_FORMS': '1',
|
|
||||||
'quotas-INITIAL_FORMS': '0',
|
|
||||||
'quotas-MIN_NUM_FORMS': '0',
|
|
||||||
'quotas-MAX_NUM_FORMS': '1000',
|
|
||||||
'quotas-0-id': '',
|
|
||||||
'quotas-0-name': 'Bar',
|
|
||||||
'quotas-0-size': '12',
|
|
||||||
'quotas-0-itemvars': str(self.ticket.pk),
|
|
||||||
'item-%d-price' % self.ticket.pk: '16',
|
|
||||||
'checkinlist_set-TOTAL_FORMS': '1',
|
|
||||||
'checkinlist_set-INITIAL_FORMS': '0',
|
|
||||||
'checkinlist_set-MIN_NUM_FORMS': '0',
|
|
||||||
'checkinlist_set-MAX_NUM_FORMS': '1000',
|
|
||||||
'checkinlist_set-0-id': '',
|
|
||||||
'checkinlist_set-0-name': 'Foo',
|
|
||||||
'checkinlist_set-0-limit_products': str(self.ticket.pk),
|
|
||||||
})
|
|
||||||
assert doc.select(".alert-success")
|
|
||||||
with scopes_disabled():
|
|
||||||
ses = list(self.event1.subevents.order_by('date_from'))
|
|
||||||
assert len(ses) == 10
|
|
||||||
|
|
||||||
assert str(ses[0].name) == "Foo"
|
|
||||||
assert ses[0].date_from.isoformat() == "2018-04-03T11:29:31+00:00"
|
|
||||||
assert ses[0].date_to.isoformat() == "2018-04-03T13:29:31+00:00"
|
|
||||||
assert not ses[0].presale_start
|
|
||||||
assert ses[0].presale_end.isoformat() == "2018-04-02T11:29:31+00:00"
|
|
||||||
with scopes_disabled():
|
|
||||||
assert ses[0].quotas.count() == 1
|
|
||||||
assert list(ses[0].quotas.first().items.all()) == [self.ticket]
|
|
||||||
assert SubEventItem.objects.get(subevent=ses[0], item=self.ticket).price == 16
|
|
||||||
assert ses[0].checkinlist_set.count() == 1
|
|
||||||
|
|
||||||
assert str(ses[1].name) == "Foo"
|
|
||||||
assert ses[1].date_from.isoformat() == "2019-04-03T11:29:31+00:00"
|
|
||||||
assert ses[1].date_to.isoformat() == "2019-04-03T13:29:31+00:00"
|
|
||||||
assert not ses[1].presale_start
|
|
||||||
assert ses[1].presale_end.isoformat() == "2019-04-02T11:29:31+00:00"
|
|
||||||
with scopes_disabled():
|
|
||||||
assert ses[1].quotas.count() == 1
|
|
||||||
assert list(ses[1].quotas.first().items.all()) == [self.ticket]
|
|
||||||
assert SubEventItem.objects.get(subevent=ses[0], item=self.ticket).price == 16
|
|
||||||
assert ses[1].checkinlist_set.count() == 1
|
|
||||||
|
|
||||||
assert ses[-1].date_from.isoformat() == "2027-04-03T11:29:31+00:00"
|
|
||||||
|
|
||||||
def test_create_bulk_daily_interval(self):
|
|
||||||
with scopes_disabled():
|
|
||||||
self.event1.subevents.all().delete()
|
|
||||||
self.event1.settings.timezone = 'Europe/Berlin'
|
|
||||||
|
|
||||||
doc = self.get_doc('/control/event/ccc/30c3/subevents/bulk_add')
|
|
||||||
assert doc.select("input[name=rruleformset-TOTAL_FORMS]")
|
|
||||||
doc = self.post_doc('/control/event/ccc/30c3/subevents/bulk_add', {
|
|
||||||
'rruleformset-TOTAL_FORMS': '1',
|
|
||||||
'rruleformset-INITIAL_FORMS': '0',
|
|
||||||
'rruleformset-MIN_NUM_FORMS': '0',
|
|
||||||
'rruleformset-MAX_NUM_FORMS': '1000',
|
|
||||||
'rruleformset-0-interval': '2',
|
|
||||||
'rruleformset-0-freq': 'daily',
|
|
||||||
'rruleformset-0-dtstart': '2018-04-03',
|
|
||||||
'rruleformset-0-yearly_same': 'on',
|
|
||||||
'rruleformset-0-yearly_bysetpos': '1',
|
|
||||||
'rruleformset-0-yearly_byweekday': 'MO',
|
|
||||||
'rruleformset-0-yearly_bymonth': '1',
|
|
||||||
'rruleformset-0-monthly_same': 'on',
|
|
||||||
'rruleformset-0-monthly_bysetpos': '1',
|
|
||||||
'rruleformset-0-monthly_byweekday': 'MO',
|
|
||||||
'rruleformset-0-end': 'until',
|
|
||||||
'rruleformset-0-count': '10',
|
|
||||||
'rruleformset-0-until': '2019-04-03',
|
|
||||||
'timeformset-TOTAL_FORMS': '1',
|
|
||||||
'timeformset-INITIAL_FORMS': '0',
|
|
||||||
'timeformset-MIN_NUM_FORMS': '1',
|
|
||||||
'timeformset-MAX_NUM_FORMS': '1000',
|
|
||||||
'timeformset-0-time_from': '13:29:31',
|
|
||||||
'timeformset-0-time_to': '15:29:31',
|
|
||||||
'name_0': 'Foo',
|
|
||||||
'active': 'on',
|
|
||||||
'frontpage_text_0': '',
|
|
||||||
'rel_presale_start_0': 'unset',
|
|
||||||
'rel_presale_start_1': '',
|
|
||||||
'rel_presale_start_2': '1',
|
|
||||||
'rel_presale_start_3': 'date_from',
|
|
||||||
'rel_presale_start_4': '',
|
|
||||||
'rel_presale_end_1': '',
|
|
||||||
'rel_presale_end_0': 'relative',
|
|
||||||
'rel_presale_end_2': '1',
|
|
||||||
'rel_presale_end_3': 'date_from',
|
|
||||||
'rel_presale_end_4': '13:29:31',
|
|
||||||
'quotas-TOTAL_FORMS': '1',
|
|
||||||
'quotas-INITIAL_FORMS': '0',
|
|
||||||
'quotas-MIN_NUM_FORMS': '1',
|
|
||||||
'quotas-MAX_NUM_FORMS': '1000',
|
|
||||||
'quotas-0-name': 'Q1',
|
|
||||||
'quotas-0-size': '50',
|
|
||||||
'quotas-0-itemvars': str(self.ticket.pk),
|
|
||||||
'checkinlist_set-TOTAL_FORMS': '0',
|
|
||||||
'checkinlist_set-INITIAL_FORMS': '0',
|
|
||||||
'checkinlist_set-MIN_NUM_FORMS': '0',
|
|
||||||
'checkinlist_set-MAX_NUM_FORMS': '1000',
|
|
||||||
})
|
|
||||||
assert doc.select(".alert-success")
|
|
||||||
with scopes_disabled():
|
|
||||||
ses = list(self.event1.subevents.order_by('date_from'))
|
|
||||||
assert len(ses) == 183
|
|
||||||
|
|
||||||
assert ses[0].date_from.isoformat() == "2018-04-03T11:29:31+00:00"
|
|
||||||
assert ses[110].date_from.isoformat() == "2018-11-09T12:29:31+00:00" # DST :)
|
|
||||||
assert ses[-1].date_from.isoformat() == "2019-04-02T11:29:31+00:00"
|
|
||||||
|
|
||||||
def test_create_bulk_daily_interval_multiple_times(self):
|
|
||||||
with scopes_disabled():
|
|
||||||
self.event1.subevents.all().delete()
|
|
||||||
self.event1.settings.timezone = 'Europe/Berlin'
|
|
||||||
|
|
||||||
doc = self.get_doc('/control/event/ccc/30c3/subevents/bulk_add')
|
|
||||||
assert doc.select("input[name=rruleformset-TOTAL_FORMS]")
|
|
||||||
doc = self.post_doc('/control/event/ccc/30c3/subevents/bulk_add', {
|
|
||||||
'rruleformset-TOTAL_FORMS': '1',
|
|
||||||
'rruleformset-INITIAL_FORMS': '0',
|
|
||||||
'rruleformset-MIN_NUM_FORMS': '0',
|
|
||||||
'rruleformset-MAX_NUM_FORMS': '1000',
|
|
||||||
'rruleformset-0-interval': '2',
|
|
||||||
'rruleformset-0-freq': 'daily',
|
|
||||||
'rruleformset-0-dtstart': '2018-04-03',
|
|
||||||
'rruleformset-0-yearly_same': 'on',
|
|
||||||
'rruleformset-0-yearly_bysetpos': '1',
|
|
||||||
'rruleformset-0-yearly_byweekday': 'MO',
|
|
||||||
'rruleformset-0-yearly_bymonth': '1',
|
|
||||||
'rruleformset-0-monthly_same': 'on',
|
|
||||||
'rruleformset-0-monthly_bysetpos': '1',
|
|
||||||
'rruleformset-0-monthly_byweekday': 'MO',
|
|
||||||
'rruleformset-0-end': 'until',
|
|
||||||
'rruleformset-0-count': '10',
|
|
||||||
'rruleformset-0-until': '2019-04-03',
|
|
||||||
'timeformset-TOTAL_FORMS': '2',
|
|
||||||
'timeformset-INITIAL_FORMS': '0',
|
|
||||||
'timeformset-MIN_NUM_FORMS': '1',
|
|
||||||
'timeformset-MAX_NUM_FORMS': '1000',
|
|
||||||
'timeformset-0-time_from': '13:29:31',
|
|
||||||
'timeformset-0-time_to': '15:29:31',
|
|
||||||
'timeformset-1-time_from': '15:29:31',
|
|
||||||
'timeformset-1-time_to': '17:29:31',
|
|
||||||
'name_0': 'Foo',
|
|
||||||
'active': 'on',
|
|
||||||
'frontpage_text_0': '',
|
|
||||||
'rel_presale_start_0': 'unset',
|
|
||||||
'rel_presale_start_1': '',
|
|
||||||
'rel_presale_start_2': '1',
|
|
||||||
'rel_presale_start_3': 'date_from',
|
|
||||||
'rel_presale_start_4': '',
|
|
||||||
'rel_presale_end_1': '',
|
|
||||||
'rel_presale_end_0': 'relative',
|
|
||||||
'rel_presale_end_2': '1',
|
|
||||||
'rel_presale_end_3': 'date_from',
|
|
||||||
'rel_presale_end_4': '13:29:31',
|
|
||||||
'quotas-TOTAL_FORMS': '1',
|
|
||||||
'quotas-INITIAL_FORMS': '0',
|
|
||||||
'quotas-MIN_NUM_FORMS': '0',
|
|
||||||
'quotas-MAX_NUM_FORMS': '1000',
|
|
||||||
'quotas-0-name': 'Q1',
|
|
||||||
'quotas-0-size': '50',
|
|
||||||
'quotas-0-itemvars': str(self.ticket.pk),
|
|
||||||
'checkinlist_set-TOTAL_FORMS': '0',
|
|
||||||
'checkinlist_set-INITIAL_FORMS': '0',
|
|
||||||
'checkinlist_set-MIN_NUM_FORMS': '0',
|
|
||||||
'checkinlist_set-MAX_NUM_FORMS': '1000',
|
|
||||||
})
|
|
||||||
assert doc.select(".alert-success")
|
|
||||||
with scopes_disabled():
|
|
||||||
ses = list(self.event1.subevents.order_by('date_from'))
|
|
||||||
assert len(ses) == 183 * 2
|
|
||||||
|
|
||||||
assert ses[0].date_from.isoformat() == "2018-04-03T11:29:31+00:00"
|
|
||||||
assert ses[1].date_from.isoformat() == "2018-04-03T13:29:31+00:00"
|
|
||||||
assert ses[220].date_from.isoformat() == "2018-11-09T12:29:31+00:00" # DST :)
|
|
||||||
assert ses[-1].date_from.isoformat() == "2019-04-02T13:29:31+00:00"
|
|
||||||
|
|
||||||
def test_create_bulk_exclude(self):
|
|
||||||
with scopes_disabled():
|
|
||||||
self.event1.subevents.all().delete()
|
|
||||||
self.event1.settings.timezone = 'Europe/Berlin'
|
|
||||||
|
|
||||||
doc = self.get_doc('/control/event/ccc/30c3/subevents/bulk_add')
|
|
||||||
assert doc.select("input[name=rruleformset-TOTAL_FORMS]")
|
|
||||||
doc = self.post_doc('/control/event/ccc/30c3/subevents/bulk_add', {
|
|
||||||
'rruleformset-TOTAL_FORMS': '2',
|
|
||||||
'rruleformset-INITIAL_FORMS': '0',
|
|
||||||
'rruleformset-MIN_NUM_FORMS': '0',
|
|
||||||
'rruleformset-MAX_NUM_FORMS': '1000',
|
|
||||||
'rruleformset-0-interval': '1',
|
|
||||||
'rruleformset-0-freq': 'daily',
|
|
||||||
'rruleformset-0-dtstart': '2018-04-03',
|
|
||||||
'rruleformset-0-yearly_same': 'on',
|
|
||||||
'rruleformset-0-yearly_bysetpos': '1',
|
|
||||||
'rruleformset-0-yearly_byweekday': 'MO',
|
|
||||||
'rruleformset-0-yearly_bymonth': '1',
|
|
||||||
'rruleformset-0-monthly_same': 'on',
|
|
||||||
'rruleformset-0-monthly_bysetpos': '1',
|
|
||||||
'rruleformset-0-monthly_byweekday': 'MO',
|
|
||||||
'rruleformset-0-end': 'until',
|
|
||||||
'rruleformset-0-count': '10',
|
|
||||||
'rruleformset-0-until': '2019-04-03',
|
|
||||||
'rruleformset-1-interval': '1',
|
|
||||||
'rruleformset-1-freq': 'weekly',
|
|
||||||
'rruleformset-1-dtstart': '2018-04-03',
|
|
||||||
'rruleformset-1-yearly_same': 'on',
|
|
||||||
'rruleformset-1-yearly_bysetpos': '1',
|
|
||||||
'rruleformset-1-yearly_byweekday': 'MO',
|
|
||||||
'rruleformset-1-yearly_bymonth': '1',
|
|
||||||
'rruleformset-1-monthly_same': 'on',
|
|
||||||
'rruleformset-1-monthly_bysetpos': '1',
|
|
||||||
'rruleformset-1-monthly_byweekday': 'MO',
|
|
||||||
'rruleformset-1-weekly_byweekday': 'MO',
|
|
||||||
'rruleformset-1-end': 'until',
|
|
||||||
'rruleformset-1-count': '10',
|
|
||||||
'rruleformset-1-until': '2019-04-03',
|
|
||||||
'rruleformset-1-exclude': 'on',
|
|
||||||
'timeformset-TOTAL_FORMS': '1',
|
|
||||||
'timeformset-INITIAL_FORMS': '0',
|
|
||||||
'timeformset-MIN_NUM_FORMS': '1',
|
|
||||||
'timeformset-MAX_NUM_FORMS': '1000',
|
|
||||||
'timeformset-0-time_from': '13:29:31',
|
|
||||||
'timeformset-0-time_to': '15:29:31',
|
|
||||||
'name_0': 'Foo',
|
|
||||||
'active': 'on',
|
|
||||||
'frontpage_text_0': '',
|
|
||||||
'rel_presale_start_0': 'unset',
|
|
||||||
'rel_presale_start_1': '',
|
|
||||||
'rel_presale_start_2': '1',
|
|
||||||
'rel_presale_start_3': 'date_from',
|
|
||||||
'rel_presale_start_4': '',
|
|
||||||
'rel_presale_end_1': '',
|
|
||||||
'rel_presale_end_0': 'relative',
|
|
||||||
'rel_presale_end_2': '1',
|
|
||||||
'rel_presale_end_3': 'date_from',
|
|
||||||
'rel_presale_end_4': '13:29:31',
|
|
||||||
'quotas-TOTAL_FORMS': '1',
|
|
||||||
'quotas-INITIAL_FORMS': '0',
|
|
||||||
'quotas-MIN_NUM_FORMS': '0',
|
|
||||||
'quotas-MAX_NUM_FORMS': '1000',
|
|
||||||
'quotas-0-name': 'Q1',
|
|
||||||
'quotas-0-size': '50',
|
|
||||||
'quotas-0-itemvars': str(self.ticket.pk),
|
|
||||||
'checkinlist_set-TOTAL_FORMS': '0',
|
|
||||||
'checkinlist_set-INITIAL_FORMS': '0',
|
|
||||||
'checkinlist_set-MIN_NUM_FORMS': '0',
|
|
||||||
'checkinlist_set-MAX_NUM_FORMS': '1000',
|
|
||||||
})
|
|
||||||
assert doc.select(".alert-success")
|
|
||||||
with scopes_disabled():
|
|
||||||
ses = list(self.event1.subevents.order_by('date_from'))
|
|
||||||
assert len(ses) == 314
|
|
||||||
|
|
||||||
assert ses[0].date_from.isoformat() == "2018-04-03T11:29:31+00:00"
|
|
||||||
assert ses[5].date_from.isoformat() == "2018-04-08T11:29:31+00:00"
|
|
||||||
assert ses[6].date_from.isoformat() == "2018-04-10T11:29:31+00:00"
|
|
||||||
|
|
||||||
def test_create_bulk_monthly_interval(self):
|
|
||||||
with scopes_disabled():
|
|
||||||
self.event1.subevents.all().delete()
|
|
||||||
self.event1.settings.timezone = 'Europe/Berlin'
|
|
||||||
|
|
||||||
doc = self.post_doc('/control/event/ccc/30c3/subevents/bulk_add', {
|
|
||||||
'rruleformset-TOTAL_FORMS': '1',
|
|
||||||
'rruleformset-INITIAL_FORMS': '0',
|
|
||||||
'rruleformset-MIN_NUM_FORMS': '0',
|
|
||||||
'rruleformset-MAX_NUM_FORMS': '1000',
|
|
||||||
'rruleformset-0-interval': '1',
|
|
||||||
'rruleformset-0-freq': 'monthly',
|
|
||||||
'rruleformset-0-dtstart': '2018-04-03',
|
|
||||||
'rruleformset-0-yearly_same': 'on',
|
|
||||||
'rruleformset-0-yearly_bysetpos': '1',
|
|
||||||
'rruleformset-0-yearly_byweekday': 'MO',
|
|
||||||
'rruleformset-0-yearly_bymonth': '1',
|
|
||||||
'rruleformset-0-monthly_same': 'off',
|
|
||||||
'rruleformset-0-monthly_bysetpos': '-1',
|
|
||||||
'rruleformset-0-monthly_byweekday': 'MO,TU,WE,TH,FR',
|
|
||||||
'rruleformset-0-weekly_byweekday': 'TH',
|
|
||||||
'rruleformset-0-end': 'until',
|
|
||||||
'rruleformset-0-count': '10',
|
|
||||||
'rruleformset-0-until': '2019-04-03',
|
|
||||||
'timeformset-TOTAL_FORMS': '1',
|
|
||||||
'timeformset-INITIAL_FORMS': '0',
|
|
||||||
'timeformset-MIN_NUM_FORMS': '1',
|
|
||||||
'timeformset-MAX_NUM_FORMS': '1000',
|
|
||||||
'timeformset-0-time_from': '13:29:31',
|
|
||||||
'timeformset-0-time_to': '15:29:31',
|
|
||||||
'name_0': 'Foo',
|
|
||||||
'active': 'on',
|
|
||||||
'frontpage_text_0': '',
|
|
||||||
'rel_presale_start_0': 'unset',
|
|
||||||
'rel_presale_start_1': '',
|
|
||||||
'rel_presale_start_2': '1',
|
|
||||||
'rel_presale_start_3': 'date_from',
|
|
||||||
'rel_presale_start_4': '',
|
|
||||||
'rel_presale_end_0': 'unset',
|
|
||||||
'rel_presale_end_1': '',
|
|
||||||
'rel_presale_end_2': '1',
|
|
||||||
'rel_presale_end_3': 'date_from',
|
|
||||||
'rel_presale_end_4': '13:29:31',
|
|
||||||
'quotas-TOTAL_FORMS': '1',
|
|
||||||
'quotas-INITIAL_FORMS': '0',
|
|
||||||
'quotas-MIN_NUM_FORMS': '0',
|
|
||||||
'quotas-MAX_NUM_FORMS': '1000',
|
|
||||||
'quotas-0-name': 'Q1',
|
|
||||||
'quotas-0-size': '50',
|
|
||||||
'quotas-0-itemvars': str(self.ticket.pk),
|
|
||||||
'checkinlist_set-TOTAL_FORMS': '0',
|
|
||||||
'checkinlist_set-INITIAL_FORMS': '0',
|
|
||||||
'checkinlist_set-MIN_NUM_FORMS': '0',
|
|
||||||
'checkinlist_set-MAX_NUM_FORMS': '1000',
|
|
||||||
})
|
|
||||||
assert doc.select(".alert-success")
|
|
||||||
with scopes_disabled():
|
|
||||||
ses = list(self.event1.subevents.order_by('date_from'))
|
|
||||||
assert len(ses) == 12
|
|
||||||
|
|
||||||
assert ses[0].date_from.isoformat() == "2018-04-30T11:29:31+00:00"
|
|
||||||
assert ses[1].date_from.isoformat() == "2018-05-31T11:29:31+00:00"
|
|
||||||
assert ses[-1].date_from.isoformat() == "2019-03-29T12:29:31+00:00"
|
|
||||||
|
|
||||||
def test_create_bulk_weekly_interval(self):
|
|
||||||
with scopes_disabled():
|
|
||||||
self.event1.subevents.all().delete()
|
|
||||||
self.event1.settings.timezone = 'Europe/Berlin'
|
|
||||||
|
|
||||||
doc = self.post_doc('/control/event/ccc/30c3/subevents/bulk_add', {
|
|
||||||
'rruleformset-TOTAL_FORMS': '1',
|
|
||||||
'rruleformset-INITIAL_FORMS': '0',
|
|
||||||
'rruleformset-MIN_NUM_FORMS': '0',
|
|
||||||
'rruleformset-MAX_NUM_FORMS': '1000',
|
|
||||||
'rruleformset-0-interval': '1',
|
|
||||||
'rruleformset-0-freq': 'weekly',
|
|
||||||
'rruleformset-0-dtstart': '2018-04-03',
|
|
||||||
'rruleformset-0-yearly_same': 'on',
|
|
||||||
'rruleformset-0-yearly_bysetpos': '1',
|
|
||||||
'rruleformset-0-yearly_byweekday': 'MO',
|
|
||||||
'rruleformset-0-yearly_bymonth': '1',
|
|
||||||
'rruleformset-0-monthly_same': 'on',
|
|
||||||
'rruleformset-0-monthly_bysetpos': '-1',
|
|
||||||
'rruleformset-0-monthly_byweekday': 'MO,TU,WE,TH,FR',
|
|
||||||
'rruleformset-0-weekly_byweekday': 'TH',
|
|
||||||
'rruleformset-0-end': 'until',
|
|
||||||
'rruleformset-0-count': '10',
|
|
||||||
'rruleformset-0-until': '2019-04-03',
|
|
||||||
'timeformset-TOTAL_FORMS': '1',
|
|
||||||
'timeformset-INITIAL_FORMS': '0',
|
|
||||||
'timeformset-MIN_NUM_FORMS': '1',
|
|
||||||
'timeformset-MAX_NUM_FORMS': '1000',
|
|
||||||
'timeformset-0-time_from': '13:29:31',
|
|
||||||
'timeformset-0-time_to': '15:29:31',
|
|
||||||
'name_0': 'Foo',
|
|
||||||
'active': 'on',
|
|
||||||
'frontpage_text_0': '',
|
|
||||||
'rel_presale_start_0': 'unset',
|
|
||||||
'rel_presale_start_1': '',
|
|
||||||
'rel_presale_start_2': '1',
|
|
||||||
'rel_presale_start_3': 'date_from',
|
|
||||||
'rel_presale_start_4': '',
|
|
||||||
'rel_presale_end_0': 'unset',
|
|
||||||
'rel_presale_end_1': '',
|
|
||||||
'rel_presale_end_2': '1',
|
|
||||||
'rel_presale_end_3': 'date_from',
|
|
||||||
'rel_presale_end_4': '13:29:31',
|
|
||||||
'quotas-TOTAL_FORMS': '1',
|
|
||||||
'quotas-INITIAL_FORMS': '0',
|
|
||||||
'quotas-MIN_NUM_FORMS': '0',
|
|
||||||
'quotas-MAX_NUM_FORMS': '1000',
|
|
||||||
'quotas-0-name': 'Q1',
|
|
||||||
'quotas-0-size': '50',
|
|
||||||
'quotas-0-itemvars': str(self.ticket.pk),
|
|
||||||
'checkinlist_set-TOTAL_FORMS': '0',
|
|
||||||
'checkinlist_set-INITIAL_FORMS': '0',
|
|
||||||
'checkinlist_set-MIN_NUM_FORMS': '0',
|
|
||||||
'checkinlist_set-MAX_NUM_FORMS': '1000',
|
|
||||||
})
|
|
||||||
assert doc.select(".alert-success")
|
|
||||||
with scopes_disabled():
|
|
||||||
ses = list(self.event1.subevents.order_by('date_from'))
|
|
||||||
assert len(ses) == 52
|
|
||||||
|
|
||||||
assert ses[0].date_from.isoformat() == "2018-04-05T11:29:31+00:00"
|
|
||||||
assert ses[1].date_from.isoformat() == "2018-04-12T11:29:31+00:00"
|
|
||||||
assert ses[-1].date_from.isoformat() == "2019-03-28T12:29:31+00:00"
|
|
||||||
|
|
||||||
def test_delete_bulk(self):
|
|
||||||
self.subevent2.active = True
|
|
||||||
self.subevent2.save()
|
|
||||||
with scopes_disabled():
|
|
||||||
o = Order.objects.create(
|
|
||||||
code='FOO', event=self.event1, email='dummy@dummy.test',
|
|
||||||
status=Order.STATUS_PENDING,
|
|
||||||
datetime=now(), expires=now() + datetime.timedelta(days=10),
|
|
||||||
total=14, locale='en'
|
|
||||||
)
|
|
||||||
OrderPosition.objects.create(
|
|
||||||
order=o,
|
|
||||||
item=self.ticket,
|
|
||||||
subevent=self.subevent1,
|
|
||||||
price=Decimal("14"),
|
|
||||||
)
|
|
||||||
doc = self.post_doc('/control/event/ccc/30c3/subevents/bulk_action', {
|
|
||||||
'subevent': [str(self.subevent1.pk), str(self.subevent2.pk)],
|
|
||||||
'action': 'delete_confirm'
|
|
||||||
}, follow=True)
|
|
||||||
assert doc.select(".alert-success")
|
|
||||||
with scopes_disabled():
|
|
||||||
assert not self.event1.subevents.filter(pk=self.subevent2.pk).exists()
|
|
||||||
assert self.event1.subevents.get(pk=self.subevent1.pk).active is False
|
|
||||||
|
|
||||||
def test_disable_bulk(self):
|
|
||||||
self.subevent2.active = True
|
|
||||||
self.subevent2.save()
|
|
||||||
doc = self.post_doc('/control/event/ccc/30c3/subevents/bulk_action', {
|
|
||||||
'subevent': str(self.subevent2.pk),
|
|
||||||
'action': 'disable'
|
|
||||||
}, follow=True)
|
|
||||||
assert doc.select(".alert-success")
|
|
||||||
with scopes_disabled():
|
|
||||||
assert self.event1.subevents.get(pk=self.subevent2.pk).active is False
|
|
||||||
|
|
||||||
def test_enable_bulk(self):
|
|
||||||
self.subevent2.active = False
|
|
||||||
self.subevent2.save()
|
|
||||||
doc = self.post_doc('/control/event/ccc/30c3/subevents/bulk_action', {
|
|
||||||
'subevent': str(self.subevent2.pk),
|
|
||||||
'action': 'enable'
|
|
||||||
}, follow=True)
|
|
||||||
assert doc.select(".alert-success")
|
|
||||||
with scopes_disabled():
|
|
||||||
assert self.event1.subevents.get(pk=self.subevent2.pk).active is True
|
|
||||||
|
|
||||||
|
|
||||||
class EventDeletionTest(SoupTest):
|
class EventDeletionTest(SoupTest):
|
||||||
@scopes_disabled()
|
@scopes_disabled()
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
|||||||
1045
src/tests/control/test_subevents.py
Normal file
1045
src/tests/control/test_subevents.py
Normal file
File diff suppressed because it is too large
Load Diff
@@ -93,9 +93,9 @@ class VoucherFormTest(SoupTestMixin, TransactionTestCase):
|
|||||||
doc = self.client.get('/control/event/%s/%s/vouchers/?download=yes' % (self.orga.slug, self.event.slug))
|
doc = self.client.get('/control/event/%s/%s/vouchers/?download=yes' % (self.orga.slug, self.event.slug))
|
||||||
assert doc.content.decode().strip() == '"Voucher code","Valid until","Product","Reserve quota",' \
|
assert doc.content.decode().strip() == '"Voucher code","Valid until","Product","Reserve quota",' \
|
||||||
'"Bypass quota","Price effect","Value","Tag","Redeemed",' \
|
'"Bypass quota","Price effect","Value","Tag","Redeemed",' \
|
||||||
'"Maximum usages","Seat"' \
|
'"Maximum usages","Seat","Comment"' \
|
||||||
'\r\n"ABCDEFG","","Early-bird ticket","No","No","No effect","","","0",' \
|
'\r\n"ABCDEFG","","Early-bird ticket","No","No","No effect","","","0",' \
|
||||||
'"1",""'
|
'"1","",""'
|
||||||
|
|
||||||
def test_filter_status_valid(self):
|
def test_filter_status_valid(self):
|
||||||
with scopes_disabled():
|
with scopes_disabled():
|
||||||
|
|||||||
Reference in New Issue
Block a user