mirror of
https://github.com/pretix/pretix.git
synced 2026-02-23 09:32:27 +00:00
Compare commits
1 Commits
pluggable-
...
fix-progra
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8164f469d3 |
@@ -197,11 +197,10 @@ Permissions & security profiles
|
|||||||
|
|
||||||
Device authentication is currently hardcoded to grant the following permissions:
|
Device authentication is currently hardcoded to grant the following permissions:
|
||||||
|
|
||||||
* Read event meta data and products etc.
|
* View event meta data and products etc.
|
||||||
* Read and write orders
|
* View orders
|
||||||
* Read and write gift cards
|
* Change orders
|
||||||
* Read and write reusable media
|
* Manage gift cards
|
||||||
* Read vouchers
|
|
||||||
|
|
||||||
Devices cannot change events or products and cannot access vouchers.
|
Devices cannot change events or products and cannot access vouchers.
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ software_brand string Device software
|
|||||||
software_version string Device software version (read-only)
|
software_version string Device software version (read-only)
|
||||||
created datetime Creation time
|
created datetime Creation time
|
||||||
initialized datetime Time of initialization (or ``null``)
|
initialized datetime Time of initialization (or ``null``)
|
||||||
initialization_token string Token for initialization (field invisible without write permission)
|
initialization_token string Token for initialization
|
||||||
revoked boolean Whether this device no longer has access
|
revoked boolean Whether this device no longer has access
|
||||||
security_profile string The name of a supported security profile restricting API access
|
security_profile string The name of a supported security profile restricting API access
|
||||||
===================================== ========================== =======================================================
|
===================================== ========================== =======================================================
|
||||||
|
|||||||
@@ -65,6 +65,8 @@ Endpoints
|
|||||||
|
|
||||||
Returns a list of all events within a given organizer the authenticated user/token has access to.
|
Returns a list of all events within a given organizer the authenticated user/token has access to.
|
||||||
|
|
||||||
|
Permission required: "Can change event settings"
|
||||||
|
|
||||||
**Example request**:
|
**Example request**:
|
||||||
|
|
||||||
.. sourcecode:: http
|
.. sourcecode:: http
|
||||||
@@ -159,6 +161,8 @@ Endpoints
|
|||||||
|
|
||||||
Returns information on one event, identified by its slug.
|
Returns information on one event, identified by its slug.
|
||||||
|
|
||||||
|
Permission required: "Can change event settings"
|
||||||
|
|
||||||
**Example request**:
|
**Example request**:
|
||||||
|
|
||||||
.. sourcecode:: http
|
.. sourcecode:: http
|
||||||
@@ -230,6 +234,8 @@ Endpoints
|
|||||||
Please note that events cannot be created as 'live' using this endpoint. Quotas and payment must be added to the
|
Please note that events cannot be created as 'live' using this endpoint. Quotas and payment must be added to the
|
||||||
event before sales can go live.
|
event before sales can go live.
|
||||||
|
|
||||||
|
Permission required: "Can create events"
|
||||||
|
|
||||||
**Example request**:
|
**Example request**:
|
||||||
|
|
||||||
.. sourcecode:: http
|
.. sourcecode:: http
|
||||||
@@ -332,6 +338,8 @@ Endpoints
|
|||||||
Please note that you can only copy from events under the same organizer this way. Use the ``clone_from`` parameter
|
Please note that you can only copy from events under the same organizer this way. Use the ``clone_from`` parameter
|
||||||
when creating a new event for this instead.
|
when creating a new event for this instead.
|
||||||
|
|
||||||
|
Permission required: "Can create events"
|
||||||
|
|
||||||
**Example request**:
|
**Example request**:
|
||||||
|
|
||||||
.. sourcecode:: http
|
.. sourcecode:: http
|
||||||
@@ -425,6 +433,8 @@ Endpoints
|
|||||||
|
|
||||||
Updates an event
|
Updates an event
|
||||||
|
|
||||||
|
Permission required: "Can change event settings"
|
||||||
|
|
||||||
**Example request**:
|
**Example request**:
|
||||||
|
|
||||||
.. sourcecode:: http
|
.. sourcecode:: http
|
||||||
@@ -500,6 +510,8 @@ Endpoints
|
|||||||
|
|
||||||
Delete an event. Note that events with orders cannot be deleted to ensure data integrity.
|
Delete an event. Note that events with orders cannot be deleted to ensure data integrity.
|
||||||
|
|
||||||
|
Permission required: "Can change event settings"
|
||||||
|
|
||||||
**Example request**:
|
**Example request**:
|
||||||
|
|
||||||
.. sourcecode:: http
|
.. sourcecode:: http
|
||||||
@@ -549,6 +561,8 @@ organizer level.
|
|||||||
|
|
||||||
Get current values of event settings.
|
Get current values of event settings.
|
||||||
|
|
||||||
|
Permission required: "Can change event settings" (Exception: with device auth, *some* settings can always be *read*.)
|
||||||
|
|
||||||
**Example request**:
|
**Example request**:
|
||||||
|
|
||||||
.. sourcecode:: http
|
.. sourcecode:: http
|
||||||
@@ -601,8 +615,6 @@ organizer level.
|
|||||||
|
|
||||||
Updates event settings. Note that ``PUT`` is not allowed here, only ``PATCH``.
|
Updates event settings. Note that ``PUT`` is not allowed here, only ``PATCH``.
|
||||||
|
|
||||||
Permission "Can change event settings" is always required. Some keys require additional permissions.
|
|
||||||
|
|
||||||
.. warning::
|
.. warning::
|
||||||
|
|
||||||
Settings can be stored at different levels in pretix. If a value is not set on event level, a default setting
|
Settings can be stored at different levels in pretix. If a value is not set on event level, a default setting
|
||||||
|
|||||||
@@ -110,6 +110,8 @@ Endpoints
|
|||||||
|
|
||||||
Updates an organizer. Currently only the ``plugins`` field may be updated.
|
Updates an organizer. Currently only the ``plugins`` field may be updated.
|
||||||
|
|
||||||
|
Permission required: "Can change organizer settings"
|
||||||
|
|
||||||
**Example request**:
|
**Example request**:
|
||||||
|
|
||||||
.. sourcecode:: http
|
.. sourcecode:: http
|
||||||
@@ -170,6 +172,8 @@ information about the properties.
|
|||||||
|
|
||||||
Get current values of organizer settings.
|
Get current values of organizer settings.
|
||||||
|
|
||||||
|
Permission required: "Can change organizer settings"
|
||||||
|
|
||||||
**Example request**:
|
**Example request**:
|
||||||
|
|
||||||
.. sourcecode:: http
|
.. sourcecode:: http
|
||||||
|
|||||||
@@ -154,7 +154,7 @@ Endpoints
|
|||||||
.. http:post:: /api/v1/organizers/(organizer)/reusablemedia/lookup/
|
.. http:post:: /api/v1/organizers/(organizer)/reusablemedia/lookup/
|
||||||
|
|
||||||
Look up a new reusable medium by its identifier. In some cases, this might lead to the automatic creation of a new
|
Look up a new reusable medium by its identifier. In some cases, this might lead to the automatic creation of a new
|
||||||
medium behind the scenes, therefore this endpoint requires write permissions.
|
medium behind the scenes.
|
||||||
|
|
||||||
This endpoint, and this endpoint only, might return media from a different organizer if there is a cross-acceptance
|
This endpoint, and this endpoint only, might return media from a different organizer if there is a cross-acceptance
|
||||||
agreement. In this case, only linked gift cards will be returned, no order position or customer records,
|
agreement. In this case, only linked gift cards will be returned, no order position or customer records,
|
||||||
|
|||||||
@@ -154,6 +154,8 @@ Endpoints
|
|||||||
|
|
||||||
Creates a new subevent.
|
Creates a new subevent.
|
||||||
|
|
||||||
|
Permission required: "Can create events"
|
||||||
|
|
||||||
**Example request**:
|
**Example request**:
|
||||||
|
|
||||||
.. sourcecode:: http
|
.. sourcecode:: http
|
||||||
@@ -298,6 +300,8 @@ Endpoints
|
|||||||
provide all fields of the resource, other fields will be reset to default. With ``PATCH``, you only need to provide
|
provide all fields of the resource, other fields will be reset to default. With ``PATCH``, you only need to provide
|
||||||
the fields that you want to change.
|
the fields that you want to change.
|
||||||
|
|
||||||
|
Permission required: "Can change event settings"
|
||||||
|
|
||||||
**Example request**:
|
**Example request**:
|
||||||
|
|
||||||
.. sourcecode:: http
|
.. sourcecode:: http
|
||||||
@@ -369,6 +373,8 @@ Endpoints
|
|||||||
|
|
||||||
Delete a sub-event. Note that events with orders cannot be deleted to ensure data integrity.
|
Delete a sub-event. Note that events with orders cannot be deleted to ensure data integrity.
|
||||||
|
|
||||||
|
Permission required: "Can change event settings"
|
||||||
|
|
||||||
**Example request**:
|
**Example request**:
|
||||||
|
|
||||||
.. sourcecode:: http
|
.. sourcecode:: http
|
||||||
|
|||||||
@@ -24,57 +24,21 @@ all_events boolean Whether this te
|
|||||||
limit_events list List of event slugs this team has access to
|
limit_events list List of event slugs this team has access to
|
||||||
require_2fa boolean Whether members of this team are required to use
|
require_2fa boolean Whether members of this team are required to use
|
||||||
two-factor authentication
|
two-factor authentication
|
||||||
all_event_permissions bool Whether members of this team are granted all event-level
|
can_create_events boolean
|
||||||
permissions, including future additions
|
can_change_teams boolean
|
||||||
limit_event_permissions list of strings The event-level permissions team members are granted
|
can_change_organizer_settings boolean
|
||||||
all_organizer_permissions bool Whether members of this team are granted all organizer-level
|
can_manage_customers boolean
|
||||||
permissions, including future additions
|
can_manage_reusable_media boolean
|
||||||
all_organizer_permissions list of strings The organizer-level permissions team members are granted
|
can_manage_gift_cards boolean
|
||||||
can_create_events boolean **DEPRECATED**. Legacy interface, use ``limit_organizer_permissions``.
|
can_change_event_settings boolean
|
||||||
can_change_teams boolean **DEPRECATED**. Legacy interface, use ``limit_organizer_permissions``.
|
can_change_items boolean
|
||||||
can_change_organizer_settings boolean **DEPRECATED**. Legacy interface, use ``limit_organizer_permissions``.
|
can_view_orders boolean
|
||||||
can_manage_customers boolean **DEPRECATED**. Legacy interface, use ``limit_organizer_permissions``.
|
can_change_orders boolean
|
||||||
can_manage_reusable_media boolean **DEPRECATED**. Legacy interface, use ``limit_organizer_permissions``.
|
can_view_vouchers boolean
|
||||||
can_manage_gift_cards boolean **DEPRECATED**. Legacy interface, use ``limit_organizer_permissions``.
|
can_change_vouchers boolean
|
||||||
can_change_event_settings boolean **DEPRECATED**. Legacy interface, use ``limit_event_permissions``.
|
can_checkin_orders boolean
|
||||||
can_change_items boolean **DEPRECATED**. Legacy interface, use ``limit_event_permissions``.
|
|
||||||
can_view_orders boolean **DEPRECATED**. Legacy interface, use ``limit_event_permissions``.
|
|
||||||
can_change_orders boolean **DEPRECATED**. Legacy interface, use ``limit_event_permissions``.
|
|
||||||
can_view_vouchers boolean **DEPRECATED**. Legacy interface, use ``limit_event_permissions``.
|
|
||||||
can_change_vouchers boolean **DEPRECATED**. Legacy interface, use ``limit_event_permissions``.
|
|
||||||
can_checkin_orders boolean **DEPRECATED**. Legacy interface, use ``limit_event_permissions``.
|
|
||||||
===================================== ========================== =======================================================
|
===================================== ========================== =======================================================
|
||||||
|
|
||||||
Possible values for ``limit_organizer_permissions`` defined in the core pretix system (plugins might add more)::
|
|
||||||
|
|
||||||
organizer.events:create
|
|
||||||
organizer.settings.general:write
|
|
||||||
organizer.teams:write
|
|
||||||
organizer.seatingplans:write
|
|
||||||
organizer.giftcards:read
|
|
||||||
organizer.giftcards:write
|
|
||||||
organizer.customers:read
|
|
||||||
organizer.customers:write
|
|
||||||
organizer.reusablemedia:read
|
|
||||||
organizer.reusablemedia:write
|
|
||||||
organizer.devices:read
|
|
||||||
organizer.devices:write
|
|
||||||
|
|
||||||
Possible values for ``limit_event_permissions`` defined in the core pretix system (plugins might add more)::
|
|
||||||
|
|
||||||
event.settings.general:write
|
|
||||||
event.settings.payment:write
|
|
||||||
event.settings.tax:write
|
|
||||||
event.settings.invoicing:write
|
|
||||||
event.subevents:write
|
|
||||||
event.items:write
|
|
||||||
event.orders:read
|
|
||||||
event.orders:write
|
|
||||||
event.orders:checkin
|
|
||||||
event.vouchers:read
|
|
||||||
event.vouchers:write
|
|
||||||
event:cancel
|
|
||||||
|
|
||||||
Team member resource
|
Team member resource
|
||||||
--------------------
|
--------------------
|
||||||
|
|
||||||
@@ -157,10 +121,6 @@ Team endpoints
|
|||||||
"all_events": true,
|
"all_events": true,
|
||||||
"limit_events": [],
|
"limit_events": [],
|
||||||
"require_2fa": true,
|
"require_2fa": true,
|
||||||
"all_event_permissions": true,
|
|
||||||
"limit_event_permissions": [],
|
|
||||||
"all_organizer_permissions": true,
|
|
||||||
"limit_organizer_permissions": [],
|
|
||||||
"can_create_events": true,
|
"can_create_events": true,
|
||||||
...
|
...
|
||||||
}
|
}
|
||||||
@@ -199,10 +159,6 @@ Team endpoints
|
|||||||
"all_events": true,
|
"all_events": true,
|
||||||
"limit_events": [],
|
"limit_events": [],
|
||||||
"require_2fa": true,
|
"require_2fa": true,
|
||||||
"all_event_permissions": true,
|
|
||||||
"limit_event_permissions": [],
|
|
||||||
"all_organizer_permissions": true,
|
|
||||||
"limit_organizer_permissions": [],
|
|
||||||
"can_create_events": true,
|
"can_create_events": true,
|
||||||
...
|
...
|
||||||
}
|
}
|
||||||
@@ -231,10 +187,7 @@ Team endpoints
|
|||||||
"all_events": true,
|
"all_events": true,
|
||||||
"limit_events": [],
|
"limit_events": [],
|
||||||
"require_2fa": true,
|
"require_2fa": true,
|
||||||
"all_event_permissions": true,
|
"can_create_events": true,
|
||||||
"limit_event_permissions": [],
|
|
||||||
"all_organizer_permissions": true,
|
|
||||||
"limit_organizer_permissions": [],
|
|
||||||
...
|
...
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -252,10 +205,6 @@ Team endpoints
|
|||||||
"all_events": true,
|
"all_events": true,
|
||||||
"limit_events": [],
|
"limit_events": [],
|
||||||
"require_2fa": true,
|
"require_2fa": true,
|
||||||
"all_event_permissions": true,
|
|
||||||
"limit_event_permissions": [],
|
|
||||||
"all_organizer_permissions": true,
|
|
||||||
"limit_organizer_permissions": [],
|
|
||||||
"can_create_events": true,
|
"can_create_events": true,
|
||||||
...
|
...
|
||||||
}
|
}
|
||||||
@@ -283,8 +232,7 @@ Team endpoints
|
|||||||
Content-Length: 94
|
Content-Length: 94
|
||||||
|
|
||||||
{
|
{
|
||||||
"all_organizer_permissions": false,
|
"can_create_events": true
|
||||||
"limit_organizer_permissions": ["organizer.events:create"]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
**Example response**:
|
**Example response**:
|
||||||
@@ -301,10 +249,6 @@ Team endpoints
|
|||||||
"all_events": true,
|
"all_events": true,
|
||||||
"limit_events": [],
|
"limit_events": [],
|
||||||
"require_2fa": true,
|
"require_2fa": true,
|
||||||
"all_event_permissions": true,
|
|
||||||
"limit_event_permissions": [],
|
|
||||||
"all_organizer_permissions": false,
|
|
||||||
"limit_organizer_permissions": ["organizer.events:create"],
|
|
||||||
"can_create_events": true,
|
"can_create_events": true,
|
||||||
...
|
...
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,12 +55,12 @@ your views:
|
|||||||
)
|
)
|
||||||
|
|
||||||
class AdminView(EventPermissionRequiredMixin, View):
|
class AdminView(EventPermissionRequiredMixin, View):
|
||||||
permission = 'event.orders:read'
|
permission = 'can_view_orders'
|
||||||
|
|
||||||
...
|
...
|
||||||
|
|
||||||
|
|
||||||
@event_permission_required('event.orders:read')
|
@event_permission_required('can_view_orders')
|
||||||
def admin_view(request, organizer, event):
|
def admin_view(request, organizer, event):
|
||||||
...
|
...
|
||||||
|
|
||||||
@@ -78,7 +78,7 @@ event-related views, there is also a signal that allows you to add the view to t
|
|||||||
@receiver(nav_event, dispatch_uid='friends_tickets_nav')
|
@receiver(nav_event, dispatch_uid='friends_tickets_nav')
|
||||||
def navbar_info(sender, request, **kwargs):
|
def navbar_info(sender, request, **kwargs):
|
||||||
url = resolve(request.path_info)
|
url = resolve(request.path_info)
|
||||||
if not request.user.has_event_permission(request.organizer, request.event, 'event.vouchers:read'):
|
if not request.user.has_event_permission(request.organizer, request.event, 'can_change_vouchers'):
|
||||||
return []
|
return []
|
||||||
return [{
|
return [{
|
||||||
'label': _('My plugin view'),
|
'label': _('My plugin view'),
|
||||||
@@ -118,7 +118,7 @@ for good integration. If you just want to display a form, you could do it like t
|
|||||||
|
|
||||||
class MySettingsView(EventSettingsViewMixin, EventSettingsFormView):
|
class MySettingsView(EventSettingsViewMixin, EventSettingsFormView):
|
||||||
model = Event
|
model = Event
|
||||||
permission = 'event.settings.general:write'
|
permission = 'can_change_settings'
|
||||||
form_class = MySettingsForm
|
form_class = MySettingsForm
|
||||||
template_name = 'my_plugin/settings.html'
|
template_name = 'my_plugin/settings.html'
|
||||||
|
|
||||||
@@ -204,13 +204,13 @@ In case of ``orga_router`` and ``event_router``, permission checking is done for
|
|||||||
in the control panel. However, you need to make sure on your own only to return the correct subset of data! ``request
|
in the control panel. However, you need to make sure on your own only to return the correct subset of data! ``request
|
||||||
.event`` and ``request.organizer`` are available as usual.
|
.event`` and ``request.organizer`` are available as usual.
|
||||||
|
|
||||||
To require a special permission like ``event.orders:read``, you do not need to inherit from a special ViewSet base
|
To require a special permission like ``can_view_orders``, you do not need to inherit from a special ViewSet base
|
||||||
class, you can just set the ``permission`` attribute on your viewset:
|
class, you can just set the ``permission`` attribute on your viewset:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
class MyViewSet(ModelViewSet):
|
class MyViewSet(ModelViewSet):
|
||||||
permission = 'event.orders:read'
|
permission = 'can_view_orders'
|
||||||
...
|
...
|
||||||
|
|
||||||
If you want to check the permission only for some methods of your viewset, you have to do it yourself. Note here that
|
If you want to check the permission only for some methods of your viewset, you have to do it yourself. Note here that
|
||||||
@@ -220,7 +220,7 @@ following:
|
|||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
perm_holder = (request.auth if isinstance(request.auth, TeamAPIToken) else request.user)
|
perm_holder = (request.auth if isinstance(request.auth, TeamAPIToken) else request.user)
|
||||||
if perm_holder.has_event_permission(request.event.organizer, request.event, 'event.orders:read'):
|
if perm_holder.has_event_permission(request.event.organizer, request.event, 'can_view_orders'):
|
||||||
...
|
...
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -80,24 +80,8 @@ The exporter class
|
|||||||
|
|
||||||
.. autoattribute:: category
|
.. autoattribute:: category
|
||||||
|
|
||||||
.. autoattribute:: feature
|
|
||||||
|
|
||||||
.. autoattribute:: export_form_fields
|
.. autoattribute:: export_form_fields
|
||||||
|
|
||||||
.. autoattribute:: repeatable_read
|
|
||||||
|
|
||||||
.. automethod:: render
|
.. automethod:: render
|
||||||
|
|
||||||
This is an abstract method, you **must** override this!
|
This is an abstract method, you **must** override this!
|
||||||
|
|
||||||
.. automethod:: available_for_user
|
|
||||||
|
|
||||||
.. automethod:: get_required_event_permission
|
|
||||||
|
|
||||||
On organizer level, by default exporters are expected to handle a *set of events* and the system will automatically
|
|
||||||
add a form field that allows the selection of events, limited to events the user has correct permissions for. If this
|
|
||||||
does not fit your exporter, because it is not related to events, you should **also** inherit from the following class:
|
|
||||||
|
|
||||||
.. class:: pretix.base.exporter.OrganizerLevelExportMixin
|
|
||||||
|
|
||||||
.. automethod:: get_required_organizer_permission
|
|
||||||
|
|||||||
@@ -14,8 +14,7 @@ Core
|
|||||||
:members: periodic_task, event_live_issues, event_copy_data, email_filter, register_notification_types, notification,
|
:members: periodic_task, event_live_issues, event_copy_data, email_filter, register_notification_types, notification,
|
||||||
item_copy_data, register_sales_channel_types, register_global_settings, quota_availability, global_email_filter,
|
item_copy_data, register_sales_channel_types, register_global_settings, quota_availability, global_email_filter,
|
||||||
register_ticket_secret_generators, gift_card_transaction_display,
|
register_ticket_secret_generators, gift_card_transaction_display,
|
||||||
register_text_placeholders, register_mail_placeholders, device_info_updated,
|
register_text_placeholders, register_mail_placeholders, device_info_updated
|
||||||
register_event_permission_groups, register_organizer_permission_groups
|
|
||||||
|
|
||||||
Order events
|
Order events
|
||||||
""""""""""""
|
""""""""""""
|
||||||
|
|||||||
@@ -196,7 +196,7 @@ A simple implementation could look like this:
|
|||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
class MyNotificationType(NotificationType):
|
class MyNotificationType(NotificationType):
|
||||||
required_permission = "event.orders:read"
|
required_permission = "can_view_orders"
|
||||||
action_type = "pretix.event.order.paid"
|
action_type = "pretix.event.order.paid"
|
||||||
verbose_name = _("Order has been paid")
|
verbose_name = _("Order has been paid")
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ Permissions
|
|||||||
===========
|
===========
|
||||||
|
|
||||||
pretix uses a fine-grained permission system to control who is allowed to control what parts of the system.
|
pretix uses a fine-grained permission system to control who is allowed to control what parts of the system.
|
||||||
The central concept here is the concept of *Teams*. You can read more on `configuring teams and permissions`_
|
The central concept here is the concept of *Teams*. You can read more on `configuring teams and permissions <user-teams>`_
|
||||||
and the :class:`pretix.base.models.Team` model in the respective parts of the documentation. The basic digest is:
|
and the :class:`pretix.base.models.Team` model in the respective parts of the documentation. The basic digest is:
|
||||||
An organizer account can have any number of teams, and any number of users can be part of a team. A team can be
|
An organizer account can have any number of teams, and any number of users can be part of a team. A team can be
|
||||||
assigned a set of permissions and connected to some or all of the events of the organizer.
|
assigned a set of permissions and connected to some or all of the events of the organizer.
|
||||||
@@ -25,8 +25,8 @@ permission level to access a view:
|
|||||||
|
|
||||||
|
|
||||||
class MyOrgaView(OrganizerPermissionRequiredMixin, View):
|
class MyOrgaView(OrganizerPermissionRequiredMixin, View):
|
||||||
permission = 'organizer.settings.general:write'
|
permission = 'can_change_organizer_settings'
|
||||||
# Only users with the permission ``organizer.settings.general:write`` on
|
# Only users with the permission ``can_change_organizer_settings`` on
|
||||||
# this organizer can access this
|
# this organizer can access this
|
||||||
|
|
||||||
|
|
||||||
@@ -35,9 +35,9 @@ permission level to access a view:
|
|||||||
# Only users with *any* permission on this organizer can access this
|
# Only users with *any* permission on this organizer can access this
|
||||||
|
|
||||||
|
|
||||||
@organizer_permission_required('organizer.settings.general:write')
|
@organizer_permission_required('can_change_organizer_settings')
|
||||||
def my_orga_view(request, organizer, **kwargs):
|
def my_orga_view(request, organizer, **kwargs):
|
||||||
# Only users with the permission ``organizer.settings.general:write`` on
|
# Only users with the permission ``can_change_organizer_settings`` on
|
||||||
# this organizer can access this
|
# this organizer can access this
|
||||||
|
|
||||||
|
|
||||||
@@ -56,8 +56,8 @@ Of course, the same is available on event level:
|
|||||||
|
|
||||||
|
|
||||||
class MyEventView(EventPermissionRequiredMixin, View):
|
class MyEventView(EventPermissionRequiredMixin, View):
|
||||||
permission = 'event.settings.general:write'
|
permission = 'can_change_event_settings'
|
||||||
# Only users with the permission ``event.settings.general:write`` on
|
# Only users with the permission ``can_change_event_settings`` on
|
||||||
# this event can access this
|
# this event can access this
|
||||||
|
|
||||||
|
|
||||||
@@ -66,9 +66,9 @@ Of course, the same is available on event level:
|
|||||||
# Only users with *any* permission on this event can access this
|
# Only users with *any* permission on this event can access this
|
||||||
|
|
||||||
|
|
||||||
@event_permission_required('event.settings.general:write')
|
@event_permission_required('can_change_event_settings')
|
||||||
def my_event_view(request, organizer, **kwargs):
|
def my_event_view(request, organizer, **kwargs):
|
||||||
# Only users with the permission ``event.settings.general:write`` on
|
# Only users with the permission ``can_change_event_settings`` on
|
||||||
# this event can access this
|
# this event can access this
|
||||||
|
|
||||||
|
|
||||||
@@ -121,7 +121,7 @@ When creating your own ``viewset`` using Django REST framework, you just need to
|
|||||||
and pretix will check it automatically for you::
|
and pretix will check it automatically for you::
|
||||||
|
|
||||||
class MyModelViewSet(viewsets.ReadOnlyModelViewSet):
|
class MyModelViewSet(viewsets.ReadOnlyModelViewSet):
|
||||||
permission = 'event.orders:read'
|
permission = 'can_view_orders'
|
||||||
|
|
||||||
Checking permission in code
|
Checking permission in code
|
||||||
---------------------------
|
---------------------------
|
||||||
@@ -136,12 +136,12 @@ Return all users that are in any team that is connected to this event::
|
|||||||
|
|
||||||
Return all users that are in a team with a specific permission for this event::
|
Return all users that are in a team with a specific permission for this event::
|
||||||
|
|
||||||
>>> event.get_users_with_permission('event.orders:read')
|
>>> event.get_users_with_permission('can_change_event_settings')
|
||||||
<QuerySet: …>
|
<QuerySet: …>
|
||||||
|
|
||||||
Determine if a user has a certain permission for a specific event::
|
Determine if a user has a certain permission for a specific event::
|
||||||
|
|
||||||
>>> user.has_event_permission(organizer, event, 'event.orders:read', request=request)
|
>>> user.has_event_permission(organizer, event, 'can_change_event_settings', request=request)
|
||||||
True
|
True
|
||||||
|
|
||||||
Determine if a user has any permission for a specific event::
|
Determine if a user has any permission for a specific event::
|
||||||
@@ -153,27 +153,27 @@ In the two previous commands, the ``request`` argument is optional, but required
|
|||||||
|
|
||||||
The same method exists for organizer-level permissions::
|
The same method exists for organizer-level permissions::
|
||||||
|
|
||||||
>>> user.has_organizer_permission(organizer, 'event.orders:read', request=request)
|
>>> user.has_organizer_permission(organizer, 'can_change_event_settings', request=request)
|
||||||
True
|
True
|
||||||
|
|
||||||
Sometimes, it might be more useful to get the set of permissions at once::
|
Sometimes, it might be more useful to get the set of permissions at once::
|
||||||
|
|
||||||
>>> user.get_event_permission_set(organizer, event)
|
>>> user.get_event_permission_set(organizer, event)
|
||||||
{'event.settings.general:write', 'event.orders:read', 'event.orders:write'}
|
{'can_change_event_settings', 'can_view_orders', 'can_change_orders'}
|
||||||
|
|
||||||
>>> user.get_organizer_permission_set(organizer, event)
|
>>> user.get_organizer_permission_set(organizer, event)
|
||||||
{'organizer.settings.general:write', 'organizer.events:create'}
|
{'can_change_organizer_settings', 'can_create_events'}
|
||||||
|
|
||||||
Within a view on the ``/control`` subpath, the results of these two methods are already available in the
|
Within a view on the ``/control`` subpath, the results of these two methods are already available in the
|
||||||
``request.eventpermset`` and ``request.orgapermset`` properties. This makes it convenient to query them in templates::
|
``request.eventpermset`` and ``request.orgapermset`` properties. This makes it convenient to query them in templates::
|
||||||
|
|
||||||
{% if "event.orders:write" in request.eventpermset %}
|
{% if "can_change_orders" in request.eventpermset %}
|
||||||
…
|
…
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
You can also do the reverse to get any events a user has access to::
|
You can also do the reverse to get any events a user has access to::
|
||||||
|
|
||||||
>>> user.get_events_with_permission('event.settings.general:write', request=request)
|
>>> user.get_events_with_permission('can_change_event_settings', request=request)
|
||||||
<QuerySet: …>
|
<QuerySet: …>
|
||||||
|
|
||||||
>>> user.get_events_with_any_permission(request=request)
|
>>> user.get_events_with_any_permission(request=request)
|
||||||
@@ -195,53 +195,3 @@ staff mode is active. You can check if a user is in staff mode using their sessi
|
|||||||
Staff mode has a hard time limit and during staff mode, a middleware will log all requests made by that user. Later,
|
Staff mode has a hard time limit and during staff mode, a middleware will log all requests made by that user. Later,
|
||||||
the user is able to also save a message to comment on what they did in their administrative session. This feature is
|
the user is able to also save a message to comment on what they did in their administrative session. This feature is
|
||||||
intended to help compliance with data protection rules as imposed e.g. by GDPR.
|
intended to help compliance with data protection rules as imposed e.g. by GDPR.
|
||||||
|
|
||||||
Adding permissions
|
|
||||||
------------------
|
|
||||||
|
|
||||||
Plugins can add permissions through the ``register_event_permission_groups`` and ``register_organizer_permission_groups``.
|
|
||||||
We recommend to use this only for very significant permissions, as the system will become less usable with too many
|
|
||||||
permission levels, especially since the team page will show all permission options, even those of disabled plugins.
|
|
||||||
|
|
||||||
To register your permissions, you need to register a **permission group** (often representing an area of functionality
|
|
||||||
or a key model). Below that group, there are **actions**, which represent the actual permissions. Permissions will be
|
|
||||||
generated as ``<group_name>:<action>``. Then, you need to define **options** which are the valid combinations of the
|
|
||||||
actions that should be possible to select for a team. This two-step mechanism exists to provide a better user experience
|
|
||||||
and avoid impossible combinations like "write but not read".
|
|
||||||
|
|
||||||
Example::
|
|
||||||
|
|
||||||
@receiver(register_event_permission_groups)
|
|
||||||
def register_plugin_event_permissions(sender, **kwargs):
|
|
||||||
return [
|
|
||||||
PermissionGroup(
|
|
||||||
name="pretix_myplugin.resource",
|
|
||||||
label=_("Resources"),
|
|
||||||
actions=["read", "write"],
|
|
||||||
options=[
|
|
||||||
PermissionOption(actions=tuple(), label=_("No access")),
|
|
||||||
PermissionOption(actions=("read",), label=_("View")),
|
|
||||||
PermissionOption(actions=("read", "write"), label=_("View and change")),
|
|
||||||
],
|
|
||||||
help_text=_("Some help text")
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
@receiver(register_organizer_permission_groups)
|
|
||||||
def register_plugin_organizer_permissions(sender, **kwargs):
|
|
||||||
return [
|
|
||||||
PermissionGroup(
|
|
||||||
name="pretix_myplugin.resource",
|
|
||||||
label=_("Resources"),
|
|
||||||
actions=["read", "write"],
|
|
||||||
options=[
|
|
||||||
PermissionOption(actions=tuple(), label=_("No access")),
|
|
||||||
PermissionOption(actions=("read",), label=_("View")),
|
|
||||||
PermissionOption(actions=("read", "write"), label=_("View and change")),
|
|
||||||
],
|
|
||||||
help_text=_("Some help text")
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
||||||
.. _configuring teams and permissions: https://docs.pretix.eu/guides/teams/
|
|
||||||
@@ -41,7 +41,7 @@ dependencies = [
|
|||||||
"django-compressor==4.6.0",
|
"django-compressor==4.6.0",
|
||||||
"django-countries==8.2.*",
|
"django-countries==8.2.*",
|
||||||
"django-filter==25.1",
|
"django-filter==25.1",
|
||||||
"django-formset-js-improved==0.5.0.5",
|
"django-formset-js-improved==0.5.0.4",
|
||||||
"django-formtools==2.5.1",
|
"django-formtools==2.5.1",
|
||||||
"django-hierarkey==2.0.*,>=2.0.1",
|
"django-hierarkey==2.0.*,>=2.0.1",
|
||||||
"django-hijack==3.7.*",
|
"django-hijack==3.7.*",
|
||||||
@@ -65,7 +65,7 @@ dependencies = [
|
|||||||
"kombu==5.6.*",
|
"kombu==5.6.*",
|
||||||
"libsass==0.23.*",
|
"libsass==0.23.*",
|
||||||
"lxml",
|
"lxml",
|
||||||
"markdown==3.10.1", # 3.3.5 requires importlib-metadata>=4.4, but django-bootstrap3 requires importlib-metadata<3.
|
"markdown==3.10", # 3.3.5 requires importlib-metadata>=4.4, but django-bootstrap3 requires importlib-metadata<3.
|
||||||
# We can upgrade markdown again once django-bootstrap3 upgrades or once we drop Python 3.6 and 3.7
|
# We can upgrade markdown again once django-bootstrap3 upgrades or once we drop Python 3.6 and 3.7
|
||||||
"mt-940==4.30.*",
|
"mt-940==4.30.*",
|
||||||
"oauthlib==3.3.*",
|
"oauthlib==3.3.*",
|
||||||
@@ -80,7 +80,7 @@ dependencies = [
|
|||||||
"protobuf==6.33.*",
|
"protobuf==6.33.*",
|
||||||
"psycopg2-binary",
|
"psycopg2-binary",
|
||||||
"pycountry",
|
"pycountry",
|
||||||
"pycparser==3.0",
|
"pycparser==2.23",
|
||||||
"pycryptodome==3.23.*",
|
"pycryptodome==3.23.*",
|
||||||
"pypdf==6.5.*",
|
"pypdf==6.5.*",
|
||||||
"python-bidi==0.6.*", # Support for Arabic in reportlab
|
"python-bidi==0.6.*", # Support for Arabic in reportlab
|
||||||
@@ -92,7 +92,7 @@ dependencies = [
|
|||||||
"redis==7.1.*",
|
"redis==7.1.*",
|
||||||
"reportlab==4.4.*",
|
"reportlab==4.4.*",
|
||||||
"requests==2.32.*",
|
"requests==2.32.*",
|
||||||
"sentry-sdk==2.50.*",
|
"sentry-sdk==2.49.*",
|
||||||
"sepaxml==2.7.*",
|
"sepaxml==2.7.*",
|
||||||
"stripe==7.9.*",
|
"stripe==7.9.*",
|
||||||
"text-unidecode==1.*",
|
"text-unidecode==1.*",
|
||||||
|
|||||||
@@ -19,4 +19,4 @@
|
|||||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||||
# <https://www.gnu.org/licenses/>.
|
# <https://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
__version__ = "2026.2.0.dev0"
|
__version__ = "2025.11.0.dev0"
|
||||||
|
|||||||
@@ -36,9 +36,7 @@ from rest_framework.permissions import SAFE_METHODS, BasePermission
|
|||||||
|
|
||||||
from pretix.api.models import OAuthAccessToken
|
from pretix.api.models import OAuthAccessToken
|
||||||
from pretix.base.models import Device, Event, User
|
from pretix.base.models import Device, Event, User
|
||||||
from pretix.base.models.auth import (
|
from pretix.base.models.auth import SuperuserPermissionSet
|
||||||
EventPermissionSet, OrganizerPermissionSet, SuperuserPermissionSet,
|
|
||||||
)
|
|
||||||
from pretix.base.models.organizer import TeamAPIToken
|
from pretix.base.models.organizer import TeamAPIToken
|
||||||
from pretix.helpers.security import (
|
from pretix.helpers.security import (
|
||||||
Session2FASetupRequired, SessionInvalid, SessionPasswordChangeRequired,
|
Session2FASetupRequired, SessionInvalid, SessionPasswordChangeRequired,
|
||||||
@@ -87,7 +85,7 @@ class EventPermission(BasePermission):
|
|||||||
if isinstance(perm_holder, User) and perm_holder.has_active_staff_session(request.session.session_key):
|
if isinstance(perm_holder, User) and perm_holder.has_active_staff_session(request.session.session_key):
|
||||||
request.eventpermset = SuperuserPermissionSet()
|
request.eventpermset = SuperuserPermissionSet()
|
||||||
else:
|
else:
|
||||||
request.eventpermset = EventPermissionSet(perm_holder.get_event_permission_set(request.organizer, request.event))
|
request.eventpermset = perm_holder.get_event_permission_set(request.organizer, request.event)
|
||||||
|
|
||||||
if isinstance(required_permission, (list, tuple)):
|
if isinstance(required_permission, (list, tuple)):
|
||||||
if not any(p in request.eventpermset for p in required_permission):
|
if not any(p in request.eventpermset for p in required_permission):
|
||||||
@@ -102,7 +100,7 @@ class EventPermission(BasePermission):
|
|||||||
if isinstance(perm_holder, User) and perm_holder.has_active_staff_session(request.session.session_key):
|
if isinstance(perm_holder, User) and perm_holder.has_active_staff_session(request.session.session_key):
|
||||||
request.orgapermset = SuperuserPermissionSet()
|
request.orgapermset = SuperuserPermissionSet()
|
||||||
else:
|
else:
|
||||||
request.orgapermset = OrganizerPermissionSet(perm_holder.get_organizer_permission_set(request.organizer))
|
request.orgapermset = perm_holder.get_organizer_permission_set(request.organizer)
|
||||||
|
|
||||||
if isinstance(required_permission, (list, tuple)):
|
if isinstance(required_permission, (list, tuple)):
|
||||||
if not any(p in request.eventpermset for p in required_permission):
|
if not any(p in request.eventpermset for p in required_permission):
|
||||||
@@ -126,12 +124,12 @@ class EventCRUDPermission(EventPermission):
|
|||||||
def has_permission(self, request, view):
|
def has_permission(self, request, view):
|
||||||
if not super(EventCRUDPermission, self).has_permission(request, view):
|
if not super(EventCRUDPermission, self).has_permission(request, view):
|
||||||
return False
|
return False
|
||||||
elif view.action == 'create' and 'organizer.events:create' not in request.orgapermset:
|
elif view.action == 'create' and 'can_create_events' not in request.orgapermset:
|
||||||
return False
|
return False
|
||||||
elif view.action == 'destroy' and 'event.settings.general:write' not in request.eventpermset:
|
elif view.action == 'destroy' and 'can_change_event_settings' not in request.eventpermset:
|
||||||
return False
|
return False
|
||||||
elif view.action in ['update', 'partial_update'] \
|
elif view.action in ['update', 'partial_update'] \
|
||||||
and 'event.settings.general:write' not in request.eventpermset:
|
and 'can_change_event_settings' not in request.eventpermset:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|||||||
@@ -300,7 +300,7 @@ class EventSerializer(SalesChannelMigrationMixin, I18nAwareModelSerializer):
|
|||||||
def ignored_meta_properties(self):
|
def ignored_meta_properties(self):
|
||||||
perm_holder = (self.context['request'].auth if isinstance(self.context['request'].auth, (Device, TeamAPIToken))
|
perm_holder = (self.context['request'].auth if isinstance(self.context['request'].auth, (Device, TeamAPIToken))
|
||||||
else self.context['request'].user)
|
else self.context['request'].user)
|
||||||
if perm_holder.has_organizer_permission(self.context['request'].organizer, 'organizer.settings.general:write', request=self.context['request']):
|
if perm_holder.has_organizer_permission(self.context['request'].organizer, 'can_change_organizer_settings', request=self.context['request']):
|
||||||
return []
|
return []
|
||||||
return [k for k, p in self.meta_properties.items() if p.protected]
|
return [k for k, p in self.meta_properties.items() if p.protected]
|
||||||
|
|
||||||
@@ -561,7 +561,7 @@ class SubEventSerializer(I18nAwareModelSerializer):
|
|||||||
def ignored_meta_properties(self):
|
def ignored_meta_properties(self):
|
||||||
perm_holder = (self.context['request'].auth if isinstance(self.context['request'].auth, (Device, TeamAPIToken))
|
perm_holder = (self.context['request'].auth if isinstance(self.context['request'].auth, (Device, TeamAPIToken))
|
||||||
else self.context['request'].user)
|
else self.context['request'].user)
|
||||||
if perm_holder.has_organizer_permission(self.context['request'].organizer, 'organizer.settings.general:write', request=self.context['request']):
|
if perm_holder.has_organizer_permission(self.context['request'].organizer, 'can_change_organizer_settings', request=self.context['request']):
|
||||||
return []
|
return []
|
||||||
return [k for k, p in self.meta_properties.items() if p.protected]
|
return [k for k, p in self.meta_properties.items() if p.protected]
|
||||||
|
|
||||||
@@ -707,10 +707,7 @@ class TaxRuleSerializer(CountryFieldMixin, I18nAwareModelSerializer):
|
|||||||
|
|
||||||
|
|
||||||
class EventSettingsSerializer(SettingsSerializer):
|
class EventSettingsSerializer(SettingsSerializer):
|
||||||
default_write_permission = 'event.settings.general:write'
|
|
||||||
default_fields = [
|
default_fields = [
|
||||||
# These are readable for all users with access to the events, therefore secrets stored in the settings store
|
|
||||||
# should not be included!
|
|
||||||
'imprint_url',
|
'imprint_url',
|
||||||
'checkout_email_helptext',
|
'checkout_email_helptext',
|
||||||
'presale_has_ended_text',
|
'presale_has_ended_text',
|
||||||
@@ -809,7 +806,6 @@ class EventSettingsSerializer(SettingsSerializer):
|
|||||||
'invoice_reissue_after_modify',
|
'invoice_reissue_after_modify',
|
||||||
'invoice_include_free',
|
'invoice_include_free',
|
||||||
'invoice_generate',
|
'invoice_generate',
|
||||||
'invoice_generate_only_business',
|
|
||||||
'invoice_period',
|
'invoice_period',
|
||||||
'invoice_numbers_consecutive',
|
'invoice_numbers_consecutive',
|
||||||
'invoice_numbers_prefix',
|
'invoice_numbers_prefix',
|
||||||
@@ -1083,16 +1079,16 @@ class SeatSerializer(I18nAwareModelSerializer):
|
|||||||
|
|
||||||
def prefetch_expanded_data(self, items, request, expand_fields):
|
def prefetch_expanded_data(self, items, request, expand_fields):
|
||||||
if 'orderposition' in expand_fields:
|
if 'orderposition' in expand_fields:
|
||||||
if 'event.orders:read' not in request.eventpermset:
|
if 'can_view_orders' not in request.eventpermset:
|
||||||
raise PermissionDenied('event.orders:read permission required for expand=orderposition')
|
raise PermissionDenied('can_view_orders permission required for expand=orderposition')
|
||||||
prefetch_by_id(items, OrderPosition.objects.prefetch_related('order'), 'orderposition_id', 'orderposition')
|
prefetch_by_id(items, OrderPosition.objects.prefetch_related('order'), 'orderposition_id', 'orderposition')
|
||||||
if 'cartposition' in expand_fields:
|
if 'cartposition' in expand_fields:
|
||||||
if 'event.orders:read' not in request.eventpermset:
|
if 'can_view_orders' not in request.eventpermset:
|
||||||
raise PermissionDenied('event.orders:read permission required for expand=cartposition')
|
raise PermissionDenied('can_view_orders permission required for expand=cartposition')
|
||||||
prefetch_by_id(items, CartPosition.objects, 'cartposition_id', 'cartposition')
|
prefetch_by_id(items, CartPosition.objects, 'cartposition_id', 'cartposition')
|
||||||
if 'voucher' in expand_fields:
|
if 'voucher' in expand_fields:
|
||||||
if 'event.vouchers:read' not in request.eventpermset:
|
if 'can_view_vouchers' not in request.eventpermset:
|
||||||
raise PermissionDenied('event.vouchers:read permission required for expand=voucher')
|
raise PermissionDenied('can_view_vouchers permission required for expand=voucher')
|
||||||
prefetch_by_id(items, Voucher.objects, 'voucher_id', 'voucher')
|
prefetch_by_id(items, Voucher.objects, 'voucher_id', 'voucher')
|
||||||
|
|
||||||
def __init__(self, instance, *args, **kwargs):
|
def __init__(self, instance, *args, **kwargs):
|
||||||
|
|||||||
@@ -55,10 +55,11 @@ class ExporterSerializer(serializers.Serializer):
|
|||||||
class JobRunSerializer(serializers.Serializer):
|
class JobRunSerializer(serializers.Serializer):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
ex = kwargs.pop('exporter')
|
ex = kwargs.pop('exporter')
|
||||||
|
events = kwargs.pop('events', None)
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
if ex.is_multievent and not isinstance(ex, OrganizerLevelExportMixin):
|
if events is not None and not isinstance(ex, OrganizerLevelExportMixin):
|
||||||
self.fields["events"] = serializers.SlugRelatedField(
|
self.fields["events"] = serializers.SlugRelatedField(
|
||||||
queryset=ex.events,
|
queryset=events,
|
||||||
required=False,
|
required=False,
|
||||||
allow_empty=False,
|
allow_empty=False,
|
||||||
slug_field='slug',
|
slug_field='slug',
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ from decimal import Decimal
|
|||||||
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from rest_framework.exceptions import PermissionDenied, ValidationError
|
from rest_framework.exceptions import ValidationError
|
||||||
|
|
||||||
from pretix.api.serializers.i18n import I18nAwareModelSerializer
|
from pretix.api.serializers.i18n import I18nAwareModelSerializer
|
||||||
from pretix.api.serializers.order import OrderPositionSerializer
|
from pretix.api.serializers.order import OrderPositionSerializer
|
||||||
@@ -66,9 +66,6 @@ class ReusableMediaSerializer(I18nAwareModelSerializer):
|
|||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
if 'linked_giftcard' in self.context['request'].query_params.getlist('expand'):
|
if 'linked_giftcard' in self.context['request'].query_params.getlist('expand'):
|
||||||
if not self.context["can_read_giftcards"]:
|
|
||||||
raise PermissionDenied("No permission to access gift card details.")
|
|
||||||
|
|
||||||
self.fields['linked_giftcard'] = NestedGiftCardSerializer(read_only=True, context=self.context)
|
self.fields['linked_giftcard'] = NestedGiftCardSerializer(read_only=True, context=self.context)
|
||||||
if 'linked_giftcard.owner_ticket' in self.context['request'].query_params.getlist('expand'):
|
if 'linked_giftcard.owner_ticket' in self.context['request'].query_params.getlist('expand'):
|
||||||
self.fields['linked_giftcard'].fields['owner_ticket'] = NestedOrderPositionSerializer(read_only=True, context=self.context)
|
self.fields['linked_giftcard'].fields['owner_ticket'] = NestedOrderPositionSerializer(read_only=True, context=self.context)
|
||||||
@@ -80,8 +77,6 @@ class ReusableMediaSerializer(I18nAwareModelSerializer):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if 'linked_orderposition' in self.context['request'].query_params.getlist('expand'):
|
if 'linked_orderposition' in self.context['request'].query_params.getlist('expand'):
|
||||||
# No additional permission check performed, documented limitation of the permission system
|
|
||||||
# Would get to complex/unusable otherwise since the permission depends on the event
|
|
||||||
self.fields['linked_orderposition'] = NestedOrderPositionSerializer(read_only=True)
|
self.fields['linked_orderposition'] = NestedOrderPositionSerializer(read_only=True)
|
||||||
else:
|
else:
|
||||||
self.fields['linked_orderposition'] = serializers.PrimaryKeyRelatedField(
|
self.fields['linked_orderposition'] = serializers.PrimaryKeyRelatedField(
|
||||||
@@ -91,9 +86,6 @@ class ReusableMediaSerializer(I18nAwareModelSerializer):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if 'customer' in self.context['request'].query_params.getlist('expand'):
|
if 'customer' in self.context['request'].query_params.getlist('expand'):
|
||||||
if not self.context["can_read_customers"]:
|
|
||||||
raise PermissionDenied("No permission to access customer details.")
|
|
||||||
|
|
||||||
self.fields['customer'] = CustomerSerializer(read_only=True)
|
self.fields['customer'] = CustomerSerializer(read_only=True)
|
||||||
else:
|
else:
|
||||||
self.fields['customer'] = serializers.SlugRelatedField(
|
self.fields['customer'] = serializers.SlugRelatedField(
|
||||||
|
|||||||
@@ -191,7 +191,7 @@ class InvoiceAddressSerializer(I18nAwareModelSerializer):
|
|||||||
{"transmission_info": {r: "This field is required for the selected type of invoice transmission."}}
|
{"transmission_info": {r: "This field is required for the selected type of invoice transmission."}}
|
||||||
)
|
)
|
||||||
break # do not call else branch of for loop
|
break # do not call else branch of for loop
|
||||||
elif t.is_exclusive(self.context["request"].event, data.get("country"), data.get("is_business")):
|
elif t.exclusive:
|
||||||
if t.is_available(self.context["request"].event, data.get("country"), data.get("is_business")):
|
if t.is_available(self.context["request"].event, data.get("country"), data.get("is_business")):
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
"transmission_type": "The transmission type '%s' must be used for this country or address type." % (
|
"transmission_type": "The transmission type '%s' must be used for this country or address type." % (
|
||||||
@@ -613,7 +613,7 @@ class OrderPositionSerializer(I18nAwareModelSerializer):
|
|||||||
# /events/…/checkinlists/…/positions/
|
# /events/…/checkinlists/…/positions/
|
||||||
# We're unable to check this on this level if we're on /checkinrpc/, in which case we rely on the view
|
# We're unable to check this on this level if we're on /checkinrpc/, in which case we rely on the view
|
||||||
# layer to not set pdf_data=true in the first place.
|
# layer to not set pdf_data=true in the first place.
|
||||||
request and hasattr(request, 'eventpermset') and 'event.orders:read' not in request.eventpermset
|
request and hasattr(request, 'eventpermset') and 'can_view_orders' not in request.eventpermset
|
||||||
)
|
)
|
||||||
if ('pdf_data' in self.context and not self.context['pdf_data']) or pdf_data_forbidden:
|
if ('pdf_data' in self.context and not self.context['pdf_data']) or pdf_data_forbidden:
|
||||||
self.fields.pop('pdf_data', None)
|
self.fields.pop('pdf_data', None)
|
||||||
@@ -704,16 +704,6 @@ class CheckinListOrderPositionSerializer(OrderPositionSerializer):
|
|||||||
if 'answers.question' in self.context['expand']:
|
if 'answers.question' in self.context['expand']:
|
||||||
self.fields['answers'].child.fields['question'] = QuestionSerializer(read_only=True)
|
self.fields['answers'].child.fields['question'] = QuestionSerializer(read_only=True)
|
||||||
|
|
||||||
if 'addons' in self.context['expand']:
|
|
||||||
# Experimental feature, undocumented on purpose for now in case we need to remove it again
|
|
||||||
# for performance reasons
|
|
||||||
subl = CheckinListOrderPositionSerializer(read_only=True, many=True, context={
|
|
||||||
**self.context,
|
|
||||||
'expand': [v for v in self.context['expand'] if v != 'addons'],
|
|
||||||
'pdf_data': False,
|
|
||||||
})
|
|
||||||
self.fields['addons'] = subl
|
|
||||||
|
|
||||||
|
|
||||||
class OrderPaymentTypeField(serializers.Field):
|
class OrderPaymentTypeField(serializers.Field):
|
||||||
# TODO: Remove after pretix 2.2
|
# TODO: Remove after pretix 2.2
|
||||||
|
|||||||
@@ -45,19 +45,12 @@ from pretix.base.models import (
|
|||||||
SalesChannel, SeatingPlan, Team, TeamAPIToken, TeamInvite, User,
|
SalesChannel, SeatingPlan, Team, TeamAPIToken, TeamInvite, User,
|
||||||
)
|
)
|
||||||
from pretix.base.models.seating import SeatingPlanLayoutValidator
|
from pretix.base.models.seating import SeatingPlanLayoutValidator
|
||||||
from pretix.base.permissions import (
|
|
||||||
get_all_event_permission_groups, get_all_organizer_permission_groups,
|
|
||||||
)
|
|
||||||
from pretix.base.plugins import (
|
from pretix.base.plugins import (
|
||||||
PLUGIN_LEVEL_EVENT, PLUGIN_LEVEL_EVENT_ORGANIZER_HYBRID,
|
PLUGIN_LEVEL_EVENT, PLUGIN_LEVEL_EVENT_ORGANIZER_HYBRID,
|
||||||
PLUGIN_LEVEL_ORGANIZER,
|
PLUGIN_LEVEL_ORGANIZER,
|
||||||
)
|
)
|
||||||
from pretix.base.services.mail import SendMailException, mail
|
from pretix.base.services.mail import SendMailException, mail
|
||||||
from pretix.base.settings import validate_organizer_settings
|
from pretix.base.settings import validate_organizer_settings
|
||||||
from pretix.helpers.permission_migration import (
|
|
||||||
OLD_TO_NEW_EVENT_COMPAT, OLD_TO_NEW_EVENT_MIGRATION,
|
|
||||||
OLD_TO_NEW_ORGANIZER_COMPAT, OLD_TO_NEW_ORGANIZER_MIGRATION,
|
|
||||||
)
|
|
||||||
from pretix.helpers.urls import build_absolute_uri as build_global_uri
|
from pretix.helpers.urls import build_absolute_uri as build_global_uri
|
||||||
from pretix.multidomain.urlreverse import build_absolute_uri
|
from pretix.multidomain.urlreverse import build_absolute_uri
|
||||||
|
|
||||||
@@ -313,128 +306,23 @@ class EventSlugField(serializers.SlugRelatedField):
|
|||||||
return self.context['organizer'].events.all()
|
return self.context['organizer'].events.all()
|
||||||
|
|
||||||
|
|
||||||
class PermissionMultipleChoiceField(serializers.MultipleChoiceField):
|
|
||||||
def to_internal_value(self, data):
|
|
||||||
return {
|
|
||||||
p: True for p in super().to_internal_value(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
def to_representation(self, value):
|
|
||||||
return [p for p, v in value.items() if v]
|
|
||||||
|
|
||||||
|
|
||||||
class TeamSerializer(serializers.ModelSerializer):
|
class TeamSerializer(serializers.ModelSerializer):
|
||||||
limit_events = EventSlugField(slug_field='slug', many=True)
|
limit_events = EventSlugField(slug_field='slug', many=True)
|
||||||
limit_event_permissions = PermissionMultipleChoiceField(choices=[], required=False, allow_null=False, allow_empty=True)
|
|
||||||
limit_organizer_permissions = PermissionMultipleChoiceField(choices=[], required=False, allow_null=False, allow_empty=True)
|
|
||||||
|
|
||||||
# Legacy fields, handled in to_representation and validate
|
|
||||||
can_change_event_settings = serializers.BooleanField(required=False, write_only=True)
|
|
||||||
can_change_items = serializers.BooleanField(required=False, write_only=True)
|
|
||||||
can_view_orders = serializers.BooleanField(required=False, write_only=True)
|
|
||||||
can_change_orders = serializers.BooleanField(required=False, write_only=True)
|
|
||||||
can_checkin_orders = serializers.BooleanField(required=False, write_only=True)
|
|
||||||
can_view_vouchers = serializers.BooleanField(required=False, write_only=True)
|
|
||||||
can_change_vouchers = serializers.BooleanField(required=False, write_only=True)
|
|
||||||
can_create_events = serializers.BooleanField(required=False, write_only=True)
|
|
||||||
can_change_organizer_settings = serializers.BooleanField(required=False, write_only=True)
|
|
||||||
can_change_teams = serializers.BooleanField(required=False, write_only=True)
|
|
||||||
can_manage_gift_cards = serializers.BooleanField(required=False, write_only=True)
|
|
||||||
can_manage_customers = serializers.BooleanField(required=False, write_only=True)
|
|
||||||
can_manage_reusable_media = serializers.BooleanField(required=False, write_only=True)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Team
|
model = Team
|
||||||
fields = (
|
fields = (
|
||||||
'id', 'name', 'require_2fa', 'all_events', 'limit_events', 'all_event_permissions', 'limit_event_permissions',
|
'id', 'name', 'require_2fa', 'all_events', 'limit_events', 'can_create_events', 'can_change_teams',
|
||||||
'all_organizer_permissions', 'limit_organizer_permissions', 'can_change_event_settings',
|
'can_change_organizer_settings', 'can_manage_gift_cards', 'can_change_event_settings',
|
||||||
'can_change_items', 'can_view_orders', 'can_change_orders', 'can_checkin_orders', 'can_view_vouchers',
|
'can_change_items', 'can_view_orders', 'can_change_orders', 'can_view_vouchers',
|
||||||
'can_change_vouchers', 'can_create_events', 'can_change_organizer_settings', 'can_change_teams',
|
'can_change_vouchers', 'can_checkin_orders', 'can_manage_customers', 'can_manage_reusable_media'
|
||||||
'can_manage_gift_cards', 'can_manage_customers', 'can_manage_reusable_media'
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
event_perms_flattened = []
|
|
||||||
organizer_perms_flattened = []
|
|
||||||
for pg in get_all_event_permission_groups().values():
|
|
||||||
for action in pg.actions:
|
|
||||||
event_perms_flattened.append(f"{pg.name}:{action}")
|
|
||||||
for pg in get_all_organizer_permission_groups().values():
|
|
||||||
for action in pg.actions:
|
|
||||||
organizer_perms_flattened.append(f"{pg.name}:{action}")
|
|
||||||
|
|
||||||
self.fields['limit_event_permissions'].choices = [(p, p) for p in event_perms_flattened]
|
|
||||||
self.fields['limit_organizer_permissions'].choices = [(p, p) for p in organizer_perms_flattened]
|
|
||||||
|
|
||||||
def to_representation(self, instance):
|
|
||||||
r = super().to_representation(instance)
|
|
||||||
for old, new in OLD_TO_NEW_EVENT_COMPAT.items():
|
|
||||||
r[old] = instance.all_event_permissions or all(instance.limit_event_permissions.get(n) for n in new)
|
|
||||||
for old, new in OLD_TO_NEW_ORGANIZER_COMPAT.items():
|
|
||||||
r[old] = instance.all_organizer_permissions or all(instance.limit_organizer_permissions.get(n) for n in new)
|
|
||||||
return r
|
|
||||||
|
|
||||||
def validate(self, data):
|
def validate(self, data):
|
||||||
old_data_set = any(k.startswith("can_") for k in data)
|
|
||||||
new_data_set = any(k in data for k in [
|
|
||||||
"all_event_permissions", "limit_event_permissions", "all_organizer_permissions", "limit_organizer_permissions"
|
|
||||||
])
|
|
||||||
if old_data_set and new_data_set:
|
|
||||||
raise ValidationError("You cannot set deprecated and current permission attributes at the same time.")
|
|
||||||
|
|
||||||
full_data = self.to_internal_value(self.to_representation(self.instance)) if self.instance else {}
|
full_data = self.to_internal_value(self.to_representation(self.instance)) if self.instance else {}
|
||||||
full_data.update(data)
|
full_data.update(data)
|
||||||
|
|
||||||
if new_data_set:
|
|
||||||
if full_data.get('limit_event_permissions') and full_data.get('all_event_permissions'):
|
|
||||||
raise ValidationError('Do not set both limit_event_permissions and all_event_permissions.')
|
|
||||||
if full_data.get('limit_organizer_permissions') and full_data.get('all_organizer_permissions'):
|
|
||||||
raise ValidationError('Do not set both limit_organizer_permissions and all_organizer_permissions.')
|
|
||||||
|
|
||||||
if old_data_set:
|
|
||||||
# Migrate with same logic as in migration 0297_pluggable_permissions
|
|
||||||
if all(full_data.get(k) is True for k in OLD_TO_NEW_EVENT_MIGRATION.keys() if k != "can_checkin_orders"):
|
|
||||||
data["all_event_permissions"] = True
|
|
||||||
data["limit_event_permissions"] = {}
|
|
||||||
else:
|
|
||||||
data["all_event_permissions"] = False
|
|
||||||
data["limit_event_permissions"] = {}
|
|
||||||
for k, v in OLD_TO_NEW_EVENT_MIGRATION.items():
|
|
||||||
if full_data.get(k) is True:
|
|
||||||
data["limit_event_permissions"].update({kk: True for kk in v})
|
|
||||||
if all(full_data.get(k) is True for k in OLD_TO_NEW_ORGANIZER_MIGRATION.keys() if k != "can_checkin_orders"):
|
|
||||||
data["all_organizer_permissions"] = True
|
|
||||||
data["limit_organizer_permissions"] = {}
|
|
||||||
else:
|
|
||||||
data["all_organizer_permissions"] = False
|
|
||||||
data["limit_organizer_permissions"] = {}
|
|
||||||
for k, v in OLD_TO_NEW_ORGANIZER_MIGRATION.items():
|
|
||||||
if full_data.get(k) is True:
|
|
||||||
data["limit_organizer_permissions"].update({kk: True for kk in v})
|
|
||||||
|
|
||||||
if full_data.get('limit_events') and full_data.get('all_events'):
|
if full_data.get('limit_events') and full_data.get('all_events'):
|
||||||
raise ValidationError('Do not set both limit_events and all_events.')
|
raise ValidationError('Do not set both limit_events and all_events.')
|
||||||
|
|
||||||
full_data.update(data)
|
|
||||||
for pg in get_all_event_permission_groups().values():
|
|
||||||
requested = ",".join(sorted(
|
|
||||||
a for a in pg.actions if self.instance and full_data["limit_event_permissions"].get(f"{pg.name}:{a}")
|
|
||||||
))
|
|
||||||
if requested not in (",".join(sorted(opt.actions)) for opt in pg.options):
|
|
||||||
possible = '\' or \''.join(','.join(opt.actions) for opt in pg.options)
|
|
||||||
raise ValidationError(f"For permission group {pg.name}, the valid combinations of actions are "
|
|
||||||
f"'{possible}' but you tried to set '{requested}'.")
|
|
||||||
for pg in get_all_organizer_permission_groups().values():
|
|
||||||
requested = ",".join(sorted(
|
|
||||||
a for a in pg.actions if self.instance and full_data["limit_organizer_permissions"].get(f"{pg.name}:{a}")
|
|
||||||
))
|
|
||||||
if requested not in (",".join(sorted(opt.actions)) for opt in pg.options):
|
|
||||||
possible = '\' or \''.join(','.join(opt.actions) for opt in pg.options)
|
|
||||||
raise ValidationError(f"For permission group {pg.name}, the valid combinations of actions are "
|
|
||||||
f"'{possible}' but you tried to set '{requested}'.")
|
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
@@ -451,7 +339,7 @@ class DeviceSerializer(serializers.ModelSerializer):
|
|||||||
created = serializers.DateTimeField(read_only=True)
|
created = serializers.DateTimeField(read_only=True)
|
||||||
revoked = serializers.BooleanField(read_only=True)
|
revoked = serializers.BooleanField(read_only=True)
|
||||||
initialized = serializers.DateTimeField(read_only=True)
|
initialized = serializers.DateTimeField(read_only=True)
|
||||||
initialization_token = serializers.CharField(read_only=True)
|
initialization_token = serializers.DateTimeField(read_only=True)
|
||||||
security_profile = serializers.ChoiceField(choices=[], required=False, default="full")
|
security_profile = serializers.ChoiceField(choices=[], required=False, default="full")
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@@ -465,8 +353,6 @@ class DeviceSerializer(serializers.ModelSerializer):
|
|||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.fields['security_profile'].choices = [(k, v.verbose_name) for k, v in get_all_security_profiles().items()]
|
self.fields['security_profile'].choices = [(k, v.verbose_name) for k, v in get_all_security_profiles().items()]
|
||||||
if not self.context['can_see_tokens']:
|
|
||||||
del self.fields['initialization_token']
|
|
||||||
|
|
||||||
|
|
||||||
class TeamInviteSerializer(serializers.ModelSerializer):
|
class TeamInviteSerializer(serializers.ModelSerializer):
|
||||||
@@ -553,10 +439,7 @@ class TeamMemberSerializer(serializers.ModelSerializer):
|
|||||||
|
|
||||||
|
|
||||||
class OrganizerSettingsSerializer(SettingsSerializer):
|
class OrganizerSettingsSerializer(SettingsSerializer):
|
||||||
default_write_permission = 'organizer.settings.general:write'
|
|
||||||
default_fields = [
|
default_fields = [
|
||||||
# These are readable for all users with access to the events, therefore secrets stored in the settings store
|
|
||||||
# should not be included!
|
|
||||||
'customer_accounts',
|
'customer_accounts',
|
||||||
'customer_accounts_native',
|
'customer_accounts_native',
|
||||||
'customer_accounts_link_by_email',
|
'customer_accounts_link_by_email',
|
||||||
|
|||||||
@@ -37,8 +37,6 @@ logger = logging.getLogger(__name__)
|
|||||||
class SettingsSerializer(serializers.Serializer):
|
class SettingsSerializer(serializers.Serializer):
|
||||||
default_fields = []
|
default_fields = []
|
||||||
readonly_fields = []
|
readonly_fields = []
|
||||||
default_write_permission = 'organizer.settings.general:write'
|
|
||||||
write_permission_required = {}
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
self.changed_data = []
|
self.changed_data = []
|
||||||
@@ -60,17 +58,9 @@ class SettingsSerializer(serializers.Serializer):
|
|||||||
f._label = str(form_kwargs.get('label', fname))
|
f._label = str(form_kwargs.get('label', fname))
|
||||||
f._help_text = str(form_kwargs.get('help_text'))
|
f._help_text = str(form_kwargs.get('help_text'))
|
||||||
f.parent = self
|
f.parent = self
|
||||||
|
|
||||||
self.write_permission_required[fname] = DEFAULTS[fname].get('write_permission', self.default_write_permission)
|
|
||||||
|
|
||||||
self.fields[fname] = f
|
self.fields[fname] = f
|
||||||
|
|
||||||
def validate(self, attrs):
|
def validate(self, attrs):
|
||||||
for k in attrs.keys():
|
|
||||||
p = self.write_permission_required.get(k, self.default_write_permission)
|
|
||||||
if p not in self.context["permissions"]:
|
|
||||||
raise ValidationError({k: f"Setting this field requires permission {p}"})
|
|
||||||
|
|
||||||
return {k: v for k, v in attrs.items() if k not in self.readonly_fields}
|
return {k: v for k, v in attrs.items() if k not in self.readonly_fields}
|
||||||
|
|
||||||
def update(self, instance: HierarkeyProxy, validated_data):
|
def update(self, instance: HierarkeyProxy, validated_data):
|
||||||
|
|||||||
@@ -52,8 +52,8 @@ class CartPositionViewSet(CreateModelMixin, DestroyModelMixin, viewsets.ReadOnly
|
|||||||
ordering = ('datetime',)
|
ordering = ('datetime',)
|
||||||
ordering_fields = ('datetime', 'cart_id')
|
ordering_fields = ('datetime', 'cart_id')
|
||||||
lookup_field = 'id'
|
lookup_field = 'id'
|
||||||
permission = 'event.orders:read'
|
permission = 'can_view_orders'
|
||||||
write_permission = 'event.orders:write'
|
write_permission = 'can_change_orders'
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return CartPosition.objects.filter(
|
return CartPosition.objects.filter(
|
||||||
|
|||||||
@@ -118,11 +118,11 @@ class CheckinListViewSet(viewsets.ModelViewSet):
|
|||||||
|
|
||||||
def _get_permission_name(self, request):
|
def _get_permission_name(self, request):
|
||||||
if request.path.endswith('/failed_checkins/'):
|
if request.path.endswith('/failed_checkins/'):
|
||||||
return 'event.orders:checkin', 'event.orders:write'
|
return 'can_checkin_orders', 'can_change_orders'
|
||||||
elif request.method in SAFE_METHODS:
|
elif request.method in SAFE_METHODS:
|
||||||
return 'event.orders:read', 'event.orders:checkin',
|
return 'can_view_orders', 'can_checkin_orders',
|
||||||
else:
|
else:
|
||||||
return 'event.settings.general:write'
|
return 'can_change_event_settings'
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
qs = self.request.event.checkin_lists.prefetch_related(
|
qs = self.request.event.checkin_lists.prefetch_related(
|
||||||
@@ -381,21 +381,15 @@ def _checkin_list_position_queryset(checkinlists, ignore_status=False, ignore_pr
|
|||||||
|
|
||||||
qs = qs.filter(reduce(operator.or_, lists_qs))
|
qs = qs.filter(reduce(operator.or_, lists_qs))
|
||||||
|
|
||||||
prefetch_related = [
|
|
||||||
Prefetch(
|
|
||||||
lookup='checkins',
|
|
||||||
queryset=Checkin.objects.filter(list_id__in=[cl.pk for cl in checkinlists]).select_related('device')
|
|
||||||
),
|
|
||||||
Prefetch('print_logs', queryset=PrintLog.objects.select_related('device')),
|
|
||||||
'answers', 'answers__options', 'answers__question',
|
|
||||||
]
|
|
||||||
select_related = [
|
|
||||||
'item', 'variation', 'order', 'addon_to', 'order__invoice_address', 'order', 'seat'
|
|
||||||
]
|
|
||||||
|
|
||||||
if pdf_data:
|
if pdf_data:
|
||||||
qs = qs.prefetch_related(
|
qs = qs.prefetch_related(
|
||||||
# Don't add to list, we don't want to propagate to addons
|
Prefetch(
|
||||||
|
lookup='checkins',
|
||||||
|
queryset=Checkin.objects.filter(list_id__in=[cl.pk for cl in checkinlists]).select_related('device')
|
||||||
|
),
|
||||||
|
Prefetch('print_logs', queryset=PrintLog.objects.select_related('device')),
|
||||||
|
'answers', 'answers__options', 'answers__question',
|
||||||
|
Prefetch('addons', OrderPosition.objects.select_related('item', 'variation')),
|
||||||
Prefetch('order', Order.objects.select_related('invoice_address').prefetch_related(
|
Prefetch('order', Order.objects.select_related('invoice_address').prefetch_related(
|
||||||
Prefetch(
|
Prefetch(
|
||||||
'event',
|
'event',
|
||||||
@@ -410,39 +404,32 @@ def _checkin_list_position_queryset(checkinlists, ignore_status=False, ignore_pr
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
))
|
))
|
||||||
|
).select_related(
|
||||||
|
'item', 'variation', 'item__category', 'addon_to', 'order', 'order__invoice_address', 'seat'
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
|
qs = qs.prefetch_related(
|
||||||
|
Prefetch(
|
||||||
|
lookup='checkins',
|
||||||
|
queryset=Checkin.objects.filter(list_id__in=[cl.pk for cl in checkinlists]).select_related('device')
|
||||||
|
),
|
||||||
|
Prefetch('print_logs', queryset=PrintLog.objects.select_related('device')),
|
||||||
|
'answers', 'answers__options', 'answers__question',
|
||||||
|
Prefetch('addons', OrderPosition.objects.select_related('item', 'variation'))
|
||||||
|
).select_related('item', 'variation', 'order', 'addon_to', 'order__invoice_address', 'order', 'seat')
|
||||||
|
|
||||||
if expand and 'subevent' in expand:
|
if expand and 'subevent' in expand:
|
||||||
prefetch_related += [
|
qs = qs.prefetch_related(
|
||||||
'subevent', 'subevent__event', 'subevent__subeventitem_set', 'subevent__subeventitemvariation_set',
|
'subevent', 'subevent__event', 'subevent__subeventitem_set', 'subevent__subeventitemvariation_set',
|
||||||
'subevent__seat_category_mappings', 'subevent__meta_values'
|
'subevent__seat_category_mappings', 'subevent__meta_values'
|
||||||
]
|
)
|
||||||
|
|
||||||
if expand and 'item' in expand:
|
if expand and 'item' in expand:
|
||||||
prefetch_related += [
|
qs = qs.prefetch_related('item', 'item__addons', 'item__bundles', 'item__meta_values',
|
||||||
'item', 'item__addons', 'item__bundles', 'item__meta_values',
|
'item__variations').select_related('item__tax_rule')
|
||||||
'item__variations',
|
|
||||||
]
|
|
||||||
select_related.append('item__tax_rule')
|
|
||||||
|
|
||||||
if expand and 'variation' in expand:
|
if expand and 'variation' in expand:
|
||||||
prefetch_related += [
|
qs = qs.prefetch_related('variation', 'variation__meta_values')
|
||||||
'variation', 'variation__meta_values',
|
|
||||||
]
|
|
||||||
|
|
||||||
if expand and 'addons' in expand:
|
|
||||||
prefetch_related += [
|
|
||||||
Prefetch('addons', OrderPosition.objects.prefetch_related(*prefetch_related).select_related(*select_related)),
|
|
||||||
]
|
|
||||||
else:
|
|
||||||
prefetch_related += [
|
|
||||||
Prefetch('addons', OrderPosition.objects.select_related('item', 'variation'))
|
|
||||||
]
|
|
||||||
|
|
||||||
if pdf_data:
|
|
||||||
select_related.remove("order") # Don't need it twice on this queryset
|
|
||||||
|
|
||||||
qs = qs.prefetch_related(*prefetch_related).select_related(*select_related)
|
|
||||||
|
|
||||||
return qs
|
return qs
|
||||||
|
|
||||||
@@ -470,7 +457,7 @@ def _redeem_process(*, checkinlists, raw_barcode, answers_data, datetime, force,
|
|||||||
'event': op.order.event,
|
'event': op.order.event,
|
||||||
'pdf_data': pdf_data and (
|
'pdf_data': pdf_data and (
|
||||||
user if user and user.is_authenticated else auth
|
user if user and user.is_authenticated else auth
|
||||||
).has_event_permission(request.organizer, event, 'event.orders:read', request),
|
).has_event_permission(request.organizer, event, 'can_view_orders', request),
|
||||||
}
|
}
|
||||||
|
|
||||||
common_checkin_args = dict(
|
common_checkin_args = dict(
|
||||||
@@ -835,8 +822,8 @@ class CheckinListPositionViewSet(viewsets.ReadOnlyModelViewSet):
|
|||||||
}
|
}
|
||||||
|
|
||||||
filterset_class = CheckinOrderPositionFilter
|
filterset_class = CheckinOrderPositionFilter
|
||||||
permission = ('event.orders:read', 'event.orders:checkin')
|
permission = ('can_view_orders', 'can_checkin_orders')
|
||||||
write_permission = ('event.orders:write', 'event.orders:checkin')
|
write_permission = ('can_change_orders', 'can_checkin_orders')
|
||||||
|
|
||||||
def get_serializer_context(self):
|
def get_serializer_context(self):
|
||||||
ctx = super().get_serializer_context()
|
ctx = super().get_serializer_context()
|
||||||
@@ -867,7 +854,7 @@ class CheckinListPositionViewSet(viewsets.ReadOnlyModelViewSet):
|
|||||||
expand=self.request.query_params.getlist('expand'),
|
expand=self.request.query_params.getlist('expand'),
|
||||||
)
|
)
|
||||||
|
|
||||||
if 'pk' not in self.request.resolver_match.kwargs and 'event.orders:read' not in self.request.eventpermset \
|
if 'pk' not in self.request.resolver_match.kwargs and 'can_view_orders' not in self.request.eventpermset \
|
||||||
and len(self.request.query_params.get('search', '')) < 3:
|
and len(self.request.query_params.get('search', '')) < 3:
|
||||||
qs = qs.none()
|
qs = qs.none()
|
||||||
|
|
||||||
@@ -916,9 +903,9 @@ class CheckinListPositionViewSet(viewsets.ReadOnlyModelViewSet):
|
|||||||
class CheckinRPCRedeemView(views.APIView):
|
class CheckinRPCRedeemView(views.APIView):
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
if isinstance(self.request.auth, (TeamAPIToken, Device)):
|
if isinstance(self.request.auth, (TeamAPIToken, Device)):
|
||||||
events = self.request.auth.get_events_with_permission(('event.orders:write', 'event.orders:checkin'))
|
events = self.request.auth.get_events_with_permission(('can_change_orders', 'can_checkin_orders'))
|
||||||
elif self.request.user.is_authenticated:
|
elif self.request.user.is_authenticated:
|
||||||
events = self.request.user.get_events_with_permission(('event.orders:write', 'event.orders:checkin'), self.request).filter(
|
events = self.request.user.get_events_with_permission(('can_change_orders', 'can_checkin_orders'), self.request).filter(
|
||||||
organizer=self.request.organizer
|
organizer=self.request.organizer
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
@@ -979,16 +966,15 @@ class CheckinRPCSearchView(ListAPIView):
|
|||||||
def get_serializer_context(self):
|
def get_serializer_context(self):
|
||||||
ctx = super().get_serializer_context()
|
ctx = super().get_serializer_context()
|
||||||
ctx['expand'] = self.request.query_params.getlist('expand')
|
ctx['expand'] = self.request.query_params.getlist('expand')
|
||||||
ctx['organizer'] = self.request.organizer
|
|
||||||
ctx['pdf_data'] = False
|
ctx['pdf_data'] = False
|
||||||
return ctx
|
return ctx
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def lists(self):
|
def lists(self):
|
||||||
if isinstance(self.request.auth, (TeamAPIToken, Device)):
|
if isinstance(self.request.auth, (TeamAPIToken, Device)):
|
||||||
events = self.request.auth.get_events_with_permission(('event.orders:read', 'event.orders:checkin'))
|
events = self.request.auth.get_events_with_permission(('can_view_orders', 'can_checkin_orders'))
|
||||||
elif self.request.user.is_authenticated:
|
elif self.request.user.is_authenticated:
|
||||||
events = self.request.user.get_events_with_permission(('event.orders:read', 'event.orders:checkin'), self.request).filter(
|
events = self.request.user.get_events_with_permission(('can_view_orders', 'can_checkin_orders'), self.request).filter(
|
||||||
organizer=self.request.organizer
|
organizer=self.request.organizer
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
@@ -1005,9 +991,9 @@ class CheckinRPCSearchView(ListAPIView):
|
|||||||
@cached_property
|
@cached_property
|
||||||
def has_full_access_permission(self):
|
def has_full_access_permission(self):
|
||||||
if isinstance(self.request.auth, (TeamAPIToken, Device)):
|
if isinstance(self.request.auth, (TeamAPIToken, Device)):
|
||||||
events = self.request.auth.get_events_with_permission('event.orders:read')
|
events = self.request.auth.get_events_with_permission('can_view_orders')
|
||||||
elif self.request.user.is_authenticated:
|
elif self.request.user.is_authenticated:
|
||||||
events = self.request.user.get_events_with_permission('event.orders:read', self.request).filter(
|
events = self.request.user.get_events_with_permission('can_view_orders', self.request).filter(
|
||||||
organizer=self.request.organizer
|
organizer=self.request.organizer
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
@@ -1034,9 +1020,9 @@ class CheckinRPCSearchView(ListAPIView):
|
|||||||
class CheckinRPCAnnulView(views.APIView):
|
class CheckinRPCAnnulView(views.APIView):
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
if isinstance(self.request.auth, (TeamAPIToken, Device)):
|
if isinstance(self.request.auth, (TeamAPIToken, Device)):
|
||||||
events = self.request.auth.get_events_with_permission(('event.orders:write', 'event.orders:checkin'))
|
events = self.request.auth.get_events_with_permission(('can_change_orders', 'can_checkin_orders'))
|
||||||
elif self.request.user.is_authenticated:
|
elif self.request.user.is_authenticated:
|
||||||
events = self.request.user.get_events_with_permission(('event.orders:write', 'event.orders:checkin'), self.request).filter(
|
events = self.request.user.get_events_with_permission(('can_change_orders', 'can_checkin_orders'), self.request).filter(
|
||||||
organizer=self.request.organizer
|
organizer=self.request.organizer
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
@@ -1114,7 +1100,7 @@ class CheckinViewSet(viewsets.ReadOnlyModelViewSet):
|
|||||||
filterset_class = CheckinFilter
|
filterset_class = CheckinFilter
|
||||||
ordering = ('created', 'id')
|
ordering = ('created', 'id')
|
||||||
ordering_fields = ('created', 'datetime', 'id',)
|
ordering_fields = ('created', 'datetime', 'id',)
|
||||||
permission = 'event.orders:read'
|
permission = 'can_view_orders'
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
qs = Checkin.all.filter().select_related(
|
qs = Checkin.all.filter().select_related(
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ class DiscountViewSet(ConditionalListView, viewsets.ModelViewSet):
|
|||||||
ordering_fields = ('id', 'position')
|
ordering_fields = ('id', 'position')
|
||||||
ordering = ('position', 'id')
|
ordering = ('position', 'id')
|
||||||
permission = None
|
permission = None
|
||||||
write_permission = 'event.items:write'
|
write_permission = 'can_change_items'
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return self.request.event.discounts.prefetch_related(
|
return self.request.event.discounts.prefetch_related(
|
||||||
|
|||||||
@@ -341,7 +341,7 @@ class CloneEventViewSet(viewsets.ModelViewSet):
|
|||||||
lookup_field = 'slug'
|
lookup_field = 'slug'
|
||||||
lookup_url_kwarg = 'event'
|
lookup_url_kwarg = 'event'
|
||||||
http_method_names = ['post']
|
http_method_names = ['post']
|
||||||
write_permission = 'event.settings.general:write'
|
write_permission = 'can_create_events'
|
||||||
|
|
||||||
def get_serializer_context(self):
|
def get_serializer_context(self):
|
||||||
ctx = super().get_serializer_context()
|
ctx = super().get_serializer_context()
|
||||||
@@ -350,12 +350,6 @@ class CloneEventViewSet(viewsets.ModelViewSet):
|
|||||||
return ctx
|
return ctx
|
||||||
|
|
||||||
def perform_create(self, serializer):
|
def perform_create(self, serializer):
|
||||||
# Weird edge case: Requires settings permission on the event (to read) but also on the organizer (two write)
|
|
||||||
perm_holder = (self.request.auth if isinstance(self.request.auth, (Device, TeamAPIToken))
|
|
||||||
else self.request.user)
|
|
||||||
if not perm_holder.has_organizer_permission(self.request.organizer, "organizer.events:create", request=self.request):
|
|
||||||
raise PermissionDenied("No permission to create events")
|
|
||||||
|
|
||||||
serializer.save(organizer=self.request.organizer)
|
serializer.save(organizer=self.request.organizer)
|
||||||
|
|
||||||
serializer.instance.log_action(
|
serializer.instance.log_action(
|
||||||
@@ -432,7 +426,7 @@ with scopes_disabled():
|
|||||||
class SubEventViewSet(ConditionalListView, viewsets.ModelViewSet):
|
class SubEventViewSet(ConditionalListView, viewsets.ModelViewSet):
|
||||||
serializer_class = SubEventSerializer
|
serializer_class = SubEventSerializer
|
||||||
queryset = SubEvent.objects.none()
|
queryset = SubEvent.objects.none()
|
||||||
write_permission = 'event.subevents:write'
|
write_permission = 'can_change_event_settings'
|
||||||
filter_backends = (DjangoFilterBackend, TotalOrderingFilter)
|
filter_backends = (DjangoFilterBackend, TotalOrderingFilter)
|
||||||
ordering = ('date_from',)
|
ordering = ('date_from',)
|
||||||
ordering_fields = ('id', 'date_from', 'last_modified')
|
ordering_fields = ('id', 'date_from', 'last_modified')
|
||||||
@@ -552,7 +546,7 @@ class SubEventViewSet(ConditionalListView, viewsets.ModelViewSet):
|
|||||||
class TaxRuleViewSet(ConditionalListView, viewsets.ModelViewSet):
|
class TaxRuleViewSet(ConditionalListView, viewsets.ModelViewSet):
|
||||||
serializer_class = TaxRuleSerializer
|
serializer_class = TaxRuleSerializer
|
||||||
queryset = TaxRule.objects.none()
|
queryset = TaxRule.objects.none()
|
||||||
write_permission = 'event.settings.tax:write'
|
write_permission = 'can_change_event_settings'
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return self.request.event.tax_rules.all()
|
return self.request.event.tax_rules.all()
|
||||||
@@ -595,7 +589,7 @@ class TaxRuleViewSet(ConditionalListView, viewsets.ModelViewSet):
|
|||||||
class ItemMetaPropertiesViewSet(viewsets.ModelViewSet):
|
class ItemMetaPropertiesViewSet(viewsets.ModelViewSet):
|
||||||
serializer_class = ItemMetaPropertiesSerializer
|
serializer_class = ItemMetaPropertiesSerializer
|
||||||
queryset = ItemMetaProperty.objects.none()
|
queryset = ItemMetaProperty.objects.none()
|
||||||
write_permission = 'event.settings.general:write'
|
write_permission = 'can_change_event_settings'
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
qs = self.request.event.item_meta_properties.all()
|
qs = self.request.event.item_meta_properties.all()
|
||||||
@@ -642,18 +636,19 @@ class ItemMetaPropertiesViewSet(viewsets.ModelViewSet):
|
|||||||
|
|
||||||
class EventSettingsView(views.APIView):
|
class EventSettingsView(views.APIView):
|
||||||
permission = None
|
permission = None
|
||||||
write_permission = 'event.settings.general:write'
|
write_permission = 'can_change_event_settings'
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
if isinstance(request.auth, Device):
|
if isinstance(request.auth, Device):
|
||||||
s = DeviceEventSettingsSerializer(instance=request.event.settings, event=request.event, context={
|
s = DeviceEventSettingsSerializer(instance=request.event.settings, event=request.event, context={
|
||||||
'request': request, 'permissions': request.eventpermset
|
'request': request
|
||||||
|
})
|
||||||
|
elif 'can_change_event_settings' in request.eventpermset:
|
||||||
|
s = EventSettingsSerializer(instance=request.event.settings, event=request.event, context={
|
||||||
|
'request': request
|
||||||
})
|
})
|
||||||
else:
|
else:
|
||||||
s = EventSettingsSerializer(instance=request.event.settings, event=request.event, context={
|
raise PermissionDenied()
|
||||||
'request': request, 'permissions': request.eventpermset,
|
|
||||||
})
|
|
||||||
|
|
||||||
if 'explain' in request.GET:
|
if 'explain' in request.GET:
|
||||||
return Response({
|
return Response({
|
||||||
fname: {
|
fname: {
|
||||||
@@ -667,7 +662,7 @@ class EventSettingsView(views.APIView):
|
|||||||
|
|
||||||
def patch(self, request, *wargs, **kwargs):
|
def patch(self, request, *wargs, **kwargs):
|
||||||
s = EventSettingsSerializer(instance=request.event.settings, data=request.data, partial=True,
|
s = EventSettingsSerializer(instance=request.event.settings, data=request.data, partial=True,
|
||||||
event=request.event, context={'request': request, 'permissions': request.eventpermset})
|
event=request.event, context={'request': request})
|
||||||
s.is_valid(raise_exception=True)
|
s.is_valid(raise_exception=True)
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
s.save()
|
s.save()
|
||||||
@@ -679,7 +674,7 @@ class EventSettingsView(views.APIView):
|
|||||||
)
|
)
|
||||||
s = EventSettingsSerializer(
|
s = EventSettingsSerializer(
|
||||||
instance=request.event.settings, event=request.event, context={
|
instance=request.event.settings, event=request.event, context={
|
||||||
'request': request, 'permissions': request.eventpermset
|
'request': request
|
||||||
})
|
})
|
||||||
return Response(s.data)
|
return Response(s.data)
|
||||||
|
|
||||||
@@ -706,7 +701,7 @@ class SeatFilter(FilterSet):
|
|||||||
class SeatViewSet(ConditionalListView, viewsets.ModelViewSet):
|
class SeatViewSet(ConditionalListView, viewsets.ModelViewSet):
|
||||||
serializer_class = SeatSerializer
|
serializer_class = SeatSerializer
|
||||||
queryset = Seat.objects.none()
|
queryset = Seat.objects.none()
|
||||||
write_permission = 'event.settings.general:write'
|
write_permission = 'can_change_event_settings'
|
||||||
filter_backends = (DjangoFilterBackend, )
|
filter_backends = (DjangoFilterBackend, )
|
||||||
filterset_class = SeatFilter
|
filterset_class = SeatFilter
|
||||||
|
|
||||||
|
|||||||
@@ -38,12 +38,14 @@ from pretix.api.serializers.exporters import (
|
|||||||
ExporterSerializer, JobRunSerializer, ScheduledEventExportSerializer,
|
ExporterSerializer, JobRunSerializer, ScheduledEventExportSerializer,
|
||||||
ScheduledOrganizerExportSerializer,
|
ScheduledOrganizerExportSerializer,
|
||||||
)
|
)
|
||||||
|
from pretix.base.exporter import OrganizerLevelExportMixin
|
||||||
from pretix.base.models import (
|
from pretix.base.models import (
|
||||||
CachedFile, Device, ScheduledEventExport, ScheduledOrganizerExport,
|
CachedFile, Device, Event, ScheduledEventExport, ScheduledOrganizerExport,
|
||||||
TeamAPIToken,
|
TeamAPIToken,
|
||||||
)
|
)
|
||||||
from pretix.base.services.export import (
|
from pretix.base.services.export import export, multiexport
|
||||||
export, init_event_exporters, init_organizer_exporters, multiexport,
|
from pretix.base.signals import (
|
||||||
|
register_data_exporters, register_multievent_data_exporters,
|
||||||
)
|
)
|
||||||
from pretix.helpers.http import ChunkBasedFileResponse
|
from pretix.helpers.http import ChunkBasedFileResponse
|
||||||
|
|
||||||
@@ -109,7 +111,7 @@ class ExportersMixin:
|
|||||||
@action(detail=True, methods=['POST'])
|
@action(detail=True, methods=['POST'])
|
||||||
def run(self, *args, **kwargs):
|
def run(self, *args, **kwargs):
|
||||||
instance = self.get_object()
|
instance = self.get_object()
|
||||||
serializer = JobRunSerializer(exporter=instance, data=self.request.data)
|
serializer = JobRunSerializer(exporter=instance, data=self.request.data, **self.get_serializer_kwargs())
|
||||||
serializer.is_valid(raise_exception=True)
|
serializer.is_valid(raise_exception=True)
|
||||||
|
|
||||||
cf = CachedFile(web_download=True)
|
cf = CachedFile(web_download=True)
|
||||||
@@ -134,34 +136,27 @@ class ExportersMixin:
|
|||||||
|
|
||||||
|
|
||||||
class EventExportersViewSet(ExportersMixin, viewsets.ViewSet):
|
class EventExportersViewSet(ExportersMixin, viewsets.ViewSet):
|
||||||
permission = None
|
permission = 'can_view_orders'
|
||||||
|
|
||||||
|
def get_serializer_kwargs(self):
|
||||||
|
return {}
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def exporters(self):
|
def exporters(self):
|
||||||
raw_exporters = list(init_event_exporters(
|
|
||||||
event=self.request.event,
|
|
||||||
user=self.request.user if self.request.user and self.request.user.is_authenticated else None,
|
|
||||||
token=self.request.auth if isinstance(self.request.auth, TeamAPIToken) else None,
|
|
||||||
device=self.request.auth if isinstance(self.request.auth, Device) else None,
|
|
||||||
request=self.request,
|
|
||||||
))
|
|
||||||
exporters = []
|
exporters = []
|
||||||
|
responses = register_data_exporters.send(self.request.event)
|
||||||
|
raw_exporters = [response(self.request.event, self.request.organizer) for r, response in responses if response]
|
||||||
|
raw_exporters = [
|
||||||
|
ex for ex in raw_exporters
|
||||||
|
if ex.available_for_user(self.request.user if self.request.user and self.request.user.is_authenticated else None)
|
||||||
|
]
|
||||||
for ex in sorted(raw_exporters, key=lambda ex: str(ex.verbose_name)):
|
for ex in sorted(raw_exporters, key=lambda ex: str(ex.verbose_name)):
|
||||||
ex._serializer = JobRunSerializer(exporter=ex)
|
ex._serializer = JobRunSerializer(exporter=ex)
|
||||||
exporters.append(ex)
|
exporters.append(ex)
|
||||||
return exporters
|
return exporters
|
||||||
|
|
||||||
def do_export(self, cf, instance, data):
|
def do_export(self, cf, instance, data):
|
||||||
return export.apply_async(args=(
|
return export.apply_async(args=(self.request.event.id, str(cf.id), instance.identifier, data))
|
||||||
self.request.event.id,
|
|
||||||
), kwargs={
|
|
||||||
'user': self.request.user.pk if self.request.user and self.request.user.is_authenticated else None,
|
|
||||||
'token': self.request.auth.pk if isinstance(self.request.auth, TeamAPIToken) else None,
|
|
||||||
'device': self.request.auth.pk if isinstance(self.request.auth, Device) else None,
|
|
||||||
'fileid': str(cf.id),
|
|
||||||
'provider': instance.identifier,
|
|
||||||
'form_data': data,
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
class OrganizerExportersViewSet(ExportersMixin, viewsets.ViewSet):
|
class OrganizerExportersViewSet(ExportersMixin, viewsets.ViewSet):
|
||||||
@@ -169,23 +164,47 @@ class OrganizerExportersViewSet(ExportersMixin, viewsets.ViewSet):
|
|||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def exporters(self):
|
def exporters(self):
|
||||||
raw_exporters = list(init_organizer_exporters(
|
|
||||||
organizer=self.request.organizer,
|
|
||||||
user=self.request.user if self.request.user and self.request.user.is_authenticated else None,
|
|
||||||
token=self.request.auth if isinstance(self.request.auth, TeamAPIToken) else None,
|
|
||||||
device=self.request.auth if isinstance(self.request.auth, Device) else None,
|
|
||||||
request=self.request,
|
|
||||||
))
|
|
||||||
exporters = []
|
exporters = []
|
||||||
|
if isinstance(self.request.auth, (Device, TeamAPIToken)):
|
||||||
|
perm_holder = self.request.auth
|
||||||
|
else:
|
||||||
|
perm_holder = self.request.user
|
||||||
|
events = perm_holder.get_events_with_permission('can_view_orders', request=self.request).filter(
|
||||||
|
organizer=self.request.organizer
|
||||||
|
)
|
||||||
|
responses = register_multievent_data_exporters.send(self.request.organizer)
|
||||||
|
raw_exporters = [
|
||||||
|
response(Event.objects.none() if issubclass(response, OrganizerLevelExportMixin) else events, self.request.organizer)
|
||||||
|
for r, response in responses
|
||||||
|
if response
|
||||||
|
]
|
||||||
|
raw_exporters = [
|
||||||
|
ex for ex in raw_exporters
|
||||||
|
if (
|
||||||
|
not isinstance(ex, OrganizerLevelExportMixin) or
|
||||||
|
perm_holder.has_organizer_permission(self.request.organizer, ex.organizer_required_permission, self.request)
|
||||||
|
) and ex.available_for_user(self.request.user if self.request.user and self.request.user.is_authenticated else None)
|
||||||
|
]
|
||||||
for ex in sorted(raw_exporters, key=lambda ex: str(ex.verbose_name)):
|
for ex in sorted(raw_exporters, key=lambda ex: str(ex.verbose_name)):
|
||||||
ex._serializer = JobRunSerializer(exporter=ex)
|
ex._serializer = JobRunSerializer(exporter=ex, events=events)
|
||||||
exporters.append(ex)
|
exporters.append(ex)
|
||||||
return exporters
|
return exporters
|
||||||
|
|
||||||
|
def get_serializer_kwargs(self):
|
||||||
|
if isinstance(self.request.auth, (Device, TeamAPIToken)):
|
||||||
|
perm_holder = self.request.auth
|
||||||
|
else:
|
||||||
|
perm_holder = self.request.user
|
||||||
|
return {
|
||||||
|
'events': perm_holder.get_events_with_permission('can_view_orders', request=self.request).filter(
|
||||||
|
organizer=self.request.organizer
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
def do_export(self, cf, instance, data):
|
def do_export(self, cf, instance, data):
|
||||||
return multiexport.apply_async(kwargs={
|
return multiexport.apply_async(kwargs={
|
||||||
'organizer': self.request.organizer.id,
|
'organizer': self.request.organizer.id,
|
||||||
'user': self.request.user.id if self.request.user and self.request.user.is_authenticated else None,
|
'user': self.request.user.id if self.request.user.is_authenticated else None,
|
||||||
'token': self.request.auth.pk if isinstance(self.request.auth, TeamAPIToken) else None,
|
'token': self.request.auth.pk if isinstance(self.request.auth, TeamAPIToken) else None,
|
||||||
'device': self.request.auth.pk if isinstance(self.request.auth, Device) else None,
|
'device': self.request.auth.pk if isinstance(self.request.auth, Device) else None,
|
||||||
'fileid': str(cf.id),
|
'fileid': str(cf.id),
|
||||||
@@ -203,11 +222,11 @@ class ScheduledExportersViewSet(viewsets.ModelViewSet):
|
|||||||
class ScheduledEventExportViewSet(ScheduledExportersViewSet):
|
class ScheduledEventExportViewSet(ScheduledExportersViewSet):
|
||||||
serializer_class = ScheduledEventExportSerializer
|
serializer_class = ScheduledEventExportSerializer
|
||||||
queryset = ScheduledEventExport.objects.none()
|
queryset = ScheduledEventExport.objects.none()
|
||||||
permission = 'event.orders:read'
|
permission = 'can_view_orders'
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
perm_holder = self.request.auth if isinstance(self.request.auth, (TeamAPIToken, Device)) else self.request.user
|
perm_holder = self.request.auth if isinstance(self.request.auth, (TeamAPIToken, Device)) else self.request.user
|
||||||
if not perm_holder.has_event_permission(self.request.organizer, self.request.event, 'event.settings.general:write',
|
if not perm_holder.has_event_permission(self.request.organizer, self.request.event, 'can_change_event_settings',
|
||||||
request=self.request):
|
request=self.request):
|
||||||
if self.request.user.is_authenticated:
|
if self.request.user.is_authenticated:
|
||||||
qs = self.request.event.scheduled_exports.filter(owner=self.request.user)
|
qs = self.request.event.scheduled_exports.filter(owner=self.request.user)
|
||||||
@@ -239,13 +258,8 @@ class ScheduledEventExportViewSet(ScheduledExportersViewSet):
|
|||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def exporters(self):
|
def exporters(self):
|
||||||
exporters = list(init_event_exporters(
|
responses = register_data_exporters.send(self.request.event)
|
||||||
event=self.request.event,
|
exporters = [response(self.request.event, self.request.organizer) for r, response in responses if response]
|
||||||
user=self.request.user if self.request.user and self.request.user.is_authenticated else None,
|
|
||||||
token=self.request.auth if isinstance(self.request.auth, TeamAPIToken) else None,
|
|
||||||
device=self.request.auth if isinstance(self.request.auth, Device) else None,
|
|
||||||
request=self.request,
|
|
||||||
))
|
|
||||||
return {e.identifier: e for e in exporters}
|
return {e.identifier: e for e in exporters}
|
||||||
|
|
||||||
def perform_update(self, serializer):
|
def perform_update(self, serializer):
|
||||||
@@ -277,7 +291,7 @@ class ScheduledOrganizerExportViewSet(ScheduledExportersViewSet):
|
|||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
perm_holder = self.request.auth if isinstance(self.request.auth, (TeamAPIToken, Device)) else self.request.user
|
perm_holder = self.request.auth if isinstance(self.request.auth, (TeamAPIToken, Device)) else self.request.user
|
||||||
if not perm_holder.has_organizer_permission(self.request.organizer, 'organizer.settings.general:write',
|
if not perm_holder.has_organizer_permission(self.request.organizer, 'can_change_organizer_settings',
|
||||||
request=self.request):
|
request=self.request):
|
||||||
if self.request.user.is_authenticated:
|
if self.request.user.is_authenticated:
|
||||||
qs = self.request.organizer.scheduled_exports.filter(owner=self.request.user)
|
qs = self.request.organizer.scheduled_exports.filter(owner=self.request.user)
|
||||||
@@ -307,15 +321,23 @@ class ScheduledOrganizerExportViewSet(ScheduledExportersViewSet):
|
|||||||
ctx['exporters'] = self.exporters
|
ctx['exporters'] = self.exporters
|
||||||
return ctx
|
return ctx
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def events(self):
|
||||||
|
if isinstance(self.request.auth, (TeamAPIToken, Device)):
|
||||||
|
return self.request.auth.get_events_with_permission('can_view_orders')
|
||||||
|
elif self.request.user.is_authenticated:
|
||||||
|
return self.request.user.get_events_with_permission('can_view_orders', self.request).filter(
|
||||||
|
organizer=self.request.organizer
|
||||||
|
)
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def exporters(self):
|
def exporters(self):
|
||||||
exporters = list(init_organizer_exporters(
|
responses = register_multievent_data_exporters.send(self.request.organizer)
|
||||||
organizer=self.request.organizer,
|
exporters = [
|
||||||
user=self.request.user if self.request.user and self.request.user.is_authenticated else None,
|
response(Event.objects.none() if issubclass(response, OrganizerLevelExportMixin) else self.events,
|
||||||
token=self.request.auth if isinstance(self.request.auth, TeamAPIToken) else None,
|
self.request.organizer)
|
||||||
device=self.request.auth if isinstance(self.request.auth, Device) else None,
|
for r, response in responses if response
|
||||||
request=self.request,
|
]
|
||||||
))
|
|
||||||
return {e.identifier: e for e in exporters}
|
return {e.identifier: e for e in exporters}
|
||||||
|
|
||||||
def perform_update(self, serializer):
|
def perform_update(self, serializer):
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ class ItemViewSet(ConditionalListView, viewsets.ModelViewSet):
|
|||||||
ordering = ('position', 'id')
|
ordering = ('position', 'id')
|
||||||
filterset_class = ItemFilter
|
filterset_class = ItemFilter
|
||||||
permission = None
|
permission = None
|
||||||
write_permission = 'event.items:write'
|
write_permission = 'can_change_items'
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return self.request.event.items.select_related('tax_rule').prefetch_related(
|
return self.request.event.items.select_related('tax_rule').prefetch_related(
|
||||||
@@ -163,7 +163,7 @@ class ItemVariationViewSet(viewsets.ModelViewSet):
|
|||||||
ordering_fields = ('id', 'position')
|
ordering_fields = ('id', 'position')
|
||||||
ordering = ('id',)
|
ordering = ('id',)
|
||||||
permission = None
|
permission = None
|
||||||
write_permission = 'event.items:write'
|
write_permission = 'can_change_items'
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def item(self):
|
def item(self):
|
||||||
@@ -234,7 +234,7 @@ class ItemBundleViewSet(viewsets.ModelViewSet):
|
|||||||
ordering_fields = ('id',)
|
ordering_fields = ('id',)
|
||||||
ordering = ('id',)
|
ordering = ('id',)
|
||||||
permission = None
|
permission = None
|
||||||
write_permission = 'event.items:write'
|
write_permission = 'can_change_items'
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def item(self):
|
def item(self):
|
||||||
@@ -286,7 +286,7 @@ class ItemProgramTimeViewSet(viewsets.ModelViewSet):
|
|||||||
ordering_fields = ('id',)
|
ordering_fields = ('id',)
|
||||||
ordering = ('id',)
|
ordering = ('id',)
|
||||||
permission = None
|
permission = None
|
||||||
write_permission = 'event.items:write'
|
write_permission = 'can_change_items'
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def item(self):
|
def item(self):
|
||||||
@@ -339,7 +339,7 @@ class ItemAddOnViewSet(viewsets.ModelViewSet):
|
|||||||
ordering_fields = ('id', 'position')
|
ordering_fields = ('id', 'position')
|
||||||
ordering = ('id',)
|
ordering = ('id',)
|
||||||
permission = None
|
permission = None
|
||||||
write_permission = 'event.items:write'
|
write_permission = 'can_change_items'
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def item(self):
|
def item(self):
|
||||||
@@ -398,7 +398,7 @@ class ItemCategoryViewSet(ConditionalListView, viewsets.ModelViewSet):
|
|||||||
ordering_fields = ('id', 'position')
|
ordering_fields = ('id', 'position')
|
||||||
ordering = ('position', 'id')
|
ordering = ('position', 'id')
|
||||||
permission = None
|
permission = None
|
||||||
write_permission = 'event.items:write'
|
write_permission = 'can_change_items'
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return self.request.event.categories.all()
|
return self.request.event.categories.all()
|
||||||
@@ -453,7 +453,7 @@ class QuestionViewSet(ConditionalListView, viewsets.ModelViewSet):
|
|||||||
ordering_fields = ('id', 'position')
|
ordering_fields = ('id', 'position')
|
||||||
ordering = ('position', 'id')
|
ordering = ('position', 'id')
|
||||||
permission = None
|
permission = None
|
||||||
write_permission = 'event.items:write'
|
write_permission = 'can_change_items'
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return self.request.event.questions.prefetch_related('options').all()
|
return self.request.event.questions.prefetch_related('options').all()
|
||||||
@@ -497,7 +497,7 @@ class QuestionOptionViewSet(viewsets.ModelViewSet):
|
|||||||
ordering_fields = ('id', 'position')
|
ordering_fields = ('id', 'position')
|
||||||
ordering = ('position',)
|
ordering = ('position',)
|
||||||
permission = None
|
permission = None
|
||||||
write_permission = 'event.items:write'
|
write_permission = 'can_change_items'
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
q = get_object_or_404(Question, pk=self.kwargs['question'], event=self.request.event)
|
q = get_object_or_404(Question, pk=self.kwargs['question'], event=self.request.event)
|
||||||
@@ -564,7 +564,7 @@ class QuotaViewSet(ConditionalListView, viewsets.ModelViewSet):
|
|||||||
ordering_fields = ('id', 'size')
|
ordering_fields = ('id', 'size')
|
||||||
ordering = ('id',)
|
ordering = ('id',)
|
||||||
permission = None
|
permission = None
|
||||||
write_permission = 'event.items:write'
|
write_permission = 'can_change_items'
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return self.request.event.quotas.select_related('subevent').prefetch_related('items', 'variations').all()
|
return self.request.event.quotas.select_related('subevent').prefetch_related('items', 'variations').all()
|
||||||
|
|||||||
@@ -62,8 +62,8 @@ with scopes_disabled():
|
|||||||
class ReusableMediaViewSet(viewsets.ModelViewSet):
|
class ReusableMediaViewSet(viewsets.ModelViewSet):
|
||||||
serializer_class = ReusableMediaSerializer
|
serializer_class = ReusableMediaSerializer
|
||||||
queryset = ReusableMedium.objects.none()
|
queryset = ReusableMedium.objects.none()
|
||||||
permission = 'organizer.reusablemedia:read'
|
permission = 'can_manage_reusable_media'
|
||||||
write_permission = 'organizer.reusablemedia:write'
|
write_permission = 'can_manage_reusable_media'
|
||||||
filter_backends = (DjangoFilterBackend, OrderingFilter)
|
filter_backends = (DjangoFilterBackend, OrderingFilter)
|
||||||
ordering = ('-updated', '-id')
|
ordering = ('-updated', '-id')
|
||||||
ordering_fields = ('created', 'updated', 'identifier', 'type', 'id')
|
ordering_fields = ('created', 'updated', 'identifier', 'type', 'id')
|
||||||
@@ -95,8 +95,6 @@ class ReusableMediaViewSet(viewsets.ModelViewSet):
|
|||||||
def get_serializer_context(self):
|
def get_serializer_context(self):
|
||||||
ctx = super().get_serializer_context()
|
ctx = super().get_serializer_context()
|
||||||
ctx['organizer'] = self.request.organizer
|
ctx['organizer'] = self.request.organizer
|
||||||
ctx['can_read_giftcards'] = 'organizer.giftcards:read' in self.request.orgapermset
|
|
||||||
ctx['can_read_customers'] = 'organizer.customers:read' in self.request.orgapermset
|
|
||||||
return ctx
|
return ctx
|
||||||
|
|
||||||
@transaction.atomic()
|
@transaction.atomic()
|
||||||
|
|||||||
@@ -317,7 +317,7 @@ class OrderViewSetMixin:
|
|||||||
|
|
||||||
class OrganizerOrderViewSet(OrderViewSetMixin, viewsets.ReadOnlyModelViewSet):
|
class OrganizerOrderViewSet(OrderViewSetMixin, viewsets.ReadOnlyModelViewSet):
|
||||||
def get_base_queryset(self):
|
def get_base_queryset(self):
|
||||||
perm = "event.orders:read" if self.request.method in SAFE_METHODS else "event.orders:write"
|
perm = "can_view_orders" if self.request.method in SAFE_METHODS else "can_change_orders"
|
||||||
if isinstance(self.request.auth, (TeamAPIToken, Device)):
|
if isinstance(self.request.auth, (TeamAPIToken, Device)):
|
||||||
return Order.objects.filter(
|
return Order.objects.filter(
|
||||||
event__organizer=self.request.organizer,
|
event__organizer=self.request.organizer,
|
||||||
@@ -338,8 +338,8 @@ class OrganizerOrderViewSet(OrderViewSetMixin, viewsets.ReadOnlyModelViewSet):
|
|||||||
|
|
||||||
|
|
||||||
class EventOrderViewSet(OrderViewSetMixin, viewsets.ModelViewSet):
|
class EventOrderViewSet(OrderViewSetMixin, viewsets.ModelViewSet):
|
||||||
permission = 'event.orders:read'
|
permission = 'can_view_orders'
|
||||||
write_permission = 'event.orders:write'
|
write_permission = 'can_change_orders'
|
||||||
|
|
||||||
def get_serializer_context(self):
|
def get_serializer_context(self):
|
||||||
ctx = super().get_serializer_context()
|
ctx = super().get_serializer_context()
|
||||||
@@ -1078,8 +1078,8 @@ class OrderPositionViewSet(viewsets.ModelViewSet):
|
|||||||
ordering = ('order__datetime', 'positionid')
|
ordering = ('order__datetime', 'positionid')
|
||||||
ordering_fields = ('order__code', 'order__datetime', 'positionid', 'attendee_name', 'order__status',)
|
ordering_fields = ('order__code', 'order__datetime', 'positionid', 'attendee_name', 'order__status',)
|
||||||
filterset_class = OrderPositionFilter
|
filterset_class = OrderPositionFilter
|
||||||
permission = 'event.orders:read'
|
permission = 'can_view_orders'
|
||||||
write_permission = 'event.orders:write'
|
write_permission = 'can_change_orders'
|
||||||
ordering_custom = {
|
ordering_custom = {
|
||||||
'attendee_name': {
|
'attendee_name': {
|
||||||
'_order': F('display_name').asc(nulls_first=True),
|
'_order': F('display_name').asc(nulls_first=True),
|
||||||
@@ -1580,8 +1580,8 @@ class OrderPositionViewSet(viewsets.ModelViewSet):
|
|||||||
class PaymentViewSet(CreateModelMixin, viewsets.ReadOnlyModelViewSet):
|
class PaymentViewSet(CreateModelMixin, viewsets.ReadOnlyModelViewSet):
|
||||||
serializer_class = OrderPaymentSerializer
|
serializer_class = OrderPaymentSerializer
|
||||||
queryset = OrderPayment.objects.none()
|
queryset = OrderPayment.objects.none()
|
||||||
permission = 'event.orders:read'
|
permission = 'can_view_orders'
|
||||||
write_permission = 'event.orders:write'
|
write_permission = 'can_change_orders'
|
||||||
lookup_field = 'local_id'
|
lookup_field = 'local_id'
|
||||||
|
|
||||||
def get_serializer_context(self):
|
def get_serializer_context(self):
|
||||||
@@ -1757,8 +1757,8 @@ class PaymentViewSet(CreateModelMixin, viewsets.ReadOnlyModelViewSet):
|
|||||||
class RefundViewSet(CreateModelMixin, viewsets.ReadOnlyModelViewSet):
|
class RefundViewSet(CreateModelMixin, viewsets.ReadOnlyModelViewSet):
|
||||||
serializer_class = OrderRefundSerializer
|
serializer_class = OrderRefundSerializer
|
||||||
queryset = OrderRefund.objects.none()
|
queryset = OrderRefund.objects.none()
|
||||||
permission = 'event.orders:read'
|
permission = 'can_view_orders'
|
||||||
write_permission = 'event.orders:write'
|
write_permission = 'can_change_orders'
|
||||||
lookup_field = 'local_id'
|
lookup_field = 'local_id'
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
@@ -1915,18 +1915,13 @@ class InvoiceViewSet(viewsets.ReadOnlyModelViewSet):
|
|||||||
ordering = ('nr',)
|
ordering = ('nr',)
|
||||||
ordering_fields = ('nr', 'date')
|
ordering_fields = ('nr', 'date')
|
||||||
filterset_class = InvoiceFilter
|
filterset_class = InvoiceFilter
|
||||||
|
permission = 'can_view_orders'
|
||||||
lookup_url_kwarg = 'number'
|
lookup_url_kwarg = 'number'
|
||||||
lookup_field = 'nr'
|
lookup_field = 'nr'
|
||||||
|
write_permission = 'can_change_orders'
|
||||||
def _get_permission_name(self, request):
|
|
||||||
if 'event' in request.resolver_match.kwargs:
|
|
||||||
if request.method not in SAFE_METHODS:
|
|
||||||
return "event.orders:write"
|
|
||||||
return "event.orders:read"
|
|
||||||
return None # org-level is handled by event__in check
|
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
perm = "event.orders:read" if self.request.method in SAFE_METHODS else "event.orders:write"
|
perm = "can_view_orders" if self.request.method in SAFE_METHODS else "can_change_orders"
|
||||||
if getattr(self.request, 'event', None):
|
if getattr(self.request, 'event', None):
|
||||||
qs = self.request.event.invoices
|
qs = self.request.event.invoices
|
||||||
elif isinstance(self.request.auth, (TeamAPIToken, Device)):
|
elif isinstance(self.request.auth, (TeamAPIToken, Device)):
|
||||||
@@ -2036,7 +2031,7 @@ class InvoiceViewSet(viewsets.ReadOnlyModelViewSet):
|
|||||||
else:
|
else:
|
||||||
order = Order.objects.select_for_update(of=OF_SELF).get(pk=inv.order_id)
|
order = Order.objects.select_for_update(of=OF_SELF).get(pk=inv.order_id)
|
||||||
c = generate_cancellation(inv)
|
c = generate_cancellation(inv)
|
||||||
if invoice_qualified(order):
|
if inv.order.status != Order.STATUS_CANCELED:
|
||||||
inv = generate_invoice(order)
|
inv = generate_invoice(order)
|
||||||
else:
|
else:
|
||||||
inv = c
|
inv = c
|
||||||
@@ -2067,8 +2062,8 @@ class RevokedSecretViewSet(viewsets.ReadOnlyModelViewSet):
|
|||||||
ordering = ('-created',)
|
ordering = ('-created',)
|
||||||
ordering_fields = ('created', 'secret')
|
ordering_fields = ('created', 'secret')
|
||||||
filterset_class = RevokedSecretFilter
|
filterset_class = RevokedSecretFilter
|
||||||
permission = 'event.orders:read'
|
permission = 'can_view_orders'
|
||||||
write_permission = 'event.orders:write'
|
write_permission = 'can_change_orders'
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return RevokedTicketSecret.objects.filter(event=self.request.event)
|
return RevokedTicketSecret.objects.filter(event=self.request.event)
|
||||||
@@ -2089,8 +2084,8 @@ class BlockedSecretViewSet(viewsets.ReadOnlyModelViewSet):
|
|||||||
filter_backends = (DjangoFilterBackend, TotalOrderingFilter)
|
filter_backends = (DjangoFilterBackend, TotalOrderingFilter)
|
||||||
ordering = ('-updated', '-pk')
|
ordering = ('-updated', '-pk')
|
||||||
filterset_class = BlockedSecretFilter
|
filterset_class = BlockedSecretFilter
|
||||||
permission = 'event.orders:read'
|
permission = 'can_view_orders'
|
||||||
write_permission = 'event.orders:write'
|
write_permission = 'can_change_orders'
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return BlockedTicketSecret.objects.filter(event=self.request.event)
|
return BlockedTicketSecret.objects.filter(event=self.request.event)
|
||||||
@@ -2125,7 +2120,7 @@ class TransactionViewSet(viewsets.ReadOnlyModelViewSet):
|
|||||||
ordering = ('datetime', 'pk')
|
ordering = ('datetime', 'pk')
|
||||||
ordering_fields = ('datetime', 'created', 'id',)
|
ordering_fields = ('datetime', 'created', 'id',)
|
||||||
filterset_class = TransactionFilter
|
filterset_class = TransactionFilter
|
||||||
permission = 'event.orders:read'
|
permission = 'can_view_orders'
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return Transaction.objects.filter(order__event=self.request.event).select_related("order")
|
return Transaction.objects.filter(order__event=self.request.event).select_related("order")
|
||||||
@@ -2142,11 +2137,11 @@ class OrganizerTransactionViewSet(TransactionViewSet):
|
|||||||
|
|
||||||
if isinstance(self.request.auth, (TeamAPIToken, Device)):
|
if isinstance(self.request.auth, (TeamAPIToken, Device)):
|
||||||
qs = qs.filter(
|
qs = qs.filter(
|
||||||
order__event__in=self.request.auth.get_events_with_permission("event.orders:read"),
|
order__event__in=self.request.auth.get_events_with_permission("can_view_orders"),
|
||||||
)
|
)
|
||||||
elif self.request.user.is_authenticated:
|
elif self.request.user.is_authenticated:
|
||||||
qs = qs.filter(
|
qs = qs.filter(
|
||||||
order__event__in=self.request.user.get_events_with_permission("event.orders:read", request=self.request)
|
order__event__in=self.request.user.get_events_with_permission("can_view_orders", request=self.request)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
raise PermissionDenied("Unknown authentication scheme")
|
raise PermissionDenied("Unknown authentication scheme")
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ class OrganizerViewSet(mixins.UpdateModelMixin, viewsets.ReadOnlyModelViewSet):
|
|||||||
filter_backends = (TotalOrderingFilter,)
|
filter_backends = (TotalOrderingFilter,)
|
||||||
ordering = ('slug',)
|
ordering = ('slug',)
|
||||||
ordering_fields = ('name', 'slug')
|
ordering_fields = ('name', 'slug')
|
||||||
write_permission = "organizer.settings.general:write"
|
write_permission = "can_change_organizer_settings"
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
if self.request.user.is_authenticated:
|
if self.request.user.is_authenticated:
|
||||||
@@ -154,8 +154,8 @@ class OrganizerViewSet(mixins.UpdateModelMixin, viewsets.ReadOnlyModelViewSet):
|
|||||||
class SeatingPlanViewSet(viewsets.ModelViewSet):
|
class SeatingPlanViewSet(viewsets.ModelViewSet):
|
||||||
serializer_class = SeatingPlanSerializer
|
serializer_class = SeatingPlanSerializer
|
||||||
queryset = SeatingPlan.objects.none()
|
queryset = SeatingPlan.objects.none()
|
||||||
permission = None
|
permission = 'can_change_organizer_settings'
|
||||||
write_permission = 'organizer.seatingplans:write'
|
write_permission = 'can_change_organizer_settings'
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return self.request.organizer.seating_plans.order_by('name')
|
return self.request.organizer.seating_plans.order_by('name')
|
||||||
@@ -221,8 +221,8 @@ with scopes_disabled():
|
|||||||
class GiftCardViewSet(viewsets.ModelViewSet):
|
class GiftCardViewSet(viewsets.ModelViewSet):
|
||||||
serializer_class = GiftCardSerializer
|
serializer_class = GiftCardSerializer
|
||||||
queryset = GiftCard.objects.none()
|
queryset = GiftCard.objects.none()
|
||||||
permission = 'organizer.giftcards:read'
|
permission = 'can_manage_gift_cards'
|
||||||
write_permission = 'organizer.giftcards:write'
|
write_permission = 'can_manage_gift_cards'
|
||||||
filter_backends = (DjangoFilterBackend,)
|
filter_backends = (DjangoFilterBackend,)
|
||||||
filterset_class = GiftCardFilter
|
filterset_class = GiftCardFilter
|
||||||
|
|
||||||
@@ -323,8 +323,8 @@ class GiftCardViewSet(viewsets.ModelViewSet):
|
|||||||
class GiftCardTransactionViewSet(viewsets.ReadOnlyModelViewSet):
|
class GiftCardTransactionViewSet(viewsets.ReadOnlyModelViewSet):
|
||||||
serializer_class = GiftCardTransactionSerializer
|
serializer_class = GiftCardTransactionSerializer
|
||||||
queryset = GiftCardTransaction.objects.none()
|
queryset = GiftCardTransaction.objects.none()
|
||||||
permission = 'organizer.giftcards:read'
|
permission = 'can_manage_gift_cards'
|
||||||
write_permission = 'organizer.giftcards:write'
|
write_permission = 'can_manage_gift_cards'
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def giftcard(self):
|
def giftcard(self):
|
||||||
@@ -341,8 +341,8 @@ class GiftCardTransactionViewSet(viewsets.ReadOnlyModelViewSet):
|
|||||||
class TeamViewSet(viewsets.ModelViewSet):
|
class TeamViewSet(viewsets.ModelViewSet):
|
||||||
serializer_class = TeamSerializer
|
serializer_class = TeamSerializer
|
||||||
queryset = Team.objects.none()
|
queryset = Team.objects.none()
|
||||||
permission = 'organizer.teams:write'
|
permission = 'can_change_teams'
|
||||||
write_permission = 'organizer.teams:write'
|
write_permission = 'can_change_teams'
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return self.request.organizer.teams.order_by('pk')
|
return self.request.organizer.teams.order_by('pk')
|
||||||
@@ -381,8 +381,8 @@ class TeamViewSet(viewsets.ModelViewSet):
|
|||||||
class TeamMemberViewSet(DestroyModelMixin, viewsets.ReadOnlyModelViewSet):
|
class TeamMemberViewSet(DestroyModelMixin, viewsets.ReadOnlyModelViewSet):
|
||||||
serializer_class = TeamMemberSerializer
|
serializer_class = TeamMemberSerializer
|
||||||
queryset = User.objects.none()
|
queryset = User.objects.none()
|
||||||
permission = 'organizer.teams:write'
|
permission = 'can_change_teams'
|
||||||
write_permission = 'organizer.teams:write'
|
write_permission = 'can_change_teams'
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def team(self):
|
def team(self):
|
||||||
@@ -410,8 +410,8 @@ class TeamMemberViewSet(DestroyModelMixin, viewsets.ReadOnlyModelViewSet):
|
|||||||
class TeamInviteViewSet(CreateModelMixin, DestroyModelMixin, viewsets.ReadOnlyModelViewSet):
|
class TeamInviteViewSet(CreateModelMixin, DestroyModelMixin, viewsets.ReadOnlyModelViewSet):
|
||||||
serializer_class = TeamInviteSerializer
|
serializer_class = TeamInviteSerializer
|
||||||
queryset = TeamInvite.objects.none()
|
queryset = TeamInvite.objects.none()
|
||||||
permission = 'organizer.teams:write'
|
permission = 'can_change_teams'
|
||||||
write_permission = 'organizer.teams:write'
|
write_permission = 'can_change_teams'
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def team(self):
|
def team(self):
|
||||||
@@ -447,8 +447,8 @@ class TeamInviteViewSet(CreateModelMixin, DestroyModelMixin, viewsets.ReadOnlyMo
|
|||||||
class TeamAPITokenViewSet(CreateModelMixin, DestroyModelMixin, viewsets.ReadOnlyModelViewSet):
|
class TeamAPITokenViewSet(CreateModelMixin, DestroyModelMixin, viewsets.ReadOnlyModelViewSet):
|
||||||
serializer_class = TeamAPITokenSerializer
|
serializer_class = TeamAPITokenSerializer
|
||||||
queryset = TeamAPIToken.objects.none()
|
queryset = TeamAPIToken.objects.none()
|
||||||
permission = 'organizer.teams:write'
|
permission = 'can_change_teams'
|
||||||
write_permission = 'organizer.teams:write'
|
write_permission = 'can_change_teams'
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def team(self):
|
def team(self):
|
||||||
@@ -511,8 +511,8 @@ class DeviceViewSet(mixins.CreateModelMixin,
|
|||||||
GenericViewSet):
|
GenericViewSet):
|
||||||
serializer_class = DeviceSerializer
|
serializer_class = DeviceSerializer
|
||||||
queryset = Device.objects.none()
|
queryset = Device.objects.none()
|
||||||
permission = 'organizer.devices:read'
|
permission = 'can_change_organizer_settings'
|
||||||
write_permission = 'organizer.devices:write'
|
write_permission = 'can_change_organizer_settings'
|
||||||
lookup_field = 'device_id'
|
lookup_field = 'device_id'
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
@@ -521,9 +521,6 @@ class DeviceViewSet(mixins.CreateModelMixin,
|
|||||||
def get_serializer_context(self):
|
def get_serializer_context(self):
|
||||||
ctx = super().get_serializer_context()
|
ctx = super().get_serializer_context()
|
||||||
ctx['organizer'] = self.request.organizer
|
ctx['organizer'] = self.request.organizer
|
||||||
ctx['can_see_tokens'] = (
|
|
||||||
self.request.user if self.request.user and self.request.user.is_authenticated else self.request.auth
|
|
||||||
).has_organizer_permission(self.request.organizer, 'organizer.devices:write', request=self.request)
|
|
||||||
return ctx
|
return ctx
|
||||||
|
|
||||||
@transaction.atomic()
|
@transaction.atomic()
|
||||||
@@ -550,11 +547,11 @@ class DeviceViewSet(mixins.CreateModelMixin,
|
|||||||
|
|
||||||
class OrganizerSettingsView(views.APIView):
|
class OrganizerSettingsView(views.APIView):
|
||||||
permission = None
|
permission = None
|
||||||
write_permission = 'organizer.settings.general:write'
|
write_permission = 'can_change_organizer_settings'
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
s = OrganizerSettingsSerializer(instance=request.organizer.settings, organizer=request.organizer, context={
|
s = OrganizerSettingsSerializer(instance=request.organizer.settings, organizer=request.organizer, context={
|
||||||
'request': request, 'permissions': request.orgapermset
|
'request': request
|
||||||
})
|
})
|
||||||
if 'explain' in request.GET:
|
if 'explain' in request.GET:
|
||||||
return Response({
|
return Response({
|
||||||
@@ -571,7 +568,7 @@ class OrganizerSettingsView(views.APIView):
|
|||||||
s = OrganizerSettingsSerializer(
|
s = OrganizerSettingsSerializer(
|
||||||
instance=request.organizer.settings, data=request.data, partial=True,
|
instance=request.organizer.settings, data=request.data, partial=True,
|
||||||
organizer=request.organizer, context={
|
organizer=request.organizer, context={
|
||||||
'request': request, 'permissions': request.orgapermset
|
'request': request
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
s.is_valid(raise_exception=True)
|
s.is_valid(raise_exception=True)
|
||||||
@@ -583,7 +580,7 @@ class OrganizerSettingsView(views.APIView):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
s = OrganizerSettingsSerializer(instance=request.organizer.settings, organizer=request.organizer, context={
|
s = OrganizerSettingsSerializer(instance=request.organizer.settings, organizer=request.organizer, context={
|
||||||
'request': request, 'permissions': request.orgapermset
|
'request': request
|
||||||
})
|
})
|
||||||
return Response(s.data)
|
return Response(s.data)
|
||||||
|
|
||||||
@@ -600,8 +597,7 @@ with scopes_disabled():
|
|||||||
class CustomerViewSet(viewsets.ModelViewSet):
|
class CustomerViewSet(viewsets.ModelViewSet):
|
||||||
serializer_class = CustomerSerializer
|
serializer_class = CustomerSerializer
|
||||||
queryset = Customer.objects.none()
|
queryset = Customer.objects.none()
|
||||||
permission = 'organizer.customers:read'
|
permission = 'can_manage_customers'
|
||||||
write_permission = 'organizer.customers:write'
|
|
||||||
lookup_field = 'identifier'
|
lookup_field = 'identifier'
|
||||||
filter_backends = (DjangoFilterBackend,)
|
filter_backends = (DjangoFilterBackend,)
|
||||||
filterset_class = CustomerFilter
|
filterset_class = CustomerFilter
|
||||||
@@ -661,7 +657,7 @@ class CustomerViewSet(viewsets.ModelViewSet):
|
|||||||
class MembershipTypeViewSet(viewsets.ModelViewSet):
|
class MembershipTypeViewSet(viewsets.ModelViewSet):
|
||||||
serializer_class = MembershipTypeSerializer
|
serializer_class = MembershipTypeSerializer
|
||||||
queryset = MembershipType.objects.none()
|
queryset = MembershipType.objects.none()
|
||||||
permission = 'organizer.settings.general:write'
|
permission = 'can_change_organizer_settings'
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
qs = self.request.organizer.membership_types.all()
|
qs = self.request.organizer.membership_types.all()
|
||||||
@@ -718,8 +714,7 @@ with scopes_disabled():
|
|||||||
class MembershipViewSet(viewsets.ModelViewSet):
|
class MembershipViewSet(viewsets.ModelViewSet):
|
||||||
serializer_class = MembershipSerializer
|
serializer_class = MembershipSerializer
|
||||||
queryset = Membership.objects.none()
|
queryset = Membership.objects.none()
|
||||||
permission = 'organizer.customers:read'
|
permission = 'can_manage_customers'
|
||||||
write_permission = 'organizer.customers:write'
|
|
||||||
filter_backends = (DjangoFilterBackend,)
|
filter_backends = (DjangoFilterBackend,)
|
||||||
filterset_class = MembershipFilter
|
filterset_class = MembershipFilter
|
||||||
|
|
||||||
@@ -769,8 +764,8 @@ with scopes_disabled():
|
|||||||
class SalesChannelViewSet(viewsets.ModelViewSet):
|
class SalesChannelViewSet(viewsets.ModelViewSet):
|
||||||
serializer_class = SalesChannelSerializer
|
serializer_class = SalesChannelSerializer
|
||||||
queryset = SalesChannel.objects.none()
|
queryset = SalesChannel.objects.none()
|
||||||
permission = 'organizer.settings.general:write'
|
permission = 'can_change_organizer_settings'
|
||||||
write_permission = 'organizer.settings.general:write'
|
write_permission = 'can_change_organizer_settings'
|
||||||
filter_backends = (DjangoFilterBackend,)
|
filter_backends = (DjangoFilterBackend,)
|
||||||
filterset_class = SalesChannelFilter
|
filterset_class = SalesChannelFilter
|
||||||
lookup_field = 'identifier'
|
lookup_field = 'identifier'
|
||||||
|
|||||||
@@ -204,7 +204,7 @@ class ShreddersMixin:
|
|||||||
|
|
||||||
|
|
||||||
class EventShreddersViewSet(ShreddersMixin, viewsets.ViewSet):
|
class EventShreddersViewSet(ShreddersMixin, viewsets.ViewSet):
|
||||||
permission = 'event.orders:write'
|
permission = 'can_change_orders'
|
||||||
|
|
||||||
def get_serializer_kwargs(self):
|
def get_serializer_kwargs(self):
|
||||||
return {}
|
return {}
|
||||||
|
|||||||
@@ -62,8 +62,8 @@ class VoucherViewSet(viewsets.ModelViewSet):
|
|||||||
ordering = ('id',)
|
ordering = ('id',)
|
||||||
ordering_fields = ('id', 'code', 'max_usages', 'valid_until', 'value')
|
ordering_fields = ('id', 'code', 'max_usages', 'valid_until', 'value')
|
||||||
filterset_class = VoucherFilter
|
filterset_class = VoucherFilter
|
||||||
permission = 'event.vouchers:read'
|
permission = 'can_view_vouchers'
|
||||||
write_permission = 'event.vouchers:write'
|
write_permission = 'can_change_vouchers'
|
||||||
|
|
||||||
@scopes_disabled() # we have an event check here, and we can save some performance on subqueries
|
@scopes_disabled() # we have an event check here, and we can save some performance on subqueries
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
|
|||||||
@@ -51,8 +51,8 @@ class WaitingListViewSet(viewsets.ModelViewSet):
|
|||||||
ordering = ('created', 'pk',)
|
ordering = ('created', 'pk',)
|
||||||
ordering_fields = ('id', 'created', 'email', 'item')
|
ordering_fields = ('id', 'created', 'email', 'item')
|
||||||
filterset_class = WaitingListFilter
|
filterset_class = WaitingListFilter
|
||||||
permission = 'event.orders:read'
|
permission = 'can_view_orders'
|
||||||
write_permission = 'event.orders:write'
|
write_permission = 'can_change_orders'
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return self.request.event.waitinglistentries.all()
|
return self.request.event.waitinglistentries.all()
|
||||||
|
|||||||
@@ -35,8 +35,8 @@ class WebhookFilter(FilterSet):
|
|||||||
class WebHookViewSet(viewsets.ModelViewSet):
|
class WebHookViewSet(viewsets.ModelViewSet):
|
||||||
serializer_class = WebHookSerializer
|
serializer_class = WebHookSerializer
|
||||||
queryset = WebHook.objects.none()
|
queryset = WebHook.objects.none()
|
||||||
permission = 'organizer.settings.general:write'
|
permission = 'can_change_organizer_settings'
|
||||||
write_permission = 'organizer.settings.general:write'
|
write_permission = 'can_change_organizer_settings'
|
||||||
filter_backends = (DjangoFilterBackend,)
|
filter_backends = (DjangoFilterBackend,)
|
||||||
filterset_class = WebhookFilter
|
filterset_class = WebhookFilter
|
||||||
|
|
||||||
|
|||||||
@@ -224,7 +224,7 @@ class HistoryPasswordValidator:
|
|||||||
).delete()
|
).delete()
|
||||||
|
|
||||||
|
|
||||||
def has_event_access_permission(request, permission='event.settings.general:write'):
|
def has_event_access_permission(request, permission='can_change_event_settings'):
|
||||||
return (
|
return (
|
||||||
request.user.is_authenticated and
|
request.user.is_authenticated and
|
||||||
request.user.has_event_permission(request.organizer, request.event, permission, request=request)
|
request.user.has_event_permission(request.organizer, request.event, permission, request=request)
|
||||||
|
|||||||
@@ -73,9 +73,6 @@ class BaseExporter:
|
|||||||
self.events = Event.objects.filter(pk=event.pk)
|
self.events = Event.objects.filter(pk=event.pk)
|
||||||
self.timezone = event.timezone
|
self.timezone = event.timezone
|
||||||
|
|
||||||
if hasattr(self, 'organizer_required_permission'):
|
|
||||||
raise TypeError("Deprecated attribute organizer_required_permission no longer supported.")
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.identifier
|
return self.identifier
|
||||||
|
|
||||||
@@ -179,30 +176,15 @@ class BaseExporter:
|
|||||||
"""
|
"""
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_required_event_permission(cls) -> str:
|
|
||||||
"""
|
|
||||||
The permission level required to use this exporter for events. For multi-event-exports, this will be used
|
|
||||||
to limit the selection of events. Will be ignored if the ``OrganizerLevelExportMixin`` mixin is used.
|
|
||||||
The default implementation returns ``"event.orders:read"``.
|
|
||||||
"""
|
|
||||||
return 'event.orders:read'
|
|
||||||
|
|
||||||
|
|
||||||
class OrganizerLevelExportMixin:
|
class OrganizerLevelExportMixin:
|
||||||
@classmethod
|
@property
|
||||||
def get_required_event_permission(cls):
|
def organizer_required_permission(self) -> str:
|
||||||
raise TypeError("required_event_permission may not be called on OrganizerLevelExportMixin")
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_required_organizer_permission(cls) -> str:
|
|
||||||
"""
|
"""
|
||||||
The permission level required to use this exporter. Must be set for organizer-level exports. Set to `None` to
|
The permission level required to use this exporter. Only useful for organizer-level exports,
|
||||||
allow everyone with any access to the organizer.
|
not for event-level exports.
|
||||||
|
|
||||||
``get_required_event_permission`` will be ignored on this class.
|
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
return 'can_view_orders'
|
||||||
|
|
||||||
|
|
||||||
class ListExporter(BaseExporter):
|
class ListExporter(BaseExporter):
|
||||||
|
|||||||
@@ -47,13 +47,10 @@ from ..signals import register_multievent_data_exporters
|
|||||||
class CustomerListExporter(OrganizerLevelExportMixin, ListExporter):
|
class CustomerListExporter(OrganizerLevelExportMixin, ListExporter):
|
||||||
identifier = 'customerlist'
|
identifier = 'customerlist'
|
||||||
verbose_name = gettext_lazy('Customer accounts')
|
verbose_name = gettext_lazy('Customer accounts')
|
||||||
|
organizer_required_permission = 'can_manage_customers'
|
||||||
category = pgettext_lazy('export_category', 'Customer accounts')
|
category = pgettext_lazy('export_category', 'Customer accounts')
|
||||||
description = gettext_lazy('Download a spreadsheet of all currently registered customer accounts.')
|
description = gettext_lazy('Download a spreadsheet of all currently registered customer accounts.')
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_required_organizer_permission(cls) -> str:
|
|
||||||
return 'organizer.customers:write'
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def additional_form_fields(self):
|
def additional_form_fields(self):
|
||||||
return OrderedDict(
|
return OrderedDict(
|
||||||
|
|||||||
@@ -39,8 +39,8 @@ from zoneinfo import ZoneInfo
|
|||||||
from django import forms
|
from django import forms
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db.models import (
|
from django.db.models import (
|
||||||
Case, CharField, Count, DateTimeField, Exists, F, IntegerField, Max, Min,
|
Case, CharField, Count, DateTimeField, F, IntegerField, Max, Min, OuterRef,
|
||||||
OuterRef, Q, Subquery, Sum, When,
|
Q, Subquery, Sum, When,
|
||||||
)
|
)
|
||||||
from django.db.models.functions import Coalesce
|
from django.db.models.functions import Coalesce
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
@@ -144,18 +144,6 @@ class OrderListExporter(MultiSheetListExporter):
|
|||||||
d = OrderedDict(d)
|
d = OrderedDict(d)
|
||||||
if not self.is_multievent and not self.event.has_subevents:
|
if not self.is_multievent and not self.event.has_subevents:
|
||||||
del d['event_date_range']
|
del d['event_date_range']
|
||||||
if not self.is_multievent:
|
|
||||||
d["items"] = forms.ModelMultipleChoiceField(
|
|
||||||
label=_("Products"),
|
|
||||||
queryset=self.event.items.all(),
|
|
||||||
widget=forms.CheckboxSelectMultiple(
|
|
||||||
attrs={"class": "scrolling-multiple-choice"}
|
|
||||||
),
|
|
||||||
help_text=_("If none are selected, all products are included. Orders are included if they contain "
|
|
||||||
"at least one position of this product. The order totals etc. still include all products "
|
|
||||||
"contained in the order."),
|
|
||||||
required=False,
|
|
||||||
)
|
|
||||||
return d
|
return d
|
||||||
|
|
||||||
def _get_all_payment_methods(self, qs):
|
def _get_all_payment_methods(self, qs):
|
||||||
@@ -261,14 +249,6 @@ class OrderListExporter(MultiSheetListExporter):
|
|||||||
pcnt=Subquery(s, output_field=IntegerField())
|
pcnt=Subquery(s, output_field=IntegerField())
|
||||||
).select_related('invoice_address', 'customer')
|
).select_related('invoice_address', 'customer')
|
||||||
|
|
||||||
if form_data.get('items'):
|
|
||||||
qs = qs.filter(
|
|
||||||
Exists(OrderPosition.all.filter(
|
|
||||||
order=OuterRef('pk'),
|
|
||||||
item__in=form_data["items"]
|
|
||||||
))
|
|
||||||
)
|
|
||||||
|
|
||||||
qs = self._date_filter(qs, form_data, rel='')
|
qs = self._date_filter(qs, form_data, rel='')
|
||||||
|
|
||||||
if form_data['paid_only']:
|
if form_data['paid_only']:
|
||||||
@@ -384,7 +364,7 @@ class OrderListExporter(MultiSheetListExporter):
|
|||||||
order.invoice_address.city,
|
order.invoice_address.city,
|
||||||
order.invoice_address.country if order.invoice_address.country else
|
order.invoice_address.country if order.invoice_address.country else
|
||||||
order.invoice_address.country_old,
|
order.invoice_address.country_old,
|
||||||
order.invoice_address.state_for_address,
|
order.invoice_address.state,
|
||||||
order.invoice_address.custom_field,
|
order.invoice_address.custom_field,
|
||||||
order.invoice_address.vat_id,
|
order.invoice_address.vat_id,
|
||||||
]
|
]
|
||||||
@@ -460,14 +440,6 @@ class OrderListExporter(MultiSheetListExporter):
|
|||||||
if form_data['paid_only']:
|
if form_data['paid_only']:
|
||||||
qs = qs.filter(order__status=Order.STATUS_PAID, canceled=False)
|
qs = qs.filter(order__status=Order.STATUS_PAID, canceled=False)
|
||||||
|
|
||||||
if form_data.get('items'):
|
|
||||||
qs = qs.filter(
|
|
||||||
Exists(OrderPosition.all.filter(
|
|
||||||
order=OuterRef('order'),
|
|
||||||
item__in=form_data["items"]
|
|
||||||
))
|
|
||||||
)
|
|
||||||
|
|
||||||
qs = self._date_filter(qs, form_data, rel='order__')
|
qs = self._date_filter(qs, form_data, rel='order__')
|
||||||
return qs
|
return qs
|
||||||
|
|
||||||
@@ -543,7 +515,7 @@ class OrderListExporter(MultiSheetListExporter):
|
|||||||
order.invoice_address.city,
|
order.invoice_address.city,
|
||||||
order.invoice_address.country if order.invoice_address.country else
|
order.invoice_address.country if order.invoice_address.country else
|
||||||
order.invoice_address.country_old,
|
order.invoice_address.country_old,
|
||||||
order.invoice_address.state_for_address,
|
order.invoice_address.state,
|
||||||
order.invoice_address.vat_id,
|
order.invoice_address.vat_id,
|
||||||
]
|
]
|
||||||
except InvoiceAddress.DoesNotExist:
|
except InvoiceAddress.DoesNotExist:
|
||||||
@@ -563,11 +535,6 @@ class OrderListExporter(MultiSheetListExporter):
|
|||||||
if form_data['paid_only']:
|
if form_data['paid_only']:
|
||||||
qs = qs.filter(order__status=Order.STATUS_PAID, canceled=False)
|
qs = qs.filter(order__status=Order.STATUS_PAID, canceled=False)
|
||||||
|
|
||||||
if form_data.get('items'):
|
|
||||||
qs = qs.filter(
|
|
||||||
item__in=form_data["items"]
|
|
||||||
)
|
|
||||||
|
|
||||||
qs = self._date_filter(qs, form_data, rel='order__')
|
qs = self._date_filter(qs, form_data, rel='order__')
|
||||||
return qs
|
return qs
|
||||||
|
|
||||||
@@ -650,7 +617,6 @@ class OrderListExporter(MultiSheetListExporter):
|
|||||||
_('Country'),
|
_('Country'),
|
||||||
pgettext('address', 'State'),
|
pgettext('address', 'State'),
|
||||||
_('Voucher'),
|
_('Voucher'),
|
||||||
_('Voucher budget usage'),
|
|
||||||
_('Pseudonymization ID'),
|
_('Pseudonymization ID'),
|
||||||
_('Ticket secret'),
|
_('Ticket secret'),
|
||||||
_('Seat ID'),
|
_('Seat ID'),
|
||||||
@@ -766,9 +732,8 @@ class OrderListExporter(MultiSheetListExporter):
|
|||||||
op.zipcode or '',
|
op.zipcode or '',
|
||||||
op.city or '',
|
op.city or '',
|
||||||
op.country if op.country else '',
|
op.country if op.country else '',
|
||||||
op.state_for_address or '',
|
op.state or '',
|
||||||
op.voucher.code if op.voucher else '',
|
op.voucher.code if op.voucher else '',
|
||||||
op.voucher_budget_use if op.voucher_budget_use else '',
|
|
||||||
op.pseudonymization_id,
|
op.pseudonymization_id,
|
||||||
op.secret,
|
op.secret,
|
||||||
]
|
]
|
||||||
@@ -832,7 +797,7 @@ class OrderListExporter(MultiSheetListExporter):
|
|||||||
order.invoice_address.city,
|
order.invoice_address.city,
|
||||||
order.invoice_address.country if order.invoice_address.country else
|
order.invoice_address.country if order.invoice_address.country else
|
||||||
order.invoice_address.country_old,
|
order.invoice_address.country_old,
|
||||||
order.invoice_address.state_for_address,
|
order.invoice_address.state,
|
||||||
order.invoice_address.vat_id,
|
order.invoice_address.vat_id,
|
||||||
]
|
]
|
||||||
except InvoiceAddress.DoesNotExist:
|
except InvoiceAddress.DoesNotExist:
|
||||||
@@ -1235,14 +1200,11 @@ class QuotaListExporter(ListExporter):
|
|||||||
class GiftcardTransactionListExporter(OrganizerLevelExportMixin, ListExporter):
|
class GiftcardTransactionListExporter(OrganizerLevelExportMixin, ListExporter):
|
||||||
identifier = 'giftcardtransactionlist'
|
identifier = 'giftcardtransactionlist'
|
||||||
verbose_name = gettext_lazy('Gift card transactions')
|
verbose_name = gettext_lazy('Gift card transactions')
|
||||||
|
organizer_required_permission = 'can_manage_gift_cards'
|
||||||
category = pgettext_lazy('export_category', 'Gift cards')
|
category = pgettext_lazy('export_category', 'Gift cards')
|
||||||
description = gettext_lazy('Download a spreadsheet of all gift card transactions.')
|
description = gettext_lazy('Download a spreadsheet of all gift card transactions.')
|
||||||
repeatable_read = False
|
repeatable_read = False
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_required_organizer_permission(cls) -> str:
|
|
||||||
return 'organizer.giftcards:read'
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def additional_form_fields(self):
|
def additional_form_fields(self):
|
||||||
d = [
|
d = [
|
||||||
@@ -1345,13 +1307,10 @@ class GiftcardRedemptionListExporter(ListExporter):
|
|||||||
class GiftcardListExporter(OrganizerLevelExportMixin, ListExporter):
|
class GiftcardListExporter(OrganizerLevelExportMixin, ListExporter):
|
||||||
identifier = 'giftcardlist'
|
identifier = 'giftcardlist'
|
||||||
verbose_name = gettext_lazy('Gift cards')
|
verbose_name = gettext_lazy('Gift cards')
|
||||||
|
organizer_required_permission = 'can_manage_gift_cards'
|
||||||
category = pgettext_lazy('export_category', 'Gift cards')
|
category = pgettext_lazy('export_category', 'Gift cards')
|
||||||
description = gettext_lazy('Download a spreadsheet of all gift cards including their current value.')
|
description = gettext_lazy('Download a spreadsheet of all gift cards including their current value.')
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_required_organizer_permission(cls) -> str:
|
|
||||||
return 'organizer.giftcards:read'
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def additional_form_fields(self):
|
def additional_form_fields(self):
|
||||||
return OrderedDict(
|
return OrderedDict(
|
||||||
|
|||||||
@@ -36,10 +36,6 @@ class ReusableMediaExporter(OrganizerLevelExportMixin, ListExporter):
|
|||||||
description = _('Download a spread sheet with the data of all reusable medias on your account.')
|
description = _('Download a spread sheet with the data of all reusable medias on your account.')
|
||||||
repeatable_read = False
|
repeatable_read = False
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_required_organizer_permission(cls) -> str:
|
|
||||||
return "organizer.reusablemedia:read"
|
|
||||||
|
|
||||||
def iterate_list(self, form_data):
|
def iterate_list(self, form_data):
|
||||||
media = ReusableMedium.objects.filter(
|
media = ReusableMedium.objects.filter(
|
||||||
organizer=self.organizer,
|
organizer=self.organizer,
|
||||||
|
|||||||
@@ -1417,7 +1417,7 @@ class BaseInvoiceAddressForm(forms.ModelForm):
|
|||||||
|
|
||||||
self.instance.transmission_type = transmission_type.identifier
|
self.instance.transmission_type = transmission_type.identifier
|
||||||
self.instance.transmission_info = transmission_type.form_data_to_transmission_info(data)
|
self.instance.transmission_info = transmission_type.form_data_to_transmission_info(data)
|
||||||
elif transmission_type.is_exclusive(self.event, data.get("country"), data.get("is_business")):
|
elif transmission_type.exclusive:
|
||||||
if transmission_type.is_available(self.event, data.get("country"), data.get("is_business")):
|
if transmission_type.is_available(self.event, data.get("country"), data.get("is_business")):
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
"transmission_type": "The transmission type '%s' must be used for this country or address type." % (
|
"transmission_type": "The transmission type '%s' must be used for this country or address type." % (
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ def get_babel_locale():
|
|||||||
|
|
||||||
for locale in try_locales:
|
for locale in try_locales:
|
||||||
if localedata.exists(locale):
|
if localedata.exists(locale):
|
||||||
return localedata.normalize_locale(locale)
|
return locale
|
||||||
|
|
||||||
return "en"
|
return "en"
|
||||||
|
|
||||||
|
|||||||
@@ -36,11 +36,9 @@ class ItalianSdITransmissionType(TransmissionType):
|
|||||||
identifier = "it_sdi"
|
identifier = "it_sdi"
|
||||||
verbose_name = pgettext_lazy("italian_invoice", "Italian Exchange System (SdI)")
|
verbose_name = pgettext_lazy("italian_invoice", "Italian Exchange System (SdI)")
|
||||||
public_name = pgettext_lazy("italian_invoice", "Exchange System (SdI)")
|
public_name = pgettext_lazy("italian_invoice", "Exchange System (SdI)")
|
||||||
|
exclusive = True
|
||||||
enforce_transmission = True
|
enforce_transmission = True
|
||||||
|
|
||||||
def is_exclusive(self, event, country: Country, is_business: bool) -> bool:
|
|
||||||
return str(country) == "IT"
|
|
||||||
|
|
||||||
def is_available(self, event, country: Country, is_business: bool):
|
def is_available(self, event, country: Country, is_business: bool):
|
||||||
return str(country) == "IT" and super().is_available(event, country, is_business)
|
return str(country) == "IT" and super().is_available(event, country, is_business)
|
||||||
|
|
||||||
|
|||||||
@@ -179,12 +179,6 @@ class PeppolTransmissionType(TransmissionType):
|
|||||||
def is_available(self, event, country: Country, is_business: bool):
|
def is_available(self, event, country: Country, is_business: bool):
|
||||||
return is_business and super().is_available(event, country, is_business)
|
return is_business and super().is_available(event, country, is_business)
|
||||||
|
|
||||||
def is_exclusive(self, event, country: Country, is_business: bool) -> bool:
|
|
||||||
if is_business and str(country) == "BE" and event and event.settings.invoice_address_from_country == "BE":
|
|
||||||
# Peppol is required to be used for intra-Belgian B2B invoices
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def invoice_address_form_fields(self) -> dict:
|
def invoice_address_form_fields(self) -> dict:
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -58,6 +58,15 @@ class TransmissionType:
|
|||||||
"""
|
"""
|
||||||
return 100
|
return 100
|
||||||
|
|
||||||
|
@property
|
||||||
|
def exclusive(self) -> bool:
|
||||||
|
"""
|
||||||
|
If a transmission type is exclusive, no other type can be chosen if this type is
|
||||||
|
available. Use e.g. if a certain transmission type is legally required in a certain
|
||||||
|
jurisdiction.
|
||||||
|
"""
|
||||||
|
return False
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def enforce_transmission(self) -> bool:
|
def enforce_transmission(self) -> bool:
|
||||||
"""
|
"""
|
||||||
@@ -73,15 +82,6 @@ class TransmissionType:
|
|||||||
for provider, _ in providers
|
for provider, _ in providers
|
||||||
)
|
)
|
||||||
|
|
||||||
def is_exclusive(self, event, country: Country, is_business: bool) -> bool:
|
|
||||||
"""
|
|
||||||
If a transmission type is exclusive, no other type can be chosen if this type is
|
|
||||||
available. Use e.g. if a certain transmission type is legally required in a certain
|
|
||||||
jurisdiction. Event can be None in organizer-level contexts. Exclusiveness has no effect if
|
|
||||||
the type is not available.
|
|
||||||
"""
|
|
||||||
return False
|
|
||||||
|
|
||||||
def invoice_address_form_fields_required(self, country: Country, is_business: bool):
|
def invoice_address_form_fields_required(self, country: Country, is_business: bool):
|
||||||
return set()
|
return set()
|
||||||
|
|
||||||
|
|||||||
@@ -1,129 +0,0 @@
|
|||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
from pretix.helpers.permission_migration import (
|
|
||||||
OLD_TO_NEW_EVENT_MIGRATION, OLD_TO_NEW_ORGANIZER_MIGRATION,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def migrate_teams_forward(apps, schema_editor):
|
|
||||||
Team = apps.get_model("pretixbase", "Team")
|
|
||||||
|
|
||||||
for team in Team.objects.iterator():
|
|
||||||
if all(getattr(team, k) for k in OLD_TO_NEW_EVENT_MIGRATION.keys() if k != "can_checkin_orders"):
|
|
||||||
team.all_event_permissions = True
|
|
||||||
team.limit_event_permissions = {}
|
|
||||||
else:
|
|
||||||
team.all_event_permissions = False
|
|
||||||
for k, v in OLD_TO_NEW_EVENT_MIGRATION.items():
|
|
||||||
if getattr(team, k):
|
|
||||||
team.limit_event_permissions.update({kk: True for kk in v})
|
|
||||||
|
|
||||||
if all(getattr(team, k) for k in OLD_TO_NEW_ORGANIZER_MIGRATION.keys()):
|
|
||||||
team.all_organizer_permissions = True
|
|
||||||
team.limit_organizer_permissions = {}
|
|
||||||
else:
|
|
||||||
team.all_organizer_permissions = False
|
|
||||||
for k, v in OLD_TO_NEW_ORGANIZER_MIGRATION.items():
|
|
||||||
if getattr(team, k):
|
|
||||||
team.limit_organizer_permissions.update({kk: True for kk in v})
|
|
||||||
|
|
||||||
team.save(update_fields=[
|
|
||||||
"all_event_permissions", "limit_event_permissions", "all_organizer_permissions", "limit_organizer_permissions"
|
|
||||||
])
|
|
||||||
|
|
||||||
|
|
||||||
def migrate_teams_backward(apps, schema_editor):
|
|
||||||
Team = apps.get_model("pretixbase", "Team")
|
|
||||||
|
|
||||||
for team in Team.objects.iterator():
|
|
||||||
for k, v in OLD_TO_NEW_EVENT_MIGRATION.items():
|
|
||||||
setattr(team, k, team.all_event_permissions or all(team.limit_event_permissions.get(kk) for kk in v))
|
|
||||||
for k, v in OLD_TO_NEW_ORGANIZER_MIGRATION.items():
|
|
||||||
setattr(team, k, team.all_organizer_permissions or all(team.limit_organizer_permissions.get(kk) for kk in v))
|
|
||||||
team.save()
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
("pretixbase", "0296_invoice_invoice_from_state"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="team",
|
|
||||||
name="all_event_permissions",
|
|
||||||
field=models.BooleanField(default=False),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="team",
|
|
||||||
name="all_organizer_permissions",
|
|
||||||
field=models.BooleanField(default=False),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="team",
|
|
||||||
name="limit_event_permissions",
|
|
||||||
field=models.JSONField(default=dict),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="team",
|
|
||||||
name="limit_organizer_permissions",
|
|
||||||
field=models.JSONField(default=dict),
|
|
||||||
),
|
|
||||||
migrations.RunPython(
|
|
||||||
migrate_teams_forward,
|
|
||||||
migrate_teams_backward,
|
|
||||||
),
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name="team",
|
|
||||||
name="can_change_event_settings",
|
|
||||||
),
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name="team",
|
|
||||||
name="can_change_items",
|
|
||||||
),
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name="team",
|
|
||||||
name="can_change_orders",
|
|
||||||
),
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name="team",
|
|
||||||
name="can_change_organizer_settings",
|
|
||||||
),
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name="team",
|
|
||||||
name="can_change_teams",
|
|
||||||
),
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name="team",
|
|
||||||
name="can_change_vouchers",
|
|
||||||
),
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name="team",
|
|
||||||
name="can_checkin_orders",
|
|
||||||
),
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name="team",
|
|
||||||
name="can_create_events",
|
|
||||||
),
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name="team",
|
|
||||||
name="can_manage_customers",
|
|
||||||
),
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name="team",
|
|
||||||
name="can_manage_gift_cards",
|
|
||||||
),
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name="team",
|
|
||||||
name="can_manage_reusable_media",
|
|
||||||
),
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name="team",
|
|
||||||
name="can_view_orders",
|
|
||||||
),
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name="team",
|
|
||||||
name="can_view_vouchers",
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -212,28 +212,6 @@ class SuperuserPermissionSet:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
class EventPermissionSet(set):
|
|
||||||
def __contains__(self, item):
|
|
||||||
from pretix.base.permissions import assert_valid_event_permission
|
|
||||||
|
|
||||||
if super().__contains__(item):
|
|
||||||
return True
|
|
||||||
|
|
||||||
assert_valid_event_permission(item, allow_tuple=False)
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
class OrganizerPermissionSet(set):
|
|
||||||
def __contains__(self, item):
|
|
||||||
from pretix.base.permissions import assert_valid_organizer_permission
|
|
||||||
|
|
||||||
if super().__contains__(item):
|
|
||||||
return True
|
|
||||||
|
|
||||||
assert_valid_organizer_permission(item, allow_tuple=False)
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
|
class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
|
||||||
"""
|
"""
|
||||||
This is the user model used by pretix for authentication.
|
This is the user model used by pretix for authentication.
|
||||||
@@ -494,7 +472,7 @@ class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
|
|||||||
:return: set
|
:return: set
|
||||||
"""
|
"""
|
||||||
teams = self._get_teams_for_event(organizer, event)
|
teams = self._get_teams_for_event(organizer, event)
|
||||||
sets = [t.event_permission_set() for t in teams]
|
sets = [t.permission_set() for t in teams]
|
||||||
if sets:
|
if sets:
|
||||||
return set.union(*sets)
|
return set.union(*sets)
|
||||||
else:
|
else:
|
||||||
@@ -508,7 +486,7 @@ class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
|
|||||||
:return: set
|
:return: set
|
||||||
"""
|
"""
|
||||||
teams = self._get_teams_for_organizer(organizer)
|
teams = self._get_teams_for_organizer(organizer)
|
||||||
sets = [t.organizer_permission_set() for t in teams]
|
sets = [t.permission_set() for t in teams]
|
||||||
if sets:
|
if sets:
|
||||||
return set.union(*sets)
|
return set.union(*sets)
|
||||||
else:
|
else:
|
||||||
@@ -523,7 +501,7 @@ class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
|
|||||||
|
|
||||||
:param organizer: The organizer of the event
|
:param organizer: The organizer of the event
|
||||||
:param event: The event to check
|
:param event: The event to check
|
||||||
:param perm_name: The permission, e.g. ``event.orders:read``
|
:param perm_name: The permission, e.g. ``can_change_teams``
|
||||||
:param request: The current request (optional)
|
:param request: The current request (optional)
|
||||||
:param session_key: The current session key (optional)
|
:param session_key: The current session key (optional)
|
||||||
:return: bool
|
:return: bool
|
||||||
@@ -535,8 +513,8 @@ class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
|
|||||||
if teams:
|
if teams:
|
||||||
self._teamcache['e{}'.format(event.pk)] = teams
|
self._teamcache['e{}'.format(event.pk)] = teams
|
||||||
if isinstance(perm_name, (tuple, list)):
|
if isinstance(perm_name, (tuple, list)):
|
||||||
return any([any(team.has_event_permission(p) for team in teams) for p in perm_name])
|
return any([any(team.has_permission(p) for team in teams) for p in perm_name])
|
||||||
if not perm_name or any([team.has_event_permission(perm_name) for team in teams]):
|
if not perm_name or any([team.has_permission(perm_name) for team in teams]):
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -546,7 +524,7 @@ class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
|
|||||||
to the organizer ``organizer``.
|
to the organizer ``organizer``.
|
||||||
|
|
||||||
:param organizer: The organizer to check
|
:param organizer: The organizer to check
|
||||||
:param perm_name: The permission, e.g. ``organizer.events:create``
|
:param perm_name: The permission, e.g. ``can_change_teams``
|
||||||
:param request: The current request (optional). Required to detect staff sessions properly.
|
:param request: The current request (optional). Required to detect staff sessions properly.
|
||||||
:return: bool
|
:return: bool
|
||||||
"""
|
"""
|
||||||
@@ -555,8 +533,8 @@ class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
|
|||||||
teams = self._get_teams_for_organizer(organizer)
|
teams = self._get_teams_for_organizer(organizer)
|
||||||
if teams:
|
if teams:
|
||||||
if isinstance(perm_name, (tuple, list)):
|
if isinstance(perm_name, (tuple, list)):
|
||||||
return any([any(team.has_organizer_permission(p) for team in teams) for p in perm_name])
|
return any([any(team.has_permission(p) for team in teams) for p in perm_name])
|
||||||
if not perm_name or any([team.has_organizer_permission(perm_name) for team in teams]):
|
if not perm_name or any([team.has_permission(perm_name) for team in teams]):
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -587,15 +565,14 @@ class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
|
|||||||
:return: Iterable of Events
|
:return: Iterable of Events
|
||||||
"""
|
"""
|
||||||
from .event import Event
|
from .event import Event
|
||||||
from .organizer import TeamQuerySet
|
|
||||||
|
|
||||||
if request and self.has_active_staff_session(request.session.session_key):
|
if request and self.has_active_staff_session(request.session.session_key):
|
||||||
return Event.objects.all()
|
return Event.objects.all()
|
||||||
|
|
||||||
if isinstance(permission, (tuple, list)):
|
if isinstance(permission, (tuple, list)):
|
||||||
q = reduce(operator.or_, [TeamQuerySet.event_permission_q(p) for p in permission])
|
q = reduce(operator.or_, [Q(**{p: True}) for p in permission])
|
||||||
else:
|
else:
|
||||||
q = TeamQuerySet.event_permission_q(permission)
|
q = Q(**{permission: True})
|
||||||
|
|
||||||
return Event.objects.filter(
|
return Event.objects.filter(
|
||||||
Q(organizer_id__in=self.teams.filter(q, all_events=True).values_list('organizer', flat=True))
|
Q(organizer_id__in=self.teams.filter(q, all_events=True).values_list('organizer', flat=True))
|
||||||
@@ -628,13 +605,14 @@ class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
|
|||||||
:return: Iterable of Organizers
|
:return: Iterable of Organizers
|
||||||
"""
|
"""
|
||||||
from .event import Organizer
|
from .event import Organizer
|
||||||
from .organizer import TeamQuerySet
|
|
||||||
|
|
||||||
if request and self.has_active_staff_session(request.session.session_key):
|
if request and self.has_active_staff_session(request.session.session_key):
|
||||||
return Organizer.objects.all()
|
return Organizer.objects.all()
|
||||||
|
|
||||||
|
kwargs = {permission: True}
|
||||||
|
|
||||||
return Organizer.objects.filter(
|
return Organizer.objects.filter(
|
||||||
id__in=self.teams.filter(TeamQuerySet.organizer_permission_q(permission)).values_list('organizer', flat=True)
|
id__in=self.teams.filter(**kwargs).values_list('organizer', flat=True)
|
||||||
)
|
)
|
||||||
|
|
||||||
def has_active_staff_session(self, session_key=None):
|
def has_active_staff_session(self, session_key=None):
|
||||||
|
|||||||
@@ -349,7 +349,7 @@ class AttendeeProfile(models.Model):
|
|||||||
def state_name(self):
|
def state_name(self):
|
||||||
sd = pycountry.subdivisions.get(code='{}-{}'.format(self.country, self.state))
|
sd = pycountry.subdivisions.get(code='{}-{}'.format(self.country, self.state))
|
||||||
if sd:
|
if sd:
|
||||||
return _(sd.name)
|
return sd.name
|
||||||
return self.state
|
return self.state
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ from django.utils.translation import gettext_lazy as _
|
|||||||
from django_scopes import ScopedManager, scopes_disabled
|
from django_scopes import ScopedManager, scopes_disabled
|
||||||
|
|
||||||
from pretix.base.models import LoggedModel
|
from pretix.base.models import LoggedModel
|
||||||
from pretix.base.permissions import assert_valid_event_permission
|
|
||||||
|
|
||||||
|
|
||||||
@scopes_disabled()
|
@scopes_disabled()
|
||||||
@@ -190,19 +189,13 @@ class Device(LoggedModel):
|
|||||||
kwargs['update_fields'] = {'device_id'}.union(kwargs['update_fields'])
|
kwargs['update_fields'] = {'device_id'}.union(kwargs['update_fields'])
|
||||||
super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
def _event_permission_set(self) -> set:
|
def permission_set(self) -> set:
|
||||||
return {
|
return {
|
||||||
'event.orders:read',
|
'can_view_orders',
|
||||||
'event.orders:write',
|
'can_change_orders',
|
||||||
'event.vouchers:read',
|
'can_view_vouchers',
|
||||||
}
|
'can_manage_gift_cards',
|
||||||
|
'can_manage_reusable_media',
|
||||||
def _organizer_permission_set(self) -> set:
|
|
||||||
return {
|
|
||||||
'organizer.giftcards:read',
|
|
||||||
'organizer.giftcards:write',
|
|
||||||
'organizer.reusablemedia:read',
|
|
||||||
'organizer.reusablemedia:write',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_event_permission_set(self, organizer, event) -> set:
|
def get_event_permission_set(self, organizer, event) -> set:
|
||||||
@@ -216,7 +209,7 @@ class Device(LoggedModel):
|
|||||||
has_event_access = (self.all_events and organizer == self.organizer) or (
|
has_event_access = (self.all_events and organizer == self.organizer) or (
|
||||||
event in self.limit_events.all()
|
event in self.limit_events.all()
|
||||||
)
|
)
|
||||||
return self._event_permission_set() if has_event_access else set()
|
return self.permission_set() if has_event_access else set()
|
||||||
|
|
||||||
def get_organizer_permission_set(self, organizer) -> set:
|
def get_organizer_permission_set(self, organizer) -> set:
|
||||||
"""
|
"""
|
||||||
@@ -225,7 +218,7 @@ class Device(LoggedModel):
|
|||||||
:param organizer: The organizer of the event
|
:param organizer: The organizer of the event
|
||||||
:return: set of permissions
|
:return: set of permissions
|
||||||
"""
|
"""
|
||||||
return self._organizer_permission_set() if self.organizer == organizer else set()
|
return self.permission_set() if self.organizer == organizer else set()
|
||||||
|
|
||||||
def has_event_permission(self, organizer, event, perm_name=None, request=None) -> bool:
|
def has_event_permission(self, organizer, event, perm_name=None, request=None) -> bool:
|
||||||
"""
|
"""
|
||||||
@@ -234,7 +227,7 @@ class Device(LoggedModel):
|
|||||||
|
|
||||||
:param organizer: The organizer of the event
|
:param organizer: The organizer of the event
|
||||||
:param event: The event to check
|
:param event: The event to check
|
||||||
:param perm_name: The permission, e.g. ``event.orders:read``
|
:param perm_name: The permission, e.g. ``can_change_teams``
|
||||||
:param request: This parameter is ignored and only defined for compatibility reasons.
|
:param request: This parameter is ignored and only defined for compatibility reasons.
|
||||||
:return: bool
|
:return: bool
|
||||||
"""
|
"""
|
||||||
@@ -242,8 +235,8 @@ class Device(LoggedModel):
|
|||||||
event in self.limit_events.all()
|
event in self.limit_events.all()
|
||||||
)
|
)
|
||||||
if isinstance(perm_name, (tuple, list)):
|
if isinstance(perm_name, (tuple, list)):
|
||||||
return has_event_access and any(p in self._event_permission_set() for p in perm_name)
|
return has_event_access and any(p in self.permission_set() for p in perm_name)
|
||||||
return has_event_access and (not perm_name or perm_name in self._event_permission_set())
|
return has_event_access and (not perm_name or perm_name in self.permission_set())
|
||||||
|
|
||||||
def has_organizer_permission(self, organizer, perm_name=None, request=None):
|
def has_organizer_permission(self, organizer, perm_name=None, request=None):
|
||||||
"""
|
"""
|
||||||
@@ -251,13 +244,13 @@ class Device(LoggedModel):
|
|||||||
to the organizer ``organizer``.
|
to the organizer ``organizer``.
|
||||||
|
|
||||||
:param organizer: The organizer to check
|
:param organizer: The organizer to check
|
||||||
:param perm_name: The permission, e.g. ``organizer.events:create``
|
:param perm_name: The permission, e.g. ``can_change_teams``
|
||||||
:param request: This parameter is ignored and only defined for compatibility reasons.
|
:param request: This parameter is ignored and only defined for compatibility reasons.
|
||||||
:return: bool
|
:return: bool
|
||||||
"""
|
"""
|
||||||
if isinstance(perm_name, (tuple, list)):
|
if isinstance(perm_name, (tuple, list)):
|
||||||
return organizer == self.organizer and any(p in self._organizer_permission_set() for p in perm_name)
|
return organizer == self.organizer and any(p in self.permission_set() for p in perm_name)
|
||||||
return organizer == self.organizer and (not perm_name or perm_name in self._organizer_permission_set())
|
return organizer == self.organizer and (not perm_name or perm_name in self.permission_set())
|
||||||
|
|
||||||
def get_events_with_any_permission(self):
|
def get_events_with_any_permission(self):
|
||||||
"""
|
"""
|
||||||
@@ -277,10 +270,9 @@ class Device(LoggedModel):
|
|||||||
:param request: Ignored, for compatibility with User model
|
:param request: Ignored, for compatibility with User model
|
||||||
:return: Iterable of Events
|
:return: Iterable of Events
|
||||||
"""
|
"""
|
||||||
assert_valid_event_permission(permission)
|
|
||||||
if (
|
if (
|
||||||
isinstance(permission, (list, tuple)) and any(p in self._event_permission_set() for p in permission)
|
isinstance(permission, (list, tuple)) and any(p in self.permission_set() for p in permission)
|
||||||
) or (isinstance(permission, str) and permission in self._event_permission_set()):
|
) or (isinstance(permission, str) and permission in self.permission_set()):
|
||||||
return self.get_events_with_any_permission()
|
return self.get_events_with_any_permission()
|
||||||
else:
|
else:
|
||||||
return self.organizer.events.none()
|
return self.organizer.events.none()
|
||||||
|
|||||||
@@ -1386,13 +1386,14 @@ class Event(EventMixin, LoggedModel):
|
|||||||
from .auth import User
|
from .auth import User
|
||||||
|
|
||||||
if permission:
|
if permission:
|
||||||
qs = Team.objects.with_event_permission(permission)
|
kwargs = {permission: True}
|
||||||
else:
|
else:
|
||||||
qs = Team.objects.all()
|
kwargs = {}
|
||||||
|
|
||||||
team_with_perm = qs.filter(
|
team_with_perm = Team.objects.filter(
|
||||||
members__pk=OuterRef('pk'),
|
members__pk=OuterRef('pk'),
|
||||||
organizer=self.organizer,
|
organizer=self.organizer,
|
||||||
|
**kwargs
|
||||||
).filter(
|
).filter(
|
||||||
Q(all_events=True) | Q(limit_events__pk=self.pk)
|
Q(all_events=True) | Q(limit_events__pk=self.pk)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -594,11 +594,10 @@ class Item(LoggedModel):
|
|||||||
on_delete=models.SET_NULL,
|
on_delete=models.SET_NULL,
|
||||||
verbose_name=_("Only show after sellout of"),
|
verbose_name=_("Only show after sellout of"),
|
||||||
help_text=_("If you select a product here, this product will only be shown when that product is "
|
help_text=_("If you select a product here, this product will only be shown when that product is "
|
||||||
"no longer available. This will happen either because the other product has sold out or because "
|
"sold out. If combined with the option to hide sold-out products, this allows you to "
|
||||||
"the time is outside of the sales window for the other product. If combined with the option "
|
"swap out products for more expensive ones once the cheaper option is sold out. There might "
|
||||||
"to hide sold-out products, this allows you to swap out products for more expensive ones once "
|
"be a short period in which both products are visible while all tickets of the referenced "
|
||||||
"the cheaper option is sold out. There might be a short period in which both products are visible "
|
"product are reserved, but not yet sold.")
|
||||||
"while all tickets of the referenced product are reserved, but not yet sold.")
|
|
||||||
)
|
)
|
||||||
hidden_if_item_available_mode = models.CharField(
|
hidden_if_item_available_mode = models.CharField(
|
||||||
choices=UNAVAIL_MODES,
|
choices=UNAVAIL_MODES,
|
||||||
|
|||||||
@@ -1675,7 +1675,7 @@ class AbstractPosition(RoundingCorrectionMixin, models.Model):
|
|||||||
def state_name(self):
|
def state_name(self):
|
||||||
sd = pycountry.subdivisions.get(code='{}-{}'.format(self.country, self.state))
|
sd = pycountry.subdivisions.get(code='{}-{}'.format(self.country, self.state))
|
||||||
if sd:
|
if sd:
|
||||||
return _(sd.name)
|
return sd.name
|
||||||
return self.state
|
return self.state
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -3480,7 +3480,7 @@ class InvoiceAddress(models.Model):
|
|||||||
def state_name(self):
|
def state_name(self):
|
||||||
sd = pycountry.subdivisions.get(code='{}-{}'.format(self.country, self.state))
|
sd = pycountry.subdivisions.get(code='{}-{}'.format(self.country, self.state))
|
||||||
if sd:
|
if sd:
|
||||||
return _(sd.name)
|
return sd.name
|
||||||
return self.state
|
return self.state
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|||||||
@@ -31,10 +31,9 @@
|
|||||||
# Unless required by applicable law or agreed to in writing, software distributed under the Apache License 2.0 is
|
# Unless required by applicable law or agreed to in writing, software distributed under the Apache License 2.0 is
|
||||||
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
# License for the specific language governing permissions and limitations under the License.
|
# License for the specific language governing permissions and limitations under the License.
|
||||||
import operator
|
|
||||||
import string
|
import string
|
||||||
from datetime import date, datetime, time
|
from datetime import date, datetime, time
|
||||||
from functools import reduce
|
|
||||||
|
|
||||||
import pytz_deprecation_shim
|
import pytz_deprecation_shim
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
@@ -54,10 +53,6 @@ from i18nfield.strings import LazyI18nString
|
|||||||
from pretix.base.models.base import LoggedModel
|
from pretix.base.models.base import LoggedModel
|
||||||
from pretix.base.validators import OrganizerSlugBanlistValidator
|
from pretix.base.validators import OrganizerSlugBanlistValidator
|
||||||
|
|
||||||
from ...helpers.permission_migration import (
|
|
||||||
OLD_TO_NEW_EVENT_COMPAT, OLD_TO_NEW_ORGANIZER_COMPAT,
|
|
||||||
LegacyPermissionProperty,
|
|
||||||
)
|
|
||||||
from ..settings import settings_hierarkey
|
from ..settings import settings_hierarkey
|
||||||
from .auth import User
|
from .auth import User
|
||||||
|
|
||||||
@@ -314,38 +309,6 @@ def generate_api_token():
|
|||||||
return get_random_string(length=64, allowed_chars=string.ascii_lowercase + string.digits)
|
return get_random_string(length=64, allowed_chars=string.ascii_lowercase + string.digits)
|
||||||
|
|
||||||
|
|
||||||
class TeamQuerySet(models.QuerySet):
|
|
||||||
@classmethod
|
|
||||||
def event_permission_q(cls, perm_name):
|
|
||||||
from ..permissions import assert_valid_event_permission
|
|
||||||
|
|
||||||
if perm_name.startswith('can_') and perm_name in OLD_TO_NEW_EVENT_COMPAT: # legacy
|
|
||||||
return reduce(operator.and_, [cls.event_permission_q(p) for p in OLD_TO_NEW_EVENT_COMPAT[perm_name]])
|
|
||||||
assert_valid_event_permission(perm_name, allow_legacy=False)
|
|
||||||
return (
|
|
||||||
Q(all_event_permissions=True) |
|
|
||||||
Q(**{f'limit_event_permissions__{perm_name}': True})
|
|
||||||
)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def organizer_permission_q(cls, perm_name):
|
|
||||||
from ..permissions import assert_valid_organizer_permission
|
|
||||||
|
|
||||||
if perm_name.startswith('can_') and perm_name in OLD_TO_NEW_ORGANIZER_COMPAT: # legacy
|
|
||||||
return reduce(operator.and_, [cls.organizer_permission_q(p) for p in OLD_TO_NEW_ORGANIZER_COMPAT[perm_name]])
|
|
||||||
assert_valid_organizer_permission(perm_name, allow_legacy=False)
|
|
||||||
return (
|
|
||||||
Q(all_organizer_permissions=True) |
|
|
||||||
Q(**{f'limit_organizer_permissions__{perm_name}': True})
|
|
||||||
)
|
|
||||||
|
|
||||||
def with_event_permission(self, perm_name):
|
|
||||||
return self.filter(self.event_permission_q(perm_name))
|
|
||||||
|
|
||||||
def with_organizer_permission(self, perm_name):
|
|
||||||
return self.filter(self.organizer_permission_q(perm_name))
|
|
||||||
|
|
||||||
|
|
||||||
class Team(LoggedModel):
|
class Team(LoggedModel):
|
||||||
"""
|
"""
|
||||||
A team is a collection of people given certain access rights to one or more events of an organizer.
|
A team is a collection of people given certain access rights to one or more events of an organizer.
|
||||||
@@ -358,10 +321,36 @@ class Team(LoggedModel):
|
|||||||
:param all_events: Whether this team has access to all events of this organizer
|
:param all_events: Whether this team has access to all events of this organizer
|
||||||
:type all_events: bool
|
:type all_events: bool
|
||||||
:param limit_events: A set of events this team has access to. Irrelevant if ``all_events`` is ``True``.
|
:param limit_events: A set of events this team has access to. Irrelevant if ``all_events`` is ``True``.
|
||||||
|
:param can_create_events: Whether or not the members can create new events with this organizer account.
|
||||||
|
:type can_create_events: bool
|
||||||
|
:param can_change_teams: If ``True``, the members can change the teams of this organizer account.
|
||||||
|
:type can_change_teams: bool
|
||||||
|
:param can_manage_customers: If ``True``, the members can view and change organizer-level customer accounts.
|
||||||
|
:type can_manage_customers: bool
|
||||||
|
:param can_manage_reusable_media: If ``True``, the members can view and change organizer-level reusable media.
|
||||||
|
:type can_manage_reusable_media: bool
|
||||||
|
:param can_change_organizer_settings: If ``True``, the members can change the settings of this organizer account.
|
||||||
|
:type can_change_organizer_settings: bool
|
||||||
|
:param can_change_event_settings: If ``True``, the members can change the settings of the associated events.
|
||||||
|
:type can_change_event_settings: bool
|
||||||
|
:param can_change_items: If ``True``, the members can change and add items and related objects for the associated events.
|
||||||
|
:type can_change_items: bool
|
||||||
|
:param can_view_orders: If ``True``, the members can inspect details of all orders of the associated events.
|
||||||
|
:type can_view_orders: bool
|
||||||
|
:param can_change_orders: If ``True``, the members can change details of orders of the associated events.
|
||||||
|
:type can_change_orders: bool
|
||||||
|
:param can_checkin_orders: If ``True``, the members can perform check-in related actions.
|
||||||
|
:type can_checkin_orders: bool
|
||||||
|
:param can_view_vouchers: If ``True``, the members can inspect details of all vouchers of the associated events.
|
||||||
|
:type can_view_vouchers: bool
|
||||||
|
:param can_change_vouchers: If ``True``, the members can change and create vouchers for the associated events.
|
||||||
|
:type can_change_vouchers: bool
|
||||||
"""
|
"""
|
||||||
organizer = models.ForeignKey(Organizer, related_name="teams", on_delete=models.CASCADE)
|
organizer = models.ForeignKey(Organizer, related_name="teams", on_delete=models.CASCADE)
|
||||||
name = models.CharField(max_length=190, verbose_name=_("Team name"))
|
name = models.CharField(max_length=190, verbose_name=_("Team name"))
|
||||||
members = models.ManyToManyField(User, related_name="teams", verbose_name=_("Team members"))
|
members = models.ManyToManyField(User, related_name="teams", verbose_name=_("Team members"))
|
||||||
|
all_events = models.BooleanField(default=False, verbose_name=_("All events (including newly created ones)"))
|
||||||
|
limit_events = models.ManyToManyField('Event', verbose_name=_("Limit to events"), blank=True)
|
||||||
require_2fa = models.BooleanField(
|
require_2fa = models.BooleanField(
|
||||||
default=False, verbose_name=_("Require all members of this team to use two-factor authentication"),
|
default=False, verbose_name=_("Require all members of this team to use two-factor authentication"),
|
||||||
help_text=_("If you turn this on, all members of the team will be required to either set up two-factor "
|
help_text=_("If you turn this on, all members of the team will be required to either set up two-factor "
|
||||||
@@ -369,33 +358,62 @@ class Team(LoggedModel):
|
|||||||
"all users.")
|
"all users.")
|
||||||
)
|
)
|
||||||
|
|
||||||
# Scope
|
can_create_events = models.BooleanField(
|
||||||
all_events = models.BooleanField(default=False, verbose_name=_("All events (including newly created ones)"))
|
default=False,
|
||||||
limit_events = models.ManyToManyField('Event', verbose_name=_("Limit to events"), blank=True)
|
verbose_name=_("Can create events"),
|
||||||
|
)
|
||||||
# Permissions
|
can_change_teams = models.BooleanField(
|
||||||
# We store them as {key: True} instead of [key] because otherwise not all lookups we need are supported on SQLite
|
default=False,
|
||||||
all_event_permissions = models.BooleanField(default=False, verbose_name=_("All event permissions"))
|
verbose_name=_("Can change teams and permissions"),
|
||||||
limit_event_permissions = models.JSONField(default=dict, verbose_name=_("Event permissions"))
|
)
|
||||||
all_organizer_permissions = models.BooleanField(default=False, verbose_name=_("All organizer permissions"))
|
can_change_organizer_settings = models.BooleanField(
|
||||||
limit_organizer_permissions = models.JSONField(default=dict, verbose_name=_("Organizer permissions"))
|
default=False,
|
||||||
|
verbose_name=_("Can change organizer settings"),
|
||||||
# Legacy lookups for plugin compatibility
|
help_text=_('Someone with this setting can get access to most data of all of your events, i.e. via privacy '
|
||||||
can_change_event_settings = LegacyPermissionProperty()
|
'reports, so be careful who you add to this team!')
|
||||||
can_change_items = LegacyPermissionProperty()
|
)
|
||||||
can_view_orders = LegacyPermissionProperty()
|
can_manage_customers = models.BooleanField(
|
||||||
can_change_orders = LegacyPermissionProperty()
|
default=False,
|
||||||
can_checkin_orders = LegacyPermissionProperty()
|
verbose_name=_("Can manage customer accounts")
|
||||||
can_view_vouchers = LegacyPermissionProperty()
|
)
|
||||||
can_change_vouchers = LegacyPermissionProperty()
|
can_manage_reusable_media = models.BooleanField(
|
||||||
can_create_events = LegacyPermissionProperty()
|
default=False,
|
||||||
can_change_organizer_settings = LegacyPermissionProperty()
|
verbose_name=_("Can manage reusable media")
|
||||||
can_change_teams = LegacyPermissionProperty()
|
)
|
||||||
can_manage_gift_cards = LegacyPermissionProperty()
|
can_manage_gift_cards = models.BooleanField(
|
||||||
can_manage_customers = LegacyPermissionProperty()
|
default=False,
|
||||||
can_manage_reusable_media = LegacyPermissionProperty()
|
verbose_name=_("Can manage gift cards")
|
||||||
|
)
|
||||||
objects = TeamQuerySet.as_manager()
|
can_change_event_settings = models.BooleanField(
|
||||||
|
default=False,
|
||||||
|
verbose_name=_("Can change event settings")
|
||||||
|
)
|
||||||
|
can_change_items = models.BooleanField(
|
||||||
|
default=False,
|
||||||
|
verbose_name=_("Can change product settings")
|
||||||
|
)
|
||||||
|
can_view_orders = models.BooleanField(
|
||||||
|
default=False,
|
||||||
|
verbose_name=_("Can view orders")
|
||||||
|
)
|
||||||
|
can_change_orders = models.BooleanField(
|
||||||
|
default=False,
|
||||||
|
verbose_name=_("Can change orders")
|
||||||
|
)
|
||||||
|
can_checkin_orders = models.BooleanField(
|
||||||
|
default=False,
|
||||||
|
verbose_name=_("Can perform check-ins"),
|
||||||
|
help_text=_('This includes searching for attendees, which can be used to obtain personal information about '
|
||||||
|
'attendees. Users with "can change orders" can also perform check-ins.')
|
||||||
|
)
|
||||||
|
can_view_vouchers = models.BooleanField(
|
||||||
|
default=False,
|
||||||
|
verbose_name=_("Can view vouchers")
|
||||||
|
)
|
||||||
|
can_change_vouchers = models.BooleanField(
|
||||||
|
default=False,
|
||||||
|
verbose_name=_("Can change vouchers")
|
||||||
|
)
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return _("%(name)s on %(object)s") % {
|
return _("%(name)s on %(object)s") % {
|
||||||
@@ -403,62 +421,21 @@ class Team(LoggedModel):
|
|||||||
'object': str(self.organizer),
|
'object': str(self.organizer),
|
||||||
}
|
}
|
||||||
|
|
||||||
def event_permission_set(self, include_legacy=True) -> set:
|
def permission_set(self) -> set:
|
||||||
from ..permissions import get_all_event_permission_groups
|
attribs = dir(self)
|
||||||
|
return {
|
||||||
result = set()
|
a for a in attribs if a.startswith('can_') and self.has_permission(a)
|
||||||
for pg in get_all_event_permission_groups().values():
|
}
|
||||||
for action in pg.actions:
|
|
||||||
if self.all_event_permissions or self.limit_event_permissions.get(f"{pg.name}:{action}"):
|
|
||||||
result.add(f"{pg.name}:{action}")
|
|
||||||
|
|
||||||
if include_legacy:
|
|
||||||
# Add legacy permissions as well for plugin compatibility
|
|
||||||
for k, v in OLD_TO_NEW_EVENT_COMPAT.items():
|
|
||||||
if self.all_event_permissions or all(self.limit_event_permissions.get(kk) for kk in v):
|
|
||||||
result.add(k)
|
|
||||||
|
|
||||||
if "can_change_event_settings" in result:
|
|
||||||
result.add("can_change_settings")
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
def organizer_permission_set(self, include_legacy=True) -> set:
|
|
||||||
from ..permissions import get_all_organizer_permission_groups
|
|
||||||
|
|
||||||
result = set()
|
|
||||||
for pg in get_all_organizer_permission_groups().values():
|
|
||||||
for action in pg.actions:
|
|
||||||
if self.all_organizer_permissions or self.limit_organizer_permissions.get(f"{pg.name}:{action}"):
|
|
||||||
result.add(f"{pg.name}:{action}")
|
|
||||||
|
|
||||||
if include_legacy:
|
|
||||||
# Add legacy permissions as well for plugin compatibility
|
|
||||||
for k, v in OLD_TO_NEW_ORGANIZER_COMPAT.items():
|
|
||||||
if self.all_organizer_permissions or all(self.limit_organizer_permissions.get(kk) for kk in v):
|
|
||||||
result.add(k)
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def can_change_settings(self): # Legacy compatibility
|
def can_change_settings(self): # Legacy compatiblilty
|
||||||
return self.can_change_event_settings
|
return self.can_change_event_settings
|
||||||
|
|
||||||
def has_event_permission(self, perm_name):
|
def has_permission(self, perm_name):
|
||||||
from ..permissions import assert_valid_event_permission
|
try:
|
||||||
|
|
||||||
if perm_name.startswith('can_') and hasattr(self, perm_name): # legacy
|
|
||||||
return getattr(self, perm_name)
|
return getattr(self, perm_name)
|
||||||
assert_valid_event_permission(perm_name, allow_legacy=False)
|
except AttributeError:
|
||||||
return self.all_event_permissions or self.limit_event_permissions.get(perm_name, False)
|
raise ValueError('Invalid required permission: %s' % perm_name)
|
||||||
|
|
||||||
def has_organizer_permission(self, perm_name):
|
|
||||||
from ..permissions import assert_valid_organizer_permission
|
|
||||||
|
|
||||||
if perm_name.startswith('can_') and hasattr(self, perm_name): # legacy
|
|
||||||
return getattr(self, perm_name)
|
|
||||||
assert_valid_organizer_permission(perm_name, allow_legacy=False)
|
|
||||||
return self.all_organizer_permissions or self.limit_organizer_permissions.get(perm_name, False)
|
|
||||||
|
|
||||||
def permission_for_event(self, event):
|
def permission_for_event(self, event):
|
||||||
if self.all_events:
|
if self.all_events:
|
||||||
@@ -470,19 +447,6 @@ class Team(LoggedModel):
|
|||||||
def active_tokens(self):
|
def active_tokens(self):
|
||||||
return self.tokens.filter(active=True)
|
return self.tokens.filter(active=True)
|
||||||
|
|
||||||
def save(self, **kwargs):
|
|
||||||
if not isinstance(self.limit_event_permissions, dict):
|
|
||||||
raise TypeError("Permissions must be a dictionary")
|
|
||||||
if not isinstance(self.limit_organizer_permissions, dict):
|
|
||||||
raise TypeError("Permissions must be a dictionary")
|
|
||||||
for k in self.limit_event_permissions.values():
|
|
||||||
if k is not True:
|
|
||||||
raise TypeError("Permissions must only contain True values")
|
|
||||||
for k in self.limit_organizer_permissions.values():
|
|
||||||
if k is not True:
|
|
||||||
raise TypeError("Permissions must only contain True values")
|
|
||||||
return super().save(**kwargs)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _("Team")
|
verbose_name = _("Team")
|
||||||
verbose_name_plural = _("Teams")
|
verbose_name_plural = _("Teams")
|
||||||
@@ -539,7 +503,7 @@ class TeamAPIToken(models.Model):
|
|||||||
has_event_access = (self.team.all_events and organizer == self.team.organizer) or (
|
has_event_access = (self.team.all_events and organizer == self.team.organizer) or (
|
||||||
event in self.team.limit_events.all()
|
event in self.team.limit_events.all()
|
||||||
)
|
)
|
||||||
return self.team.event_permission_set() if has_event_access else set()
|
return self.team.permission_set() if has_event_access else set()
|
||||||
|
|
||||||
def get_organizer_permission_set(self, organizer) -> set:
|
def get_organizer_permission_set(self, organizer) -> set:
|
||||||
"""
|
"""
|
||||||
@@ -548,7 +512,7 @@ class TeamAPIToken(models.Model):
|
|||||||
:param organizer: The organizer of the event
|
:param organizer: The organizer of the event
|
||||||
:return: set of permissions
|
:return: set of permissions
|
||||||
"""
|
"""
|
||||||
return self.team.organizer_permission_set() if self.team.organizer == organizer else set()
|
return self.team.permission_set() if self.team.organizer == organizer else set()
|
||||||
|
|
||||||
def has_event_permission(self, organizer, event, perm_name=None, request=None) -> bool:
|
def has_event_permission(self, organizer, event, perm_name=None, request=None) -> bool:
|
||||||
"""
|
"""
|
||||||
@@ -557,7 +521,7 @@ class TeamAPIToken(models.Model):
|
|||||||
|
|
||||||
:param organizer: The organizer of the event
|
:param organizer: The organizer of the event
|
||||||
:param event: The event to check
|
:param event: The event to check
|
||||||
:param perm_name: The permission, e.g. ``event.orders:read``
|
:param perm_name: The permission, e.g. ``can_change_teams``
|
||||||
:param request: This parameter is ignored and only defined for compatibility reasons.
|
:param request: This parameter is ignored and only defined for compatibility reasons.
|
||||||
:return: bool
|
:return: bool
|
||||||
"""
|
"""
|
||||||
@@ -565,8 +529,8 @@ class TeamAPIToken(models.Model):
|
|||||||
event in self.team.limit_events.all()
|
event in self.team.limit_events.all()
|
||||||
)
|
)
|
||||||
if isinstance(perm_name, (tuple, list)):
|
if isinstance(perm_name, (tuple, list)):
|
||||||
return has_event_access and any(self.team.has_event_permission(p) for p in perm_name)
|
return has_event_access and any(self.team.has_permission(p) for p in perm_name)
|
||||||
return has_event_access and (not perm_name or self.team.has_event_permission(perm_name))
|
return has_event_access and (not perm_name or self.team.has_permission(perm_name))
|
||||||
|
|
||||||
def has_organizer_permission(self, organizer, perm_name=None, request=None):
|
def has_organizer_permission(self, organizer, perm_name=None, request=None):
|
||||||
"""
|
"""
|
||||||
@@ -574,13 +538,13 @@ class TeamAPIToken(models.Model):
|
|||||||
to the organizer ``organizer``.
|
to the organizer ``organizer``.
|
||||||
|
|
||||||
:param organizer: The organizer to check
|
:param organizer: The organizer to check
|
||||||
:param perm_name: The permission, e.g. ``organizer.events:create``
|
:param perm_name: The permission, e.g. ``can_change_teams``
|
||||||
:param request: This parameter is ignored and only defined for compatibility reasons.
|
:param request: This parameter is ignored and only defined for compatibility reasons.
|
||||||
:return: bool
|
:return: bool
|
||||||
"""
|
"""
|
||||||
if isinstance(perm_name, (tuple, list)):
|
if isinstance(perm_name, (tuple, list)):
|
||||||
return organizer == self.team.organizer and any(self.team.has_organizer_permission(p) for p in perm_name)
|
return organizer == self.team.organizer and any(self.team.has_permission(p) for p in perm_name)
|
||||||
return organizer == self.team.organizer and (not perm_name or self.team.has_organizer_permission(perm_name))
|
return organizer == self.team.organizer and (not perm_name or self.team.has_permission(perm_name))
|
||||||
|
|
||||||
def get_events_with_any_permission(self):
|
def get_events_with_any_permission(self):
|
||||||
"""
|
"""
|
||||||
@@ -601,8 +565,8 @@ class TeamAPIToken(models.Model):
|
|||||||
:return: Iterable of Events
|
:return: Iterable of Events
|
||||||
"""
|
"""
|
||||||
if (
|
if (
|
||||||
isinstance(permission, (list, tuple)) and any(self.team.has_event_permission(p) for p in permission)
|
isinstance(permission, (list, tuple)) and any(getattr(self.team, p, False) for p in permission)
|
||||||
) or (isinstance(permission, str) and self.team.has_event_permission(permission)):
|
) or (isinstance(permission, str) and getattr(self.team, permission, False)):
|
||||||
return self.get_events_with_any_permission()
|
return self.get_events_with_any_permission()
|
||||||
else:
|
else:
|
||||||
return self.team.organizer.events.none()
|
return self.team.organizer.events.none()
|
||||||
|
|||||||
@@ -159,7 +159,6 @@ class WaitingListEntry(LoggedModel):
|
|||||||
if availability[1] is None or availability[1] < 1:
|
if availability[1] is None or availability[1] < 1:
|
||||||
raise WaitingListException(_('This product is currently not available.'))
|
raise WaitingListException(_('This product is currently not available.'))
|
||||||
|
|
||||||
event = self.event
|
|
||||||
ev = self.subevent or self.event
|
ev = self.subevent or self.event
|
||||||
if ev.seat_category_mappings.filter(product=self.item).exists():
|
if ev.seat_category_mappings.filter(product=self.item).exists():
|
||||||
# Generally, we advertise the waiting list to be based on quotas only. This makes it dangerous
|
# Generally, we advertise the waiting list to be based on quotas only. This makes it dangerous
|
||||||
@@ -192,7 +191,6 @@ class WaitingListEntry(LoggedModel):
|
|||||||
|
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
locked_wle = WaitingListEntry.objects.select_for_update(of=OF_SELF).get(pk=self.pk)
|
locked_wle = WaitingListEntry.objects.select_for_update(of=OF_SELF).get(pk=self.pk)
|
||||||
locked_wle.event = event
|
|
||||||
if locked_wle.voucher:
|
if locked_wle.voucher:
|
||||||
raise WaitingListException(_('A voucher has already been sent to this person.'))
|
raise WaitingListException(_('A voucher has already been sent to this person.'))
|
||||||
e = locked_wle.email
|
e = locked_wle.email
|
||||||
@@ -229,7 +227,6 @@ class WaitingListEntry(LoggedModel):
|
|||||||
locked_wle.save()
|
locked_wle.save()
|
||||||
|
|
||||||
self.refresh_from_db()
|
self.refresh_from_db()
|
||||||
self.event = event
|
|
||||||
|
|
||||||
with language(self.locale, self.event.settings.region):
|
with language(self.locale, self.event.settings.region):
|
||||||
self.send_mail(
|
self.send_mail(
|
||||||
|
|||||||
@@ -151,7 +151,7 @@ def get_all_notification_types(event=None):
|
|||||||
|
|
||||||
|
|
||||||
class ParametrizedOrderNotificationType(NotificationType):
|
class ParametrizedOrderNotificationType(NotificationType):
|
||||||
required_permission = "event.orders:read"
|
required_permission = "can_view_orders"
|
||||||
|
|
||||||
def __init__(self, event, action_type, verbose_name, title):
|
def __init__(self, event, action_type, verbose_name, title):
|
||||||
self._action_type = action_type
|
self._action_type = action_type
|
||||||
|
|||||||
@@ -1,332 +0,0 @@
|
|||||||
#
|
|
||||||
# This file is part of pretix (Community Edition).
|
|
||||||
#
|
|
||||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
|
||||||
# Copyright (C) 2020-today pretix GmbH and contributors
|
|
||||||
#
|
|
||||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
|
||||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
|
||||||
#
|
|
||||||
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
|
|
||||||
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
|
|
||||||
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
|
|
||||||
# this file, see <https://pretix.eu/about/en/license>.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
|
||||||
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
|
||||||
# details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
|
||||||
# <https://www.gnu.org/licenses/>.
|
|
||||||
#
|
|
||||||
|
|
||||||
import logging
|
|
||||||
import warnings
|
|
||||||
from collections import OrderedDict
|
|
||||||
from typing import Dict, List, NamedTuple, Set, Tuple
|
|
||||||
|
|
||||||
from django.apps import apps
|
|
||||||
from django.dispatch import receiver
|
|
||||||
from django.utils.functional import Promise
|
|
||||||
from django.utils.translation import gettext_lazy as _, pgettext_lazy
|
|
||||||
|
|
||||||
from pretix.base.signals import (
|
|
||||||
register_event_permission_groups, register_organizer_permission_groups,
|
|
||||||
)
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
_ALL_EVENT_PERMISSION_GROUPS = None
|
|
||||||
_ALL_ORGANIZER_PERMISSION_GROUPS = None
|
|
||||||
_ALL_EVENT_PERMISSIONS = None
|
|
||||||
_ALL_ORGANIZER_PERMISSIONS = None
|
|
||||||
_CACHE_TIME_APPS_READY = None # hack: we need to clear the cache after plugins are loaded during startup
|
|
||||||
|
|
||||||
|
|
||||||
class PermissionOption(NamedTuple):
|
|
||||||
actions: Tuple[str, ...]
|
|
||||||
label: str | Promise
|
|
||||||
help_text: str | Promise = None
|
|
||||||
|
|
||||||
|
|
||||||
class PermissionGroup(NamedTuple):
|
|
||||||
name: str
|
|
||||||
label: str | Promise
|
|
||||||
actions: List[str]
|
|
||||||
options: List[PermissionOption]
|
|
||||||
help_text: str | Promise = None
|
|
||||||
|
|
||||||
|
|
||||||
def get_all_event_permission_groups() -> Dict[str, PermissionGroup]:
|
|
||||||
global _ALL_EVENT_PERMISSION_GROUPS, _CACHE_TIME_APPS_READY
|
|
||||||
|
|
||||||
if _ALL_EVENT_PERMISSION_GROUPS and apps.ready == _CACHE_TIME_APPS_READY:
|
|
||||||
return _ALL_EVENT_PERMISSION_GROUPS
|
|
||||||
|
|
||||||
types = OrderedDict()
|
|
||||||
for recv, ret in register_event_permission_groups.send(None):
|
|
||||||
if isinstance(ret, (list, tuple)):
|
|
||||||
for r in ret:
|
|
||||||
types[r.name] = r
|
|
||||||
else:
|
|
||||||
types[ret.name] = ret
|
|
||||||
_ALL_EVENT_PERMISSION_GROUPS = types
|
|
||||||
_CACHE_TIME_APPS_READY = apps.ready
|
|
||||||
return types
|
|
||||||
|
|
||||||
|
|
||||||
def get_all_organizer_permission_groups() -> Dict[str, PermissionGroup]:
|
|
||||||
global _ALL_ORGANIZER_PERMISSION_GROUPS, _CACHE_TIME_APPS_READY
|
|
||||||
|
|
||||||
if _ALL_ORGANIZER_PERMISSION_GROUPS and apps.ready == _CACHE_TIME_APPS_READY:
|
|
||||||
return _ALL_ORGANIZER_PERMISSION_GROUPS
|
|
||||||
|
|
||||||
types = OrderedDict()
|
|
||||||
for recv, ret in register_organizer_permission_groups.send(None):
|
|
||||||
if isinstance(ret, (list, tuple)):
|
|
||||||
for r in ret:
|
|
||||||
types[r.name] = r
|
|
||||||
else:
|
|
||||||
types[ret.name] = ret
|
|
||||||
_ALL_ORGANIZER_PERMISSION_GROUPS = types
|
|
||||||
_CACHE_TIME_APPS_READY = apps.ready
|
|
||||||
return types
|
|
||||||
|
|
||||||
|
|
||||||
def get_all_event_permissions() -> Set[str]:
|
|
||||||
from pretix.helpers.permission_migration import OLD_TO_NEW_EVENT_COMPAT
|
|
||||||
global _ALL_EVENT_PERMISSIONS, _CACHE_TIME_APPS_READY
|
|
||||||
|
|
||||||
if _ALL_EVENT_PERMISSIONS and apps.ready == _CACHE_TIME_APPS_READY:
|
|
||||||
return _ALL_EVENT_PERMISSIONS
|
|
||||||
|
|
||||||
res = set(OLD_TO_NEW_EVENT_COMPAT.keys())
|
|
||||||
for pg in get_all_event_permission_groups().values():
|
|
||||||
for a in pg.actions:
|
|
||||||
res.add(f"{pg.name}:{a}")
|
|
||||||
|
|
||||||
_ALL_EVENT_PERMISSIONS = res
|
|
||||||
_CACHE_TIME_APPS_READY = apps.ready
|
|
||||||
return res
|
|
||||||
|
|
||||||
|
|
||||||
def get_all_organizer_permissions() -> Set[str]:
|
|
||||||
from pretix.helpers.permission_migration import OLD_TO_NEW_ORGANIZER_COMPAT
|
|
||||||
global _ALL_ORGANIZER_PERMISSIONS, _CACHE_TIME_APPS_READY
|
|
||||||
|
|
||||||
if _ALL_ORGANIZER_PERMISSIONS and apps.ready == _CACHE_TIME_APPS_READY:
|
|
||||||
return _ALL_ORGANIZER_PERMISSIONS
|
|
||||||
|
|
||||||
res = set(OLD_TO_NEW_ORGANIZER_COMPAT.keys())
|
|
||||||
for pg in get_all_organizer_permission_groups().values():
|
|
||||||
for a in pg.actions:
|
|
||||||
res.add(f"{pg.name}:{a}")
|
|
||||||
|
|
||||||
_ALL_ORGANIZER_PERMISSIONS = res
|
|
||||||
_CACHE_TIME_APPS_READY = apps.ready
|
|
||||||
return res
|
|
||||||
|
|
||||||
|
|
||||||
def assert_valid_event_permission(permission, allow_legacy=True, allow_tuple=True):
|
|
||||||
if not apps.ready:
|
|
||||||
# can't really check yet
|
|
||||||
return
|
|
||||||
if allow_legacy and permission == "can_change_settings":
|
|
||||||
permission = "can_change_event_settings"
|
|
||||||
if permission is None:
|
|
||||||
return
|
|
||||||
if isinstance(permission, (list, tuple)) and allow_tuple:
|
|
||||||
for p in permission:
|
|
||||||
assert_valid_event_permission(p)
|
|
||||||
return
|
|
||||||
if not allow_legacy and ':' not in permission:
|
|
||||||
raise ValueError(f"Not allowed to use legacy permission '{permission}'")
|
|
||||||
all_permissions = get_all_event_permissions()
|
|
||||||
if permission not in all_permissions:
|
|
||||||
# Warning *and* exception because exception is silently caught when used in if statements in Django templates
|
|
||||||
warnings.warn(f"Use of undefined permission '{permission}'")
|
|
||||||
raise Exception(f"Undefined permission '{permission}'")
|
|
||||||
|
|
||||||
|
|
||||||
def assert_valid_organizer_permission(permission, allow_legacy=True, allow_tuple=True):
|
|
||||||
if not apps.ready:
|
|
||||||
# can't really check yet
|
|
||||||
return
|
|
||||||
if permission is None:
|
|
||||||
return
|
|
||||||
if isinstance(permission, (list, tuple)) and allow_tuple:
|
|
||||||
for p in permission:
|
|
||||||
assert_valid_organizer_permission(p)
|
|
||||||
return
|
|
||||||
if not allow_legacy and ':' not in permission:
|
|
||||||
raise ValueError(f"Not allowed to use legacy permission '{permission}'")
|
|
||||||
all_permissions = get_all_organizer_permissions()
|
|
||||||
if permission not in all_permissions:
|
|
||||||
# Warning *and* exception because warning is silently caught when used in if statements in Django templates
|
|
||||||
warnings.warn(f"Use of undefined permission '{permission}'")
|
|
||||||
raise Exception(f"Undefined permission '{permission}'")
|
|
||||||
|
|
||||||
|
|
||||||
OPTS_ALL_READ = [
|
|
||||||
PermissionOption(actions=tuple(), label=pgettext_lazy("permission_level", "View")),
|
|
||||||
PermissionOption(actions=("write",), label=pgettext_lazy("permission_level", "View and change")),
|
|
||||||
]
|
|
||||||
OPTS_ALL_READ_SETTINGS_API = [
|
|
||||||
PermissionOption(actions=tuple(), label=pgettext_lazy("permission_level", "View"),
|
|
||||||
help_text=_("API only")),
|
|
||||||
PermissionOption(actions=("write",), label=pgettext_lazy("permission_level", "View and change")),
|
|
||||||
]
|
|
||||||
OPTS_ALL_READ_SETTINGS_PARENT = [
|
|
||||||
PermissionOption(actions=tuple(), label=pgettext_lazy("permission_level", "View"),
|
|
||||||
help_text=_("Menu item will only show up if the user has permission for general settings.")),
|
|
||||||
PermissionOption(actions=("write",), label=pgettext_lazy("permission_level", "View and change")),
|
|
||||||
]
|
|
||||||
OPTS_READ_WRITE = [
|
|
||||||
PermissionOption(actions=tuple(), label=pgettext_lazy("permission_level", "No access")),
|
|
||||||
PermissionOption(actions=("read",), label=pgettext_lazy("permission_level", "View")),
|
|
||||||
PermissionOption(actions=("read", "write"), label=pgettext_lazy("permission_level", "View and change")),
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
@receiver(register_event_permission_groups, dispatch_uid="base_register_default_event_permissions")
|
|
||||||
def register_default_event_permissions(sender, **kwargs):
|
|
||||||
return [
|
|
||||||
PermissionGroup(
|
|
||||||
name="event.settings.general",
|
|
||||||
label=_("General settings"),
|
|
||||||
actions=["write"],
|
|
||||||
options=OPTS_ALL_READ_SETTINGS_API,
|
|
||||||
help_text=_(
|
|
||||||
"This includes access to all settings not listed explicitly below, including plugin settings."
|
|
||||||
),
|
|
||||||
),
|
|
||||||
PermissionGroup(
|
|
||||||
name="event.settings.payment",
|
|
||||||
label=_("Payment settings"),
|
|
||||||
actions=["write"],
|
|
||||||
options=OPTS_ALL_READ_SETTINGS_PARENT,
|
|
||||||
),
|
|
||||||
PermissionGroup(
|
|
||||||
name="event.settings.tax",
|
|
||||||
label=_("Tax settings"),
|
|
||||||
actions=["write"],
|
|
||||||
options=OPTS_ALL_READ_SETTINGS_PARENT,
|
|
||||||
),
|
|
||||||
PermissionGroup(
|
|
||||||
name="event.settings.invoicing",
|
|
||||||
label=_("Invoicing settings"),
|
|
||||||
actions=["write"],
|
|
||||||
options=OPTS_ALL_READ_SETTINGS_PARENT,
|
|
||||||
),
|
|
||||||
PermissionGroup(
|
|
||||||
name="event.subevents",
|
|
||||||
label=_("Event series dates"),
|
|
||||||
actions=["write"],
|
|
||||||
options=OPTS_ALL_READ,
|
|
||||||
),
|
|
||||||
PermissionGroup(
|
|
||||||
name="event.items",
|
|
||||||
label=_("Products, quotas and questions"),
|
|
||||||
actions=["write"],
|
|
||||||
options=OPTS_ALL_READ,
|
|
||||||
help_text=_("Also includes related objects like categories or discounts."),
|
|
||||||
),
|
|
||||||
PermissionGroup(
|
|
||||||
name="event.orders",
|
|
||||||
label=_("Orders"),
|
|
||||||
actions=["read", "write", "checkin"],
|
|
||||||
options=[
|
|
||||||
PermissionOption(actions=tuple(), label=pgettext_lazy("permission_level", "No access")),
|
|
||||||
PermissionOption(actions=("checkin",), label=pgettext_lazy("permission_level", "Only check-in")),
|
|
||||||
PermissionOption(actions=("read",), label=pgettext_lazy("permission_level", "View all")),
|
|
||||||
PermissionOption(actions=("read", "checkin"), label=pgettext_lazy("permission_level", "View all and check-in")),
|
|
||||||
PermissionOption(actions=("read", "write"), label=pgettext_lazy("permission_level", "View all and change"),
|
|
||||||
help_text=_("Includes the ability to cancel and refund individual orders.")),
|
|
||||||
],
|
|
||||||
help_text=_("Also includes related objects like the waiting list."),
|
|
||||||
),
|
|
||||||
PermissionGroup(
|
|
||||||
name="event.vouchers",
|
|
||||||
label=_("Vouchers"),
|
|
||||||
actions=["read", "write"],
|
|
||||||
options=OPTS_READ_WRITE,
|
|
||||||
),
|
|
||||||
PermissionGroup(
|
|
||||||
name="event",
|
|
||||||
label=_("Full event or date cancellation"),
|
|
||||||
actions=["cancel"],
|
|
||||||
options=[
|
|
||||||
# If we ever add more actions, we need a new UI idea here
|
|
||||||
PermissionOption(actions=tuple(), label=pgettext_lazy("permission_level", "Not allowed")),
|
|
||||||
PermissionOption(actions=("cancel",), label=pgettext_lazy("permission_level", "Allowed")),
|
|
||||||
],
|
|
||||||
help_text="",
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
@receiver(register_organizer_permission_groups, dispatch_uid="base_register_default_organizer_permissions")
|
|
||||||
def register_default_organizer_permissions(sender, **kwargs):
|
|
||||||
return [
|
|
||||||
PermissionGroup(
|
|
||||||
name="organizer.events",
|
|
||||||
label=_("Events"),
|
|
||||||
actions=["create"],
|
|
||||||
options=[
|
|
||||||
PermissionOption(actions=tuple(), label=pgettext_lazy("permission_level", "Only existing events")),
|
|
||||||
PermissionOption(actions=("create",), label=pgettext_lazy("permission_level", "Create new events")),
|
|
||||||
],
|
|
||||||
help_text="",
|
|
||||||
),
|
|
||||||
PermissionGroup(
|
|
||||||
name="organizer.settings.general",
|
|
||||||
label=_("Settings"),
|
|
||||||
actions=["write"],
|
|
||||||
options=OPTS_ALL_READ_SETTINGS_API,
|
|
||||||
help_text=_("This includes access to all organizer-level functionality not listed explicitly below, including plugin settings."),
|
|
||||||
),
|
|
||||||
PermissionGroup(
|
|
||||||
name="organizer.teams",
|
|
||||||
label=_("Teams"),
|
|
||||||
actions=["write"],
|
|
||||||
options=[
|
|
||||||
PermissionOption(actions=tuple(), label=pgettext_lazy("permission_level", "No access")),
|
|
||||||
PermissionOption(actions=("write",), label=pgettext_lazy("permission_level", "View and change"),
|
|
||||||
help_text=_("Includes the ability to give someone (including oneself) additional permissions.")),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
PermissionGroup(
|
|
||||||
name="organizer.giftcards",
|
|
||||||
label=_("Gift cards"),
|
|
||||||
actions=["read", "write"],
|
|
||||||
options=OPTS_READ_WRITE,
|
|
||||||
),
|
|
||||||
PermissionGroup(
|
|
||||||
name="organizer.customers",
|
|
||||||
label=_("Customers"),
|
|
||||||
actions=["read", "write"],
|
|
||||||
options=OPTS_READ_WRITE,
|
|
||||||
),
|
|
||||||
PermissionGroup(
|
|
||||||
name="organizer.reusablemedia",
|
|
||||||
label=_("Reusable media"),
|
|
||||||
actions=["read", "write"],
|
|
||||||
options=OPTS_READ_WRITE,
|
|
||||||
),
|
|
||||||
PermissionGroup(
|
|
||||||
name="organizer.devices",
|
|
||||||
label=_("Devices"),
|
|
||||||
actions=["read", "write"],
|
|
||||||
options=[
|
|
||||||
PermissionOption(actions=tuple(), label=pgettext_lazy("permission_level", "No access")),
|
|
||||||
PermissionOption(actions=("read",), label=pgettext_lazy("permission_level", "View")),
|
|
||||||
PermissionOption(actions=("read", "write"), label=pgettext_lazy("permission_level", "View and change"),
|
|
||||||
help_text=_("Includes the ability to give access to events and data oneself does not have access to.")),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
PermissionGroup(
|
|
||||||
name="organizer.seatingplans",
|
|
||||||
label=_("Seating plans"),
|
|
||||||
actions=["write"],
|
|
||||||
options=OPTS_ALL_READ,
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -34,7 +34,7 @@ from django_scopes import scopes_disabled
|
|||||||
from i18nfield.strings import LazyI18nString
|
from i18nfield.strings import LazyI18nString
|
||||||
|
|
||||||
from pretix.base.email import get_email_context
|
from pretix.base.email import get_email_context
|
||||||
from pretix.base.exporter import BaseExporter, OrganizerLevelExportMixin
|
from pretix.base.exporter import OrganizerLevelExportMixin
|
||||||
from pretix.base.i18n import LazyLocaleException, language
|
from pretix.base.i18n import LazyLocaleException, language
|
||||||
from pretix.base.models import (
|
from pretix.base.models import (
|
||||||
CachedFile, Device, Event, Organizer, ScheduledEventExport, TeamAPIToken,
|
CachedFile, Device, Event, Organizer, ScheduledEventExport, TeamAPIToken,
|
||||||
@@ -64,15 +64,7 @@ class ExportEmptyError(ExportError):
|
|||||||
|
|
||||||
|
|
||||||
@app.task(base=ProfiledEventTask, throws=(ExportError, ExportEmptyError), bind=True)
|
@app.task(base=ProfiledEventTask, throws=(ExportError, ExportEmptyError), bind=True)
|
||||||
def export(self, event: Event, user: User, device: int, token: int, fileid: str, provider: str,
|
def export(self, event: Event, fileid: str, provider: str, form_data: Dict[str, Any]) -> None:
|
||||||
form_data: Dict[str, Any], staff_session=False) -> None:
|
|
||||||
if user:
|
|
||||||
user = User.objects.get(pk=user)
|
|
||||||
if device:
|
|
||||||
device = Device.objects.get(pk=device)
|
|
||||||
if token:
|
|
||||||
device = TeamAPIToken.objects.get(pk=token)
|
|
||||||
|
|
||||||
def set_progress(val):
|
def set_progress(val):
|
||||||
if not self.request.called_directly:
|
if not self.request.called_directly:
|
||||||
self.update_state(
|
self.update_state(
|
||||||
@@ -80,38 +72,30 @@ def export(self, event: Event, user: User, device: int, token: int, fileid: str,
|
|||||||
meta={'value': val}
|
meta={'value': val}
|
||||||
)
|
)
|
||||||
|
|
||||||
ex = init_event_exporter(
|
|
||||||
identifier=provider,
|
|
||||||
event=event,
|
|
||||||
user=user,
|
|
||||||
token=token,
|
|
||||||
device=device,
|
|
||||||
staff_session=staff_session,
|
|
||||||
progress_callback=set_progress,
|
|
||||||
)
|
|
||||||
if not ex:
|
|
||||||
raise ExportError(
|
|
||||||
gettext('Export not found or you do not have sufficient permission to perform this export.')
|
|
||||||
)
|
|
||||||
|
|
||||||
file = CachedFile.objects.get(id=fileid)
|
file = CachedFile.objects.get(id=fileid)
|
||||||
with language(event.settings.locale, event.settings.region), override(event.settings.timezone):
|
with language(event.settings.locale, event.settings.region), override(event.settings.timezone):
|
||||||
if ex.repeatable_read:
|
responses = register_data_exporters.send(event)
|
||||||
with repeatable_reads_transaction():
|
for recv, response in responses:
|
||||||
d = ex.render(form_data)
|
if not response:
|
||||||
else:
|
continue
|
||||||
d = ex.render(form_data)
|
ex = response(event, event.organizer, set_progress)
|
||||||
|
if ex.identifier == provider:
|
||||||
|
if ex.repeatable_read:
|
||||||
|
with repeatable_reads_transaction():
|
||||||
|
d = ex.render(form_data)
|
||||||
|
else:
|
||||||
|
d = ex.render(form_data)
|
||||||
|
|
||||||
if d is None:
|
if d is None:
|
||||||
raise ExportError(
|
raise ExportError(
|
||||||
gettext('Your export did not contain any data.')
|
gettext('Your export did not contain any data.')
|
||||||
)
|
)
|
||||||
file.filename, file.type, data = d
|
file.filename, file.type, data = d
|
||||||
|
|
||||||
close_old_connections() # This task can run very long, we might need a new DB connection
|
close_old_connections() # This task can run very long, we might need a new DB connection
|
||||||
|
|
||||||
f = ContentFile(data)
|
f = ContentFile(data)
|
||||||
file.file.save(cachedfile_name(file, file.filename), f)
|
file.file.save(cachedfile_name(file, file.filename), f)
|
||||||
return str(file.pk)
|
return str(file.pk)
|
||||||
|
|
||||||
|
|
||||||
@@ -121,7 +105,10 @@ def multiexport(self, organizer: Organizer, user: User, device: int, token: int,
|
|||||||
if device:
|
if device:
|
||||||
device = Device.objects.get(pk=device)
|
device = Device.objects.get(pk=device)
|
||||||
if token:
|
if token:
|
||||||
token = TeamAPIToken.objects.get(pk=token)
|
device = TeamAPIToken.objects.get(pk=token)
|
||||||
|
allowed_events = (device or token or user).get_events_with_permission('can_view_orders')
|
||||||
|
if user and staff_session:
|
||||||
|
allowed_events = organizer.events.all()
|
||||||
|
|
||||||
def set_progress(val):
|
def set_progress(val):
|
||||||
if not self.request.called_directly:
|
if not self.request.called_directly:
|
||||||
@@ -131,35 +118,12 @@ def multiexport(self, organizer: Organizer, user: User, device: int, token: int,
|
|||||||
)
|
)
|
||||||
|
|
||||||
file = CachedFile.objects.get(id=fileid)
|
file = CachedFile.objects.get(id=fileid)
|
||||||
|
|
||||||
event_qs = organizer.events.all()
|
|
||||||
if form_data.get('events') is not None and not form_data.get('all_events'):
|
|
||||||
if isinstance(form_data['events'][0], str):
|
|
||||||
event_qs = event_qs.filter(slug__in=form_data.get('events'))
|
|
||||||
else:
|
|
||||||
event_qs = event_qs.filter(pk__in=form_data.get('events'))
|
|
||||||
|
|
||||||
ex = init_organizer_exporter(
|
|
||||||
identifier=provider,
|
|
||||||
organizer=organizer,
|
|
||||||
user=user,
|
|
||||||
token=token,
|
|
||||||
device=device,
|
|
||||||
staff_session=staff_session,
|
|
||||||
progress_callback=set_progress,
|
|
||||||
event_qs=event_qs,
|
|
||||||
)
|
|
||||||
if not ex:
|
|
||||||
raise ExportError(
|
|
||||||
gettext('Export not found or you do not have sufficient permission to perform this export.')
|
|
||||||
)
|
|
||||||
|
|
||||||
if user:
|
if user:
|
||||||
locale = user.locale
|
locale = user.locale
|
||||||
timezone = user.timezone
|
timezone = user.timezone
|
||||||
region = None # todo: add to user?
|
region = None # todo: add to user?
|
||||||
else:
|
else:
|
||||||
e = ex.events.first()
|
e = allowed_events.first()
|
||||||
if e:
|
if e:
|
||||||
locale = e.settings.locale
|
locale = e.settings.locale
|
||||||
timezone = e.settings.timezone
|
timezone = e.settings.timezone
|
||||||
@@ -169,138 +133,45 @@ def multiexport(self, organizer: Organizer, user: User, device: int, token: int,
|
|||||||
timezone = organizer.settings.timezone or settings.TIME_ZONE
|
timezone = organizer.settings.timezone or settings.TIME_ZONE
|
||||||
region = organizer.settings.region
|
region = organizer.settings.region
|
||||||
with language(locale, region), override(timezone):
|
with language(locale, region), override(timezone):
|
||||||
if ex.repeatable_read:
|
if form_data.get('events') is not None and not form_data.get('all_events'):
|
||||||
with repeatable_reads_transaction():
|
if isinstance(form_data['events'][0], str):
|
||||||
d = ex.render(form_data)
|
events = allowed_events.filter(slug__in=form_data.get('events'), organizer=organizer)
|
||||||
|
else:
|
||||||
|
events = allowed_events.filter(pk__in=form_data.get('events'), organizer=organizer)
|
||||||
else:
|
else:
|
||||||
d = ex.render(form_data)
|
events = allowed_events.filter(organizer=organizer)
|
||||||
if d is None:
|
responses = register_multievent_data_exporters.send(organizer)
|
||||||
raise ExportError(
|
|
||||||
gettext('Your export did not contain any data.')
|
|
||||||
)
|
|
||||||
file.filename, file.type, data = d
|
|
||||||
|
|
||||||
close_old_connections() # This task can run very long, we might need a new DB connection
|
for recv, response in responses:
|
||||||
|
if not response:
|
||||||
f = ContentFile(data)
|
|
||||||
file.file.save(cachedfile_name(file, file.filename), f)
|
|
||||||
return str(file.pk)
|
|
||||||
|
|
||||||
|
|
||||||
def init_event_exporter(identifier, **kwargs):
|
|
||||||
for ex in init_event_exporters(**kwargs):
|
|
||||||
if ex.identifier == identifier:
|
|
||||||
return ex
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def init_event_exporters(event, user=None, token=None, device=None, request=None, progress_callback=None, staff_session=False):
|
|
||||||
if not user and not token and not device:
|
|
||||||
raise ValueError("No auth source given.")
|
|
||||||
perm_holder = device or token or user
|
|
||||||
|
|
||||||
responses = register_data_exporters.send(event)
|
|
||||||
for r, response in responses:
|
|
||||||
if not response:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if issubclass(response, OrganizerLevelExportMixin):
|
|
||||||
raise TypeError("Cannot use organizer-level exporter on event level")
|
|
||||||
|
|
||||||
permission_name = response.get_required_event_permission()
|
|
||||||
if not perm_holder.has_event_permission(event.organizer, event, permission_name, request) and not staff_session:
|
|
||||||
continue
|
|
||||||
|
|
||||||
exporter: BaseExporter = response(event=event, organizer=event.organizer, progress_callback=progress_callback)
|
|
||||||
|
|
||||||
if not exporter.available_for_user(user if user and user.is_authenticated else None):
|
|
||||||
continue
|
|
||||||
|
|
||||||
yield exporter
|
|
||||||
|
|
||||||
|
|
||||||
def init_organizer_exporter(identifier, **kwargs):
|
|
||||||
for ex in init_organizer_exporters(**kwargs):
|
|
||||||
if ex.identifier == identifier:
|
|
||||||
return ex
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def init_organizer_exporters(
|
|
||||||
organizer, user=None, token=None, device=None, request=None, progress_callback=None, staff_session=False, event_qs=None
|
|
||||||
):
|
|
||||||
if not user and not token and not device:
|
|
||||||
raise ValueError("No auth source given.")
|
|
||||||
perm_holder = device or token or user
|
|
||||||
|
|
||||||
_event_list_cache = {}
|
|
||||||
_has_permission_on_any_team_cache = {}
|
|
||||||
_team_cache = None
|
|
||||||
|
|
||||||
responses = register_multievent_data_exporters.send(organizer)
|
|
||||||
for r, response in responses:
|
|
||||||
if not response:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if issubclass(response, OrganizerLevelExportMixin):
|
|
||||||
exporter: BaseExporter = response(event=Event.objects.none(), organizer=organizer, progress_callback=progress_callback)
|
|
||||||
|
|
||||||
try:
|
|
||||||
if not perm_holder.has_organizer_permission(organizer, response.get_required_organizer_permission(), request) and not staff_session:
|
|
||||||
continue
|
|
||||||
except NotImplementedError:
|
|
||||||
logger.error(f"Not showing export {response} because get_required_organizer_permission() is not implemented.")
|
|
||||||
continue
|
continue
|
||||||
|
ex = response(events, organizer, set_progress)
|
||||||
else:
|
if ex.identifier == provider:
|
||||||
permission_name = response.get_required_event_permission()
|
if (
|
||||||
|
isinstance(ex, OrganizerLevelExportMixin) and
|
||||||
if permission_name not in _event_list_cache:
|
not staff_session and
|
||||||
if staff_session:
|
not (device or token or user).has_organizer_permission(organizer, ex.organizer_required_permission)
|
||||||
events = event_qs.all()
|
):
|
||||||
elif event_qs is not None:
|
raise ExportError(
|
||||||
events = event_qs.filter(
|
gettext('You do not have sufficient permission to perform this export.')
|
||||||
pk__in=perm_holder.get_events_with_permission(
|
|
||||||
permission_name, request=request
|
|
||||||
).filter(
|
|
||||||
organizer=organizer
|
|
||||||
).values("id")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if ex.repeatable_read:
|
||||||
|
with repeatable_reads_transaction():
|
||||||
|
d = ex.render(form_data)
|
||||||
else:
|
else:
|
||||||
events = perm_holder.get_events_with_permission(
|
d = ex.render(form_data)
|
||||||
permission_name, request=request
|
if d is None:
|
||||||
).filter(
|
raise ExportError(
|
||||||
organizer=organizer
|
gettext('Your export did not contain any data.')
|
||||||
)
|
)
|
||||||
|
file.filename, file.type, data = d
|
||||||
|
|
||||||
_event_list_cache[permission_name] = events
|
close_old_connections() # This task can run very long, we might need a new DB connection
|
||||||
|
|
||||||
if permission_name not in _has_permission_on_any_team_cache:
|
f = ContentFile(data)
|
||||||
# Check if the user has this event permission on any teams they are part of to decide whether to show
|
file.file.save(cachedfile_name(file, file.filename), f)
|
||||||
# the export at all.
|
return str(file.pk)
|
||||||
# This is different from _event_list_cache[permission_name].exists() for the case of an organizer with
|
|
||||||
# zero events in total, or a team with zero events. In these cases, we still want people to be able
|
|
||||||
# to see waht exports they'll get once they have events.
|
|
||||||
if user:
|
|
||||||
if _team_cache is None:
|
|
||||||
_team_cache = list(user.teams.filter(organizer=organizer))
|
|
||||||
_has_permission_on_any_team_cache[permission_name] = staff_session or any(
|
|
||||||
t.has_event_permission(permission_name) for t in _team_cache
|
|
||||||
)
|
|
||||||
elif token:
|
|
||||||
_has_permission_on_any_team_cache[permission_name] = token.team.has_event_permission(permission_name)
|
|
||||||
elif device:
|
|
||||||
_has_permission_on_any_team_cache[permission_name] = device.has_event_permission(permission_name)
|
|
||||||
|
|
||||||
if not _has_permission_on_any_team_cache[permission_name]:
|
|
||||||
continue
|
|
||||||
|
|
||||||
exporter: BaseExporter = response(event=_event_list_cache[permission_name], organizer=organizer, progress_callback=progress_callback)
|
|
||||||
|
|
||||||
if not exporter.available_for_user(user if user and user.is_authenticated else None):
|
|
||||||
continue
|
|
||||||
|
|
||||||
yield exporter
|
|
||||||
|
|
||||||
|
|
||||||
def _run_scheduled_export(schedule, context: Union[Event, Organizer], exporter, config_url, retry_func, has_permission):
|
def _run_scheduled_export(schedule, context: Union[Event, Organizer], exporter, config_url, retry_func, has_permission):
|
||||||
@@ -346,7 +217,7 @@ def _run_scheduled_export(schedule, context: Union[Event, Organizer], exporter,
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
if not exporter:
|
if not exporter:
|
||||||
raise ExportError("Export type not found or permission denied.")
|
raise ExportError("Export type not found.")
|
||||||
if exporter.repeatable_read:
|
if exporter.repeatable_read:
|
||||||
with repeatable_reads_transaction():
|
with repeatable_reads_transaction():
|
||||||
d = exporter.render(schedule.export_form_data)
|
d = exporter.render(schedule.export_form_data)
|
||||||
@@ -420,20 +291,31 @@ def _run_scheduled_export(schedule, context: Union[Event, Organizer], exporter,
|
|||||||
def scheduled_organizer_export(self, organizer: Organizer, schedule: int) -> None:
|
def scheduled_organizer_export(self, organizer: Organizer, schedule: int) -> None:
|
||||||
schedule = organizer.scheduled_exports.get(pk=schedule)
|
schedule = organizer.scheduled_exports.get(pk=schedule)
|
||||||
|
|
||||||
event_qs = organizer.events.all()
|
allowed_events = schedule.owner.get_events_with_permission('can_view_orders')
|
||||||
if schedule.export_form_data.get('events') is not None and not schedule.export_form_data.get('all_events'):
|
if schedule.export_form_data.get('events') is not None and not schedule.export_form_data.get('all_events'):
|
||||||
if isinstance(schedule.export_form_data['events'][0], str):
|
if isinstance(schedule.export_form_data['events'][0], str):
|
||||||
event_qs = event_qs.filter(slug__in=schedule.export_form_data.get('events'))
|
events = allowed_events.filter(slug__in=schedule.export_form_data.get('events'), organizer=organizer)
|
||||||
else:
|
else:
|
||||||
event_qs = event_qs.filter(pk__in=schedule.export_form_data.get('events'))
|
events = allowed_events.filter(pk__in=schedule.export_form_data.get('events'), organizer=organizer)
|
||||||
|
else:
|
||||||
|
events = allowed_events.filter(organizer=organizer)
|
||||||
|
|
||||||
|
responses = register_multievent_data_exporters.send(organizer)
|
||||||
|
exporter = None
|
||||||
|
for recv, response in responses:
|
||||||
|
if not response:
|
||||||
|
continue
|
||||||
|
ex = response(events, organizer)
|
||||||
|
if ex.identifier == schedule.export_identifier:
|
||||||
|
exporter = ex
|
||||||
|
break
|
||||||
|
|
||||||
exporter = init_organizer_exporter(
|
|
||||||
identifier=schedule.export_identifier,
|
|
||||||
organizer=organizer,
|
|
||||||
user=schedule.owner,
|
|
||||||
event_qs=event_qs,
|
|
||||||
)
|
|
||||||
has_permission = schedule.owner.is_active
|
has_permission = schedule.owner.is_active
|
||||||
|
if isinstance(exporter, OrganizerLevelExportMixin):
|
||||||
|
if not schedule.owner.has_organizer_permission(organizer, exporter.organizer_required_permission):
|
||||||
|
has_permission = False
|
||||||
|
if exporter and not exporter.available_for_user(schedule.owner):
|
||||||
|
has_permission = False
|
||||||
|
|
||||||
_run_scheduled_export(
|
_run_scheduled_export(
|
||||||
schedule,
|
schedule,
|
||||||
@@ -454,12 +336,17 @@ def scheduled_organizer_export(self, organizer: Organizer, schedule: int) -> Non
|
|||||||
def scheduled_event_export(self, event: Event, schedule: int) -> None:
|
def scheduled_event_export(self, event: Event, schedule: int) -> None:
|
||||||
schedule = event.scheduled_exports.get(pk=schedule)
|
schedule = event.scheduled_exports.get(pk=schedule)
|
||||||
|
|
||||||
exporter = init_event_exporter(
|
responses = register_data_exporters.send(event)
|
||||||
identifier=schedule.export_identifier,
|
exporter = None
|
||||||
event=event,
|
for recv, response in responses:
|
||||||
user=schedule.owner,
|
if not response:
|
||||||
)
|
continue
|
||||||
has_permission = schedule.owner.is_active
|
ex = response(event, event.organizer)
|
||||||
|
if ex.identifier == schedule.export_identifier:
|
||||||
|
exporter = ex
|
||||||
|
break
|
||||||
|
|
||||||
|
has_permission = schedule.owner.is_active and schedule.owner.has_event_permission(event.organizer, event, 'can_view_orders')
|
||||||
|
|
||||||
_run_scheduled_export(
|
_run_scheduled_export(
|
||||||
schedule,
|
schedule,
|
||||||
|
|||||||
@@ -521,20 +521,9 @@ def invoice_pdf_task(invoice: int):
|
|||||||
|
|
||||||
|
|
||||||
def invoice_qualified(order: Order):
|
def invoice_qualified(order: Order):
|
||||||
if order.total == Decimal('0.00'):
|
if order.total == Decimal('0.00') or order.require_approval or \
|
||||||
|
order.sales_channel.identifier not in order.event.settings.get('invoice_generate_sales_channels'):
|
||||||
return False
|
return False
|
||||||
if order.require_approval:
|
|
||||||
return False
|
|
||||||
if order.sales_channel.identifier not in order.event.settings.invoice_generate_sales_channels:
|
|
||||||
return False
|
|
||||||
if order.status in (Order.STATUS_CANCELED, Order.STATUS_EXPIRED):
|
|
||||||
return False
|
|
||||||
if order.event.settings.invoice_generate_only_business:
|
|
||||||
try:
|
|
||||||
ia = order.invoice_address
|
|
||||||
return ia.is_business
|
|
||||||
except InvoiceAddress.DoesNotExist:
|
|
||||||
return False
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -112,8 +112,7 @@ def dictsum(*dicts) -> dict:
|
|||||||
|
|
||||||
def order_overview(
|
def order_overview(
|
||||||
event: Event, subevent: SubEvent=None, date_filter='', date_from=None, date_until=None, fees=False,
|
event: Event, subevent: SubEvent=None, date_filter='', date_from=None, date_until=None, fees=False,
|
||||||
admission_only=False, base_qs=None, base_fees_qs=None, subevent_date_from=None, subevent_date_until=None,
|
admission_only=False, base_qs=None, base_fees_qs=None, subevent_date_from=None, subevent_date_until=None
|
||||||
skip_empty_lines=False,
|
|
||||||
) -> Tuple[List[Tuple[ItemCategory, List[Item]]], Dict[str, Tuple[Decimal, Decimal]]]:
|
) -> Tuple[List[Tuple[ItemCategory, List[Item]]], Dict[str, Tuple[Decimal, Decimal]]]:
|
||||||
items = event.items.all().select_related(
|
items = event.items.all().select_related(
|
||||||
'category', # for re-grouping
|
'category', # for re-grouping
|
||||||
@@ -206,21 +205,13 @@ def order_overview(
|
|||||||
for l in states.keys():
|
for l in states.keys():
|
||||||
var.num[l] = num[l].get((item.id, variid), (0, 0, 0))
|
var.num[l] = num[l].get((item.id, variid), (0, 0, 0))
|
||||||
var.num['total'] = num['total'].get((item.id, variid), (0, 0, 0))
|
var.num['total'] = num['total'].get((item.id, variid), (0, 0, 0))
|
||||||
var._skip = all(v[0] == 0 for v in var.num.values())
|
|
||||||
for l in states.keys():
|
for l in states.keys():
|
||||||
item.num[l] = tuplesum(var.num[l] for var in item.all_variations)
|
item.num[l] = tuplesum(var.num[l] for var in item.all_variations)
|
||||||
item.num['total'] = tuplesum(var.num['total'] for var in item.all_variations)
|
item.num['total'] = tuplesum(var.num['total'] for var in item.all_variations)
|
||||||
if skip_empty_lines:
|
|
||||||
item.all_variations = [v for v in item.all_variations if not v._skip]
|
|
||||||
item._skip = not item.all_variations
|
|
||||||
else:
|
else:
|
||||||
for l in states.keys():
|
for l in states.keys():
|
||||||
item.num[l] = num[l].get((item.id, None), (0, 0, 0))
|
item.num[l] = num[l].get((item.id, None), (0, 0, 0))
|
||||||
item.num['total'] = num['total'].get((item.id, None), (0, 0, 0))
|
item.num['total'] = num['total'].get((item.id, None), (0, 0, 0))
|
||||||
item._skip = all(v[0] == 0 for v in item.num.values())
|
|
||||||
|
|
||||||
if skip_empty_lines:
|
|
||||||
items = [i for i in items if not i._skip]
|
|
||||||
|
|
||||||
nonecat = ItemCategory(name=_('Uncategorized'))
|
nonecat = ItemCategory(name=_('Uncategorized'))
|
||||||
# Regroup those by category
|
# Regroup those by category
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ from pretix.base.validators import multimail_validate
|
|||||||
from pretix.control.forms import (
|
from pretix.control.forms import (
|
||||||
ExtFileField, FontSelect, MultipleLanguagesWidget, SingleLanguageWidget,
|
ExtFileField, FontSelect, MultipleLanguagesWidget, SingleLanguageWidget,
|
||||||
)
|
)
|
||||||
from pretix.helpers.countries import CachedCountries, pycountry_add
|
from pretix.helpers.countries import CachedCountries
|
||||||
|
|
||||||
ROUNDING_MODES = (
|
ROUNDING_MODES = (
|
||||||
('line', _('Compute taxes for every line individually')),
|
('line', _('Compute taxes for every line individually')),
|
||||||
@@ -344,7 +344,6 @@ DEFAULTS = {
|
|||||||
'type': bool,
|
'type': bool,
|
||||||
'form_class': forms.BooleanField,
|
'form_class': forms.BooleanField,
|
||||||
'serializer_class': serializers.BooleanField,
|
'serializer_class': serializers.BooleanField,
|
||||||
'write_permission': 'event.settings.tax:write',
|
|
||||||
'form_kwargs': dict(
|
'form_kwargs': dict(
|
||||||
label=_("Show net prices instead of gross prices in the product list"),
|
label=_("Show net prices instead of gross prices in the product list"),
|
||||||
help_text=_("Independent of your choice, the cart will show gross prices as this is the price that needs to be "
|
help_text=_("Independent of your choice, the cart will show gross prices as this is the price that needs to be "
|
||||||
@@ -492,7 +491,6 @@ DEFAULTS = {
|
|||||||
'type': str,
|
'type': str,
|
||||||
'form_class': forms.ChoiceField,
|
'form_class': forms.ChoiceField,
|
||||||
'serializer_class': serializers.ChoiceField,
|
'serializer_class': serializers.ChoiceField,
|
||||||
'write_permission': 'event.settings.tax:write',
|
|
||||||
'form_kwargs': dict(
|
'form_kwargs': dict(
|
||||||
label=_("Rounding of taxes"),
|
label=_("Rounding of taxes"),
|
||||||
widget=forms.RadioSelect,
|
widget=forms.RadioSelect,
|
||||||
@@ -512,17 +510,15 @@ DEFAULTS = {
|
|||||||
'type': bool,
|
'type': bool,
|
||||||
'form_class': forms.BooleanField,
|
'form_class': forms.BooleanField,
|
||||||
'serializer_class': serializers.BooleanField,
|
'serializer_class': serializers.BooleanField,
|
||||||
'write_permission': 'event.settings.invoicing:write',
|
|
||||||
'form_kwargs': dict(
|
'form_kwargs': dict(
|
||||||
label=_("Ask for invoice address"),
|
label=_("Ask for invoice address"),
|
||||||
),
|
)
|
||||||
},
|
},
|
||||||
'invoice_address_not_asked_free': {
|
'invoice_address_not_asked_free': {
|
||||||
'default': 'False',
|
'default': 'False',
|
||||||
'type': bool,
|
'type': bool,
|
||||||
'form_class': forms.BooleanField,
|
'form_class': forms.BooleanField,
|
||||||
'serializer_class': serializers.BooleanField,
|
'serializer_class': serializers.BooleanField,
|
||||||
'write_permission': 'event.settings.invoicing:write',
|
|
||||||
'form_kwargs': dict(
|
'form_kwargs': dict(
|
||||||
label=_('Do not ask for invoice address if an order is free'),
|
label=_('Do not ask for invoice address if an order is free'),
|
||||||
)
|
)
|
||||||
@@ -532,7 +528,6 @@ DEFAULTS = {
|
|||||||
'type': bool,
|
'type': bool,
|
||||||
'form_class': forms.BooleanField,
|
'form_class': forms.BooleanField,
|
||||||
'serializer_class': serializers.BooleanField,
|
'serializer_class': serializers.BooleanField,
|
||||||
'write_permission': 'event.settings.invoicing:write',
|
|
||||||
'form_kwargs': dict(
|
'form_kwargs': dict(
|
||||||
label=_("Require customer name"),
|
label=_("Require customer name"),
|
||||||
)
|
)
|
||||||
@@ -542,7 +537,6 @@ DEFAULTS = {
|
|||||||
'type': bool,
|
'type': bool,
|
||||||
'form_class': forms.BooleanField,
|
'form_class': forms.BooleanField,
|
||||||
'serializer_class': serializers.BooleanField,
|
'serializer_class': serializers.BooleanField,
|
||||||
'write_permission': 'event.settings.invoicing:write',
|
|
||||||
'form_kwargs': dict(
|
'form_kwargs': dict(
|
||||||
label=_("Show attendee names on invoices"),
|
label=_("Show attendee names on invoices"),
|
||||||
)
|
)
|
||||||
@@ -552,7 +546,6 @@ DEFAULTS = {
|
|||||||
'type': bool,
|
'type': bool,
|
||||||
'form_class': forms.BooleanField,
|
'form_class': forms.BooleanField,
|
||||||
'serializer_class': serializers.BooleanField,
|
'serializer_class': serializers.BooleanField,
|
||||||
'write_permission': 'event.settings.invoicing:write',
|
|
||||||
'form_kwargs': dict(
|
'form_kwargs': dict(
|
||||||
label=_("Show event location on invoices"),
|
label=_("Show event location on invoices"),
|
||||||
help_text=_("The event location will be shown below the list of products if it is the same for all "
|
help_text=_("The event location will be shown below the list of products if it is the same for all "
|
||||||
@@ -564,7 +557,6 @@ DEFAULTS = {
|
|||||||
'type': str,
|
'type': str,
|
||||||
'form_class': forms.ChoiceField,
|
'form_class': forms.ChoiceField,
|
||||||
'serializer_class': serializers.ChoiceField,
|
'serializer_class': serializers.ChoiceField,
|
||||||
'write_permission': 'event.settings.invoicing:write',
|
|
||||||
'form_kwargs': dict(
|
'form_kwargs': dict(
|
||||||
label=_("Show exchange rates"),
|
label=_("Show exchange rates"),
|
||||||
widget=forms.RadioSelect,
|
widget=forms.RadioSelect,
|
||||||
@@ -588,7 +580,6 @@ DEFAULTS = {
|
|||||||
'default': 'False',
|
'default': 'False',
|
||||||
'form_class': forms.BooleanField,
|
'form_class': forms.BooleanField,
|
||||||
'serializer_class': serializers.BooleanField,
|
'serializer_class': serializers.BooleanField,
|
||||||
'write_permission': 'event.settings.invoicing:write',
|
|
||||||
'type': bool,
|
'type': bool,
|
||||||
'form_kwargs': dict(
|
'form_kwargs': dict(
|
||||||
label=_("Require invoice address"),
|
label=_("Require invoice address"),
|
||||||
@@ -599,7 +590,6 @@ DEFAULTS = {
|
|||||||
'default': 'False',
|
'default': 'False',
|
||||||
'form_class': forms.BooleanField,
|
'form_class': forms.BooleanField,
|
||||||
'serializer_class': serializers.BooleanField,
|
'serializer_class': serializers.BooleanField,
|
||||||
'write_permission': 'event.settings.invoicing:write',
|
|
||||||
'type': bool,
|
'type': bool,
|
||||||
'form_kwargs': dict(
|
'form_kwargs': dict(
|
||||||
label=_("Require a business address"),
|
label=_("Require a business address"),
|
||||||
@@ -612,7 +602,6 @@ DEFAULTS = {
|
|||||||
'type': bool,
|
'type': bool,
|
||||||
'form_class': forms.BooleanField,
|
'form_class': forms.BooleanField,
|
||||||
'serializer_class': serializers.BooleanField,
|
'serializer_class': serializers.BooleanField,
|
||||||
'write_permission': 'event.settings.invoicing:write',
|
|
||||||
'form_kwargs': dict(
|
'form_kwargs': dict(
|
||||||
label=_("Ask for beneficiary"),
|
label=_("Ask for beneficiary"),
|
||||||
widget=forms.CheckboxInput(attrs={'data-checkbox-dependency': '#id_invoice_address_asked'}),
|
widget=forms.CheckboxInput(attrs={'data-checkbox-dependency': '#id_invoice_address_asked'}),
|
||||||
@@ -623,7 +612,6 @@ DEFAULTS = {
|
|||||||
'type': LazyI18nString,
|
'type': LazyI18nString,
|
||||||
'form_class': I18nFormField,
|
'form_class': I18nFormField,
|
||||||
'serializer_class': I18nField,
|
'serializer_class': I18nField,
|
||||||
'write_permission': 'event.settings.invoicing:write',
|
|
||||||
'form_kwargs': dict(
|
'form_kwargs': dict(
|
||||||
label=_("Custom recipient field label"),
|
label=_("Custom recipient field label"),
|
||||||
widget=I18nTextInput,
|
widget=I18nTextInput,
|
||||||
@@ -639,7 +627,6 @@ DEFAULTS = {
|
|||||||
'type': LazyI18nString,
|
'type': LazyI18nString,
|
||||||
'form_class': I18nFormField,
|
'form_class': I18nFormField,
|
||||||
'serializer_class': I18nField,
|
'serializer_class': I18nField,
|
||||||
'write_permission': 'event.settings.invoicing:write',
|
|
||||||
'form_kwargs': dict(
|
'form_kwargs': dict(
|
||||||
label=_("Custom recipient field help text"),
|
label=_("Custom recipient field help text"),
|
||||||
widget=I18nTextInput,
|
widget=I18nTextInput,
|
||||||
@@ -652,7 +639,6 @@ DEFAULTS = {
|
|||||||
'type': bool,
|
'type': bool,
|
||||||
'form_class': forms.BooleanField,
|
'form_class': forms.BooleanField,
|
||||||
'serializer_class': serializers.BooleanField,
|
'serializer_class': serializers.BooleanField,
|
||||||
'write_permission': 'event.settings.invoicing:write',
|
|
||||||
'form_kwargs': dict(
|
'form_kwargs': dict(
|
||||||
label=_("Ask for VAT ID"),
|
label=_("Ask for VAT ID"),
|
||||||
help_text=format_lazy(
|
help_text=format_lazy(
|
||||||
@@ -668,7 +654,6 @@ DEFAULTS = {
|
|||||||
'type': list,
|
'type': list,
|
||||||
'form_class': forms.MultipleChoiceField,
|
'form_class': forms.MultipleChoiceField,
|
||||||
'serializer_class': serializers.MultipleChoiceField,
|
'serializer_class': serializers.MultipleChoiceField,
|
||||||
'write_permission': 'event.settings.invoicing:write',
|
|
||||||
'serializer_kwargs': dict(
|
'serializer_kwargs': dict(
|
||||||
choices=lazy(
|
choices=lazy(
|
||||||
lambda *args: sorted([(cc, gettext(Country(cc).name)) for cc in VAT_ID_COUNTRIES], key=lambda c: c[1]),
|
lambda *args: sorted([(cc, gettext(Country(cc).name)) for cc in VAT_ID_COUNTRIES], key=lambda c: c[1]),
|
||||||
@@ -696,7 +681,6 @@ DEFAULTS = {
|
|||||||
'type': LazyI18nString,
|
'type': LazyI18nString,
|
||||||
'form_class': I18nFormField,
|
'form_class': I18nFormField,
|
||||||
'serializer_class': I18nField,
|
'serializer_class': I18nField,
|
||||||
'write_permission': 'event.settings.invoicing:write',
|
|
||||||
'form_kwargs': dict(
|
'form_kwargs': dict(
|
||||||
label=_("Invoice address explanation"),
|
label=_("Invoice address explanation"),
|
||||||
widget=I18nMarkdownTextarea,
|
widget=I18nMarkdownTextarea,
|
||||||
@@ -709,7 +693,6 @@ DEFAULTS = {
|
|||||||
'type': bool,
|
'type': bool,
|
||||||
'form_class': forms.BooleanField,
|
'form_class': forms.BooleanField,
|
||||||
'serializer_class': serializers.BooleanField,
|
'serializer_class': serializers.BooleanField,
|
||||||
'write_permission': 'event.settings.invoicing:write',
|
|
||||||
'form_kwargs': dict(
|
'form_kwargs': dict(
|
||||||
label=_("Show paid amount on partially paid invoices"),
|
label=_("Show paid amount on partially paid invoices"),
|
||||||
help_text=_("If an invoice has already been paid partially, this option will add the paid and pending "
|
help_text=_("If an invoice has already been paid partially, this option will add the paid and pending "
|
||||||
@@ -721,7 +704,6 @@ DEFAULTS = {
|
|||||||
'type': bool,
|
'type': bool,
|
||||||
'form_class': forms.BooleanField,
|
'form_class': forms.BooleanField,
|
||||||
'serializer_class': serializers.BooleanField,
|
'serializer_class': serializers.BooleanField,
|
||||||
'write_permission': 'event.settings.invoicing:write',
|
|
||||||
'form_kwargs': dict(
|
'form_kwargs': dict(
|
||||||
label=_("Show free products on invoices"),
|
label=_("Show free products on invoices"),
|
||||||
help_text=_("Note that invoices will never be generated for orders that contain only free "
|
help_text=_("Note that invoices will never be generated for orders that contain only free "
|
||||||
@@ -733,7 +715,6 @@ DEFAULTS = {
|
|||||||
'type': bool,
|
'type': bool,
|
||||||
'form_class': forms.BooleanField,
|
'form_class': forms.BooleanField,
|
||||||
'serializer_class': serializers.BooleanField,
|
'serializer_class': serializers.BooleanField,
|
||||||
'write_permission': 'event.settings.invoicing:write',
|
|
||||||
'form_kwargs': dict(
|
'form_kwargs': dict(
|
||||||
label=_("Show expiration date of order"),
|
label=_("Show expiration date of order"),
|
||||||
help_text=_("The expiration date will not be shown if the invoice is generated after the order is paid."),
|
help_text=_("The expiration date will not be shown if the invoice is generated after the order is paid."),
|
||||||
@@ -745,7 +726,6 @@ DEFAULTS = {
|
|||||||
'form_class': forms.IntegerField,
|
'form_class': forms.IntegerField,
|
||||||
'serializer_class': serializers.IntegerField,
|
'serializer_class': serializers.IntegerField,
|
||||||
'serializer_kwargs': dict(),
|
'serializer_kwargs': dict(),
|
||||||
'write_permission': 'event.settings.invoicing:write',
|
|
||||||
'form_kwargs': dict(
|
'form_kwargs': dict(
|
||||||
label=_("Minimum length of invoice number after prefix"),
|
label=_("Minimum length of invoice number after prefix"),
|
||||||
help_text=_("The part of your invoice number after your prefix will be filled up with leading zeros up to this length, e.g. INV-001 or INV-00001."),
|
help_text=_("The part of your invoice number after your prefix will be filled up with leading zeros up to this length, e.g. INV-001 or INV-00001."),
|
||||||
@@ -759,7 +739,6 @@ DEFAULTS = {
|
|||||||
'type': bool,
|
'type': bool,
|
||||||
'form_class': forms.BooleanField,
|
'form_class': forms.BooleanField,
|
||||||
'serializer_class': serializers.BooleanField,
|
'serializer_class': serializers.BooleanField,
|
||||||
'write_permission': 'event.settings.invoicing:write',
|
|
||||||
'form_kwargs': dict(
|
'form_kwargs': dict(
|
||||||
label=_("Generate invoices with consecutive numbers"),
|
label=_("Generate invoices with consecutive numbers"),
|
||||||
help_text=_("If deactivated, the order code will be used in the invoice number."),
|
help_text=_("If deactivated, the order code will be used in the invoice number."),
|
||||||
@@ -770,7 +749,6 @@ DEFAULTS = {
|
|||||||
'type': str,
|
'type': str,
|
||||||
'form_class': forms.CharField,
|
'form_class': forms.CharField,
|
||||||
'serializer_class': serializers.CharField,
|
'serializer_class': serializers.CharField,
|
||||||
'write_permission': 'event.settings.invoicing:write',
|
|
||||||
'form_kwargs': dict(
|
'form_kwargs': dict(
|
||||||
label=_("Invoice number prefix"),
|
label=_("Invoice number prefix"),
|
||||||
help_text=_("This will be prepended to invoice numbers. If you leave this field empty, your event slug will "
|
help_text=_("This will be prepended to invoice numbers. If you leave this field empty, your event slug will "
|
||||||
@@ -798,7 +776,6 @@ DEFAULTS = {
|
|||||||
'type': str,
|
'type': str,
|
||||||
'form_class': forms.CharField,
|
'form_class': forms.CharField,
|
||||||
'serializer_class': serializers.CharField,
|
'serializer_class': serializers.CharField,
|
||||||
'write_permission': 'event.settings.invoicing:write',
|
|
||||||
'form_kwargs': dict(
|
'form_kwargs': dict(
|
||||||
label=_("Invoice number prefix for cancellations"),
|
label=_("Invoice number prefix for cancellations"),
|
||||||
help_text=_("This will be prepended to invoice numbers of cancellations. If you leave this field empty, "
|
help_text=_("This will be prepended to invoice numbers of cancellations. If you leave this field empty, "
|
||||||
@@ -822,7 +799,6 @@ DEFAULTS = {
|
|||||||
'type': bool,
|
'type': bool,
|
||||||
'form_class': forms.BooleanField,
|
'form_class': forms.BooleanField,
|
||||||
'serializer_class': serializers.BooleanField,
|
'serializer_class': serializers.BooleanField,
|
||||||
'write_permission': 'event.settings.invoicing:write',
|
|
||||||
'form_kwargs': dict(
|
'form_kwargs': dict(
|
||||||
label=_("Highlight order code to make it stand out visibly"),
|
label=_("Highlight order code to make it stand out visibly"),
|
||||||
help_text=_("Only respected by some invoice renderers."),
|
help_text=_("Only respected by some invoice renderers."),
|
||||||
@@ -834,7 +810,6 @@ DEFAULTS = {
|
|||||||
'form_class': forms.ChoiceField,
|
'form_class': forms.ChoiceField,
|
||||||
'serializer_class': serializers.ChoiceField,
|
'serializer_class': serializers.ChoiceField,
|
||||||
'serializer_kwargs': lambda: dict(**invoice_font_kwargs()),
|
'serializer_kwargs': lambda: dict(**invoice_font_kwargs()),
|
||||||
'write_permission': 'event.settings.invoicing:write',
|
|
||||||
'form_kwargs': lambda: dict(
|
'form_kwargs': lambda: dict(
|
||||||
label=_('Font'),
|
label=_('Font'),
|
||||||
help_text=_("Only respected by some invoice renderers."),
|
help_text=_("Only respected by some invoice renderers."),
|
||||||
@@ -845,7 +820,6 @@ DEFAULTS = {
|
|||||||
'invoice_renderer': {
|
'invoice_renderer': {
|
||||||
'default': 'classic', # default for new events is 'modern1'
|
'default': 'classic', # default for new events is 'modern1'
|
||||||
'type': str,
|
'type': str,
|
||||||
'write_permission': 'event.settings.invoicing:write',
|
|
||||||
},
|
},
|
||||||
'ticket_secret_generator': {
|
'ticket_secret_generator': {
|
||||||
'default': 'random',
|
'default': 'random',
|
||||||
@@ -922,7 +896,6 @@ DEFAULTS = {
|
|||||||
'type': LazyI18nString,
|
'type': LazyI18nString,
|
||||||
'form_class': I18nFormField,
|
'form_class': I18nFormField,
|
||||||
'serializer_class': I18nField,
|
'serializer_class': I18nField,
|
||||||
'write_permission': 'event.settings.payment:write',
|
|
||||||
'form_kwargs': dict(
|
'form_kwargs': dict(
|
||||||
widget=I18nMarkdownTextarea,
|
widget=I18nMarkdownTextarea,
|
||||||
widget_kwargs={'attrs': {
|
widget_kwargs={'attrs': {
|
||||||
@@ -944,7 +917,6 @@ DEFAULTS = {
|
|||||||
('minutes', _("in minutes"))
|
('minutes', _("in minutes"))
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
'write_permission': 'event.settings.payment:write',
|
|
||||||
'form_kwargs': dict(
|
'form_kwargs': dict(
|
||||||
label=_("Set payment term"),
|
label=_("Set payment term"),
|
||||||
widget=forms.RadioSelect,
|
widget=forms.RadioSelect,
|
||||||
@@ -962,7 +934,6 @@ DEFAULTS = {
|
|||||||
'type': int,
|
'type': int,
|
||||||
'form_class': forms.IntegerField,
|
'form_class': forms.IntegerField,
|
||||||
'serializer_class': serializers.IntegerField,
|
'serializer_class': serializers.IntegerField,
|
||||||
'write_permission': 'event.settings.payment:write',
|
|
||||||
'form_kwargs': dict(
|
'form_kwargs': dict(
|
||||||
label=_('Payment term in days'),
|
label=_('Payment term in days'),
|
||||||
widget=forms.NumberInput(
|
widget=forms.NumberInput(
|
||||||
@@ -988,7 +959,6 @@ DEFAULTS = {
|
|||||||
'type': bool,
|
'type': bool,
|
||||||
'form_class': forms.BooleanField,
|
'form_class': forms.BooleanField,
|
||||||
'serializer_class': serializers.BooleanField,
|
'serializer_class': serializers.BooleanField,
|
||||||
'write_permission': 'event.settings.payment:write',
|
|
||||||
'form_kwargs': dict(
|
'form_kwargs': dict(
|
||||||
label=_('Only end payment terms on weekdays'),
|
label=_('Only end payment terms on weekdays'),
|
||||||
help_text=_("If this is activated and the payment term of any order ends on a Saturday or Sunday, it will be "
|
help_text=_("If this is activated and the payment term of any order ends on a Saturday or Sunday, it will be "
|
||||||
@@ -1006,7 +976,6 @@ DEFAULTS = {
|
|||||||
'type': int,
|
'type': int,
|
||||||
'form_class': forms.IntegerField,
|
'form_class': forms.IntegerField,
|
||||||
'serializer_class': serializers.IntegerField,
|
'serializer_class': serializers.IntegerField,
|
||||||
'write_permission': 'event.settings.payment:write',
|
|
||||||
'form_kwargs': dict(
|
'form_kwargs': dict(
|
||||||
label=_('Payment term in minutes'),
|
label=_('Payment term in minutes'),
|
||||||
help_text=_("The number of minutes after placing an order the user has to pay to preserve their reservation. "
|
help_text=_("The number of minutes after placing an order the user has to pay to preserve their reservation. "
|
||||||
@@ -1031,7 +1000,6 @@ DEFAULTS = {
|
|||||||
'type': RelativeDateWrapper,
|
'type': RelativeDateWrapper,
|
||||||
'form_class': RelativeDateField,
|
'form_class': RelativeDateField,
|
||||||
'serializer_class': SerializerRelativeDateField,
|
'serializer_class': SerializerRelativeDateField,
|
||||||
'write_permission': 'event.settings.payment:write',
|
|
||||||
'form_kwargs': dict(
|
'form_kwargs': dict(
|
||||||
label=_('Last date of payments'),
|
label=_('Last date of payments'),
|
||||||
help_text=_("The last date any payments are accepted. This has precedence over the terms "
|
help_text=_("The last date any payments are accepted. This has precedence over the terms "
|
||||||
@@ -1044,7 +1012,6 @@ DEFAULTS = {
|
|||||||
'type': bool,
|
'type': bool,
|
||||||
'form_class': forms.BooleanField,
|
'form_class': forms.BooleanField,
|
||||||
'serializer_class': serializers.BooleanField,
|
'serializer_class': serializers.BooleanField,
|
||||||
'write_permission': 'event.settings.payment:write',
|
|
||||||
'form_kwargs': dict(
|
'form_kwargs': dict(
|
||||||
label=_('Automatically expire unpaid orders'),
|
label=_('Automatically expire unpaid orders'),
|
||||||
help_text=_("If checked, all unpaid orders will automatically go from 'pending' to 'expired' "
|
help_text=_("If checked, all unpaid orders will automatically go from 'pending' to 'expired' "
|
||||||
@@ -1057,7 +1024,6 @@ DEFAULTS = {
|
|||||||
'type': int,
|
'type': int,
|
||||||
'form_class': forms.IntegerField,
|
'form_class': forms.IntegerField,
|
||||||
'serializer_class': serializers.IntegerField,
|
'serializer_class': serializers.IntegerField,
|
||||||
'write_permission': 'event.settings.payment:write',
|
|
||||||
'form_kwargs': dict(
|
'form_kwargs': dict(
|
||||||
label=_('Expiration delay'),
|
label=_('Expiration delay'),
|
||||||
help_text=_("The order will only actually expire this many days after the expiration date communicated "
|
help_text=_("The order will only actually expire this many days after the expiration date communicated "
|
||||||
@@ -1080,7 +1046,6 @@ DEFAULTS = {
|
|||||||
'type': bool,
|
'type': bool,
|
||||||
'form_class': forms.BooleanField,
|
'form_class': forms.BooleanField,
|
||||||
'serializer_class': serializers.BooleanField,
|
'serializer_class': serializers.BooleanField,
|
||||||
'write_permission': 'event.settings.payment:write',
|
|
||||||
'form_kwargs': dict(
|
'form_kwargs': dict(
|
||||||
label=_('Hide "payment pending" state on customer-facing pages'),
|
label=_('Hide "payment pending" state on customer-facing pages'),
|
||||||
help_text=_("The payment instructions panel will still be shown to the primary customer, but no indication "
|
help_text=_("The payment instructions panel will still be shown to the primary customer, but no indication "
|
||||||
@@ -1092,11 +1057,9 @@ DEFAULTS = {
|
|||||||
'default': 'True',
|
'default': 'True',
|
||||||
'type': bool,
|
'type': bool,
|
||||||
'serializer_class': serializers.BooleanField,
|
'serializer_class': serializers.BooleanField,
|
||||||
'write_permission': 'event.settings.payment:write',
|
|
||||||
},
|
},
|
||||||
'payment_giftcard_public_name': {
|
'payment_giftcard_public_name': {
|
||||||
'default': LazyI18nString.from_gettext(gettext_noop('Gift card')),
|
'default': LazyI18nString.from_gettext(gettext_noop('Gift card')),
|
||||||
'write_permission': 'event.settings.payment:write',
|
|
||||||
'type': LazyI18nString
|
'type': LazyI18nString
|
||||||
},
|
},
|
||||||
'payment_giftcard_public_description': {
|
'payment_giftcard_public_description': {
|
||||||
@@ -1105,12 +1068,10 @@ DEFAULTS = {
|
|||||||
'enough credit to pay for the full order, you will be shown this page again and you can either '
|
'enough credit to pay for the full order, you will be shown this page again and you can either '
|
||||||
'redeem another gift card or select a different payment method for the difference.'
|
'redeem another gift card or select a different payment method for the difference.'
|
||||||
)),
|
)),
|
||||||
'write_permission': 'event.settings.payment:write',
|
|
||||||
'type': LazyI18nString
|
'type': LazyI18nString
|
||||||
},
|
},
|
||||||
'payment_resellers__restrict_to_sales_channels': {
|
'payment_resellers__restrict_to_sales_channels': {
|
||||||
'default': ['resellers'],
|
'default': ['resellers'],
|
||||||
'write_permission': 'event.settings.payment:write',
|
|
||||||
'type': list
|
'type': list
|
||||||
},
|
},
|
||||||
'payment_term_accept_late': {
|
'payment_term_accept_late': {
|
||||||
@@ -1118,7 +1079,6 @@ DEFAULTS = {
|
|||||||
'type': bool,
|
'type': bool,
|
||||||
'form_class': forms.BooleanField,
|
'form_class': forms.BooleanField,
|
||||||
'serializer_class': serializers.BooleanField,
|
'serializer_class': serializers.BooleanField,
|
||||||
'write_permission': 'event.settings.payment:write',
|
|
||||||
'form_kwargs': dict(
|
'form_kwargs': dict(
|
||||||
label=_('Accept late payments'),
|
label=_('Accept late payments'),
|
||||||
help_text=_("Accept payments for orders even when they are in 'expired' state as long as enough "
|
help_text=_("Accept payments for orders even when they are in 'expired' state as long as enough "
|
||||||
@@ -1148,7 +1108,6 @@ DEFAULTS = {
|
|||||||
('none', _('Charge no taxes')),
|
('none', _('Charge no taxes')),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
'write_permission': 'event.settings.payment:write',
|
|
||||||
'form_kwargs': dict(
|
'form_kwargs': dict(
|
||||||
label=_("Tax handling on payment fees"),
|
label=_("Tax handling on payment fees"),
|
||||||
widget=forms.RadioSelect,
|
widget=forms.RadioSelect,
|
||||||
@@ -1195,7 +1154,6 @@ DEFAULTS = {
|
|||||||
('paid', _('Automatically on payment or when required by payment method')),
|
('paid', _('Automatically on payment or when required by payment method')),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
'write_permission': 'event.settings.invoicing:write',
|
|
||||||
'form_kwargs': dict(
|
'form_kwargs': dict(
|
||||||
label=_("Generate invoices"),
|
label=_("Generate invoices"),
|
||||||
widget=forms.RadioSelect,
|
widget=forms.RadioSelect,
|
||||||
@@ -1224,7 +1182,6 @@ DEFAULTS = {
|
|||||||
('invoice_date', _('Invoice date')),
|
('invoice_date', _('Invoice date')),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
'write_permission': 'event.settings.invoicing:write',
|
|
||||||
'form_kwargs': dict(
|
'form_kwargs': dict(
|
||||||
label=_("Date of service"),
|
label=_("Date of service"),
|
||||||
widget=forms.RadioSelect,
|
widget=forms.RadioSelect,
|
||||||
@@ -1245,7 +1202,6 @@ DEFAULTS = {
|
|||||||
'type': bool,
|
'type': bool,
|
||||||
'form_class': forms.BooleanField,
|
'form_class': forms.BooleanField,
|
||||||
'serializer_class': serializers.BooleanField,
|
'serializer_class': serializers.BooleanField,
|
||||||
'write_permission': 'event.settings.invoicing:write',
|
|
||||||
'form_kwargs': dict(
|
'form_kwargs': dict(
|
||||||
label=_("Automatically cancel and reissue invoice on address changes"),
|
label=_("Automatically cancel and reissue invoice on address changes"),
|
||||||
help_text=_("If customers change their invoice address on an existing order, the invoice will "
|
help_text=_("If customers change their invoice address on an existing order, the invoice will "
|
||||||
@@ -1258,7 +1214,6 @@ DEFAULTS = {
|
|||||||
'type': bool,
|
'type': bool,
|
||||||
'form_class': forms.BooleanField,
|
'form_class': forms.BooleanField,
|
||||||
'serializer_class': serializers.BooleanField,
|
'serializer_class': serializers.BooleanField,
|
||||||
'write_permission': 'event.settings.invoicing:write',
|
|
||||||
'form_kwargs': dict(
|
'form_kwargs': dict(
|
||||||
label=_("Allow to update existing invoices"),
|
label=_("Allow to update existing invoices"),
|
||||||
help_text=_("By default, invoices can never again be changed once they are issued. In most countries, we "
|
help_text=_("By default, invoices can never again be changed once they are issued. In most countries, we "
|
||||||
@@ -1268,24 +1223,13 @@ DEFAULTS = {
|
|||||||
},
|
},
|
||||||
'invoice_generate_sales_channels': {
|
'invoice_generate_sales_channels': {
|
||||||
'default': json.dumps(['web']),
|
'default': json.dumps(['web']),
|
||||||
'write_permission': 'event.settings.invoicing:write',
|
|
||||||
'type': list
|
'type': list
|
||||||
},
|
},
|
||||||
'invoice_generate_only_business': {
|
|
||||||
'default': 'False',
|
|
||||||
'type': bool,
|
|
||||||
'form_class': forms.BooleanField,
|
|
||||||
'serializer_class': serializers.BooleanField,
|
|
||||||
'form_kwargs': dict(
|
|
||||||
label=_("Only issue invoices to business customers"),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
'invoice_address_from': {
|
'invoice_address_from': {
|
||||||
'default': '',
|
'default': '',
|
||||||
'type': str,
|
'type': str,
|
||||||
'form_class': forms.CharField,
|
'form_class': forms.CharField,
|
||||||
'serializer_class': serializers.CharField,
|
'serializer_class': serializers.CharField,
|
||||||
'write_permission': 'event.settings.invoicing:write',
|
|
||||||
'form_kwargs': dict(
|
'form_kwargs': dict(
|
||||||
label=_("Address line"),
|
label=_("Address line"),
|
||||||
widget=forms.Textarea(attrs={
|
widget=forms.Textarea(attrs={
|
||||||
@@ -1301,7 +1245,6 @@ DEFAULTS = {
|
|||||||
'type': str,
|
'type': str,
|
||||||
'form_class': forms.CharField,
|
'form_class': forms.CharField,
|
||||||
'serializer_class': serializers.CharField,
|
'serializer_class': serializers.CharField,
|
||||||
'write_permission': 'event.settings.invoicing:write',
|
|
||||||
'form_kwargs': dict(
|
'form_kwargs': dict(
|
||||||
max_length=190,
|
max_length=190,
|
||||||
label=_("Company name"),
|
label=_("Company name"),
|
||||||
@@ -1312,7 +1255,6 @@ DEFAULTS = {
|
|||||||
'type': str,
|
'type': str,
|
||||||
'form_class': forms.CharField,
|
'form_class': forms.CharField,
|
||||||
'serializer_class': serializers.CharField,
|
'serializer_class': serializers.CharField,
|
||||||
'write_permission': 'event.settings.invoicing:write',
|
|
||||||
'form_kwargs': dict(
|
'form_kwargs': dict(
|
||||||
widget=forms.TextInput(attrs={
|
widget=forms.TextInput(attrs={
|
||||||
'placeholder': '12345'
|
'placeholder': '12345'
|
||||||
@@ -1326,7 +1268,6 @@ DEFAULTS = {
|
|||||||
'type': str,
|
'type': str,
|
||||||
'form_class': forms.CharField,
|
'form_class': forms.CharField,
|
||||||
'serializer_class': serializers.CharField,
|
'serializer_class': serializers.CharField,
|
||||||
'write_permission': 'event.settings.invoicing:write',
|
|
||||||
'form_kwargs': dict(
|
'form_kwargs': dict(
|
||||||
widget=forms.TextInput(attrs={
|
widget=forms.TextInput(attrs={
|
||||||
'placeholder': _('Random City')
|
'placeholder': _('Random City')
|
||||||
@@ -1343,7 +1284,6 @@ DEFAULTS = {
|
|||||||
'serializer_kwargs': {
|
'serializer_kwargs': {
|
||||||
'choices': [('', '')],
|
'choices': [('', '')],
|
||||||
},
|
},
|
||||||
'write_permission': 'event.settings.invoicing:write',
|
|
||||||
'form_kwargs': {
|
'form_kwargs': {
|
||||||
"label": pgettext_lazy('address', 'State'),
|
"label": pgettext_lazy('address', 'State'),
|
||||||
'choices': [('', '')],
|
'choices': [('', '')],
|
||||||
@@ -1355,7 +1295,6 @@ DEFAULTS = {
|
|||||||
'form_class': forms.ChoiceField,
|
'form_class': forms.ChoiceField,
|
||||||
'serializer_class': serializers.ChoiceField,
|
'serializer_class': serializers.ChoiceField,
|
||||||
'serializer_kwargs': lambda: dict(**country_choice_kwargs()),
|
'serializer_kwargs': lambda: dict(**country_choice_kwargs()),
|
||||||
'write_permission': 'event.settings.invoicing:write',
|
|
||||||
'form_kwargs': lambda: dict(
|
'form_kwargs': lambda: dict(
|
||||||
label=_('Country'),
|
label=_('Country'),
|
||||||
widget=forms.Select(attrs={
|
widget=forms.Select(attrs={
|
||||||
@@ -1369,7 +1308,6 @@ DEFAULTS = {
|
|||||||
'type': str,
|
'type': str,
|
||||||
'form_class': forms.CharField,
|
'form_class': forms.CharField,
|
||||||
'serializer_class': serializers.CharField,
|
'serializer_class': serializers.CharField,
|
||||||
'write_permission': 'event.settings.invoicing:write',
|
|
||||||
'form_kwargs': dict(
|
'form_kwargs': dict(
|
||||||
label=_("Domestic tax ID"),
|
label=_("Domestic tax ID"),
|
||||||
help_text=_("e.g. tax number in Germany, ABN in Australia, …"),
|
help_text=_("e.g. tax number in Germany, ABN in Australia, …"),
|
||||||
@@ -1381,7 +1319,6 @@ DEFAULTS = {
|
|||||||
'type': str,
|
'type': str,
|
||||||
'form_class': forms.CharField,
|
'form_class': forms.CharField,
|
||||||
'serializer_class': serializers.CharField,
|
'serializer_class': serializers.CharField,
|
||||||
'write_permission': 'event.settings.invoicing:write',
|
|
||||||
'form_kwargs': dict(
|
'form_kwargs': dict(
|
||||||
label=_("EU VAT ID"),
|
label=_("EU VAT ID"),
|
||||||
max_length=190,
|
max_length=190,
|
||||||
@@ -1392,7 +1329,6 @@ DEFAULTS = {
|
|||||||
'type': LazyI18nString,
|
'type': LazyI18nString,
|
||||||
'form_class': I18nFormField,
|
'form_class': I18nFormField,
|
||||||
'serializer_class': I18nField,
|
'serializer_class': I18nField,
|
||||||
'write_permission': 'event.settings.invoicing:write',
|
|
||||||
'form_kwargs': dict(
|
'form_kwargs': dict(
|
||||||
widget=I18nTextarea,
|
widget=I18nTextarea,
|
||||||
widget_kwargs={'attrs': {
|
widget_kwargs={'attrs': {
|
||||||
@@ -1410,7 +1346,6 @@ DEFAULTS = {
|
|||||||
'type': LazyI18nString,
|
'type': LazyI18nString,
|
||||||
'form_class': I18nFormField,
|
'form_class': I18nFormField,
|
||||||
'serializer_class': I18nField,
|
'serializer_class': I18nField,
|
||||||
'write_permission': 'event.settings.invoicing:write',
|
|
||||||
'form_kwargs': dict(
|
'form_kwargs': dict(
|
||||||
widget=I18nTextarea,
|
widget=I18nTextarea,
|
||||||
widget_kwargs={'attrs': {
|
widget_kwargs={'attrs': {
|
||||||
@@ -1428,7 +1363,6 @@ DEFAULTS = {
|
|||||||
'type': LazyI18nString,
|
'type': LazyI18nString,
|
||||||
'form_class': I18nFormField,
|
'form_class': I18nFormField,
|
||||||
'serializer_class': I18nField,
|
'serializer_class': I18nField,
|
||||||
'write_permission': 'event.settings.invoicing:write',
|
|
||||||
'form_kwargs': dict(
|
'form_kwargs': dict(
|
||||||
widget=I18nTextarea,
|
widget=I18nTextarea,
|
||||||
widget_kwargs={'attrs': {
|
widget_kwargs={'attrs': {
|
||||||
@@ -1443,7 +1377,6 @@ DEFAULTS = {
|
|||||||
},
|
},
|
||||||
'invoice_language': {
|
'invoice_language': {
|
||||||
'default': '__user__',
|
'default': '__user__',
|
||||||
'write_permission': 'event.settings.invoicing:write',
|
|
||||||
'type': str
|
'type': str
|
||||||
},
|
},
|
||||||
'invoice_email_attachment': {
|
'invoice_email_attachment': {
|
||||||
@@ -1451,7 +1384,6 @@ DEFAULTS = {
|
|||||||
'type': bool,
|
'type': bool,
|
||||||
'form_class': forms.BooleanField,
|
'form_class': forms.BooleanField,
|
||||||
'serializer_class': serializers.BooleanField,
|
'serializer_class': serializers.BooleanField,
|
||||||
'write_permission': 'event.settings.invoicing:write',
|
|
||||||
'form_kwargs': dict(
|
'form_kwargs': dict(
|
||||||
label=_("Attach invoices to emails"),
|
label=_("Attach invoices to emails"),
|
||||||
help_text=_("If invoices are automatically generated for all orders, they will be attached to the order "
|
help_text=_("If invoices are automatically generated for all orders, they will be attached to the order "
|
||||||
@@ -1465,7 +1397,6 @@ DEFAULTS = {
|
|||||||
'type': str,
|
'type': str,
|
||||||
'form_class': forms.CharField,
|
'form_class': forms.CharField,
|
||||||
'serializer_class': serializers.CharField,
|
'serializer_class': serializers.CharField,
|
||||||
'write_permission': 'event.settings.invoicing:write',
|
|
||||||
'form_kwargs': dict(
|
'form_kwargs': dict(
|
||||||
label=_("Email address to receive a copy of each invoice"),
|
label=_("Email address to receive a copy of each invoice"),
|
||||||
help_text=_("Each newly created invoice will be sent to this email address shortly after creation. You can "
|
help_text=_("Each newly created invoice will be sent to this email address shortly after creation. You can "
|
||||||
@@ -3297,8 +3228,7 @@ Your {organizer} team""")) # noqa: W291
|
|||||||
'image/png', 'image/jpeg', 'image/gif'
|
'image/png', 'image/jpeg', 'image/gif'
|
||||||
],
|
],
|
||||||
max_size=settings.FILE_UPLOAD_MAX_SIZE_IMAGE,
|
max_size=settings.FILE_UPLOAD_MAX_SIZE_IMAGE,
|
||||||
),
|
)
|
||||||
'write_permission': 'event.settings.invoicing:write',
|
|
||||||
},
|
},
|
||||||
'frontpage_text': {
|
'frontpage_text': {
|
||||||
'default': '',
|
'default': '',
|
||||||
@@ -3997,7 +3927,7 @@ COUNTRIES_WITH_STATE_IN_ADDRESS = {
|
|||||||
'MX': (['State', 'Federal district', 'Federal entity'], 'short'),
|
'MX': (['State', 'Federal district', 'Federal entity'], 'short'),
|
||||||
'US': (['State', 'Outlying area', 'District'], 'short'),
|
'US': (['State', 'Outlying area', 'District'], 'short'),
|
||||||
'IT': (['Province', 'Free municipal consortium', 'Metropolitan city', 'Autonomous province',
|
'IT': (['Province', 'Free municipal consortium', 'Metropolitan city', 'Autonomous province',
|
||||||
'Decentralized regional entity'], 'short'),
|
'Free municipal consortium', 'Decentralized regional entity'], 'short'),
|
||||||
}
|
}
|
||||||
COUNTRY_STATE_LABEL = {
|
COUNTRY_STATE_LABEL = {
|
||||||
# Countries in which the "State" field should not be called "State"
|
# Countries in which the "State" field should not be called "State"
|
||||||
@@ -4005,8 +3935,6 @@ COUNTRY_STATE_LABEL = {
|
|||||||
'JP': pgettext_lazy('address', 'Prefecture'),
|
'JP': pgettext_lazy('address', 'Prefecture'),
|
||||||
'IT': pgettext_lazy('address', 'Province'),
|
'IT': pgettext_lazy('address', 'Province'),
|
||||||
}
|
}
|
||||||
# Workaround for https://github.com/pretix/pretix/issues/5796
|
|
||||||
pycountry_add(pycountry.subdivisions, code="IT-AO", country_code="IT", name="Valle d'Aosta", parent="23", parent_code="IT-23", type="Province")
|
|
||||||
|
|
||||||
settings_hierarkey = Hierarkey(attribute_name='settings')
|
settings_hierarkey = Hierarkey(attribute_name='settings')
|
||||||
|
|
||||||
|
|||||||
@@ -561,18 +561,6 @@ however for this signal, the ``sender`` **may also be None** to allow creating t
|
|||||||
notification settings!
|
notification settings!
|
||||||
"""
|
"""
|
||||||
|
|
||||||
register_event_permission_groups = GlobalSignal()
|
|
||||||
"""
|
|
||||||
This signal is sent out to get all known permissions. Receivers should return an
|
|
||||||
instance of pretix.base.permissions.PermissionGroup or a list of such instances.
|
|
||||||
"""
|
|
||||||
|
|
||||||
register_organizer_permission_groups = GlobalSignal()
|
|
||||||
"""
|
|
||||||
This signal is sent out to get all known permissions. Receivers should return an
|
|
||||||
instance of pretix.base.permissions.PermissionGroup or a list of such instances.
|
|
||||||
"""
|
|
||||||
|
|
||||||
notification = EventPluginSignal()
|
notification = EventPluginSignal()
|
||||||
"""
|
"""
|
||||||
Arguments: ``logentry_id``, ``notification_type``
|
Arguments: ``logentry_id``, ``notification_type``
|
||||||
@@ -1110,9 +1098,6 @@ api_event_settings_fields = EventPluginSignal()
|
|||||||
This signal is sent out to collect serializable settings fields for the API. You are expected to
|
This signal is sent out to collect serializable settings fields for the API. You are expected to
|
||||||
return a dictionary mapping names of attributes in the settings store to DRF serializer field instances.
|
return a dictionary mapping names of attributes in the settings store to DRF serializer field instances.
|
||||||
|
|
||||||
These are readable for all users with access to the events, therefore secrets stored in the settings store
|
|
||||||
should not be included!
|
|
||||||
|
|
||||||
As with all event-plugin signals, the ``sender`` keyword argument will contain the event.
|
As with all event-plugin signals, the ``sender`` keyword argument will contain the event.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|||||||
@@ -32,11 +32,7 @@ from pretix.base.models import ItemVariation
|
|||||||
from pretix.base.reldate import RelativeDateWrapper
|
from pretix.base.reldate import RelativeDateWrapper
|
||||||
from pretix.base.signals import timeline_events
|
from pretix.base.signals import timeline_events
|
||||||
|
|
||||||
TimelineEvent = namedtuple(
|
TimelineEvent = namedtuple('TimelineEvent', ('event', 'subevent', 'datetime', 'description', 'edit_url'))
|
||||||
'TimelineEvent',
|
|
||||||
('event', 'subevent', 'datetime', 'description', 'edit_url', 'edit_permission'),
|
|
||||||
defaults=(None, None, None, None, None, 'event.settings.general:write')
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def timeline_for_event(event, subevent=None):
|
def timeline_for_event(event, subevent=None):
|
||||||
@@ -50,7 +46,6 @@ def timeline_for_event(event, subevent=None):
|
|||||||
'subevent': subevent.pk
|
'subevent': subevent.pk
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
ev_edit_permission = 'event.subevents:write'
|
|
||||||
else:
|
else:
|
||||||
ev_edit_url = reverse(
|
ev_edit_url = reverse(
|
||||||
'control:event.settings', kwargs={
|
'control:event.settings', kwargs={
|
||||||
@@ -58,14 +53,12 @@ def timeline_for_event(event, subevent=None):
|
|||||||
'organizer': event.organizer.slug
|
'organizer': event.organizer.slug
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
ev_edit_permission = 'event.settings.general:write'
|
|
||||||
|
|
||||||
tl.append(TimelineEvent(
|
tl.append(TimelineEvent(
|
||||||
event=event, subevent=subevent,
|
event=event, subevent=subevent,
|
||||||
datetime=ev.date_from,
|
datetime=ev.date_from,
|
||||||
description=pgettext_lazy('timeline', 'Your event starts'),
|
description=pgettext_lazy('timeline', 'Your event starts'),
|
||||||
edit_url=ev_edit_url + '#id_date_from_0',
|
edit_url=ev_edit_url + '#id_date_from_0'
|
||||||
edit_permission=ev_edit_permission,
|
|
||||||
))
|
))
|
||||||
|
|
||||||
if ev.date_to:
|
if ev.date_to:
|
||||||
@@ -73,8 +66,7 @@ def timeline_for_event(event, subevent=None):
|
|||||||
event=event, subevent=subevent,
|
event=event, subevent=subevent,
|
||||||
datetime=ev.date_to,
|
datetime=ev.date_to,
|
||||||
description=pgettext_lazy('timeline', 'Your event ends'),
|
description=pgettext_lazy('timeline', 'Your event ends'),
|
||||||
edit_url=ev_edit_url + '#id_date_to_0',
|
edit_url=ev_edit_url + '#id_date_to_0'
|
||||||
edit_permission=ev_edit_permission,
|
|
||||||
))
|
))
|
||||||
|
|
||||||
if ev.date_admission:
|
if ev.date_admission:
|
||||||
@@ -82,8 +74,7 @@ def timeline_for_event(event, subevent=None):
|
|||||||
event=event, subevent=subevent,
|
event=event, subevent=subevent,
|
||||||
datetime=ev.date_admission,
|
datetime=ev.date_admission,
|
||||||
description=pgettext_lazy('timeline', 'Admissions for your event start'),
|
description=pgettext_lazy('timeline', 'Admissions for your event start'),
|
||||||
edit_url=ev_edit_url + '#id_date_admission_0',
|
edit_url=ev_edit_url + '#id_date_admission_0'
|
||||||
edit_permission=ev_edit_permission,
|
|
||||||
))
|
))
|
||||||
|
|
||||||
if ev.presale_start:
|
if ev.presale_start:
|
||||||
@@ -91,8 +82,7 @@ def timeline_for_event(event, subevent=None):
|
|||||||
event=event, subevent=subevent,
|
event=event, subevent=subevent,
|
||||||
datetime=ev.presale_start,
|
datetime=ev.presale_start,
|
||||||
description=pgettext_lazy('timeline', 'Start of ticket sales'),
|
description=pgettext_lazy('timeline', 'Start of ticket sales'),
|
||||||
edit_url=ev_edit_url + '#id_presale_start_0',
|
edit_url=ev_edit_url + '#id_presale_start_0'
|
||||||
edit_permission=ev_edit_permission,
|
|
||||||
))
|
))
|
||||||
|
|
||||||
tl.append(TimelineEvent(
|
tl.append(TimelineEvent(
|
||||||
@@ -107,8 +97,7 @@ def timeline_for_event(event, subevent=None):
|
|||||||
) if not ev.presale_end else (
|
) if not ev.presale_end else (
|
||||||
pgettext_lazy('timeline', 'End of ticket sales')
|
pgettext_lazy('timeline', 'End of ticket sales')
|
||||||
),
|
),
|
||||||
edit_url=ev_edit_url + '#id_presale_end_0',
|
edit_url=ev_edit_url + '#id_presale_end_0'
|
||||||
edit_permission=ev_edit_permission,
|
|
||||||
))
|
))
|
||||||
|
|
||||||
rd = event.settings.get('last_order_modification_date', as_type=RelativeDateWrapper)
|
rd = event.settings.get('last_order_modification_date', as_type=RelativeDateWrapper)
|
||||||
@@ -117,8 +106,7 @@ def timeline_for_event(event, subevent=None):
|
|||||||
event=event, subevent=subevent,
|
event=event, subevent=subevent,
|
||||||
datetime=rd.datetime(ev),
|
datetime=rd.datetime(ev),
|
||||||
description=pgettext_lazy('timeline', 'Customers can no longer modify their order information'),
|
description=pgettext_lazy('timeline', 'Customers can no longer modify their order information'),
|
||||||
edit_url=ev_edit_url + '#id_settings-last_order_modification_date_0_0',
|
edit_url=ev_edit_url + '#id_settings-last_order_modification_date_0_0'
|
||||||
edit_permission='event.settings.general:write',
|
|
||||||
))
|
))
|
||||||
|
|
||||||
rd = event.settings.get('payment_term_last', as_type=RelativeDateWrapper)
|
rd = event.settings.get('payment_term_last', as_type=RelativeDateWrapper)
|
||||||
@@ -134,8 +122,7 @@ def timeline_for_event(event, subevent=None):
|
|||||||
edit_url=reverse('control:event.settings.payment', kwargs={
|
edit_url=reverse('control:event.settings.payment', kwargs={
|
||||||
'event': event.slug,
|
'event': event.slug,
|
||||||
'organizer': event.organizer.slug
|
'organizer': event.organizer.slug
|
||||||
}),
|
})
|
||||||
edit_permission='event.settings.payment:write',
|
|
||||||
))
|
))
|
||||||
|
|
||||||
rd = event.settings.get('ticket_download_date', as_type=RelativeDateWrapper)
|
rd = event.settings.get('ticket_download_date', as_type=RelativeDateWrapper)
|
||||||
@@ -147,8 +134,7 @@ def timeline_for_event(event, subevent=None):
|
|||||||
edit_url=reverse('control:event.settings.tickets', kwargs={
|
edit_url=reverse('control:event.settings.tickets', kwargs={
|
||||||
'event': event.slug,
|
'event': event.slug,
|
||||||
'organizer': event.organizer.slug
|
'organizer': event.organizer.slug
|
||||||
}),
|
})
|
||||||
edit_permission='event.settings.general:write',
|
|
||||||
))
|
))
|
||||||
|
|
||||||
rd = event.settings.get('cancel_allow_user_until', as_type=RelativeDateWrapper)
|
rd = event.settings.get('cancel_allow_user_until', as_type=RelativeDateWrapper)
|
||||||
@@ -160,8 +146,7 @@ def timeline_for_event(event, subevent=None):
|
|||||||
edit_url=reverse('control:event.settings.cancel', kwargs={
|
edit_url=reverse('control:event.settings.cancel', kwargs={
|
||||||
'event': event.slug,
|
'event': event.slug,
|
||||||
'organizer': event.organizer.slug
|
'organizer': event.organizer.slug
|
||||||
}),
|
})
|
||||||
edit_permission='event.settings.general:write',
|
|
||||||
))
|
))
|
||||||
|
|
||||||
rd = event.settings.get('cancel_allow_user_paid_until', as_type=RelativeDateWrapper)
|
rd = event.settings.get('cancel_allow_user_paid_until', as_type=RelativeDateWrapper)
|
||||||
@@ -173,8 +158,7 @@ def timeline_for_event(event, subevent=None):
|
|||||||
edit_url=reverse('control:event.settings.cancel', kwargs={
|
edit_url=reverse('control:event.settings.cancel', kwargs={
|
||||||
'event': event.slug,
|
'event': event.slug,
|
||||||
'organizer': event.organizer.slug
|
'organizer': event.organizer.slug
|
||||||
}),
|
})
|
||||||
edit_permission='event.settings.general:write',
|
|
||||||
))
|
))
|
||||||
|
|
||||||
rd = event.settings.get('change_allow_user_until', as_type=RelativeDateWrapper)
|
rd = event.settings.get('change_allow_user_until', as_type=RelativeDateWrapper)
|
||||||
@@ -186,8 +170,7 @@ def timeline_for_event(event, subevent=None):
|
|||||||
edit_url=reverse('control:event.settings.cancel', kwargs={
|
edit_url=reverse('control:event.settings.cancel', kwargs={
|
||||||
'event': event.slug,
|
'event': event.slug,
|
||||||
'organizer': event.organizer.slug
|
'organizer': event.organizer.slug
|
||||||
}),
|
})
|
||||||
edit_permission='event.settings.general:write',
|
|
||||||
))
|
))
|
||||||
|
|
||||||
rd = event.settings.get('waiting_list_auto_disable', as_type=RelativeDateWrapper)
|
rd = event.settings.get('waiting_list_auto_disable', as_type=RelativeDateWrapper)
|
||||||
@@ -199,8 +182,7 @@ def timeline_for_event(event, subevent=None):
|
|||||||
edit_url=reverse('control:event.settings', kwargs={
|
edit_url=reverse('control:event.settings', kwargs={
|
||||||
'event': event.slug,
|
'event': event.slug,
|
||||||
'organizer': event.organizer.slug
|
'organizer': event.organizer.slug
|
||||||
}) + '#waiting-list-open',
|
}) + '#waiting-list-open'
|
||||||
edit_permission='event.settings.general:write',
|
|
||||||
))
|
))
|
||||||
|
|
||||||
if not event.has_subevents:
|
if not event.has_subevents:
|
||||||
@@ -214,8 +196,7 @@ def timeline_for_event(event, subevent=None):
|
|||||||
edit_url=reverse('control:event.settings.mail', kwargs={
|
edit_url=reverse('control:event.settings.mail', kwargs={
|
||||||
'event': event.slug,
|
'event': event.slug,
|
||||||
'organizer': event.organizer.slug
|
'organizer': event.organizer.slug
|
||||||
}),
|
})
|
||||||
edit_permission='event.settings.general:write',
|
|
||||||
))
|
))
|
||||||
|
|
||||||
if subevent:
|
if subevent:
|
||||||
@@ -229,8 +210,7 @@ def timeline_for_event(event, subevent=None):
|
|||||||
'event': event.slug,
|
'event': event.slug,
|
||||||
'organizer': event.organizer.slug,
|
'organizer': event.organizer.slug,
|
||||||
'subevent': subevent.pk,
|
'subevent': subevent.pk,
|
||||||
}),
|
})
|
||||||
edit_permission='event.subevents:write',
|
|
||||||
))
|
))
|
||||||
if sei.available_until:
|
if sei.available_until:
|
||||||
tl.append(TimelineEvent(
|
tl.append(TimelineEvent(
|
||||||
@@ -241,8 +221,7 @@ def timeline_for_event(event, subevent=None):
|
|||||||
'event': event.slug,
|
'event': event.slug,
|
||||||
'organizer': event.organizer.slug,
|
'organizer': event.organizer.slug,
|
||||||
'subevent': subevent.pk,
|
'subevent': subevent.pk,
|
||||||
}),
|
})
|
||||||
edit_permission='event.subevents:write',
|
|
||||||
))
|
))
|
||||||
for sei in subevent.var_overrides.values():
|
for sei in subevent.var_overrides.values():
|
||||||
if sei.available_from:
|
if sei.available_from:
|
||||||
@@ -255,8 +234,7 @@ def timeline_for_event(event, subevent=None):
|
|||||||
'event': event.slug,
|
'event': event.slug,
|
||||||
'organizer': event.organizer.slug,
|
'organizer': event.organizer.slug,
|
||||||
'subevent': subevent.pk,
|
'subevent': subevent.pk,
|
||||||
}),
|
})
|
||||||
edit_permission='event.subevents:write',
|
|
||||||
))
|
))
|
||||||
if sei.available_until:
|
if sei.available_until:
|
||||||
tl.append(TimelineEvent(
|
tl.append(TimelineEvent(
|
||||||
@@ -268,8 +246,7 @@ def timeline_for_event(event, subevent=None):
|
|||||||
'event': event.slug,
|
'event': event.slug,
|
||||||
'organizer': event.organizer.slug,
|
'organizer': event.organizer.slug,
|
||||||
'subevent': subevent.pk,
|
'subevent': subevent.pk,
|
||||||
}),
|
})
|
||||||
edit_permission='event.subevents:write',
|
|
||||||
))
|
))
|
||||||
|
|
||||||
for d in event.discounts.filter(Q(available_from__isnull=False) | Q(available_until__isnull=False)):
|
for d in event.discounts.filter(Q(available_from__isnull=False) | Q(available_until__isnull=False)):
|
||||||
@@ -282,8 +259,7 @@ def timeline_for_event(event, subevent=None):
|
|||||||
'event': event.slug,
|
'event': event.slug,
|
||||||
'organizer': event.organizer.slug,
|
'organizer': event.organizer.slug,
|
||||||
'discount': d.pk,
|
'discount': d.pk,
|
||||||
}),
|
})
|
||||||
edit_permission='event.items:write',
|
|
||||||
))
|
))
|
||||||
if d.available_until:
|
if d.available_until:
|
||||||
tl.append(TimelineEvent(
|
tl.append(TimelineEvent(
|
||||||
@@ -294,8 +270,7 @@ def timeline_for_event(event, subevent=None):
|
|||||||
'event': event.slug,
|
'event': event.slug,
|
||||||
'organizer': event.organizer.slug,
|
'organizer': event.organizer.slug,
|
||||||
'discount': d.pk,
|
'discount': d.pk,
|
||||||
}),
|
})
|
||||||
edit_permission='event.items:write',
|
|
||||||
))
|
))
|
||||||
|
|
||||||
for p in event.items.filter(Q(available_from__isnull=False) | Q(available_until__isnull=False)):
|
for p in event.items.filter(Q(available_from__isnull=False) | Q(available_until__isnull=False)):
|
||||||
@@ -308,8 +283,7 @@ def timeline_for_event(event, subevent=None):
|
|||||||
'event': event.slug,
|
'event': event.slug,
|
||||||
'organizer': event.organizer.slug,
|
'organizer': event.organizer.slug,
|
||||||
'item': p.pk,
|
'item': p.pk,
|
||||||
}) + '#id_available_from_0',
|
}) + '#id_available_from_0'
|
||||||
edit_permission='event.items:write',
|
|
||||||
))
|
))
|
||||||
if p.available_until:
|
if p.available_until:
|
||||||
tl.append(TimelineEvent(
|
tl.append(TimelineEvent(
|
||||||
@@ -320,8 +294,7 @@ def timeline_for_event(event, subevent=None):
|
|||||||
'event': event.slug,
|
'event': event.slug,
|
||||||
'organizer': event.organizer.slug,
|
'organizer': event.organizer.slug,
|
||||||
'item': p.pk,
|
'item': p.pk,
|
||||||
}) + '#id_available_until_0',
|
}) + '#id_available_until_0'
|
||||||
edit_permission='event.items:write',
|
|
||||||
))
|
))
|
||||||
|
|
||||||
for v in ItemVariation.objects.filter(
|
for v in ItemVariation.objects.filter(
|
||||||
@@ -340,8 +313,7 @@ def timeline_for_event(event, subevent=None):
|
|||||||
'event': event.slug,
|
'event': event.slug,
|
||||||
'organizer': event.organizer.slug,
|
'organizer': event.organizer.slug,
|
||||||
'item': v.item.pk,
|
'item': v.item.pk,
|
||||||
}) + '#tab-0-3-open',
|
}) + '#tab-0-3-open'
|
||||||
edit_permission='event.items:write',
|
|
||||||
))
|
))
|
||||||
if v.available_until:
|
if v.available_until:
|
||||||
tl.append(TimelineEvent(
|
tl.append(TimelineEvent(
|
||||||
@@ -355,8 +327,7 @@ def timeline_for_event(event, subevent=None):
|
|||||||
'event': event.slug,
|
'event': event.slug,
|
||||||
'organizer': event.organizer.slug,
|
'organizer': event.organizer.slug,
|
||||||
'item': v.item.pk,
|
'item': v.item.pk,
|
||||||
}) + '#tab-0-3-open',
|
}) + '#tab-0-3-open'
|
||||||
edit_permission='event.items:write',
|
|
||||||
))
|
))
|
||||||
|
|
||||||
pprovs = event.get_payment_providers()
|
pprovs = event.get_payment_providers()
|
||||||
@@ -386,8 +357,7 @@ def timeline_for_event(event, subevent=None):
|
|||||||
'event': event.slug,
|
'event': event.slug,
|
||||||
'organizer': event.organizer.slug,
|
'organizer': event.organizer.slug,
|
||||||
'provider': pprov.identifier,
|
'provider': pprov.identifier,
|
||||||
}),
|
})
|
||||||
edit_permission='event.settings.payment:write',
|
|
||||||
))
|
))
|
||||||
availability_date = pprov.settings.get('_availability_date', as_type=RelativeDateWrapper)
|
availability_date = pprov.settings.get('_availability_date', as_type=RelativeDateWrapper)
|
||||||
if availability_date:
|
if availability_date:
|
||||||
@@ -405,8 +375,7 @@ def timeline_for_event(event, subevent=None):
|
|||||||
'event': event.slug,
|
'event': event.slug,
|
||||||
'organizer': event.organizer.slug,
|
'organizer': event.organizer.slug,
|
||||||
'provider': pprov.identifier,
|
'provider': pprov.identifier,
|
||||||
}),
|
})
|
||||||
edit_permission='event.settings.payment:write',
|
|
||||||
))
|
))
|
||||||
|
|
||||||
for recv, resp in timeline_events.send(sender=event, subevent=subevent):
|
for recv, resp in timeline_events.send(sender=event, subevent=subevent):
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ def _info(cc):
|
|||||||
statelist = [s for s in pycountry.subdivisions.get(country_code=cc) if s.type in types]
|
statelist = [s for s in pycountry.subdivisions.get(country_code=cc) if s.type in types]
|
||||||
return {
|
return {
|
||||||
'data': [
|
'data': [
|
||||||
{'name': gettext(s.name), 'code': s.code[3:]}
|
{'name': s.name, 'code': s.code[3:]}
|
||||||
for s in sorted(statelist, key=lambda s: s.name)
|
for s in sorted(statelist, key=lambda s: s.name)
|
||||||
],
|
],
|
||||||
**info,
|
**info,
|
||||||
@@ -109,7 +109,7 @@ def address_form(request):
|
|||||||
for t in get_transmission_types():
|
for t in get_transmission_types():
|
||||||
if t.is_available(event=event, country=country, is_business=is_business):
|
if t.is_available(event=event, country=country, is_business=is_business):
|
||||||
result = {"name": str(t.public_name), "code": t.identifier}
|
result = {"name": str(t.public_name), "code": t.identifier}
|
||||||
if t.is_exclusive(event=event, country=country, is_business=is_business):
|
if t.exclusive:
|
||||||
info["transmission_types"] = [result]
|
info["transmission_types"] = [result]
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ def _default_context(request):
|
|||||||
complain_testmode_orders = request.event.orders.filter(testmode=True).exists()
|
complain_testmode_orders = request.event.orders.filter(testmode=True).exists()
|
||||||
request.event.cache.set('complain_testmode_orders', complain_testmode_orders, 30)
|
request.event.cache.set('complain_testmode_orders', complain_testmode_orders, 30)
|
||||||
ctx['complain_testmode_orders'] = complain_testmode_orders and request.user.has_event_permission(
|
ctx['complain_testmode_orders'] = complain_testmode_orders and request.user.has_event_permission(
|
||||||
request.organizer, request.event, 'event.orders:read', request=request
|
request.organizer, request.event, 'can_view_orders', request=request
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
ctx['complain_testmode_orders'] = False
|
ctx['complain_testmode_orders'] = False
|
||||||
|
|||||||
@@ -62,7 +62,6 @@ from pretix.base.forms import (
|
|||||||
)
|
)
|
||||||
from pretix.base.models import Event, Organizer, TaxRule, Team
|
from pretix.base.models import Event, Organizer, TaxRule, Team
|
||||||
from pretix.base.models.event import EventFooterLink, EventMetaValue, SubEvent
|
from pretix.base.models.event import EventFooterLink, EventMetaValue, SubEvent
|
||||||
from pretix.base.models.organizer import TeamQuerySet
|
|
||||||
from pretix.base.models.tax import TAX_CODE_LISTS
|
from pretix.base.models.tax import TAX_CODE_LISTS
|
||||||
from pretix.base.reldate import RelativeDateField, RelativeDateTimeField
|
from pretix.base.reldate import RelativeDateField, RelativeDateTimeField
|
||||||
from pretix.base.services.placeholders import FormPlaceholderMixin
|
from pretix.base.services.placeholders import FormPlaceholderMixin
|
||||||
@@ -105,7 +104,7 @@ class EventWizardFoundationForm(forms.Form):
|
|||||||
qs = Organizer.objects.all()
|
qs = Organizer.objects.all()
|
||||||
if not self.user.has_active_staff_session(self.session.session_key):
|
if not self.user.has_active_staff_session(self.session.session_key):
|
||||||
qs = qs.filter(
|
qs = qs.filter(
|
||||||
id__in=self.user.teams.filter(TeamQuerySet.organizer_permission_q("organizer.events:create")).values_list('organizer', flat=True)
|
id__in=self.user.teams.filter(can_create_events=True).values_list('organizer', flat=True)
|
||||||
)
|
)
|
||||||
self.fields['organizer'] = forms.ModelChoiceField(
|
self.fields['organizer'] = forms.ModelChoiceField(
|
||||||
label=_("Organizer"),
|
label=_("Organizer"),
|
||||||
@@ -263,12 +262,8 @@ class EventWizardBasicsForm(I18nModelForm):
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def has_control_rights(user, organizer, session):
|
def has_control_rights(user, organizer, session):
|
||||||
return user.teams.filter(
|
return user.teams.filter(
|
||||||
TeamQuerySet.event_permission_q("event.items:write"),
|
organizer=organizer, all_events=True, can_change_event_settings=True, can_change_items=True,
|
||||||
TeamQuerySet.event_permission_q("event.orders:write"),
|
can_change_orders=True, can_change_vouchers=True
|
||||||
TeamQuerySet.event_permission_q("event.vouchers:write"),
|
|
||||||
TeamQuerySet.event_permission_q("event.settings.general:write"),
|
|
||||||
organizer=organizer,
|
|
||||||
all_events=True,
|
|
||||||
).exists() or user.has_active_staff_session(session.session_key)
|
).exists() or user.has_active_staff_session(session.session_key)
|
||||||
|
|
||||||
|
|
||||||
@@ -299,14 +294,9 @@ class EventWizardCopyForm(forms.Form):
|
|||||||
return Event.objects.all()
|
return Event.objects.all()
|
||||||
return Event.objects.filter(
|
return Event.objects.filter(
|
||||||
Q(organizer_id__in=user.teams.filter(
|
Q(organizer_id__in=user.teams.filter(
|
||||||
# TODO: review these!
|
all_events=True, can_change_event_settings=True, can_change_items=True
|
||||||
# Restrict cross-organizer copying further than same-organizer copying?
|
|
||||||
TeamQuerySet.event_permission_q("event.settings.general:write"),
|
|
||||||
TeamQuerySet.event_permission_q("event.items:write"),
|
|
||||||
all_events=True,
|
|
||||||
).values_list('organizer', flat=True)) | Q(id__in=user.teams.filter(
|
).values_list('organizer', flat=True)) | Q(id__in=user.teams.filter(
|
||||||
TeamQuerySet.event_permission_q("event.settings.general:write"),
|
can_change_event_settings=True, can_change_items=True
|
||||||
TeamQuerySet.event_permission_q("event.items:write"),
|
|
||||||
).values_list('limit_events__id', flat=True))
|
).values_list('limit_events__id', flat=True))
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -949,7 +939,6 @@ class InvoiceSettingsForm(EventSettingsValidationMixin, SettingsForm):
|
|||||||
'invoice_show_payments',
|
'invoice_show_payments',
|
||||||
'invoice_reissue_after_modify',
|
'invoice_reissue_after_modify',
|
||||||
'invoice_generate',
|
'invoice_generate',
|
||||||
'invoice_generate_only_business',
|
|
||||||
'invoice_period',
|
'invoice_period',
|
||||||
'invoice_attendee_name',
|
'invoice_attendee_name',
|
||||||
'invoice_event_location',
|
'invoice_event_location',
|
||||||
|
|||||||
@@ -1110,7 +1110,7 @@ class OrderPaymentSearchFilterForm(forms.Form):
|
|||||||
self.fields['organizer'].queryset = Organizer.objects.filter(
|
self.fields['organizer'].queryset = Organizer.objects.filter(
|
||||||
pk__in=self.request.user.teams.values_list('organizer', flat=True)
|
pk__in=self.request.user.teams.values_list('organizer', flat=True)
|
||||||
)
|
)
|
||||||
self.fields['event'].queryset = self.request.user.get_events_with_permission('event.orders:read')
|
self.fields['event'].queryset = self.request.user.get_events_with_permission('can_view_orders')
|
||||||
|
|
||||||
self.fields['provider'].choices += get_all_payment_providers()
|
self.fields['provider'].choices += get_all_payment_providers()
|
||||||
|
|
||||||
@@ -1315,10 +1315,10 @@ class QuestionAnswerFilterForm(forms.Form):
|
|||||||
|
|
||||||
if date_range is not None:
|
if date_range is not None:
|
||||||
d_start, d_end = resolve_timeframe_to_datetime_start_inclusive_end_exclusive(now(), date_range, self.event.timezone)
|
d_start, d_end = resolve_timeframe_to_datetime_start_inclusive_end_exclusive(now(), date_range, self.event.timezone)
|
||||||
if d_start:
|
opqs = opqs.filter(
|
||||||
opqs = opqs.filter(subevent__date_from__gte=d_start)
|
subevent__date_from__gte=d_start,
|
||||||
if d_end:
|
subevent__date_from__lt=d_end
|
||||||
opqs = opqs.filter(subevent__date_from__lt=d_end)
|
)
|
||||||
|
|
||||||
s = fdata.get("status", Order.STATUS_PENDING + Order.STATUS_PAID)
|
s = fdata.get("status", Order.STATUS_PENDING + Order.STATUS_PAID)
|
||||||
if s != "":
|
if s != "":
|
||||||
|
|||||||
@@ -75,10 +75,7 @@ from pretix.base.models import (
|
|||||||
ReusableMedium, SalesChannel, Team,
|
ReusableMedium, SalesChannel, Team,
|
||||||
)
|
)
|
||||||
from pretix.base.models.customers import CustomerSSOClient, CustomerSSOProvider
|
from pretix.base.models.customers import CustomerSSOClient, CustomerSSOProvider
|
||||||
from pretix.base.models.organizer import OrganizerFooterLink, TeamQuerySet
|
from pretix.base.models.organizer import OrganizerFooterLink
|
||||||
from pretix.base.permissions import (
|
|
||||||
get_all_event_permission_groups, get_all_organizer_permission_groups,
|
|
||||||
)
|
|
||||||
from pretix.base.settings import (
|
from pretix.base.settings import (
|
||||||
PERSON_NAME_SCHEMES, PERSON_NAME_TITLE_GROUPS, validate_organizer_settings,
|
PERSON_NAME_SCHEMES, PERSON_NAME_TITLE_GROUPS, validate_organizer_settings,
|
||||||
)
|
)
|
||||||
@@ -300,34 +297,7 @@ class MembershipTypeForm(I18nModelForm):
|
|||||||
fields = ['name', 'transferable', 'allow_parallel_usage', 'max_usages']
|
fields = ['name', 'transferable', 'allow_parallel_usage', 'max_usages']
|
||||||
|
|
||||||
|
|
||||||
class PermissionMultipleChoiceField(forms.MultipleChoiceField):
|
|
||||||
def to_python(self, value):
|
|
||||||
return {
|
|
||||||
k: True for k in super().to_python(value) if k
|
|
||||||
}
|
|
||||||
|
|
||||||
def prepare_value(self, value):
|
|
||||||
if isinstance(value, dict):
|
|
||||||
return [k for k, v in value.items() if v is True]
|
|
||||||
return super().prepare_value(value)
|
|
||||||
|
|
||||||
|
|
||||||
class TeamForm(forms.ModelForm):
|
class TeamForm(forms.ModelForm):
|
||||||
def _make_label(self, p):
|
|
||||||
source = '{}'
|
|
||||||
params = [p.label]
|
|
||||||
|
|
||||||
if p.plugin_name:
|
|
||||||
source = '<span class="fa fa-puzzle-piece text-muted" data-toggle="tooltip" title="{}"></span> ' + source
|
|
||||||
params.insert(0, _("Provided by a plugin"))
|
|
||||||
|
|
||||||
if p.help_text:
|
|
||||||
source += ' <span class="fa fa-info-circle text-muted" data-toggle="tooltip" title="{}"></span>'
|
|
||||||
params.append(p.help_text)
|
|
||||||
|
|
||||||
source += ' (<code>{}</code>)'
|
|
||||||
params.append(p.name)
|
|
||||||
return format_html(source, *params)
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
organizer = kwargs.pop('organizer')
|
organizer = kwargs.pop('organizer')
|
||||||
@@ -335,62 +305,16 @@ class TeamForm(forms.ModelForm):
|
|||||||
self.fields['limit_events'].queryset = organizer.events.all().order_by(
|
self.fields['limit_events'].queryset = organizer.events.all().order_by(
|
||||||
'-has_subevents', '-date_from'
|
'-has_subevents', '-date_from'
|
||||||
)
|
)
|
||||||
self.event_field_names = []
|
|
||||||
for pg in get_all_event_permission_groups().values():
|
|
||||||
initial = ",".join(sorted(
|
|
||||||
a for a in pg.actions if self.instance and self.instance.limit_event_permissions.get(f"{pg.name}:{a}")
|
|
||||||
)) or "EMPTY"
|
|
||||||
self.fields[f'event_{pg.name}'] = forms.ChoiceField(
|
|
||||||
choices=[
|
|
||||||
(
|
|
||||||
",".join(sorted(opt.actions)) or "EMPTY",
|
|
||||||
format_html(
|
|
||||||
'{label} '
|
|
||||||
'<span class="fa fa-question-circle fa-fw text-muted" data-toggle="tooltip"'
|
|
||||||
' data-placement="right" title="{help_text}"></span>',
|
|
||||||
label=opt.label,
|
|
||||||
help_text=opt.help_text,
|
|
||||||
) if opt.help_text else opt.label,
|
|
||||||
)
|
|
||||||
for opt in pg.options
|
|
||||||
],
|
|
||||||
label=pg.label,
|
|
||||||
help_text=pg.help_text,
|
|
||||||
initial=initial,
|
|
||||||
widget=forms.RadioSelect,
|
|
||||||
)
|
|
||||||
self.event_field_names.append(f'event_{pg.name}')
|
|
||||||
self.organizer_field_names = []
|
|
||||||
for pg in get_all_organizer_permission_groups().values():
|
|
||||||
initial = ",".join(sorted(
|
|
||||||
a for a in pg.actions if self.instance and self.instance.limit_organizer_permissions.get(f"{pg.name}:{a}")
|
|
||||||
)) or "EMPTY"
|
|
||||||
self.fields[f'organizer_{pg.name}'] = forms.ChoiceField(
|
|
||||||
choices=[
|
|
||||||
(
|
|
||||||
",".join(sorted(opt.actions)) or "EMPTY",
|
|
||||||
format_html(
|
|
||||||
'{label} '
|
|
||||||
'<span class="fa fa-question-circle fa-fw text-muted" data-toggle="tooltip"'
|
|
||||||
' data-placement="right" title="{help_text}"></span>',
|
|
||||||
label=opt.label,
|
|
||||||
help_text=opt.help_text,
|
|
||||||
) if opt.help_text else opt.label,
|
|
||||||
)
|
|
||||||
for opt in pg.options
|
|
||||||
],
|
|
||||||
label=pg.label,
|
|
||||||
help_text=pg.help_text,
|
|
||||||
initial=initial,
|
|
||||||
widget=forms.RadioSelect,
|
|
||||||
)
|
|
||||||
self.organizer_field_names.append(f'organizer_{pg.name}')
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Team
|
model = Team
|
||||||
fields = ['name', 'require_2fa', 'all_events', 'limit_events',
|
fields = ['name', 'require_2fa', 'all_events', 'limit_events', 'can_create_events',
|
||||||
'all_event_permissions',
|
'can_change_teams', 'can_change_organizer_settings',
|
||||||
'all_organizer_permissions',]
|
'can_manage_gift_cards', 'can_manage_customers',
|
||||||
|
'can_manage_reusable_media',
|
||||||
|
'can_change_event_settings', 'can_change_items',
|
||||||
|
'can_view_orders', 'can_change_orders', 'can_checkin_orders',
|
||||||
|
'can_view_vouchers', 'can_change_vouchers']
|
||||||
widgets = {
|
widgets = {
|
||||||
'limit_events': forms.CheckboxSelectMultiple(attrs={
|
'limit_events': forms.CheckboxSelectMultiple(attrs={
|
||||||
'data-inverse-dependency': '#id_all_events',
|
'data-inverse-dependency': '#id_all_events',
|
||||||
@@ -403,57 +327,15 @@ class TeamForm(forms.ModelForm):
|
|||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
data = super().clean()
|
data = super().clean()
|
||||||
|
if self.instance.pk and not data['can_change_teams']:
|
||||||
data['limit_event_permissions'] = {}
|
|
||||||
if not data['all_event_permissions']:
|
|
||||||
for pg in get_all_event_permission_groups().values():
|
|
||||||
selected = data.get(f'event_{pg.name}', 'EMPTY')
|
|
||||||
if selected == "EMPTY":
|
|
||||||
selected_actions = []
|
|
||||||
else:
|
|
||||||
selected_actions = selected.split(',')
|
|
||||||
for action in pg.actions:
|
|
||||||
if action in selected_actions:
|
|
||||||
data['limit_event_permissions'][f"{pg.name}:{action}"] = True
|
|
||||||
self.instance.limit_event_permissions = data['limit_event_permissions']
|
|
||||||
|
|
||||||
data['limit_organizer_permissions'] = {}
|
|
||||||
if not data['all_organizer_permissions']:
|
|
||||||
for pg in get_all_organizer_permission_groups().values():
|
|
||||||
selected = data.get(f'organizer_{pg.name}', 'EMPTY')
|
|
||||||
if selected == "EMPTY":
|
|
||||||
selected_actions = []
|
|
||||||
else:
|
|
||||||
selected_actions = selected.split(',')
|
|
||||||
for action in pg.actions:
|
|
||||||
if action in selected_actions:
|
|
||||||
data['limit_organizer_permissions'][f"{pg.name}:{action}"] = True
|
|
||||||
self.instance.limit_organizer_permissions = data['limit_organizer_permissions']
|
|
||||||
|
|
||||||
if self.instance.pk and not data['all_organizer_permissions'] and 'organizer.teams:write' not in data.get('limit_organizer_permissions', []):
|
|
||||||
if not self.instance.organizer.teams.exclude(pk=self.instance.pk).filter(
|
if not self.instance.organizer.teams.exclude(pk=self.instance.pk).filter(
|
||||||
TeamQuerySet.organizer_permission_q("organizer.teams:write"),
|
can_change_teams=True, members__isnull=False
|
||||||
members__isnull=False
|
|
||||||
).exists():
|
).exists():
|
||||||
raise ValidationError(_('The changes could not be saved because there would be no remaining team with '
|
raise ValidationError(_('The changes could not be saved because there would be no remaining team with '
|
||||||
'the permission to change teams and permissions.'))
|
'the permission to change teams and permissions.'))
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
@property
|
|
||||||
def changed_data_for_log(self):
|
|
||||||
r = {}
|
|
||||||
for k in self.changed_data:
|
|
||||||
if k == "limit_events":
|
|
||||||
r[k] = [e.id for e in getattr(self.instance, k).all()]
|
|
||||||
elif k.startswith("event_"):
|
|
||||||
r["limit_event_permissions"] = self.instance.limit_event_permissions
|
|
||||||
elif k.startswith("organizer_"):
|
|
||||||
r["limit_organizer_permissions"] = self.instance.limit_organizer_permissions
|
|
||||||
else:
|
|
||||||
r[k] = getattr(self.instance, k)
|
|
||||||
return r
|
|
||||||
|
|
||||||
|
|
||||||
class GateForm(forms.ModelForm):
|
class GateForm(forms.ModelForm):
|
||||||
|
|
||||||
|
|||||||
@@ -45,9 +45,7 @@ from django.utils.translation import gettext as _
|
|||||||
from django_scopes import scope
|
from django_scopes import scope
|
||||||
|
|
||||||
from pretix.base.models import Event, Organizer
|
from pretix.base.models import Event, Organizer
|
||||||
from pretix.base.models.auth import (
|
from pretix.base.models.auth import SuperuserPermissionSet, User
|
||||||
EventPermissionSet, OrganizerPermissionSet, SuperuserPermissionSet, User,
|
|
||||||
)
|
|
||||||
from pretix.helpers.http import redirect_to_url
|
from pretix.helpers.http import redirect_to_url
|
||||||
from pretix.helpers.security import (
|
from pretix.helpers.security import (
|
||||||
Session2FASetupRequired, SessionInvalid, SessionPasswordChangeRequired,
|
Session2FASetupRequired, SessionInvalid, SessionPasswordChangeRequired,
|
||||||
@@ -172,7 +170,7 @@ class PermissionMiddleware:
|
|||||||
if request.user.has_active_staff_session(request.session.session_key):
|
if request.user.has_active_staff_session(request.session.session_key):
|
||||||
request.eventpermset = SuperuserPermissionSet()
|
request.eventpermset = SuperuserPermissionSet()
|
||||||
else:
|
else:
|
||||||
request.eventpermset = EventPermissionSet(request.user.get_event_permission_set(request.organizer, request.event))
|
request.eventpermset = request.user.get_event_permission_set(request.organizer, request.event)
|
||||||
elif 'organizer' in url.kwargs:
|
elif 'organizer' in url.kwargs:
|
||||||
if url.kwargs['organizer'] == '-':
|
if url.kwargs['organizer'] == '-':
|
||||||
# This is a hack that just takes the user to ANY organizer. It's useful to link to features in support
|
# This is a hack that just takes the user to ANY organizer. It's useful to link to features in support
|
||||||
@@ -194,7 +192,7 @@ class PermissionMiddleware:
|
|||||||
if request.user.has_active_staff_session(request.session.session_key):
|
if request.user.has_active_staff_session(request.session.session_key):
|
||||||
request.orgapermset = SuperuserPermissionSet()
|
request.orgapermset = SuperuserPermissionSet()
|
||||||
else:
|
else:
|
||||||
request.orgapermset = OrganizerPermissionSet(request.user.get_organizer_permission_set(request.organizer))
|
request.orgapermset = request.user.get_organizer_permission_set(request.organizer)
|
||||||
|
|
||||||
with scope(organizer=getattr(request, 'organizer', None)):
|
with scope(organizer=getattr(request, 'organizer', None)):
|
||||||
r = self.get_response(request)
|
r = self.get_response(request)
|
||||||
|
|||||||
@@ -43,29 +43,24 @@ def get_event_navigation(request: HttpRequest):
|
|||||||
'icon': 'dashboard',
|
'icon': 'dashboard',
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
event_settings = []
|
if 'can_change_event_settings' in request.eventpermset:
|
||||||
if "event.settings.general:write" in request.eventpermset:
|
event_settings = [
|
||||||
event_settings.append({
|
{
|
||||||
'label': _('General'),
|
'label': _('General'),
|
||||||
'url': reverse('control:event.settings', kwargs={
|
'url': reverse('control:event.settings', kwargs={
|
||||||
'event': request.event.slug,
|
'event': request.event.slug,
|
||||||
'organizer': request.event.organizer.slug,
|
'organizer': request.event.organizer.slug,
|
||||||
}),
|
}),
|
||||||
'active': url.url_name == 'event.settings',
|
'active': url.url_name == 'event.settings',
|
||||||
})
|
},
|
||||||
|
{
|
||||||
if "event.settings.payment:write" in request.eventpermset or "event.settings.general:write" in request.eventpermset:
|
'label': _('Payment'),
|
||||||
event_settings.append({
|
'url': reverse('control:event.settings.payment', kwargs={
|
||||||
'label': _('Payment'),
|
'event': request.event.slug,
|
||||||
'url': reverse('control:event.settings.payment', kwargs={
|
'organizer': request.event.organizer.slug,
|
||||||
'event': request.event.slug,
|
}),
|
||||||
'organizer': request.event.organizer.slug,
|
'active': url.url_name in ('event.settings.payment', 'event.settings.payment.provider'),
|
||||||
}),
|
},
|
||||||
'active': url.url_name in ('event.settings.payment', 'event.settings.payment.provider'),
|
|
||||||
})
|
|
||||||
|
|
||||||
if "event.settings.general:write" in request.eventpermset:
|
|
||||||
event_settings += [
|
|
||||||
{
|
{
|
||||||
'label': _('Plugins'),
|
'label': _('Plugins'),
|
||||||
'url': reverse('control:event.settings.plugins', kwargs={
|
'url': reverse('control:event.settings.plugins', kwargs={
|
||||||
@@ -89,31 +84,23 @@ def get_event_navigation(request: HttpRequest):
|
|||||||
'organizer': request.event.organizer.slug,
|
'organizer': request.event.organizer.slug,
|
||||||
}),
|
}),
|
||||||
'active': url.url_name == 'event.settings.mail',
|
'active': url.url_name == 'event.settings.mail',
|
||||||
}
|
},
|
||||||
]
|
{
|
||||||
|
'label': _('Taxes'),
|
||||||
if "event.settings.tax:write" in request.eventpermset or "event.settings.general:write" in request.eventpermset:
|
'url': reverse('control:event.settings.tax', kwargs={
|
||||||
event_settings.append({
|
'event': request.event.slug,
|
||||||
'label': _('Taxes'),
|
'organizer': request.event.organizer.slug,
|
||||||
'url': reverse('control:event.settings.tax', kwargs={
|
}),
|
||||||
'event': request.event.slug,
|
'active': url.url_name.startswith('event.settings.tax'),
|
||||||
'organizer': request.event.organizer.slug,
|
},
|
||||||
}),
|
{
|
||||||
'active': url.url_name.startswith('event.settings.tax'),
|
'label': _('Invoicing'),
|
||||||
})
|
'url': reverse('control:event.settings.invoice', kwargs={
|
||||||
|
'event': request.event.slug,
|
||||||
if "event.settings.invoicing:write" in request.eventpermset or "event.settings.general:write" in request.eventpermset:
|
'organizer': request.event.organizer.slug,
|
||||||
event_settings.append({
|
}),
|
||||||
'label': _('Invoicing'),
|
'active': url.url_name == 'event.settings.invoice',
|
||||||
'url': reverse('control:event.settings.invoice', kwargs={
|
},
|
||||||
'event': request.event.slug,
|
|
||||||
'organizer': request.event.organizer.slug,
|
|
||||||
}),
|
|
||||||
'active': url.url_name == 'event.settings.invoice',
|
|
||||||
})
|
|
||||||
|
|
||||||
if "event.settings.general:write" in request.eventpermset:
|
|
||||||
event_settings += [
|
|
||||||
{
|
{
|
||||||
'label': pgettext_lazy('action', 'Cancellation'),
|
'label': pgettext_lazy('action', 'Cancellation'),
|
||||||
'url': reverse('control:event.settings.cancel', kwargs={
|
'url': reverse('control:event.settings.cancel', kwargs={
|
||||||
@@ -131,87 +118,88 @@ def get_event_navigation(request: HttpRequest):
|
|||||||
'active': url.url_name == 'event.settings.widget',
|
'active': url.url_name == 'event.settings.widget',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
# It would be better to allow plugins to handle the permission themselves, but for backwards compatibility
|
|
||||||
# we need to have it in the "if" statement
|
|
||||||
event_settings += sorted(
|
event_settings += sorted(
|
||||||
sum((list(a[1]) for a in nav_event_settings.send(request.event, request=request)), []),
|
sum((list(a[1]) for a in nav_event_settings.send(request.event, request=request)), []),
|
||||||
key=lambda r: r['label']
|
key=lambda r: r['label']
|
||||||
)
|
)
|
||||||
if event_settings:
|
|
||||||
nav.append({
|
nav.append({
|
||||||
'label': _('Settings'),
|
'label': _('Settings'),
|
||||||
'url': event_settings[0]["url"],
|
'url': reverse('control:event.settings', kwargs={
|
||||||
|
'event': request.event.slug,
|
||||||
|
'organizer': request.event.organizer.slug,
|
||||||
|
}),
|
||||||
'active': False,
|
'active': False,
|
||||||
'icon': 'wrench',
|
'icon': 'wrench',
|
||||||
'children': event_settings
|
'children': event_settings
|
||||||
})
|
})
|
||||||
|
|
||||||
nav.append({
|
if 'can_change_items' in request.eventpermset:
|
||||||
'label': _('Products'),
|
|
||||||
'url': reverse('control:event.items', kwargs={
|
|
||||||
'event': request.event.slug,
|
|
||||||
'organizer': request.event.organizer.slug,
|
|
||||||
}),
|
|
||||||
'active': False,
|
|
||||||
'icon': 'ticket',
|
|
||||||
'children': [
|
|
||||||
{
|
|
||||||
'label': _('Products'),
|
|
||||||
'url': reverse('control:event.items', kwargs={
|
|
||||||
'event': request.event.slug,
|
|
||||||
'organizer': request.event.organizer.slug,
|
|
||||||
}),
|
|
||||||
'active': url.url_name in (
|
|
||||||
'event.item', 'event.items.add', 'event.items') or "event.item." in url.url_name,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'label': _('Quotas'),
|
|
||||||
'url': reverse('control:event.items.quotas', kwargs={
|
|
||||||
'event': request.event.slug,
|
|
||||||
'organizer': request.event.organizer.slug,
|
|
||||||
}),
|
|
||||||
'active': 'event.items.quota' in url.url_name,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'label': _('Categories'),
|
|
||||||
'url': reverse('control:event.items.categories', kwargs={
|
|
||||||
'event': request.event.slug,
|
|
||||||
'organizer': request.event.organizer.slug,
|
|
||||||
}),
|
|
||||||
'active': 'event.items.categories' in url.url_name,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'label': _('Questions'),
|
|
||||||
'url': reverse('control:event.items.questions', kwargs={
|
|
||||||
'event': request.event.slug,
|
|
||||||
'organizer': request.event.organizer.slug,
|
|
||||||
}),
|
|
||||||
'active': 'event.items.questions' in url.url_name,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'label': _('Discounts'),
|
|
||||||
'url': reverse('control:event.items.discounts', kwargs={
|
|
||||||
'event': request.event.slug,
|
|
||||||
'organizer': request.event.organizer.slug,
|
|
||||||
}),
|
|
||||||
'active': 'event.items.discounts' in url.url_name,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
})
|
|
||||||
|
|
||||||
if request.event.has_subevents:
|
|
||||||
nav.append({
|
nav.append({
|
||||||
'label': pgettext_lazy('subevent', 'Dates'),
|
'label': _('Products'),
|
||||||
'url': reverse('control:event.subevents', kwargs={
|
'url': reverse('control:event.items', kwargs={
|
||||||
'event': request.event.slug,
|
'event': request.event.slug,
|
||||||
'organizer': request.event.organizer.slug,
|
'organizer': request.event.organizer.slug,
|
||||||
}),
|
}),
|
||||||
'active': ('event.subevent' in url.url_name),
|
'active': False,
|
||||||
'icon': 'calendar',
|
'icon': 'ticket',
|
||||||
|
'children': [
|
||||||
|
{
|
||||||
|
'label': _('Products'),
|
||||||
|
'url': reverse('control:event.items', kwargs={
|
||||||
|
'event': request.event.slug,
|
||||||
|
'organizer': request.event.organizer.slug,
|
||||||
|
}),
|
||||||
|
'active': url.url_name in (
|
||||||
|
'event.item', 'event.items.add', 'event.items') or "event.item." in url.url_name,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'label': _('Quotas'),
|
||||||
|
'url': reverse('control:event.items.quotas', kwargs={
|
||||||
|
'event': request.event.slug,
|
||||||
|
'organizer': request.event.organizer.slug,
|
||||||
|
}),
|
||||||
|
'active': 'event.items.quota' in url.url_name,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'label': _('Categories'),
|
||||||
|
'url': reverse('control:event.items.categories', kwargs={
|
||||||
|
'event': request.event.slug,
|
||||||
|
'organizer': request.event.organizer.slug,
|
||||||
|
}),
|
||||||
|
'active': 'event.items.categories' in url.url_name,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'label': _('Questions'),
|
||||||
|
'url': reverse('control:event.items.questions', kwargs={
|
||||||
|
'event': request.event.slug,
|
||||||
|
'organizer': request.event.organizer.slug,
|
||||||
|
}),
|
||||||
|
'active': 'event.items.questions' in url.url_name,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'label': _('Discounts'),
|
||||||
|
'url': reverse('control:event.items.discounts', kwargs={
|
||||||
|
'event': request.event.slug,
|
||||||
|
'organizer': request.event.organizer.slug,
|
||||||
|
}),
|
||||||
|
'active': 'event.items.discounts' in url.url_name,
|
||||||
|
},
|
||||||
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
if 'event.orders:read' in request.eventpermset:
|
if 'can_change_event_settings' in request.eventpermset:
|
||||||
|
if request.event.has_subevents:
|
||||||
|
nav.append({
|
||||||
|
'label': pgettext_lazy('subevent', 'Dates'),
|
||||||
|
'url': reverse('control:event.subevents', kwargs={
|
||||||
|
'event': request.event.slug,
|
||||||
|
'organizer': request.event.organizer.slug,
|
||||||
|
}),
|
||||||
|
'active': ('event.subevent' in url.url_name),
|
||||||
|
'icon': 'calendar',
|
||||||
|
})
|
||||||
|
|
||||||
|
if 'can_view_orders' in request.eventpermset:
|
||||||
children = [
|
children = [
|
||||||
{
|
{
|
||||||
'label': _('All orders'),
|
'label': _('All orders'),
|
||||||
@@ -254,7 +242,7 @@ def get_event_navigation(request: HttpRequest):
|
|||||||
'active': 'event.orders.waitinglist' in url.url_name,
|
'active': 'event.orders.waitinglist' in url.url_name,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
if 'event.orders:write' in request.eventpermset:
|
if 'can_change_orders' in request.eventpermset:
|
||||||
children.append({
|
children.append({
|
||||||
'label': _('Import'),
|
'label': _('Import'),
|
||||||
'url': reverse('control:event.orders.import', kwargs={
|
'url': reverse('control:event.orders.import', kwargs={
|
||||||
@@ -273,18 +261,8 @@ def get_event_navigation(request: HttpRequest):
|
|||||||
'icon': 'shopping-cart',
|
'icon': 'shopping-cart',
|
||||||
'children': children
|
'children': children
|
||||||
})
|
})
|
||||||
else:
|
|
||||||
nav.append({
|
|
||||||
'label': _('Export'),
|
|
||||||
'url': reverse('control:event.orders.export', kwargs={
|
|
||||||
'event': request.event.slug,
|
|
||||||
'organizer': request.event.organizer.slug,
|
|
||||||
}),
|
|
||||||
'active': 'event.orders.export' in url.url_name,
|
|
||||||
'icon': 'download',
|
|
||||||
})
|
|
||||||
|
|
||||||
if 'event.vouchers:read' in request.eventpermset:
|
if 'can_view_vouchers' in request.eventpermset:
|
||||||
nav.append({
|
nav.append({
|
||||||
'label': _('Vouchers'),
|
'label': _('Vouchers'),
|
||||||
'url': reverse('control:event.vouchers', kwargs={
|
'url': reverse('control:event.vouchers', kwargs={
|
||||||
@@ -313,7 +291,7 @@ def get_event_navigation(request: HttpRequest):
|
|||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
if 'event.orders:read' in request.eventpermset or 'event.settings.general:write' in request.eventpermset:
|
if 'can_view_orders' in request.eventpermset:
|
||||||
nav.append({
|
nav.append({
|
||||||
'label': pgettext_lazy('navigation', 'Check-in'),
|
'label': pgettext_lazy('navigation', 'Check-in'),
|
||||||
'url': reverse('control:event.orders.checkinlists', kwargs={
|
'url': reverse('control:event.orders.checkinlists', kwargs={
|
||||||
@@ -502,7 +480,7 @@ def get_organizer_navigation(request):
|
|||||||
'icon': 'calendar',
|
'icon': 'calendar',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
if 'organizer.settings.general:write' in request.orgapermset:
|
if 'can_change_organizer_settings' in request.orgapermset:
|
||||||
nav.append({
|
nav.append({
|
||||||
'label': _('Settings'),
|
'label': _('Settings'),
|
||||||
'url': reverse('control:organizer.edit', kwargs={
|
'url': reverse('control:organizer.edit', kwargs={
|
||||||
@@ -556,7 +534,7 @@ def get_organizer_navigation(request):
|
|||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
if 'organizer.teams:write' in request.orgapermset:
|
if 'can_change_teams' in request.orgapermset:
|
||||||
nav.append({
|
nav.append({
|
||||||
'label': _('Teams'),
|
'label': _('Teams'),
|
||||||
'url': reverse('control:organizer.teams', kwargs={
|
'url': reverse('control:organizer.teams', kwargs={
|
||||||
@@ -566,7 +544,7 @@ def get_organizer_navigation(request):
|
|||||||
'icon': 'group',
|
'icon': 'group',
|
||||||
})
|
})
|
||||||
|
|
||||||
if 'organizer.giftcards:read' in request.orgapermset or 'organizer.giftcards:write' in request.orgapermset:
|
if 'can_manage_gift_cards' in request.orgapermset:
|
||||||
children = []
|
children = []
|
||||||
children.append({
|
children.append({
|
||||||
'label': _('Gift cards'),
|
'label': _('Gift cards'),
|
||||||
@@ -576,7 +554,7 @@ def get_organizer_navigation(request):
|
|||||||
'active': 'organizer.giftcard' in url.url_name and 'acceptance' not in url.url_name,
|
'active': 'organizer.giftcard' in url.url_name and 'acceptance' not in url.url_name,
|
||||||
'children': children,
|
'children': children,
|
||||||
})
|
})
|
||||||
if 'organizer.settings.general:write' in request.orgapermset:
|
if 'can_change_organizer_settings' in request.orgapermset:
|
||||||
children.append(
|
children.append(
|
||||||
{
|
{
|
||||||
'label': _('Acceptance'),
|
'label': _('Acceptance'),
|
||||||
@@ -597,7 +575,7 @@ def get_organizer_navigation(request):
|
|||||||
|
|
||||||
if request.organizer.settings.customer_accounts:
|
if request.organizer.settings.customer_accounts:
|
||||||
children = []
|
children = []
|
||||||
if 'organizer.customers:read' in request.orgapermset or 'organizer.customers:write' in request.orgapermset:
|
if 'can_manage_customers' in request.orgapermset:
|
||||||
children.append(
|
children.append(
|
||||||
{
|
{
|
||||||
'label': _('Customers'),
|
'label': _('Customers'),
|
||||||
@@ -607,7 +585,7 @@ def get_organizer_navigation(request):
|
|||||||
'active': 'organizer.customer' in url.url_name,
|
'active': 'organizer.customer' in url.url_name,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
if 'organizer.settings.general:write' in request.orgapermset:
|
if 'can_change_organizer_settings' in request.orgapermset:
|
||||||
children.append(
|
children.append(
|
||||||
{
|
{
|
||||||
'label': _('Membership types'),
|
'label': _('Membership types'),
|
||||||
@@ -646,17 +624,16 @@ def get_organizer_navigation(request):
|
|||||||
})
|
})
|
||||||
|
|
||||||
if request.organizer.settings.reusable_media_active:
|
if request.organizer.settings.reusable_media_active:
|
||||||
if 'organizer.reusablemedia:read' in request.orgapermset or 'organizer.reusablemedia:write' in request.orgapermset:
|
nav.append({
|
||||||
nav.append({
|
'label': _('Reusable media'),
|
||||||
'label': _('Reusable media'),
|
'url': reverse('control:organizer.reusable_media', kwargs={
|
||||||
'url': reverse('control:organizer.reusable_media', kwargs={
|
'organizer': request.organizer.slug
|
||||||
'organizer': request.organizer.slug
|
}),
|
||||||
}),
|
'icon': 'key',
|
||||||
'icon': 'key',
|
'active': 'organizer.reusable_medi' in url.url_name,
|
||||||
'active': 'organizer.reusable_medi' in url.url_name,
|
})
|
||||||
})
|
|
||||||
|
|
||||||
if 'organizer.devices:read' in request.orgapermset or 'organizer.devices:write' in request.orgapermset:
|
if 'can_change_organizer_settings' in request.orgapermset:
|
||||||
nav.append({
|
nav.append({
|
||||||
'label': _('Devices'),
|
'label': _('Devices'),
|
||||||
'url': reverse('control:organizer.devices', kwargs={
|
'url': reverse('control:organizer.devices', kwargs={
|
||||||
@@ -690,7 +667,7 @@ def get_organizer_navigation(request):
|
|||||||
'icon': 'download',
|
'icon': 'download',
|
||||||
})
|
})
|
||||||
|
|
||||||
if 'organizer.settings.general:write' in request.orgapermset:
|
if 'can_change_organizer_settings' in request.orgapermset:
|
||||||
merge_in(nav, [{
|
merge_in(nav, [{
|
||||||
'parent': reverse('control:organizer.export', kwargs={
|
'parent': reverse('control:organizer.export', kwargs={
|
||||||
'organizer': request.organizer.slug,
|
'organizer': request.organizer.slug,
|
||||||
|
|||||||
@@ -38,9 +38,6 @@ from django.core.exceptions import PermissionDenied
|
|||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
from pretix.base.permissions import (
|
|
||||||
assert_valid_event_permission, assert_valid_organizer_permission,
|
|
||||||
)
|
|
||||||
from pretix.helpers.http import redirect_to_url
|
from pretix.helpers.http import redirect_to_url
|
||||||
|
|
||||||
|
|
||||||
@@ -58,9 +55,7 @@ def event_permission_required(permission):
|
|||||||
"""
|
"""
|
||||||
if permission == 'can_change_settings':
|
if permission == 'can_change_settings':
|
||||||
# Legacy support
|
# Legacy support
|
||||||
permission = 'event.settings.general:write'
|
permission = 'can_change_event_settings'
|
||||||
|
|
||||||
assert_valid_event_permission(permission)
|
|
||||||
|
|
||||||
def decorator(function):
|
def decorator(function):
|
||||||
def wrapper(request, *args, **kw):
|
def wrapper(request, *args, **kw):
|
||||||
@@ -84,7 +79,7 @@ class EventPermissionRequiredMixin:
|
|||||||
This mixin is equivalent to the event_permission_required view decorator but
|
This mixin is equivalent to the event_permission_required view decorator but
|
||||||
is in a form suitable for class-based views.
|
is in a form suitable for class-based views.
|
||||||
"""
|
"""
|
||||||
permission = None # None means "any permission"
|
permission = ''
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def as_view(cls, **initkwargs):
|
def as_view(cls, **initkwargs):
|
||||||
@@ -97,11 +92,9 @@ def organizer_permission_required(permission):
|
|||||||
This view decorator rejects all requests with a 403 response which are not from
|
This view decorator rejects all requests with a 403 response which are not from
|
||||||
users having the given permission for the event the request is associated with.
|
users having the given permission for the event the request is associated with.
|
||||||
"""
|
"""
|
||||||
if permission in ('event.settings.general:write', 'can_change_settings', 'can_change_event_settings'):
|
if permission == 'can_change_settings':
|
||||||
# Legacy support
|
# Legacy support
|
||||||
permission = 'organizer.settings.general:write'
|
permission = 'can_change_organizer_settings'
|
||||||
|
|
||||||
assert_valid_organizer_permission(permission)
|
|
||||||
|
|
||||||
def decorator(function):
|
def decorator(function):
|
||||||
def wrapper(request, *args, **kw):
|
def wrapper(request, *args, **kw):
|
||||||
@@ -123,7 +116,7 @@ class OrganizerPermissionRequiredMixin:
|
|||||||
This mixin is equivalent to the organizer_permission_required view decorator but
|
This mixin is equivalent to the organizer_permission_required view decorator but
|
||||||
is in a form suitable for class-based views.
|
is in a form suitable for class-based views.
|
||||||
"""
|
"""
|
||||||
permission = None # None means "any permission"
|
permission = ''
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def as_view(cls, **initkwargs):
|
def as_view(cls, **initkwargs):
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<h1>
|
<h1>
|
||||||
{% blocktrans with name=checkinlist.name %}Check-in list: {{ name }}{% endblocktrans %}
|
{% blocktrans with name=checkinlist.name %}Check-in list: {{ name }}{% endblocktrans %}
|
||||||
{% if 'event.settings.general:write' in request.eventpermset %}
|
{% if 'can_change_event_settings' in request.eventpermset %}
|
||||||
<a href="{% url "control:event.orders.checkinlists.edit" event=request.event.slug organizer=request.event.organizer.slug list=checkinlist.pk %}"
|
<a href="{% url "control:event.orders.checkinlists.edit" event=request.event.slug organizer=request.event.organizer.slug list=checkinlist.pk %}"
|
||||||
class="btn btn-default">
|
class="btn btn-default">
|
||||||
<span class="fa fa-wrench"></span>
|
<span class="fa fa-wrench"></span>
|
||||||
@@ -87,7 +87,7 @@
|
|||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>
|
<th>
|
||||||
{% if "event.orders:write" in request.eventpermset or "event.orders:checkin" in request.eventpermset %}
|
{% if "can_change_orders" in request.eventpermset or "can_checkin_orders" in request.eventpermset %}
|
||||||
<label aria-label="{% trans "select all rows for batch-operation" %}"
|
<label aria-label="{% trans "select all rows for batch-operation" %}"
|
||||||
class="batch-select-label"><input type="checkbox" data-toggle-table/></label>
|
class="batch-select-label"><input type="checkbox" data-toggle-table/></label>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -132,7 +132,7 @@
|
|||||||
{% for e in entries %}
|
{% for e in entries %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
{% if "event.orders:write" in request.eventpermset or "event.orders:checkin" in request.eventpermset %}
|
{% if "can_change_orders" in request.eventpermset or "can_checkin_orders" in request.eventpermset %}
|
||||||
<input type="checkbox" name="checkin" id="id_checkin" class="" value="{{ e.pk }}"/>
|
<input type="checkbox" name="checkin" id="id_checkin" class="" value="{{ e.pk }}"/>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
@@ -207,7 +207,7 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div class="batch-select-actions">
|
<div class="batch-select-actions">
|
||||||
{% if "event.orders:write" in request.eventpermset or "event.orders:checkin" in request.eventpermset %}
|
{% if "can_change_orders" in request.eventpermset or "can_checkin_orders" in request.eventpermset %}
|
||||||
<button type="submit" class="btn btn-primary btn-save">
|
<button type="submit" class="btn btn-primary btn-save">
|
||||||
<span class="fa fa-sign-in" aria-hidden="true"></span>
|
<span class="fa fa-sign-in" aria-hidden="true"></span>
|
||||||
{% trans "Check-In selected attendees" %}
|
{% trans "Check-In selected attendees" %}
|
||||||
@@ -217,7 +217,7 @@
|
|||||||
{% trans "Check-Out selected attendees" %}
|
{% trans "Check-Out selected attendees" %}
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if "event.orders:write" in request.eventpermset %}
|
{% if "can_change_orders" in request.eventpermset %}
|
||||||
<button type="submit" class="btn btn-danger btn-save" name="revert"
|
<button type="submit" class="btn btn-danger btn-save" name="revert"
|
||||||
formaction="{% url "control:event.orders.checkinlists.bulk_revert" event=request.event.slug organizer=request.event.organizer.slug list=checkinlist.pk %}"
|
formaction="{% url "control:event.orders.checkinlists.bulk_revert" event=request.event.slug organizer=request.event.organizer.slug list=checkinlist.pk %}"
|
||||||
data-no-asynctask
|
data-no-asynctask
|
||||||
|
|||||||
@@ -63,27 +63,27 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{% if "event.settings.general:write" in request.eventpermset %}
|
{% if "can_change_event_settings" in request.eventpermset %}
|
||||||
<a href="{% url "control:event.orders.checkinlists.add" organizer=request.event.organizer.slug event=request.event.slug %}"
|
<a href="{% url "control:event.orders.checkinlists.add" organizer=request.event.organizer.slug event=request.event.slug %}"
|
||||||
class="btn btn-primary btn-lg"><i class="fa fa-plus"></i> {% trans "Create a new check-in list" %}
|
class="btn btn-primary btn-lg"><i class="fa fa-plus"></i> {% trans "Create a new check-in list" %}
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if link_device_settings %}
|
{% if can_change_organizer_settings %}
|
||||||
<a href="{% url "control:organizer.devices" organizer=request.organizer.slug %}"
|
<a href="{% url "control:organizer.devices" organizer=request.organizer.slug %}"
|
||||||
class="btn btn-default btn-lg"><i class="fa fa-tablet"></i> {% trans "Connected devices" %}</a>
|
class="btn btn-default btn-lg"><i class="fa fa-tablet"></i> {% trans "Connected devices" %}</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<p>
|
<p>
|
||||||
{% if "event.settings.general:write" in request.eventpermset %}
|
{% if "can_change_event_settings" in request.eventpermset %}
|
||||||
<a href="{% url "control:event.orders.checkinlists.add" organizer=request.event.organizer.slug event=request.event.slug %}"
|
<a href="{% url "control:event.orders.checkinlists.add" organizer=request.event.organizer.slug event=request.event.slug %}"
|
||||||
class="btn btn-default"><i class="fa fa-plus"></i> {% trans "Create a new check-in list" %}</a>
|
class="btn btn-default"><i class="fa fa-plus"></i> {% trans "Create a new check-in list" %}</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if link_device_settings %}
|
{% if can_change_organizer_settings %}
|
||||||
<a href="{% url "control:organizer.devices" organizer=request.organizer.slug %}"
|
<a href="{% url "control:organizer.devices" organizer=request.organizer.slug %}"
|
||||||
class="btn btn-default"><i class="fa fa-tablet"></i> {% trans "Connected devices" %}</a>
|
class="btn btn-default"><i class="fa fa-tablet"></i> {% trans "Connected devices" %}</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if "event.settings.general:write" in request.eventpermset and "event.orders:write" in request.eventpermset %}
|
{% if "can_change_orders" in request.eventpermset %}
|
||||||
<a href="{% url "control:event.orders.checkinlists.reset" organizer=request.event.organizer.slug event=request.event.slug %}"
|
<a href="{% url "control:event.orders.checkinlists.reset" organizer=request.event.organizer.slug event=request.event.slug %}"
|
||||||
class="btn btn-default">
|
class="btn btn-default">
|
||||||
<span class="fa fa-repeat"></span>
|
<span class="fa fa-repeat"></span>
|
||||||
@@ -100,9 +100,7 @@
|
|||||||
<a href="?{% url_replace request 'ordering' '-name' %}"><i class="fa fa-caret-down"></i></a>
|
<a href="?{% url_replace request 'ordering' '-name' %}"><i class="fa fa-caret-down"></i></a>
|
||||||
<a href="?{% url_replace request 'ordering' 'name' %}"><i class="fa fa-caret-up"></i></a>
|
<a href="?{% url_replace request 'ordering' 'name' %}"><i class="fa fa-caret-up"></i></a>
|
||||||
</th>
|
</th>
|
||||||
{% if "event.orders:read" in request.eventpermset %}
|
<th>{% trans "Checked in" %}</th>
|
||||||
<th>{% trans "Checked in" %}</th>
|
|
||||||
{% endif %}
|
|
||||||
{% if request.event.has_subevents %}
|
{% if request.event.has_subevents %}
|
||||||
<th>
|
<th>
|
||||||
{% trans "Date" context "subevent" %}
|
{% trans "Date" context "subevent" %}
|
||||||
@@ -121,20 +119,18 @@
|
|||||||
<strong><a
|
<strong><a
|
||||||
href="{% url "control:event.orders.checkinlists.show" organizer=request.event.organizer.slug event=request.event.slug list=cl.id %}">{{ cl.name }}</a></strong>
|
href="{% url "control:event.orders.checkinlists.show" organizer=request.event.organizer.slug event=request.event.slug list=cl.id %}">{{ cl.name }}</a></strong>
|
||||||
</td>
|
</td>
|
||||||
{% if "event.orders:read" in request.eventpermset %}
|
<td>
|
||||||
<td>
|
<div class="quotabox availability">
|
||||||
<div class="quotabox availability">
|
<div class="progress">
|
||||||
<div class="progress">
|
<div class="progress-bar progress-bar-success progress-bar-{{ cl.percent }}">
|
||||||
<div class="progress-bar progress-bar-success progress-bar-{{ cl.percent }}">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="numbers">
|
|
||||||
{{ cl.checkin_count|default_if_none:"0" }} /
|
|
||||||
{{ cl.position_count|default_if_none:"0" }}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
<div class="numbers">
|
||||||
{% endif %}
|
{{ cl.checkin_count|default_if_none:"0" }} /
|
||||||
|
{{ cl.position_count|default_if_none:"0" }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
{% if request.event.has_subevents %}
|
{% if request.event.has_subevents %}
|
||||||
{% if cl.subevent %}
|
{% if cl.subevent %}
|
||||||
<td>
|
<td>
|
||||||
@@ -160,18 +156,16 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td class="text-right flip">
|
<td class="text-right flip">
|
||||||
{% if "event.orders:read" in request.eventpermset %}
|
<a href="{% url "control:event.orders.checkinlists.show" organizer=request.event.organizer.slug event=request.event.slug list=cl.id %}"
|
||||||
<a href="{% url "control:event.orders.checkinlists.show" organizer=request.event.organizer.slug event=request.event.slug list=cl.id %}"
|
class="btn btn-default btn-sm"><i class="fa fa-eye"></i></a>
|
||||||
class="btn btn-default btn-sm"><i class="fa fa-eye"></i></a>
|
{% if "can_change_event_settings" in request.eventpermset %}
|
||||||
<a href="{% url "control:event.orders.checkinlists.simulator" organizer=request.event.organizer.slug event=request.event.slug list=cl.id %}"
|
|
||||||
title="{% trans "Check-in simulator" %}" data-toggle="tooltip"
|
|
||||||
class="btn btn-default btn-sm"><i class="fa fa-flask"></i></a>
|
|
||||||
{% endif %}
|
|
||||||
{% if "event.settings.general:write" in request.eventpermset %}
|
|
||||||
<a href="{% url "control:event.orders.checkinlists.add" organizer=request.event.organizer.slug event=request.event.slug %}?copy_from={{ cl.id }}"
|
<a href="{% url "control:event.orders.checkinlists.add" organizer=request.event.organizer.slug event=request.event.slug %}?copy_from={{ cl.id }}"
|
||||||
class="btn btn-sm btn-default" title="{% trans "Clone" %}" data-toggle="tooltip">
|
class="btn btn-sm btn-default" title="{% trans "Clone" %}" data-toggle="tooltip">
|
||||||
<span class="fa fa-copy"></span>
|
<span class="fa fa-copy"></span>
|
||||||
</a>
|
</a>
|
||||||
|
<a href="{% url "control:event.orders.checkinlists.simulator" organizer=request.event.organizer.slug event=request.event.slug list=cl.id %}"
|
||||||
|
title="{% trans "Check-in simulator" %}" data-toggle="tooltip"
|
||||||
|
class="btn btn-default btn-sm"><i class="fa fa-flask"></i></a>
|
||||||
<a href="{% url "control:event.orders.checkinlists.edit" organizer=request.event.organizer.slug event=request.event.slug list=cl.id %}"
|
<a href="{% url "control:event.orders.checkinlists.edit" organizer=request.event.organizer.slug event=request.event.slug list=cl.id %}"
|
||||||
class="btn btn-default btn-sm"><i class="fa fa-wrench"></i></a>
|
class="btn btn-default btn-sm"><i class="fa fa-wrench"></i></a>
|
||||||
<a href="{% url "control:event.orders.checkinlists.delete" organizer=request.event.organizer.slug event=request.event.slug list=cl.id %}"
|
<a href="{% url "control:event.orders.checkinlists.delete" organizer=request.event.organizer.slug event=request.event.slug list=cl.id %}"
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
{% block inside %}
|
{% block inside %}
|
||||||
<h1>
|
<h1>
|
||||||
{% blocktrans with name=checkinlist.name %}Check-in list: {{ name }}{% endblocktrans %}
|
{% blocktrans with name=checkinlist.name %}Check-in list: {{ name }}{% endblocktrans %}
|
||||||
{% if 'event.settings.general:write' in request.eventpermset %}
|
{% if 'can_change_event_settings' in request.eventpermset %}
|
||||||
<a href="{% url "control:event.orders.checkinlists.edit" event=request.event.slug organizer=request.event.organizer.slug list=checkinlist.pk %}"
|
<a href="{% url "control:event.orders.checkinlists.edit" event=request.event.slug organizer=request.event.organizer.slug list=checkinlist.pk %}"
|
||||||
class="btn btn-default">
|
class="btn btn-default">
|
||||||
<span class="fa fa-wrench"></span>
|
<span class="fa fa-wrench"></span>
|
||||||
|
|||||||
@@ -11,20 +11,18 @@
|
|||||||
<ul class="list-group">
|
<ul class="list-group">
|
||||||
{% for identifier, display_name, pending, objects in providers %}
|
{% for identifier, display_name, pending, objects in providers %}
|
||||||
<li class="list-group-item">
|
<li class="list-group-item">
|
||||||
{% if "event.orders:write" in request.eventpermset %}
|
<form action="{% url "control:event.order.sync_job" organizer=event.organizer.slug event=event.slug code=order.code provider=identifier %}" method="post" class="form-inline pull-right">
|
||||||
<form action="{% url "control:event.order.sync_job" organizer=event.organizer.slug event=event.slug code=order.code provider=identifier %}" method="post" class="form-inline pull-right">
|
{% csrf_token %}
|
||||||
{% csrf_token %}
|
{% if pending %}
|
||||||
{% if pending %}
|
{% if pending.not_before > now or pending.need_manual_retry %}
|
||||||
{% if pending.not_before > now or pending.need_manual_retry %}
|
<button type="submit" name="run_job_now" value="{{ pending.pk }}" class="btn btn-default"><i class="fa fa-refresh"></i> {% trans "Retry now" %}</button>
|
||||||
<button type="submit" name="run_job_now" value="{{ pending.pk }}" class="btn btn-default"><i class="fa fa-refresh"></i> {% trans "Retry now" %}</button>
|
|
||||||
{% endif %}
|
|
||||||
<button type="submit" name="cancel_job" value="{{ pending.pk }}" class="btn btn-danger"><i class="fa fa-times"></i> {% trans "Cancel" %}</button>
|
|
||||||
{% else %}
|
|
||||||
<button type="submit" class="btn btn-default"><i class="fa fa-refresh"></i> {% trans "Sync now" %}</button>
|
|
||||||
<input type="hidden" name="queue_sync" value="true">
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</form>
|
<button type="submit" name="cancel_job" value="{{ pending.pk }}" class="btn btn-danger"><i class="fa fa-times"></i> {% trans "Cancel" %}</button>
|
||||||
{% endif %}
|
{% else %}
|
||||||
|
<button type="submit" class="btn btn-default"><i class="fa fa-refresh"></i> {% trans "Sync now" %}</button>
|
||||||
|
<input type="hidden" name="queue_sync" value="true">
|
||||||
|
{% endif %}
|
||||||
|
</form>
|
||||||
<p><b>{{ display_name }}</b></p>
|
<p><b>{{ display_name }}</b></p>
|
||||||
{% if pending %}
|
{% if pending %}
|
||||||
<p>
|
<p>
|
||||||
|
|||||||
@@ -40,16 +40,12 @@
|
|||||||
this option.
|
this option.
|
||||||
{% endblocktrans %}
|
{% endblocktrans %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-12 col-md-3 text-center">
|
<div class="col-sm-12 col-md-3">
|
||||||
{% if "event:cancel" in request.eventpermset %}
|
<a href="{% url "control:event.cancel" organizer=request.organizer.slug event=request.event.slug %}"
|
||||||
<a href="{% url "control:event.cancel" organizer=request.organizer.slug event=request.event.slug %}"
|
class="btn btn-danger btn-block btn-lg">
|
||||||
class="btn btn-danger btn-block btn-lg">
|
<span class="fa fa-ban"></span>
|
||||||
<span class="fa fa-ban"></span>
|
{% trans "Cancel event" %}
|
||||||
{% trans "Cancel event" %}
|
</a>
|
||||||
</a>
|
|
||||||
{% else %}
|
|
||||||
{% trans "No permission" %}
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
<span class="{% if e.time < nearly_now %}text-muted{% endif %}">
|
<span class="{% if e.time < nearly_now %}text-muted{% endif %}">
|
||||||
{{ e.entry.description }}
|
{{ e.entry.description }}
|
||||||
</span>
|
</span>
|
||||||
{% if e.entry.edit_url and e.entry.edit_permission in request.eventpermset %}
|
{% if e.entry.edit_url %}
|
||||||
|
|
||||||
<a href="{{ e.entry.edit_url }}" class="text-muted">
|
<a href="{{ e.entry.edit_url }}" class="text-muted">
|
||||||
<span class="fa fa-edit"></span>
|
<span class="fa fa-edit"></span>
|
||||||
|
|||||||
@@ -155,24 +155,22 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% if "event.orders:read" in request.eventpermset or "event.orders:write" in request.eventpermset or "event.settings.general:write" in request.eventpermset or "event.items:write" in request.eventpermset %}
|
<div class="panel panel-default">
|
||||||
<div class="panel panel-default">
|
<div class="panel-heading">
|
||||||
<div class="panel-heading">
|
<h3 class="panel-title">
|
||||||
<h3 class="panel-title">
|
{% trans "Event logs" %}
|
||||||
{% trans "Event logs" %}
|
</h3>
|
||||||
</h3>
|
|
||||||
</div>
|
|
||||||
<ul class="list-group" id="logs_target">
|
|
||||||
<div class="logs-lazy-loading">
|
|
||||||
<span class="fa fa-cog fa-4x"></span>
|
|
||||||
</div>
|
|
||||||
</ul>
|
|
||||||
<div class="panel-footer">
|
|
||||||
<a href="{% url "control:event.log" event=request.event.slug organizer=request.event.organizer.slug %}">
|
|
||||||
{% trans "Show more logs" %}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
<ul class="list-group" id="logs_target">
|
||||||
|
<div class="logs-lazy-loading">
|
||||||
|
<span class="fa fa-cog fa-4x"></span>
|
||||||
|
</div>
|
||||||
|
</ul>
|
||||||
|
<div class="panel-footer">
|
||||||
|
<a href="{% url "control:event.log" event=request.event.slug organizer=request.event.organizer.slug %}">
|
||||||
|
{% trans "Show more logs" %}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -12,7 +12,6 @@
|
|||||||
<legend>{% trans "Invoice generation" %}</legend>
|
<legend>{% trans "Invoice generation" %}</legend>
|
||||||
{% bootstrap_field form.invoice_generate layout="control" %}
|
{% bootstrap_field form.invoice_generate layout="control" %}
|
||||||
{% bootstrap_field form.invoice_generate_sales_channels layout="control" %}
|
{% bootstrap_field form.invoice_generate_sales_channels layout="control" %}
|
||||||
{% bootstrap_field form.invoice_generate_only_business layout="control" %}
|
|
||||||
{% bootstrap_field form.invoice_email_attachment layout="control" %}
|
{% bootstrap_field form.invoice_email_attachment layout="control" %}
|
||||||
{% bootstrap_field form.invoice_email_organizer layout="control" %}
|
{% bootstrap_field form.invoice_email_organizer layout="control" %}
|
||||||
{% bootstrap_field form.invoice_language layout="control" %}
|
{% bootstrap_field form.invoice_language layout="control" %}
|
||||||
@@ -112,6 +111,11 @@
|
|||||||
<span class="text-success">
|
<span class="text-success">
|
||||||
<span class="fa fa-check fa-fw"></span>
|
<span class="fa fa-check fa-fw"></span>
|
||||||
{% trans "Available" %}
|
{% trans "Available" %}
|
||||||
|
{% if t.exclusive %}
|
||||||
|
<span data-toggle="tooltip" title="{% trans "When this type is available for an invoice address, no other type can be selected." %}">
|
||||||
|
{% trans "(exclusive)" %}
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
</span>
|
</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="text-muted">
|
<span class="text-muted">
|
||||||
@@ -165,15 +169,13 @@
|
|||||||
</p>
|
</p>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</div>
|
</div>
|
||||||
{% if "event.settings.invoicing:write" in request.eventpermset %}
|
<div class="form-group submit-group">
|
||||||
<div class="form-group submit-group">
|
<button type="submit" class="btn btn-default btn-lg" name="preview" value="preview" formtarget="_blank">
|
||||||
<button type="submit" class="btn btn-default btn-lg" name="preview" value="preview" formtarget="_blank">
|
{% trans "Save and show preview" %}
|
||||||
{% trans "Save and show preview" %}
|
</button>
|
||||||
</button>
|
<button type="submit" class="btn btn-primary btn-save">
|
||||||
<button type="submit" class="btn btn-primary btn-save">
|
{% trans "Save" %}
|
||||||
{% trans "Save" %}
|
</button>
|
||||||
</button>
|
</div>
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</form>
|
</form>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -41,17 +41,14 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</td>
|
</td>
|
||||||
<td class="text-right flip">
|
<td class="text-right flip">
|
||||||
{% if "event.settings.payment:write" in request.eventpermset %}
|
<a href="{% url 'control:event.settings.payment.provider' event=request.event.slug organizer=request.organizer.slug provider=provider.identifier %}"
|
||||||
<a href="{% url 'control:event.settings.payment.provider' event=request.event.slug organizer=request.organizer.slug provider=provider.identifier %}"
|
class="btn btn-default">
|
||||||
class="btn btn-default">
|
<span class="fa fa-cog"></span>
|
||||||
<span class="fa fa-cog"></span>
|
{% trans "Settings" %}
|
||||||
{% trans "Settings" %}
|
</a>
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% if "event.settings.general:write" in request.eventpermset %}
|
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="4">
|
<td colspan="4">
|
||||||
<br>
|
<br>
|
||||||
@@ -61,7 +58,6 @@
|
|||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
@@ -87,12 +83,10 @@
|
|||||||
{% bootstrap_field form.payment_explanation layout="control" %}
|
{% bootstrap_field form.payment_explanation layout="control" %}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</div>
|
</div>
|
||||||
{% if "event.settings.payment:write" in request.eventpermset %}
|
<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">
|
{% trans "Save" %}
|
||||||
{% trans "Save" %}
|
</button>
|
||||||
</button>
|
</div>
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</form>
|
</form>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -23,10 +23,8 @@
|
|||||||
{% endblocktrans %}
|
{% endblocktrans %}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{% if "event.settings.tax:write" in request.eventpermset %}
|
<a href="{% url "control:event.settings.tax.add" organizer=request.event.organizer.slug event=request.event.slug %}"
|
||||||
<a href="{% url "control:event.settings.tax.add" organizer=request.event.organizer.slug event=request.event.slug %}"
|
class="btn btn-primary btn-lg"><i class="fa fa-plus"></i> {% trans "Create a new tax rule" %}</a>
|
||||||
class="btn btn-primary btn-lg"><i class="fa fa-plus"></i> {% trans "Create a new tax rule" %}</a>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
@@ -44,14 +42,10 @@
|
|||||||
{% for tr in taxrules %}
|
{% for tr in taxrules %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
{% if "event.settings.tax:write" in request.eventpermset %}
|
<strong><a
|
||||||
<strong><a
|
href="{% url "control:event.settings.tax.edit" organizer=request.event.organizer.slug event=request.event.slug rule=tr.id %}">
|
||||||
href="{% url "control:event.settings.tax.edit" organizer=request.event.organizer.slug event=request.event.slug rule=tr.id %}">
|
{{ tr.internal_name|default:tr.name }}
|
||||||
{{ tr.internal_name|default:tr.name }}
|
</a></strong>
|
||||||
</a></strong>
|
|
||||||
{% else %}
|
|
||||||
<strong>{{ tr.internal_name|default:tr.name }}</strong>
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{% if tr.default %}
|
{% if tr.default %}
|
||||||
@@ -59,7 +53,7 @@
|
|||||||
<span class="fa fa-check"></span>
|
<span class="fa fa-check"></span>
|
||||||
{% trans "Default" %}
|
{% trans "Default" %}
|
||||||
</span>
|
</span>
|
||||||
{% elif "event.settings.tax:write" in request.eventpermset %}
|
{% else %}
|
||||||
<form class="form-inline" method="post"
|
<form class="form-inline" method="post"
|
||||||
action="{% url "control:event.settings.tax.default" organizer=request.event.organizer.slug event=request.event.slug rule=tr.id %}">
|
action="{% url "control:event.settings.tax.default" organizer=request.event.organizer.slug event=request.event.slug rule=tr.id %}">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
@@ -89,12 +83,10 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td class="text-right flip">
|
<td class="text-right flip">
|
||||||
{% if "event.settings.tax:write" in request.eventpermset %}
|
<a href="{% url "control:event.settings.tax.edit" organizer=request.event.organizer.slug event=request.event.slug rule=tr.id %}"
|
||||||
<a href="{% url "control:event.settings.tax.edit" organizer=request.event.organizer.slug event=request.event.slug rule=tr.id %}"
|
class="btn btn-default btn-sm"><i class="fa fa-edit"></i></a>
|
||||||
class="btn btn-default btn-sm"><i class="fa fa-edit"></i></a>
|
<a href="{% url "control:event.settings.tax.delete" organizer=request.event.organizer.slug event=request.event.slug rule=tr.id %}"
|
||||||
<a href="{% url "control:event.settings.tax.delete" organizer=request.event.organizer.slug event=request.event.slug rule=tr.id %}"
|
class="btn btn-danger btn-sm"><i class="fa fa-trash"></i></a>
|
||||||
class="btn btn-danger btn-sm"><i class="fa fa-trash"></i></a>
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
@@ -102,11 +94,9 @@
|
|||||||
<tfoot>
|
<tfoot>
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="5">
|
<td colspan="5">
|
||||||
{% if "event.settings.tax:write" in request.eventpermset %}
|
<a href="{% url "control:event.settings.tax.add" organizer=request.event.organizer.slug event=request.event.slug %}"
|
||||||
<a href="{% url "control:event.settings.tax.add" organizer=request.event.organizer.slug event=request.event.slug %}"
|
class="btn btn-default"><i class="fa fa-plus"></i> {% trans "Create a new tax rule" %}
|
||||||
class="btn btn-default"><i class="fa fa-plus"></i> {% trans "Create a new tax rule" %}
|
</a>
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tfoot>
|
</tfoot>
|
||||||
@@ -121,12 +111,10 @@
|
|||||||
{% bootstrap_field form.tax_rounding layout="control" %}
|
{% bootstrap_field form.tax_rounding layout="control" %}
|
||||||
{% bootstrap_field form.display_net_prices layout="control" %}
|
{% bootstrap_field form.display_net_prices layout="control" %}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
{% if "event.settings.tax:write" in request.eventpermset %}
|
<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">
|
{% trans "Save" %}
|
||||||
{% trans "Save" %}
|
</button>
|
||||||
</button>
|
</div>
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</form>
|
</form>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -16,18 +16,14 @@
|
|||||||
{% endblocktrans %}
|
{% endblocktrans %}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{% if 'event.items:write' in request.eventpermset %}
|
<a href="{% url "control:event.items.categories.add" organizer=request.event.organizer.slug event=request.event.slug %}"
|
||||||
<a href="{% url "control:event.items.categories.add" organizer=request.event.organizer.slug event=request.event.slug %}"
|
class="btn btn-primary btn-lg"><i class="fa fa-plus"></i> {% trans "Create a new category" %}</a>
|
||||||
class="btn btn-primary btn-lg"><i class="fa fa-plus"></i> {% trans "Create a new category" %}</a>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
{% if 'event.items:write' in request.eventpermset %}
|
<p>
|
||||||
<p>
|
<a href="{% url "control:event.items.categories.add" organizer=request.event.organizer.slug event=request.event.slug %}" class="btn btn-default"><i class="fa fa-plus"></i> {% trans "Create a new category" %}
|
||||||
<a href="{% url "control:event.items.categories.add" organizer=request.event.organizer.slug event=request.event.slug %}" class="btn btn-default"><i class="fa fa-plus"></i> {% trans "Create a new category" %}
|
</a>
|
||||||
</a>
|
</p>
|
||||||
</p>
|
|
||||||
{% endif %}
|
|
||||||
<form method="post">
|
<form method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
@@ -43,11 +39,7 @@
|
|||||||
{% for c in categories %}
|
{% for c in categories %}
|
||||||
<tr data-dnd-id="{{ c.id }}">
|
<tr data-dnd-id="{{ c.id }}">
|
||||||
<td>
|
<td>
|
||||||
{% if 'event.items:write' in request.eventpermset %}
|
<strong><a href="{% url "control:event.items.categories.edit" organizer=request.event.organizer.slug event=request.event.slug category=c.id %}">{{ c.internal_name|default:c.name }}</a></strong>
|
||||||
<strong><a href="{% url "control:event.items.categories.edit" organizer=request.event.organizer.slug event=request.event.slug category=c.id %}">{{ c.internal_name|default:c.name }}</a></strong>
|
|
||||||
{% else %}
|
|
||||||
<strong>{{ c.internal_name|default:c.name }}</strong>
|
|
||||||
{% endif %}
|
|
||||||
<br>
|
<br>
|
||||||
<small class="text-muted">
|
<small class="text-muted">
|
||||||
#{{ c.pk }}
|
#{{ c.pk }}
|
||||||
@@ -57,17 +49,15 @@
|
|||||||
{{ c.get_category_type_display }}
|
{{ c.get_category_type_display }}
|
||||||
</td>
|
</td>
|
||||||
<td class="text-right flip">
|
<td class="text-right flip">
|
||||||
{% if 'event.items:write' in request.eventpermset %}
|
<button title="{% trans "Move up" %}" formaction="{% url "control:event.items.categories.up" organizer=request.event.organizer.slug event=request.event.slug category=c.id %}" class="btn btn-default btn-sm sortable-up"{% if forloop.counter0 == 0 and not page_obj.has_previous %} disabled{% endif %}><i class="fa fa-arrow-up"></i></button>
|
||||||
<button title="{% trans "Move up" %}" formaction="{% url "control:event.items.categories.up" organizer=request.event.organizer.slug event=request.event.slug category=c.id %}" class="btn btn-default btn-sm sortable-up"{% if forloop.counter0 == 0 and not page_obj.has_previous %} disabled{% endif %}><i class="fa fa-arrow-up"></i></button>
|
<button title="{% trans "Move down" %}" formaction="{% url "control:event.items.categories.down" organizer=request.event.organizer.slug event=request.event.slug category=c.id %}" class="btn btn-default btn-sm sortable-down"{% if forloop.revcounter0 == 0 and not page_obj.has_next %} disabled{% endif %}><i class="fa fa-arrow-down"></i></button>
|
||||||
<button title="{% trans "Move down" %}" formaction="{% url "control:event.items.categories.down" organizer=request.event.organizer.slug event=request.event.slug category=c.id %}" class="btn btn-default btn-sm sortable-down"{% if forloop.revcounter0 == 0 and not page_obj.has_next %} disabled{% endif %}><i class="fa fa-arrow-down"></i></button>
|
<span class="dnd-container" title="{% trans "Click and drag this button to reorder. Double click to show buttons for reordering." %}"></span>
|
||||||
<span class="dnd-container" title="{% trans "Click and drag this button to reorder. Double click to show buttons for reordering." %}"></span>
|
<a title="{% trans "Edit" %}" href="{% url "control:event.items.categories.edit" organizer=request.event.organizer.slug event=request.event.slug category=c.id %}" class="btn btn-default btn-sm"><i class="fa fa-edit"></i></a>
|
||||||
<a title="{% trans "Edit" %}" href="{% url "control:event.items.categories.edit" organizer=request.event.organizer.slug event=request.event.slug category=c.id %}" class="btn btn-default btn-sm"><i class="fa fa-edit"></i></a>
|
<a href="{% url "control:event.items.categories.add" organizer=request.event.organizer.slug event=request.event.slug %}?copy_from={{ c.id }}"
|
||||||
<a href="{% url "control:event.items.categories.add" organizer=request.event.organizer.slug event=request.event.slug %}?copy_from={{ c.id }}"
|
class="btn btn-sm btn-default" title="{% trans "Clone" %}" data-toggle="tooltip">
|
||||||
class="btn btn-sm btn-default" title="{% trans "Clone" %}" data-toggle="tooltip">
|
<span class="fa fa-copy"></span>
|
||||||
<span class="fa fa-copy"></span>
|
</a>
|
||||||
</a>
|
<a title="{% trans "Delete" %}" href="{% url "control:event.items.categories.delete" organizer=request.event.organizer.slug event=request.event.slug category=c.id %}" class="btn btn-danger btn-sm"><i class="fa fa-trash"></i></a>
|
||||||
<a title="{% trans "Delete" %}" href="{% url "control:event.items.categories.delete" organizer=request.event.organizer.slug event=request.event.slug category=c.id %}" class="btn btn-danger btn-sm"><i class="fa fa-trash"></i></a>
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
@@ -39,19 +39,15 @@
|
|||||||
{% endblocktrans %}
|
{% endblocktrans %}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{% if 'event.items:write' in request.eventpermset %}
|
<a href="{% url "control:event.items.discounts.add" organizer=request.event.organizer.slug event=request.event.slug %}"
|
||||||
<a href="{% url "control:event.items.discounts.add" organizer=request.event.organizer.slug event=request.event.slug %}"
|
class="btn btn-primary btn-lg"><i class="fa fa-plus"></i> {% trans "Create a new discount" %}</a>
|
||||||
class="btn btn-primary btn-lg"><i class="fa fa-plus"></i> {% trans "Create a new discount" %}</a>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
{% if 'event.items:write' in request.eventpermset %}
|
<p>
|
||||||
<p>
|
<a href="{% url "control:event.items.discounts.add" organizer=request.event.organizer.slug event=request.event.slug %}"
|
||||||
<a href="{% url "control:event.items.discounts.add" organizer=request.event.organizer.slug event=request.event.slug %}"
|
class="btn btn-default"><i class="fa fa-plus"></i> {% trans "Create a new discount" %}
|
||||||
class="btn btn-default"><i class="fa fa-plus"></i> {% trans "Create a new discount" %}
|
</a>
|
||||||
</a>
|
</p>
|
||||||
</p>
|
|
||||||
{% endif %}
|
|
||||||
<form method="post">
|
<form method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
@@ -74,12 +70,8 @@
|
|||||||
{% else %}
|
{% else %}
|
||||||
<del>
|
<del>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if 'event.items:write' in request.eventpermset %}
|
<a href="{% url "control:event.items.discounts.edit" organizer=request.event.organizer.slug event=request.event.slug discount=d.id %}">
|
||||||
<a href="{% url "control:event.items.discounts.edit" organizer=request.event.organizer.slug event=request.event.slug discount=d.id %}">
|
|
||||||
{{ d.internal_name }}</a>
|
{{ d.internal_name }}</a>
|
||||||
{% else %}
|
|
||||||
{{ d.internal_name }}
|
|
||||||
{% endif %}
|
|
||||||
{% if d.active %}
|
{% if d.active %}
|
||||||
</strong>
|
</strong>
|
||||||
{% else %}
|
{% else %}
|
||||||
@@ -142,25 +134,23 @@
|
|||||||
</td>
|
</td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<td class="text-right flip">
|
<td class="text-right flip">
|
||||||
{% if 'event.items:write' in request.eventpermset %}
|
<button formaction="{% url "control:event.items.discounts.up" organizer=request.event.organizer.slug event=request.event.slug discount=d.id %}"
|
||||||
<button formaction="{% url "control:event.items.discounts.up" organizer=request.event.organizer.slug event=request.event.slug discount=d.id %}"
|
class="btn btn-default btn-sm sortable-up" title="{% trans "Move up" %}"
|
||||||
class="btn btn-default btn-sm sortable-up" title="{% trans "Move up" %}"
|
{% if forloop.counter0 == 0 and not page_obj.has_previous %}
|
||||||
{% if forloop.counter0 == 0 and not page_obj.has_previous %}
|
disabled{% endif %}><i class="fa fa-arrow-up"></i></button>
|
||||||
disabled{% endif %}><i class="fa fa-arrow-up"></i></button>
|
<button formaction="{% url "control:event.items.discounts.down" organizer=request.event.organizer.slug event=request.event.slug discount=d.id %}"
|
||||||
<button formaction="{% url "control:event.items.discounts.down" organizer=request.event.organizer.slug event=request.event.slug discount=d.id %}"
|
class="btn btn-default btn-sm sortable-down" title="{% trans "Move down" %}"
|
||||||
class="btn btn-default btn-sm sortable-down" title="{% trans "Move down" %}"
|
{% if forloop.revcounter0 == 0 and not page_obj.has_next %} disabled{% endif %}>
|
||||||
{% if forloop.revcounter0 == 0 and not page_obj.has_next %} disabled{% endif %}>
|
<i class="fa fa-arrow-down"></i></button>
|
||||||
<i class="fa fa-arrow-down"></i></button>
|
<span class="dnd-container" title="{% trans "Click and drag this button to reorder. Double click to show buttons for reordering." %}"></span>
|
||||||
<span class="dnd-container" title="{% trans "Click and drag this button to reorder. Double click to show buttons for reordering." %}"></span>
|
<a href="{% url "control:event.items.discounts.edit" organizer=request.event.organizer.slug event=request.event.slug discount=d.id %}"
|
||||||
<a href="{% url "control:event.items.discounts.edit" organizer=request.event.organizer.slug event=request.event.slug discount=d.id %}"
|
class="btn btn-default btn-sm"><i class="fa fa-edit"></i></a>
|
||||||
class="btn btn-default btn-sm"><i class="fa fa-edit"></i></a>
|
<a href="{% url "control:event.items.discounts.add" organizer=request.event.organizer.slug event=request.event.slug %}?copy_from={{ d.id }}"
|
||||||
<a href="{% url "control:event.items.discounts.add" organizer=request.event.organizer.slug event=request.event.slug %}?copy_from={{ d.id }}"
|
class="btn btn-sm btn-default" title="{% trans "Clone" %}" data-toggle="tooltip">
|
||||||
class="btn btn-sm btn-default" title="{% trans "Clone" %}" data-toggle="tooltip">
|
<span class="fa fa-copy"></span>
|
||||||
<span class="fa fa-copy"></span>
|
</a>
|
||||||
</a>
|
<a href="{% url "control:event.items.discounts.delete" organizer=request.event.organizer.slug event=request.event.slug discount=d.id %}"
|
||||||
<a href="{% url "control:event.items.discounts.delete" organizer=request.event.organizer.slug event=request.event.slug discount=d.id %}"
|
class="btn btn-danger btn-sm"><i class="fa fa-trash"></i></a>
|
||||||
class="btn btn-danger btn-sm"><i class="fa fa-trash"></i></a>
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
@@ -21,18 +21,14 @@
|
|||||||
{% endblocktrans %}
|
{% endblocktrans %}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{% if 'event.items:write' in request.eventpermset %}
|
<a href="{% url "control:event.items.add" organizer=request.event.organizer.slug event=request.event.slug %}"
|
||||||
<a href="{% url "control:event.items.add" organizer=request.event.organizer.slug event=request.event.slug %}"
|
class="btn btn-primary btn-lg"><i class="fa fa-plus"></i> {% trans "Create a new product" %}</a>
|
||||||
class="btn btn-primary btn-lg"><i class="fa fa-plus"></i> {% trans "Create a new product" %}</a>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
{% if 'event.items:write' in request.eventpermset %}
|
<p>
|
||||||
<p>
|
<a href="{% url "control:event.items.add" organizer=request.event.organizer.slug event=request.event.slug %}"
|
||||||
<a href="{% url "control:event.items.add" organizer=request.event.organizer.slug event=request.event.slug %}"
|
class="btn btn-default"><i class="fa fa-plus"></i> {% trans "Create a new product" %}</a>
|
||||||
class="btn btn-default"><i class="fa fa-plus"></i> {% trans "Create a new product" %}</a>
|
</p>
|
||||||
</p>
|
|
||||||
{% endif %}
|
|
||||||
<form method="post">
|
<form method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
@@ -55,9 +51,7 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
<tr class="sortable-disabled"><th colspan="9" scope="colgroup" class="text-muted">
|
<tr class="sortable-disabled"><th colspan="9" scope="colgroup" class="text-muted">
|
||||||
{{ c.internal_name|default:c.name }}{% if c.category_type != "normal" %} <span class="font-normal">({{ c.get_category_type_display }})</span>{% endif %}
|
{{ c.internal_name|default:c.name }}{% if c.category_type != "normal" %} <span class="font-normal">({{ c.get_category_type_display }})</span>{% endif %}
|
||||||
{% if 'event.items:write' in request.eventpermset %}
|
<a href="{% url "control:event.items.categories.edit" organizer=request.event.organizer.slug event=request.event.slug category=c.id %}" title="{% trans "Edit" %}"><span class="fa fa-edit fa-fw"></span></a>
|
||||||
<a href="{% url "control:event.items.categories.edit" organizer=request.event.organizer.slug event=request.event.slug category=c.id %}" title="{% trans "Edit" %}"><span class="fa fa-edit fa-fw"></span></a>
|
|
||||||
{% endif %}
|
|
||||||
</th></tr>
|
</th></tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -68,11 +62,7 @@
|
|||||||
<tr data-dnd-id="{{ i.id }}" {% if not i.active %}class="row-muted"{% endif %}>
|
<tr data-dnd-id="{{ i.id }}" {% if not i.active %}class="row-muted"{% endif %}>
|
||||||
<td><strong>
|
<td><strong>
|
||||||
{% if not i.active %}<strike>{% endif %}
|
{% if not i.active %}<strike>{% endif %}
|
||||||
{% if 'event.items:write' in request.eventpermset %}
|
<a href="{% url "control:event.item" organizer=request.event.organizer.slug event=request.event.slug item=i.id %}">{{ i }}</a>
|
||||||
<a href="{% url "control:event.item" organizer=request.event.organizer.slug event=request.event.slug item=i.id %}">{{ i }}</a>
|
|
||||||
{% else %}
|
|
||||||
{{ i }}
|
|
||||||
{% endif %}
|
|
||||||
{% if not i.active %}</strike>{% endif %}
|
{% if not i.active %}</strike>{% endif %}
|
||||||
</strong>
|
</strong>
|
||||||
<br>
|
<br>
|
||||||
@@ -168,14 +158,12 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td class="text-right flip col-actions">
|
<td class="text-right flip col-actions">
|
||||||
{% if 'event.items:write' in request.eventpermset %}
|
<button title="{% trans "Move up" %}" formaction="{% url "control:event.items.up" organizer=request.event.organizer.slug event=request.event.slug item=i.id %}" class="btn btn-default btn-sm sortable-up"{% if forloop.counter0 == 0 %} disabled{% endif %}><i class="fa fa-arrow-up"></i></button>
|
||||||
<button title="{% trans "Move up" %}" formaction="{% url "control:event.items.up" organizer=request.event.organizer.slug event=request.event.slug item=i.id %}" class="btn btn-default btn-sm sortable-up"{% if forloop.counter0 == 0 %} disabled{% endif %}><i class="fa fa-arrow-up"></i></button>
|
<button title="{% trans "Move down" %}" formaction="{% url "control:event.items.down" organizer=request.event.organizer.slug event=request.event.slug item=i.id %}" class="btn btn-default btn-sm sortable-down"{% if forloop.revcounter0 == 0 %} disabled{% endif %}><i class="fa fa-arrow-down"></i></button>
|
||||||
<button title="{% trans "Move down" %}" formaction="{% url "control:event.items.down" organizer=request.event.organizer.slug event=request.event.slug item=i.id %}" class="btn btn-default btn-sm sortable-down"{% if forloop.revcounter0 == 0 %} disabled{% endif %}><i class="fa fa-arrow-down"></i></button>
|
<span class="dnd-container" title="{% trans "Click and drag this button to reorder. Double click to show buttons for reordering." %}"></span>
|
||||||
<span class="dnd-container" title="{% trans "Click and drag this button to reorder. Double click to show buttons for reordering." %}"></span>
|
<a href="{% url "control:event.item" organizer=request.event.organizer.slug event=request.event.slug item=i.id %}" class="btn btn-default btn-sm" title="{% trans "Edit" %}"><i class="fa fa-edit"></i></a>
|
||||||
<a href="{% url "control:event.item" organizer=request.event.organizer.slug event=request.event.slug item=i.id %}" class="btn btn-default btn-sm" title="{% trans "Edit" %}"><i class="fa fa-edit"></i></a>
|
<a href="{% url "control:event.items.add" organizer=request.event.organizer.slug event=request.event.slug %}?copy_from={{ i.id }}" class="btn btn-default btn-sm" title="{% trans "Clone" %}" data-toggle="tooltip"><i class="fa fa-copy"></i></a>
|
||||||
<a href="{% url "control:event.items.add" organizer=request.event.organizer.slug event=request.event.slug %}?copy_from={{ i.id }}" class="btn btn-default btn-sm" title="{% trans "Clone" %}" data-toggle="tooltip"><i class="fa fa-copy"></i></a>
|
<a href="{% url "control:event.items.delete" organizer=request.event.organizer.slug event=request.event.slug item=i.id %}" class="btn btn-danger btn-sm" title="{% trans "Delete" %}"><i class="fa fa-trash"></i></a>
|
||||||
<a href="{% url "control:event.items.delete" organizer=request.event.organizer.slug event=request.event.slug item=i.id %}" class="btn btn-danger btn-sm" title="{% trans "Delete" %}"><i class="fa fa-trash"></i></a>
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
@@ -7,57 +7,45 @@
|
|||||||
{% block inside %}
|
{% block inside %}
|
||||||
<h1>
|
<h1>
|
||||||
{% blocktrans with name=question.question %}Question: {{ name }}{% endblocktrans %}
|
{% blocktrans with name=question.question %}Question: {{ name }}{% endblocktrans %}
|
||||||
{% if 'event.items:write' in request.eventpermset %}
|
<a href="{% url "control:event.items.questions.edit" event=request.event.slug organizer=request.event.organizer.slug question=question.pk %}"
|
||||||
<a href="{% url "control:event.items.questions.edit" event=request.event.slug organizer=request.event.organizer.slug question=question.pk %}"
|
class="btn btn-default">
|
||||||
class="btn btn-default">
|
<span class="fa fa-edit"></span>
|
||||||
<span class="fa fa-edit"></span>
|
{% trans "Edit question" %}
|
||||||
{% trans "Edit question" %}
|
</a>
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
{% if 'event.orders:read' in request.eventpermset %}
|
<div class="panel panel-default">
|
||||||
<div class="panel panel-default">
|
<div class="panel-heading">
|
||||||
<div class="panel-heading">
|
<h3 class="panel-title">{% trans "Filter" %}</h3>
|
||||||
<h3 class="panel-title">{% trans "Filter" %}</h3>
|
|
||||||
</div>
|
|
||||||
<form class="panel-body filter-form" action="" method="get">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-2 col-xs-6">
|
|
||||||
{% bootstrap_field form.status %}
|
|
||||||
</div>
|
|
||||||
<div class="col-md-3 col-xs-6">
|
|
||||||
{% bootstrap_field form.item %}
|
|
||||||
</div>
|
|
||||||
{% if has_subevents %}
|
|
||||||
<div class="col-md-3 col-xs-6">
|
|
||||||
{% bootstrap_field form.subevent %}
|
|
||||||
</div>
|
|
||||||
<div class="col-md-4 col-xs-6">
|
|
||||||
{% bootstrap_field form.date_range %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
<div class="text-right">
|
|
||||||
<button class="btn btn-primary btn-lg" type="submit">
|
|
||||||
<span class="fa fa-filter"></span>
|
|
||||||
{% trans "Filter" %}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
<form class="panel-body filter-form" action="" method="get">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-2 col-xs-6">
|
||||||
|
{% bootstrap_field form.status %}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3 col-xs-6">
|
||||||
|
{% bootstrap_field form.item %}
|
||||||
|
</div>
|
||||||
|
{% if has_subevents %}
|
||||||
|
<div class="col-md-3 col-xs-6">
|
||||||
|
{% bootstrap_field form.subevent %}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 col-xs-6">
|
||||||
|
{% bootstrap_field form.date_range %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="text-right">
|
||||||
|
<button class="btn btn-primary btn-lg" type="submit">
|
||||||
|
<span class="fa fa-filter"></span>
|
||||||
|
{% trans "Filter" %}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
{% if 'event.orders:read' not in request.eventpermset %}
|
{% if not stats %}
|
||||||
<div class="empty-collection col-md-10 col-xs-12">
|
|
||||||
<p>
|
|
||||||
{% blocktrans trimmed %}
|
|
||||||
No permission to view answers.
|
|
||||||
{% endblocktrans %}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
{% elif not stats %}
|
|
||||||
<div class="empty-collection col-md-10 col-xs-12">
|
<div class="empty-collection col-md-10 col-xs-12">
|
||||||
<p>
|
<p>
|
||||||
{% blocktrans trimmed %}
|
{% blocktrans trimmed %}
|
||||||
|
|||||||
@@ -10,12 +10,10 @@
|
|||||||
{% endblocktrans %}
|
{% endblocktrans %}
|
||||||
</p>
|
</p>
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{% if 'event.items:write' in request.eventpermset %}
|
<p>
|
||||||
<p>
|
<a href="{% url "control:event.items.questions.add" organizer=request.event.organizer.slug event=request.event.slug %}" class="btn btn-default"><i class="fa fa-plus"></i> {% trans "Create a new question" %}
|
||||||
<a href="{% url "control:event.items.questions.add" organizer=request.event.organizer.slug event=request.event.slug %}" class="btn btn-default"><i class="fa fa-plus"></i> {% trans "Create a new question" %}
|
</a>
|
||||||
</a>
|
</p>
|
||||||
</p>
|
|
||||||
{% endif %}
|
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table table-hover table-quotas">
|
<table class="table table-hover table-quotas">
|
||||||
<thead>
|
<thead>
|
||||||
@@ -26,9 +24,7 @@
|
|||||||
<th class="iconcol"></th>
|
<th class="iconcol"></th>
|
||||||
<th class="iconcol"></th>
|
<th class="iconcol"></th>
|
||||||
<th>{% trans "Products" %}</th>
|
<th>{% trans "Products" %}</th>
|
||||||
{% if 'event.items:write' in request.eventpermset %}
|
<th class="action-col-2"></th>
|
||||||
<th class="action-col-2"></th>
|
|
||||||
{% endif %}
|
|
||||||
<th class="action-col-2"></th>
|
<th class="action-col-2"></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -83,22 +79,16 @@
|
|||||||
<small>{% trans "All personalized products" %}</small>
|
<small>{% trans "All personalized products" %}</small>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
{% if 'event.items:write' in request.eventpermset %}
|
<td class="dnd-container">
|
||||||
<td class="dnd-container">
|
</td>
|
||||||
</td>
|
|
||||||
{% endif %}
|
|
||||||
<td class="text-right flip">
|
<td class="text-right flip">
|
||||||
{% if q.pk %}
|
{% if q.pk %}
|
||||||
<a href="{% url "control:event.items.questions.show" organizer=request.event.organizer.slug event=request.event.slug question=q.id %}" class="btn btn-default btn-sm"><i class="fa fa-bar-chart"></i></a>
|
<a href="{% url "control:event.items.questions.show" organizer=request.event.organizer.slug event=request.event.slug question=q.id %}" class="btn btn-default btn-sm"><i class="fa fa-bar-chart"></i></a>
|
||||||
{% if 'event.items:write' in request.eventpermset %}
|
<a href="{% url "control:event.items.questions.edit" organizer=request.event.organizer.slug event=request.event.slug question=q.id %}" class="btn btn-default btn-sm"><i class="fa fa-edit"></i></a>
|
||||||
<a href="{% url "control:event.items.questions.edit" organizer=request.event.organizer.slug event=request.event.slug question=q.id %}" class="btn btn-default btn-sm"><i class="fa fa-edit"></i></a>
|
<a href="{% url "control:event.items.questions.delete" organizer=request.event.organizer.slug event=request.event.slug question=q.id %}" class="btn btn-danger btn-sm"><i class="fa fa-trash"></i></a>
|
||||||
<a href="{% url "control:event.items.questions.delete" organizer=request.event.organizer.slug event=request.event.slug question=q.id %}" class="btn btn-danger btn-sm"><i class="fa fa-trash"></i></a>
|
|
||||||
{% endif %}
|
|
||||||
{% else %}
|
{% else %}
|
||||||
{% if 'event.settings.general:write' in request.eventpermset %}
|
<a href="{% url "control:event.settings" organizer=request.event.organizer.slug event=request.event.slug %}#tab-0-2-open"
|
||||||
<a href="{% url "control:event.settings" organizer=request.event.organizer.slug event=request.event.slug %}#tab-0-2-open"
|
class="btn btn-default btn-sm"><i class="fa fa-wrench"></i></a>
|
||||||
class="btn btn-default btn-sm"><i class="fa fa-wrench"></i></a>
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
{% block inside %}
|
{% block inside %}
|
||||||
<h1>
|
<h1>
|
||||||
{% blocktrans with name=quota.name %}Quota: {{ name }}{% endblocktrans %}
|
{% blocktrans with name=quota.name %}Quota: {{ name }}{% endblocktrans %}
|
||||||
{% if 'event.items:write' in request.eventpermset %}
|
{% if 'can_change_items' in request.eventpermset %}
|
||||||
<a href="{% url "control:event.items.quotas.edit" event=request.event.slug organizer=request.event.organizer.slug quota=quota.pk %}"
|
<a href="{% url "control:event.items.quotas.edit" event=request.event.slug organizer=request.event.organizer.slug quota=quota.pk %}"
|
||||||
class="btn btn-default">
|
class="btn btn-default">
|
||||||
<span class="fa fa-edit"></span>
|
<span class="fa fa-edit"></span>
|
||||||
|
|||||||
@@ -30,18 +30,14 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{% if 'event.items:write' in request.eventpermset %}
|
<a href="{% url "control:event.items.quotas.add" organizer=request.event.organizer.slug event=request.event.slug %}"
|
||||||
<a href="{% url "control:event.items.quotas.add" organizer=request.event.organizer.slug event=request.event.slug %}"
|
class="btn btn-primary btn-lg"><i class="fa fa-plus"></i> {% trans "Create a new quota" %}</a>
|
||||||
class="btn btn-primary btn-lg"><i class="fa fa-plus"></i> {% trans "Create a new quota" %}</a>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
{% if 'event.items:write' in request.eventpermset %}
|
<p>
|
||||||
<p>
|
<a href="{% url "control:event.items.quotas.add" organizer=request.event.organizer.slug event=request.event.slug %}" class="btn btn-default"><i class="fa fa-plus"></i> {% trans "Create a new quota" %}
|
||||||
<a href="{% url "control:event.items.quotas.add" organizer=request.event.organizer.slug event=request.event.slug %}" class="btn btn-default"><i class="fa fa-plus"></i> {% trans "Create a new quota" %}
|
</a>
|
||||||
</a>
|
</p>
|
||||||
</p>
|
|
||||||
{% endif %}
|
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table table-hover table-quotas">
|
<table class="table table-hover table-quotas">
|
||||||
<thead>
|
<thead>
|
||||||
@@ -95,14 +91,12 @@
|
|||||||
<td>{% if q.size == None %}Unlimited{% else %}{{ q.size }}{% endif %}</td>
|
<td>{% if q.size == None %}Unlimited{% else %}{{ q.size }}{% endif %}</td>
|
||||||
<td>{% include "pretixcontrol/items/fragment_quota_availability.html" with availability=q.cached_avail closed=q.closed %}</td>
|
<td>{% include "pretixcontrol/items/fragment_quota_availability.html" with availability=q.cached_avail closed=q.closed %}</td>
|
||||||
<td class="text-right flip">
|
<td class="text-right flip">
|
||||||
{% if 'event.items:write' in request.eventpermset %}
|
<a href="{% url "control:event.items.quotas.edit" organizer=request.event.organizer.slug event=request.event.slug quota=q.id %}" class="btn btn-default btn-sm"><i class="fa fa-edit"></i></a>
|
||||||
<a href="{% url "control:event.items.quotas.edit" organizer=request.event.organizer.slug event=request.event.slug quota=q.id %}" class="btn btn-default btn-sm"><i class="fa fa-edit"></i></a>
|
<a href="{% url "control:event.items.quotas.add" organizer=request.event.organizer.slug event=request.event.slug %}?copy_from={{ q.id }}"
|
||||||
<a href="{% url "control:event.items.quotas.add" organizer=request.event.organizer.slug event=request.event.slug %}?copy_from={{ q.id }}"
|
class="btn btn-sm btn-default" title="{% trans "Clone" %}" data-toggle="tooltip">
|
||||||
class="btn btn-sm btn-default" title="{% trans "Clone" %}" data-toggle="tooltip">
|
<span class="fa fa-copy"></span>
|
||||||
<span class="fa fa-copy"></span>
|
</a>
|
||||||
</a>
|
<a href="{% url "control:event.items.quotas.delete" organizer=request.event.organizer.slug event=request.event.slug quota=q.id %}" class="btn btn-danger btn-sm"><i class="fa fa-trash"></i></a>
|
||||||
<a href="{% url "control:event.items.quotas.delete" organizer=request.event.organizer.slug event=request.event.slug quota=q.id %}" class="btn btn-danger btn-sm"><i class="fa fa-trash"></i></a>
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% include "pretixcontrol/orders/fragment_order_status.html" with order=order class="pull-right flip" %}
|
{% include "pretixcontrol/orders/fragment_order_status.html" with order=order class="pull-right flip" %}
|
||||||
</h1>
|
</h1>
|
||||||
{% if 'event.orders:write' in request.eventpermset %}
|
{% if 'can_change_orders' in request.eventpermset %}
|
||||||
<form action="{% url "control:event.order.transition" event=request.event.slug organizer=request.event.organizer.slug code=order.code %}"
|
<form action="{% url "control:event.order.transition" event=request.event.slug organizer=request.event.organizer.slug code=order.code %}"
|
||||||
method="post">
|
method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
@@ -193,7 +193,7 @@
|
|||||||
<dt>{% trans "Order locale" %}</dt>
|
<dt>{% trans "Order locale" %}</dt>
|
||||||
<dd>
|
<dd>
|
||||||
{{ display_locale }}
|
{{ display_locale }}
|
||||||
{% if "event.orders:write" in request.eventpermset %}
|
{% if "can_change_orders" in request.eventpermset %}
|
||||||
<a href="{% url "control:event.order.locale" event=request.event.slug organizer=request.event.organizer.slug code=order.code %}" class="btn btn-default btn-xs">
|
<a href="{% url "control:event.order.locale" event=request.event.slug organizer=request.event.organizer.slug code=order.code %}" class="btn btn-default btn-xs">
|
||||||
<span class="fa fa-edit"></span>
|
<span class="fa fa-edit"></span>
|
||||||
</a>
|
</a>
|
||||||
@@ -220,7 +220,7 @@
|
|||||||
{{ order.customer.identifier }} – {{ order.customer.email }}
|
{{ order.customer.identifier }} – {{ order.customer.email }}
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if "event.orders:write" in request.eventpermset %}
|
{% if "can_change_orders" in request.eventpermset %}
|
||||||
<a href="{% url "control:event.order.contact" event=request.event.slug organizer=request.event.organizer.slug code=order.code %}" class="btn btn-default btn-xs">
|
<a href="{% url "control:event.order.contact" event=request.event.slug organizer=request.event.organizer.slug code=order.code %}" class="btn btn-default btn-xs">
|
||||||
<span class="fa fa-edit"></span>
|
<span class="fa fa-edit"></span>
|
||||||
</a>
|
</a>
|
||||||
@@ -233,7 +233,7 @@
|
|||||||
{% if order.email and order.email_known_to_work %}
|
{% if order.email and order.email_known_to_work %}
|
||||||
<span class="fa fa-check-circle text-success" data-toggle="tooltip" title="{% trans "We know that this email address works because the user clicked a link we sent them." %}"></span>
|
<span class="fa fa-check-circle text-success" data-toggle="tooltip" title="{% trans "We know that this email address works because the user clicked a link we sent them." %}"></span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if "event.orders:write" in request.eventpermset %}
|
{% if "can_change_orders" in request.eventpermset %}
|
||||||
<a href="{% url "control:event.order.contact" event=request.event.slug organizer=request.event.organizer.slug code=order.code %}" class="btn btn-default btn-xs">
|
<a href="{% url "control:event.order.contact" event=request.event.slug organizer=request.event.organizer.slug code=order.code %}" class="btn btn-default btn-xs">
|
||||||
<span class="fa fa-edit"></span>
|
<span class="fa fa-edit"></span>
|
||||||
</a>
|
</a>
|
||||||
@@ -257,7 +257,7 @@
|
|||||||
<dt>{% trans "Phone number" %}</dt>
|
<dt>{% trans "Phone number" %}</dt>
|
||||||
<dd>
|
<dd>
|
||||||
{{ order.phone|default_if_none:""|phone_format }}
|
{{ order.phone|default_if_none:""|phone_format }}
|
||||||
{% if "event.orders:write" in request.eventpermset %}
|
{% if "can_change_orders" in request.eventpermset %}
|
||||||
<a href="{% url "control:event.order.contact" event=request.event.slug organizer=request.event.organizer.slug code=order.code %}" class="btn btn-default btn-xs">
|
<a href="{% url "control:event.order.contact" event=request.event.slug organizer=request.event.organizer.slug code=order.code %}" class="btn btn-default btn-xs">
|
||||||
<span class="fa fa-edit"></span>
|
<span class="fa fa-edit"></span>
|
||||||
</a>
|
</a>
|
||||||
@@ -319,7 +319,7 @@
|
|||||||
<span class="fa fa-check text-success fa-stack-1x fa-stack-shifted"></span>
|
<span class="fa fa-check text-success fa-stack-1x fa-stack-shifted"></span>
|
||||||
</span>
|
</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if i.transmission_status != "inflight" and "event.orders:write" in request.eventpermset %}
|
{% if i.transmission_status != "inflight" %}
|
||||||
<form class="form-inline helper-display-inline" method="post"
|
<form class="form-inline helper-display-inline" method="post"
|
||||||
action="{% url "control:event.order.retransmitinvoice" event=request.event.slug organizer=request.event.organizer.slug code=order.code id=i.pk %}">
|
action="{% url "control:event.order.retransmitinvoice" event=request.event.slug organizer=request.event.organizer.slug code=order.code id=i.pk %}">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
@@ -334,7 +334,7 @@
|
|||||||
</form>
|
</form>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if not i.canceled %}
|
{% if not i.canceled %}
|
||||||
{% if i.regenerate_allowed and "event.orders:write" in request.eventpermset %}
|
{% if i.regenerate_allowed %}
|
||||||
<form class="form-inline helper-display-inline" method="post"
|
<form class="form-inline helper-display-inline" method="post"
|
||||||
action="{% url "control:event.order.regeninvoice" event=request.event.slug organizer=request.event.organizer.slug code=order.code id=i.pk %}">
|
action="{% url "control:event.order.regeninvoice" event=request.event.slug organizer=request.event.organizer.slug code=order.code id=i.pk %}">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
@@ -344,7 +344,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if not i.is_cancellation and "event.orders:write" in request.eventpermset %}
|
{% if not i.is_cancellation %}
|
||||||
<form class="form-inline helper-display-inline" method="post"
|
<form class="form-inline helper-display-inline" method="post"
|
||||||
action="{% url "control:event.order.reissueinvoice" event=request.event.slug organizer=request.event.organizer.slug code=order.code id=i.pk %}">
|
action="{% url "control:event.order.reissueinvoice" event=request.event.slug organizer=request.event.organizer.slug code=order.code id=i.pk %}">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
@@ -353,7 +353,7 @@
|
|||||||
data-toggle="tooltip"
|
data-toggle="tooltip"
|
||||||
title="{% trans 'Generate a cancellation document for this invoice and create a new invoice with a new invoice number.' %}"
|
title="{% trans 'Generate a cancellation document for this invoice and create a new invoice with a new invoice number.' %}"
|
||||||
{% endif %}>
|
{% endif %}>
|
||||||
{% if order.status == "c" or not invoice_qualified %}
|
{% if order.status == "c" %}
|
||||||
{% trans "Generate cancellation" %}
|
{% trans "Generate cancellation" %}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% trans "Cancel and reissue" %}
|
{% trans "Cancel and reissue" %}
|
||||||
@@ -371,7 +371,7 @@
|
|||||||
<br/>
|
<br/>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% if can_generate_invoice and 'event.orders:write' in request.eventpermset %}
|
{% if can_generate_invoice and 'can_change_orders' in request.eventpermset %}
|
||||||
<br/>
|
<br/>
|
||||||
<form class="form-inline helper-display-inline" method="post"
|
<form class="form-inline helper-display-inline" method="post"
|
||||||
action="{% url "control:event.order.geninvoice" event=request.event.slug organizer=request.event.organizer.slug code=order.code %}">
|
action="{% url "control:event.order.geninvoice" event=request.event.slug organizer=request.event.organizer.slug code=order.code %}">
|
||||||
@@ -382,7 +382,7 @@
|
|||||||
</form>
|
</form>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</dd>
|
</dd>
|
||||||
{% elif can_generate_invoice and 'event.orders:write' in request.eventpermset %}
|
{% elif can_generate_invoice and 'can_change_orders' in request.eventpermset %}
|
||||||
<dt>{% trans "Invoices" %}</dt>
|
<dt>{% trans "Invoices" %}</dt>
|
||||||
<dd>
|
<dd>
|
||||||
<form class="form-inline helper-display-inline" method="post"
|
<form class="form-inline helper-display-inline" method="post"
|
||||||
@@ -400,7 +400,7 @@
|
|||||||
<div class="panel panel-default items">
|
<div class="panel panel-default items">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
<div class="pull-right flip">
|
<div class="pull-right flip">
|
||||||
{% if 'event.orders:write' in request.eventpermset %}
|
{% if 'can_change_orders' in request.eventpermset %}
|
||||||
<a href="{% url "control:event.order.info" event=request.event.slug organizer=request.event.organizer.slug code=order.code %}">
|
<a href="{% url "control:event.order.info" event=request.event.slug organizer=request.event.organizer.slug code=order.code %}">
|
||||||
<span class="fa fa-edit"></span>
|
<span class="fa fa-edit"></span>
|
||||||
{% trans "Change answers" %}
|
{% trans "Change answers" %}
|
||||||
@@ -893,7 +893,7 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
{% if order.payment_refund_sum > 0 and "event.orders:write" in request.eventpermset %}
|
{% if order.payment_refund_sum > 0 and "can_change_orders" in request.eventpermset %}
|
||||||
<a href="{% url "control:event.order.refunds.start" event=request.event.slug organizer=request.event.organizer.slug code=order.code %}" class="btn btn-default">
|
<a href="{% url "control:event.order.refunds.start" event=request.event.slug organizer=request.event.organizer.slug code=order.code %}" class="btn btn-default">
|
||||||
{% trans "Create a refund" %}
|
{% trans "Create a refund" %}
|
||||||
</a>
|
</a>
|
||||||
@@ -1012,7 +1012,7 @@
|
|||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
<div class="pull-right flip">
|
<div class="pull-right flip">
|
||||||
{% if 'event.orders:write' in request.eventpermset %}
|
{% if 'can_change_orders' in request.eventpermset %}
|
||||||
<a href="{% url "control:event.order.info" event=request.event.slug organizer=request.event.organizer.slug code=order.code %}">
|
<a href="{% url "control:event.order.info" event=request.event.slug organizer=request.event.organizer.slug code=order.code %}">
|
||||||
<span class="fa fa-edit"></span>
|
<span class="fa fa-edit"></span>
|
||||||
{% trans "Change" %}
|
{% trans "Change" %}
|
||||||
@@ -1088,7 +1088,7 @@
|
|||||||
{% bootstrap_field comment_form.custom_followup_at %}
|
{% bootstrap_field comment_form.custom_followup_at %}
|
||||||
{% bootstrap_field comment_form.checkin_attention show_help=True show_label=False %}
|
{% bootstrap_field comment_form.checkin_attention show_help=True show_label=False %}
|
||||||
{% bootstrap_field comment_form.checkin_text show_help=True show_label=False %}
|
{% bootstrap_field comment_form.checkin_text show_help=True show_label=False %}
|
||||||
{% if "event.orders:write" in request.eventpermset %}
|
{% if "can_change_orders" in request.eventpermset %}
|
||||||
<button class="btn btn-default">
|
<button class="btn btn-default">
|
||||||
{% trans "Update comment" %}
|
{% trans "Update comment" %}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
{{ s.owner.fullname|default:s.owner.email }}
|
{{ s.owner.fullname|default:s.owner.email }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-lg-4 col-md-5 col-xs-12">
|
<div class="col-lg-5 col-md-6 col-xs-12">
|
||||||
{% if s.schedule_next_run %}
|
{% if s.schedule_next_run %}
|
||||||
<span class="fa fa-clock-o fa-fw"></span>
|
<span class="fa fa-clock-o fa-fw"></span>
|
||||||
{% trans "Next run:" %}
|
{% trans "Next run:" %}
|
||||||
@@ -53,7 +53,7 @@
|
|||||||
{{ s.mail_subject }}
|
{{ s.mail_subject }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-lg-3 col-md-3 col-xs-12 text-right">
|
<div class="col-lg-2 col-md-2 col-xs-12 text-right">
|
||||||
<form action="{% url "control:event.orders.export.do" event=request.event.slug organizer=request.organizer.slug %}"
|
<form action="{% url "control:event.orders.export.do" event=request.event.slug organizer=request.organizer.slug %}"
|
||||||
method="post" class="form-horizontal" data-asynctask data-asynctask-download
|
method="post" class="form-horizontal" data-asynctask data-asynctask-download
|
||||||
data-asynctask-long>
|
data-asynctask-long>
|
||||||
@@ -73,9 +73,6 @@
|
|||||||
<a href="?identifier={{ s.export_identifier }}&scheduled={{ s.pk }}" class="btn btn-default" title="{% trans "Edit" %}" data-toggle="tooltip">
|
<a href="?identifier={{ s.export_identifier }}&scheduled={{ s.pk }}" class="btn btn-default" title="{% trans "Edit" %}" data-toggle="tooltip">
|
||||||
<span class="fa fa-edit"></span>
|
<span class="fa fa-edit"></span>
|
||||||
</a>
|
</a>
|
||||||
<a href="?identifier={{ s.export_identifier }}&scheduled_copy_from={{ s.pk }}" class="btn btn-default" title="{% trans "Copy" %}" data-toggle="tooltip">
|
|
||||||
<span class="fa fa-copy"></span>
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<a href="{% url "control:event.orders.export.scheduled.delete" event=request.event.slug organizer=request.event.organizer.slug pk=s.pk %}" class="btn btn-danger" title="{% trans "Delete" %}" data-toggle="tooltip">
|
<a href="{% url "control:event.orders.export.scheduled.delete" event=request.event.slug organizer=request.event.organizer.slug pk=s.pk %}" class="btn btn-danger" title="{% trans "Delete" %}" data-toggle="tooltip">
|
||||||
<span class="fa fa-trash"></span>
|
<span class="fa fa-trash"></span>
|
||||||
@@ -115,9 +112,5 @@
|
|||||||
</a>
|
</a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% empty %}
|
|
||||||
<p class="empty-collection">
|
|
||||||
{% trans "There are no exporters available for you." %}
|
|
||||||
</p>
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -42,11 +42,7 @@
|
|||||||
<div class="form-group submit-group">
|
<div class="form-group submit-group">
|
||||||
<button formaction="{{ request.get_full_path }}" name="schedule" value="save" type="submit"
|
<button formaction="{{ request.get_full_path }}" name="schedule" value="save" type="submit"
|
||||||
class="btn btn-primary btn-save" data-no-asynctask>
|
class="btn btn-primary btn-save" data-no-asynctask>
|
||||||
{% if scheduled_copy_from %}
|
{% trans "Save" %}
|
||||||
{% trans "Save copy" %}
|
|
||||||
{% else %}
|
|
||||||
{% trans "Save" %}
|
|
||||||
{% endif %}
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|||||||
@@ -122,7 +122,7 @@
|
|||||||
<table class="table table-condensed table-hover table-orders">
|
<table class="table table-condensed table-hover table-orders">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
{% if "event.orders:write" in request.eventpermset %}
|
{% if "can_change_orders" in request.eventpermset %}
|
||||||
<th>
|
<th>
|
||||||
<label aria-label="{% trans "select all rows for batch-operation" %}"
|
<label aria-label="{% trans "select all rows for batch-operation" %}"
|
||||||
class="batch-select-label"><input type="checkbox" data-toggle-table/></label>
|
class="batch-select-label"><input type="checkbox" data-toggle-table/></label>
|
||||||
@@ -154,7 +154,7 @@
|
|||||||
<a href="?{% url_replace request 'ordering' 'status' %}"><i class="fa fa-caret-up"></i></a>
|
<a href="?{% url_replace request 'ordering' 'status' %}"><i class="fa fa-caret-up"></i></a>
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
{% if page_obj.paginator.num_pages > 1 and "event.orders:write" in request.eventpermset %}
|
{% if page_obj.paginator.num_pages > 1 and "can_change_orders" in request.eventpermset %}
|
||||||
<tr class="table-select-all warning hidden">
|
<tr class="table-select-all warning hidden">
|
||||||
<td>
|
<td>
|
||||||
<input type="checkbox" name="__ALL" id="__all"
|
<input type="checkbox" name="__ALL" id="__all"
|
||||||
@@ -171,7 +171,7 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
{% for o in orders %}
|
{% for o in orders %}
|
||||||
<tr>
|
<tr>
|
||||||
{% if "event.orders:write" in request.eventpermset %}
|
{% if "can_change_orders" in request.eventpermset %}
|
||||||
<td>
|
<td>
|
||||||
<label aria-label="{% trans "select row for batch-operation" %}"
|
<label aria-label="{% trans "select row for batch-operation" %}"
|
||||||
class="batch-select-label"><input type="checkbox" name="order"
|
class="batch-select-label"><input type="checkbox" name="order"
|
||||||
@@ -281,7 +281,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
{% if "event.orders:write" in request.eventpermset %}
|
{% if "can_change_orders" in request.eventpermset %}
|
||||||
<div class="batch-select-actions">
|
<div class="batch-select-actions">
|
||||||
<div class="btn-group dropup">
|
<div class="btn-group dropup">
|
||||||
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown"
|
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown"
|
||||||
|
|||||||
@@ -100,30 +100,28 @@
|
|||||||
{{ r.amount|money:request.event.currency }}
|
{{ r.amount|money:request.event.currency }}
|
||||||
</td>
|
</td>
|
||||||
<td class="text-right flip">
|
<td class="text-right flip">
|
||||||
{% if "event.orders:write" in request.eventpermset %}
|
{% if r.state == "transit" or r.state == "created" %}
|
||||||
{% if r.state == "transit" or r.state == "created" %}
|
<a href="{% url "control:event.order.refunds.cancel" event=request.event.slug organizer=request.event.organizer.slug code=r.order.code refund=r.pk %}?next={{ request.get_full_path|urlencode }}"
|
||||||
<a href="{% url "control:event.order.refunds.cancel" event=request.event.slug organizer=request.event.organizer.slug code=r.order.code refund=r.pk %}?next={{ request.get_full_path|urlencode }}"
|
class="btn btn-danger btn-xs" data-toggle="tooltip">
|
||||||
class="btn btn-danger btn-xs" data-toggle="tooltip">
|
<span class="fa fa-times"></span>
|
||||||
<span class="fa fa-times"></span>
|
{% trans "Cancel" %}
|
||||||
{% trans "Cancel" %}
|
</a>
|
||||||
</a>
|
<a href="{% url "control:event.order.refunds.done" event=request.event.slug organizer=request.event.organizer.slug code=r.order.code refund=r.pk %}?next={{ request.get_full_path|urlencode }}"
|
||||||
<a href="{% url "control:event.order.refunds.done" event=request.event.slug organizer=request.event.organizer.slug code=r.order.code refund=r.pk %}?next={{ request.get_full_path|urlencode }}"
|
class="btn btn-primary btn-xs" data-toggle="tooltip">
|
||||||
class="btn btn-primary btn-xs" data-toggle="tooltip">
|
<span class="fa fa-check"></span>
|
||||||
<span class="fa fa-check"></span>
|
{% trans "Confirm as done" %}
|
||||||
{% trans "Confirm as done" %}
|
</a>
|
||||||
</a>
|
|
||||||
{% elif r.state == "external" %}
|
{% elif r.state == "external" %}
|
||||||
<a href="{% url "control:event.order.refunds.cancel" event=request.event.slug organizer=request.event.organizer.slug code=r.order.code refund=r.pk %}?next={{ request.get_full_path|urlencode }}"
|
<a href="{% url "control:event.order.refunds.cancel" event=request.event.slug organizer=request.event.organizer.slug code=r.order.code refund=r.pk %}?next={{ request.get_full_path|urlencode }}"
|
||||||
class="btn btn-default btn-xs" data-toggle="tooltip">
|
class="btn btn-default btn-xs" data-toggle="tooltip">
|
||||||
<span class="fa fa-times"></span>
|
<span class="fa fa-times"></span>
|
||||||
{% trans "Ignore" %}
|
{% trans "Ignore" %}
|
||||||
</a>
|
</a>
|
||||||
<a href="{% url "control:event.order.refunds.process" event=request.event.slug organizer=request.event.organizer.slug code=r.order.code refund=r.pk %}?next={{ request.get_full_path|urlencode }}"
|
<a href="{% url "control:event.order.refunds.process" event=request.event.slug organizer=request.event.organizer.slug code=r.order.code refund=r.pk %}?next={{ request.get_full_path|urlencode }}"
|
||||||
class="btn btn-primary btn-xs" data-toggle="tooltip">
|
class="btn btn-primary btn-xs" data-toggle="tooltip">
|
||||||
<span class="fa fa-check"></span>
|
<span class="fa fa-check"></span>
|
||||||
{% trans "Process refund" %}
|
{% trans "Process refund" %}
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -93,18 +93,16 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</dl>
|
</dl>
|
||||||
</form>
|
</form>
|
||||||
{% if "organizer.customers:write" in request.orgapermset %}
|
<div class="text-right">
|
||||||
<div class="text-right">
|
<a href="{% url "control:organizer.customer.edit" organizer=request.organizer.slug customer=customer.identifier %}"
|
||||||
<a href="{% url "control:organizer.customer.edit" organizer=request.organizer.slug customer=customer.identifier %}"
|
class="btn btn-default">
|
||||||
class="btn btn-default">
|
<i class="fa fa-edit"></i> {% trans "Edit" %}
|
||||||
<i class="fa fa-edit"></i> {% trans "Edit" %}
|
</a>
|
||||||
</a>
|
<a href="{% url "control:organizer.customer.anonymize" organizer=request.organizer.slug customer=customer.identifier %}"
|
||||||
<a href="{% url "control:organizer.customer.anonymize" organizer=request.organizer.slug customer=customer.identifier %}"
|
class="btn btn-danger">
|
||||||
class="btn btn-danger">
|
<i class="fa fa-trash"></i> {% trans "Anonymize" %}
|
||||||
<i class="fa fa-trash"></i> {% trans "Anonymize" %}
|
</a>
|
||||||
</a>
|
</div>
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel panel-default items">
|
<div class="panel panel-default items">
|
||||||
@@ -164,39 +162,35 @@
|
|||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="text-right flip">
|
<td class="text-right flip">
|
||||||
{% if "organizer.customers:write" in request.orgapermset %}
|
<a href="{% url "control:organizer.customer.membership.edit" organizer=request.organizer.slug customer=customer.identifier id=m.pk %}"
|
||||||
<a href="{% url "control:organizer.customer.membership.edit" organizer=request.organizer.slug customer=customer.identifier id=m.pk %}"
|
data-toggle="tooltip"
|
||||||
|
title="{% trans "Edit" %}"
|
||||||
|
class="btn btn-default">
|
||||||
|
<i class="fa fa-edit"></i>
|
||||||
|
</a>
|
||||||
|
{% if m.testmode %}
|
||||||
|
<a href="{% url "control:organizer.customer.membership.delete" organizer=request.organizer.slug customer=customer.identifier id=m.pk %}"
|
||||||
data-toggle="tooltip"
|
data-toggle="tooltip"
|
||||||
title="{% trans "Edit" %}"
|
title="{% trans "Delete" %}"
|
||||||
class="btn btn-default">
|
class="btn btn-danger">
|
||||||
<i class="fa fa-edit"></i>
|
<i class="fa fa-trash"></i>
|
||||||
</a>
|
</a>
|
||||||
{% if m.testmode %}
|
|
||||||
<a href="{% url "control:organizer.customer.membership.delete" organizer=request.organizer.slug customer=customer.identifier id=m.pk %}"
|
|
||||||
data-toggle="tooltip"
|
|
||||||
title="{% trans "Delete" %}"
|
|
||||||
class="btn btn-danger">
|
|
||||||
<i class="fa fa-trash"></i>
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
{% if "organizer.customers:write" in request.orgapermset %}
|
<tfoot>
|
||||||
<tfoot>
|
<tr>
|
||||||
<tr>
|
<td colspan="7">
|
||||||
<td colspan="7">
|
<a href="{% url "control:organizer.customer.membership.add" organizer=request.organizer.slug customer=customer.identifier %}"
|
||||||
<a href="{% url "control:organizer.customer.membership.add" organizer=request.organizer.slug customer=customer.identifier %}"
|
class="btn btn-default">
|
||||||
class="btn btn-default">
|
<i class="fa fa-plus"></i>
|
||||||
<i class="fa fa-plus"></i>
|
{% trans "Add membership" %}
|
||||||
{% trans "Add membership" %}
|
</a>
|
||||||
</a>
|
</td>
|
||||||
</td>
|
</tr>
|
||||||
</tr>
|
</tfoot>
|
||||||
</tfoot>
|
|
||||||
{% endif %}
|
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel panel-default items">
|
<div class="panel panel-default items">
|
||||||
@@ -306,18 +300,14 @@
|
|||||||
{% for gc in gift_cards %}
|
{% for gc in gift_cards %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
{% if "organizer.giftcards:read" in request.orgapermset %}
|
<a href="{% url "control:organizer.giftcard" organizer=organizer.slug giftcard=gc.id %}">
|
||||||
<a href="{% url "control:organizer.giftcard" organizer=organizer.slug giftcard=gc.id %}">
|
<strong>{{ gc.secret }}</strong></a>
|
||||||
<strong>{{ gc.secret }}</strong></a>
|
{% if gc.testmode %}
|
||||||
{% else %}
|
<span class="label label-warning">{% trans "TEST MODE" %}</span>
|
||||||
<strong>{{ gc.secret|slice:":3" }}…</strong>
|
{% endif %}
|
||||||
{% endif %}
|
{% if gc.expired %}
|
||||||
{% if gc.testmode %}
|
<span class="label label-danger">{% trans "Expired" %}</span>
|
||||||
<span class="label label-warning">{% trans "TEST MODE" %}</span>
|
{% endif %}
|
||||||
{% endif %}
|
|
||||||
{% if gc.expired %}
|
|
||||||
<span class="label label-danger">{% trans "Expired" %}</span>
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
</td>
|
||||||
<td>{{ gc.issuance|date:"SHORT_DATETIME_FORMAT" }}</td>
|
<td>{{ gc.issuance|date:"SHORT_DATETIME_FORMAT" }}</td>
|
||||||
<td>{% if gc.expires %}{{ gc.expires|date:"SHORT_DATETIME_FORMAT" }}{% endif %}</td>
|
<td>{% if gc.expires %}{{ gc.expires|date:"SHORT_DATETIME_FORMAT" }}{% endif %}</td>
|
||||||
@@ -326,12 +316,10 @@
|
|||||||
<p class="text-right">{{ gc.value|money:gc.currency }}</p>
|
<p class="text-right">{{ gc.value|money:gc.currency }}</p>
|
||||||
</td>
|
</td>
|
||||||
<td class="text-right">
|
<td class="text-right">
|
||||||
{% if "organizer.giftcards:read" in request.orgapermset %}
|
<a href="{% url "control:organizer.giftcard" organizer=organizer.slug giftcard=gc.id %}"
|
||||||
<a href="{% url "control:organizer.giftcard" organizer=organizer.slug giftcard=gc.id %}"
|
class="btn btn-default btn-sm" data-toggle="tooltip" title="{% trans "Details" %}">
|
||||||
class="btn btn-default btn-sm" data-toggle="tooltip" title="{% trans "Details" %}">
|
<i class="fa fa-eye"></i>
|
||||||
<i class="fa fa-eye"></i>
|
</a>
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
@@ -15,10 +15,8 @@
|
|||||||
No customer accounts have been created yet.
|
No customer accounts have been created yet.
|
||||||
{% endblocktrans %}
|
{% endblocktrans %}
|
||||||
</p>
|
</p>
|
||||||
{% if "organizer.customers:write" in request.orgapermset %}
|
<a href="{% url "control:organizer.customer.create" organizer=request.organizer.slug %}"
|
||||||
<a href="{% url "control:organizer.customer.create" organizer=request.organizer.slug %}"
|
class="btn btn-primary btn-lg"><i class="fa fa-plus"></i> {% trans "Create a new customer" %}</a>
|
||||||
class="btn btn-primary btn-lg"><i class="fa fa-plus"></i> {% trans "Create a new customer" %}</a>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
@@ -45,12 +43,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
{% if "organizer.customers:write" in request.orgapermset %}
|
<p>
|
||||||
<p>
|
<a href="{% url "control:organizer.customer.create" organizer=request.organizer.slug %}"
|
||||||
<a href="{% url "control:organizer.customer.create" organizer=request.organizer.slug %}"
|
class="btn btn-default"><i class="fa fa-plus"></i> {% trans "Create a new customer" %}</a>
|
||||||
class="btn btn-default"><i class="fa fa-plus"></i> {% trans "Create a new customer" %}</a>
|
</p>
|
||||||
</p>
|
|
||||||
{% endif %}
|
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table table-condensed table-hover">
|
<table class="table table-condensed table-hover">
|
||||||
<thead>
|
<thead>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
{% blocktrans with name=request.organizer.name %}Organizer: {{ name }}{% endblocktrans %}
|
{% blocktrans with name=request.organizer.name %}Organizer: {{ name }}{% endblocktrans %}
|
||||||
</h1>
|
</h1>
|
||||||
{% if events|length == 0 and not filter_form.filtered %}
|
{% if events|length == 0 and not filter_form.filtered %}
|
||||||
{% if "organizer.events:create" in request.orgapermset %}
|
{% if "can_create_events" in request.orgapermset %}
|
||||||
<p>
|
<p>
|
||||||
<a href="{% url "control:events.add" %}?organizer={{ request.organizer.slug }}" class="btn btn-primary">
|
<a href="{% url "control:events.add" %}?organizer={{ request.organizer.slug }}" class="btn btn-primary">
|
||||||
<span class="fa fa-plus"></span>
|
<span class="fa fa-plus"></span>
|
||||||
@@ -50,7 +50,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
{% if "organizer.events:create" in request.orgapermset %}
|
{% if "can_create_events" in request.orgapermset %}
|
||||||
<p>
|
<p>
|
||||||
<a href="{% url "control:events.add" %}?organizer={{ request.organizer.slug }}" class="btn btn-primary">
|
<a href="{% url "control:events.add" %}?organizer={{ request.organizer.slug }}" class="btn btn-primary">
|
||||||
<span class="fa fa-plus"></span>
|
<span class="fa fa-plus"></span>
|
||||||
@@ -125,7 +125,7 @@
|
|||||||
data-toggle="tooltip">
|
data-toggle="tooltip">
|
||||||
<span class="fa fa-eye"></span>
|
<span class="fa fa-eye"></span>
|
||||||
</a>
|
</a>
|
||||||
{% if "organizer.events:create" in request.orgapermset %}
|
{% if "can_create_events" in request.orgapermset %}
|
||||||
<a href="{% url "control:events.add" %}?clone={{ e.pk }}" class="btn btn-sm btn-default"
|
<a href="{% url "control:events.add" %}?clone={{ e.pk }}" class="btn btn-sm btn-default"
|
||||||
title="{% trans "Clone event" %}" data-toggle="tooltip">
|
title="{% trans "Clone event" %}" data-toggle="tooltip">
|
||||||
<span class="fa fa-copy"></span>
|
<span class="fa fa-copy"></span>
|
||||||
|
|||||||
@@ -51,12 +51,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
{% if "organizer.devices:write" in request.orgapermset %}
|
<p>
|
||||||
<p>
|
<a href="{% url "control:organizer.device.add" organizer=request.organizer.slug %}"
|
||||||
<a href="{% url "control:organizer.device.add" organizer=request.organizer.slug %}"
|
class="btn btn-default"><i class="fa fa-plus"></i> {% trans "Connect a device" %}</a>
|
||||||
class="btn btn-default"><i class="fa fa-plus"></i> {% trans "Connect a device" %}</a>
|
</p>
|
||||||
</p>
|
|
||||||
{% endif %}
|
|
||||||
<form action="{% url "control:organizer.device.bulk_edit" organizer=request.organizer.slug %}" method="post">
|
<form action="{% url "control:organizer.device.bulk_edit" organizer=request.organizer.slug %}" method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{% for field in filter_form %}
|
{% for field in filter_form %}
|
||||||
@@ -66,12 +64,10 @@
|
|||||||
<table class="table table-condensed table-hover table-quotas">
|
<table class="table table-condensed table-hover table-quotas">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
{% if "organizer.devices:write" in request.orgapermset %}
|
<th>
|
||||||
<th>
|
<label aria-label="{% trans "select all rows for batch-operation" %}"
|
||||||
<label aria-label="{% trans "select all rows for batch-operation" %}"
|
class="batch-select-label"><input type="checkbox" data-toggle-table/></label>
|
||||||
class="batch-select-label"><input type="checkbox" data-toggle-table/></label>
|
</th>
|
||||||
</th>
|
|
||||||
{% endif %}
|
|
||||||
<th>{% trans "Device ID" %}
|
<th>{% trans "Device ID" %}
|
||||||
<a href="?{% url_replace request 'ordering' '-device_id' %}"><i
|
<a href="?{% url_replace request 'ordering' '-device_id' %}"><i
|
||||||
class="fa fa-caret-down"></i></a>
|
class="fa fa-caret-down"></i></a>
|
||||||
@@ -109,14 +105,12 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
{% for d in devices %}
|
{% for d in devices %}
|
||||||
<tr {% if d.revoked %}class="text-muted"{% endif %}>
|
<tr {% if d.revoked %}class="text-muted"{% endif %}>
|
||||||
{% if "organizer.devices:write" in request.orgapermset %}
|
<td>
|
||||||
<td>
|
<label aria-label="{% trans "select row for batch-operation" %}"
|
||||||
<label aria-label="{% trans "select row for batch-operation" %}"
|
class="batch-select-label"><input type="checkbox" name="device"
|
||||||
class="batch-select-label"><input type="checkbox" name="device"
|
class="batch-select-checkbox"
|
||||||
class="batch-select-checkbox"
|
value="{{ d.pk }}"/></label>
|
||||||
value="{{ d.pk }}"/></label>
|
</td>
|
||||||
</td>
|
|
||||||
{% endif %}
|
|
||||||
<td>
|
<td>
|
||||||
{{ d.device_id }}
|
{{ d.device_id }}
|
||||||
</td>
|
</td>
|
||||||
@@ -164,17 +158,15 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td class="text-right flip">
|
<td class="text-right flip">
|
||||||
{% if "organizer.devices:write" in request.orgapermset %}
|
{% if not d.initialized %}
|
||||||
{% if not d.initialized %}
|
<a href="{% url "control:organizer.device.connect" organizer=request.organizer.slug device=d.id %}"
|
||||||
<a href="{% url "control:organizer.device.connect" organizer=request.organizer.slug device=d.id %}"
|
class="btn btn-primary btn-sm"><i class="fa fa-link"></i>
|
||||||
class="btn btn-primary btn-sm"><i class="fa fa-link"></i>
|
{% trans "Connect" %}</a>
|
||||||
{% trans "Connect" %}</a>
|
{% endif %}
|
||||||
{% endif %}
|
{% if not d.initialized or d.api_token %}
|
||||||
{% if not d.initialized or d.api_token %}
|
<a href="{% url "control:organizer.device.revoke" organizer=request.organizer.slug device=d.id %}"
|
||||||
<a href="{% url "control:organizer.device.revoke" organizer=request.organizer.slug device=d.id %}"
|
class="btn btn-default btn-sm">
|
||||||
class="btn btn-default btn-sm">
|
{% trans "Revoke access" %}</a>
|
||||||
{% trans "Revoke access" %}</a>
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if d.initialized %}
|
{% if d.initialized %}
|
||||||
<a href="{% url "control:organizer.device.logs" organizer=request.organizer.slug device=d.id %}"
|
<a href="{% url "control:organizer.device.logs" organizer=request.organizer.slug device=d.id %}"
|
||||||
@@ -183,23 +175,19 @@
|
|||||||
{% trans "Logs" %}
|
{% trans "Logs" %}
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if "organizer.devices:write" in request.orgapermset %}
|
<a href="{% url "control:organizer.device.edit" organizer=request.organizer.slug device=d.id %}"
|
||||||
<a href="{% url "control:organizer.device.edit" organizer=request.organizer.slug device=d.id %}"
|
class="btn btn-default btn-sm"><i class="fa fa-edit"></i></a>
|
||||||
class="btn btn-default btn-sm"><i class="fa fa-edit"></i></a>
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
{% if "organizer.devices:write" in request.orgapermset %}
|
<div class="batch-select-actions">
|
||||||
<div class="batch-select-actions">
|
<button type="submit" class="btn btn-primary btn-save" name="action" value="edit">
|
||||||
<button type="submit" class="btn btn-primary btn-save" name="action" value="edit">
|
<i class="fa fa-edit"></i>{% trans "Edit selected" %}
|
||||||
<i class="fa fa-edit"></i>{% trans "Edit selected" %}
|
</button>
|
||||||
</button>
|
</div>
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</form>
|
</form>
|
||||||
{% include "pretixcontrol/pagination.html" %}
|
{% include "pretixcontrol/pagination.html" %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
{{ s.owner.fullname|default:s.owner.email }}
|
{{ s.owner.fullname|default:s.owner.email }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-lg-4 col-md-5 col-xs-12">
|
<div class="col-lg-5 col-md-6 col-xs-12">
|
||||||
{% if s.schedule_next_run %}
|
{% if s.schedule_next_run %}
|
||||||
<span class="fa fa-clock-o fa-fw"></span>
|
<span class="fa fa-clock-o fa-fw"></span>
|
||||||
{% trans "Next run:" %}
|
{% trans "Next run:" %}
|
||||||
@@ -53,7 +53,7 @@
|
|||||||
{{ s.mail_subject }}
|
{{ s.mail_subject }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-lg-3 col-md-3 col-xs-12 text-right">
|
<div class="col-lg-2 col-md-2 col-xs-12 text-right">
|
||||||
<form action="{% url "control:organizer.export.do" organizer=request.organizer.slug %}"
|
<form action="{% url "control:organizer.export.do" organizer=request.organizer.slug %}"
|
||||||
method="post" class="form-horizontal" data-asynctask data-asynctask-download
|
method="post" class="form-horizontal" data-asynctask data-asynctask-download
|
||||||
data-asynctask-long>
|
data-asynctask-long>
|
||||||
@@ -73,9 +73,6 @@
|
|||||||
<a href="?identifier={{ s.export_identifier }}&scheduled={{ s.pk }}" class="btn btn-default" title="{% trans "Edit" %}" data-toggle="tooltip">
|
<a href="?identifier={{ s.export_identifier }}&scheduled={{ s.pk }}" class="btn btn-default" title="{% trans "Edit" %}" data-toggle="tooltip">
|
||||||
<span class="fa fa-edit"></span>
|
<span class="fa fa-edit"></span>
|
||||||
</a>
|
</a>
|
||||||
<a href="?identifier={{ s.export_identifier }}&scheduled_copy_from={{ s.pk }}" class="btn btn-default" title="{% trans "Copy" %}" data-toggle="tooltip">
|
|
||||||
<span class="fa fa-copy"></span>
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<a href="{% url "control:organizer.export.scheduled.delete" organizer=request.organizer.slug pk=s.pk %}" class="btn btn-danger" title="{% trans "Delete" %}" data-toggle="tooltip">
|
<a href="{% url "control:organizer.export.scheduled.delete" organizer=request.organizer.slug pk=s.pk %}" class="btn btn-danger" title="{% trans "Delete" %}" data-toggle="tooltip">
|
||||||
<span class="fa fa-trash"></span>
|
<span class="fa fa-trash"></span>
|
||||||
@@ -115,9 +112,5 @@
|
|||||||
</a>
|
</a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% empty %}
|
|
||||||
<p class="empty-collection">
|
|
||||||
{% trans "There are no exporters available for you." %}
|
|
||||||
</p>
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -43,11 +43,7 @@
|
|||||||
<div class="form-group submit-group">
|
<div class="form-group submit-group">
|
||||||
<button formaction="{{ request.get_full_path }}" name="schedule" value="save" type="submit"
|
<button formaction="{{ request.get_full_path }}" name="schedule" value="save" type="submit"
|
||||||
class="btn btn-primary btn-save" data-no-asynctask>
|
class="btn btn-primary btn-save" data-no-asynctask>
|
||||||
{% if scheduled_copy_from %}
|
{% trans "Save" %}
|
||||||
{% trans "Save copy" %}
|
|
||||||
{% else %}
|
|
||||||
{% trans "Save" %}
|
|
||||||
{% endif %}
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|||||||
@@ -6,12 +6,10 @@
|
|||||||
<p>
|
<p>
|
||||||
{% trans "The list below shows gates that you can use to group check-in devices." %}
|
{% trans "The list below shows gates that you can use to group check-in devices." %}
|
||||||
</p>
|
</p>
|
||||||
{% if "organizer.devices:write" in request.orgapermset %}
|
<a href="{% url "control:organizer.gate.add" organizer=request.organizer.slug %}" class="btn btn-default">
|
||||||
<a href="{% url "control:organizer.gate.add" organizer=request.organizer.slug %}" class="btn btn-default">
|
<span class="fa fa-plus"></span>
|
||||||
<span class="fa fa-plus"></span>
|
{% trans "Create a new gate" %}
|
||||||
{% trans "Create a new gate" %}
|
</a>
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
<table class="table table-condensed table-hover">
|
<table class="table table-condensed table-hover">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -23,21 +21,15 @@
|
|||||||
{% for g in gates %}
|
{% for g in gates %}
|
||||||
<tr>
|
<tr>
|
||||||
<td><strong>
|
<td><strong>
|
||||||
{% if "organizer.devices:write" in request.orgapermset %}
|
<a href="{% url "control:organizer.gate.edit" organizer=request.organizer.slug gate=g.id %}">
|
||||||
<a href="{% url "control:organizer.gate.edit" organizer=request.organizer.slug gate=g.id %}">
|
|
||||||
{{ g.name }}
|
|
||||||
</a>
|
|
||||||
{% else %}
|
|
||||||
{{ g.name }}
|
{{ g.name }}
|
||||||
{% endif %}
|
</a>
|
||||||
</strong></td>
|
</strong></td>
|
||||||
<td class="text-right flip">
|
<td class="text-right flip">
|
||||||
{% if "organizer.devices:write" in request.orgapermset %}
|
<a href="{% url "control:organizer.gate.edit" organizer=request.organizer.slug gate=g.id %}"
|
||||||
<a href="{% url "control:organizer.gate.edit" organizer=request.organizer.slug gate=g.id %}"
|
class="btn btn-default btn-sm"><i class="fa fa-edit"></i></a>
|
||||||
class="btn btn-default btn-sm"><i class="fa fa-edit"></i></a>
|
<a href="{% url "control:organizer.gate.delete" organizer=request.organizer.slug gate=g.id %}"
|
||||||
<a href="{% url "control:organizer.gate.delete" organizer=request.organizer.slug gate=g.id %}"
|
class="btn btn-danger btn-sm"><i class="fa fa-trash"></i></a>
|
||||||
class="btn btn-danger btn-sm"><i class="fa fa-trash"></i></a>
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
@@ -10,12 +10,10 @@
|
|||||||
{% if card.testmode %}
|
{% if card.testmode %}
|
||||||
<span class="label label-warning">{% trans "TEST MODE" %}</span>
|
<span class="label label-warning">{% trans "TEST MODE" %}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if "organizer.giftcards:write" in request.orgapermset %}
|
<a href="{% url "control:organizer.giftcard.edit" organizer=request.organizer.slug giftcard=card.id %}"
|
||||||
<a href="{% url "control:organizer.giftcard.edit" organizer=request.organizer.slug giftcard=card.id %}"
|
class="btn btn-default">
|
||||||
class="btn btn-default">
|
<i class="fa fa-edit"></i> {% trans "Edit" %}
|
||||||
<i class="fa fa-edit"></i> {% trans "Edit" %}
|
</a>
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
</h1>
|
</h1>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-10 col-xs-12">
|
<div class="col-md-10 col-xs-12">
|
||||||
@@ -114,24 +112,22 @@
|
|||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
{% if "organizer.giftcards:write" in request.orgapermset %}
|
<tfoot>
|
||||||
<tfoot>
|
<tr>
|
||||||
<tr>
|
<td></td>
|
||||||
<td></td>
|
<td>
|
||||||
<td>
|
<input type="text" class="form-control helper-display-block" placeholder="{% trans "Text" %}"
|
||||||
<input type="text" class="form-control helper-display-block" placeholder="{% trans "Text" %}"
|
name="text">
|
||||||
name="text">
|
</td>
|
||||||
</td>
|
<td class="text-right form-inline">
|
||||||
<td class="text-right form-inline">
|
<input type="text" class="form-control input-sm" placeholder="{% trans "Value" %}" name="value">
|
||||||
<input type="text" class="form-control input-sm" placeholder="{% trans "Value" %}" name="value">
|
<button type="submit" class="btn btn-primary">
|
||||||
<button type="submit" class="btn btn-primary">
|
<span class="fa fa-plus"></span>
|
||||||
<span class="fa fa-plus"></span>
|
</button>
|
||||||
</button>
|
</td>
|
||||||
</td>
|
|
||||||
|
|
||||||
</tr>
|
</tr>
|
||||||
</tfoot>
|
</tfoot>
|
||||||
{% endif %}
|
|
||||||
</table>
|
</table>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -15,11 +15,10 @@
|
|||||||
or you can manually issue gift cards.
|
or you can manually issue gift cards.
|
||||||
{% endblocktrans %}
|
{% endblocktrans %}
|
||||||
</p>
|
</p>
|
||||||
{% if "organizer.giftcards:write" in request.orgapermset %}
|
|
||||||
<a href="{% url "control:organizer.giftcard.add" organizer=request.organizer.slug %}"
|
<a href="{% url "control:organizer.giftcard.add" organizer=request.organizer.slug %}"
|
||||||
class="btn btn-default btn-lg"><i class="fa fa-plus"></i> {% trans "Manually issue a gift card" %}
|
class="btn btn-default btn-lg"><i class="fa fa-plus"></i> {% trans "Manually issue a gift card" %}
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
@@ -46,12 +45,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
{% if "organizer.giftcards:write" in request.orgapermset %}
|
<p>
|
||||||
<p>
|
<a href="{% url "control:organizer.giftcard.add" organizer=request.organizer.slug %}"
|
||||||
<a href="{% url "control:organizer.giftcard.add" organizer=request.organizer.slug %}"
|
class="btn btn-default"><i class="fa fa-plus"></i> {% trans "Manually issue a gift card" %}</a>
|
||||||
class="btn btn-default"><i class="fa fa-plus"></i> {% trans "Manually issue a gift card" %}</a>
|
</p>
|
||||||
</p>
|
|
||||||
{% endif %}
|
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table table-condensed table-hover">
|
<table class="table table-condensed table-hover">
|
||||||
<thead>
|
<thead>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user