Compare commits

..

9 Commits

Author SHA1 Message Date
Raphael Michel
a973484505 Make tests pass 2026-01-09 17:30:51 +01:00
Raphael Michel
91d6273f13 Eliminate old names 2026-01-09 15:15:20 +01:00
Raphael Michel
65fe1f9cdb Update docs for device auth 2026-01-09 14:36:57 +01:00
Raphael Michel
560a4019b2 Docs, tests and fixes for teams api 2026-01-09 14:34:28 +01:00
Raphael Michel
47d0505306 Big string replace 2026-01-09 13:58:54 +01:00
Raphael Michel
871bdebbe6 API serializer 2026-01-09 13:58:54 +01:00
Raphael Michel
09b66d28c8 Backend UI 2026-01-09 13:58:54 +01:00
Raphael Michel
5d56daeb64 Refactor query and assignment usages of old permissions 2026-01-09 13:58:54 +01:00
Raphael Michel
41749cc942 Data model draft 2026-01-09 13:58:53 +01:00
159 changed files with 2192 additions and 1371 deletions

View File

@@ -23,13 +23,13 @@ jobs:
name: Tests
strategy:
matrix:
python-version: ["3.10", "3.11", "3.13"]
python-version: ["3.9", "3.10", "3.11"]
database: [sqlite, postgres]
exclude:
- database: sqlite
python-version: "3.10"
python-version: "3.9"
- database: sqlite
python-version: "3.11"
python-version: "3.10"
services:
postgres:
image: postgres:15

View File

@@ -197,10 +197,11 @@ Permissions & security profiles
Device authentication is currently hardcoded to grant the following permissions:
* View event meta data and products etc.
* View orders
* Change orders
* Manage gift cards
* Read event meta data and products etc.
* Read and write orders
* Read and write gift cards
* Read and write reusable media
* Read vouchers
Devices cannot change events or products and cannot access vouchers.

View File

@@ -24,21 +24,58 @@ all_events boolean Whether this te
limit_events list List of event slugs this team has access to
require_2fa boolean Whether members of this team are required to use
two-factor authentication
can_create_events boolean
can_change_teams boolean
can_change_organizer_settings boolean
can_manage_customers boolean
can_manage_reusable_media boolean
can_manage_gift_cards boolean
can_change_event_settings boolean
can_change_items boolean
can_view_orders boolean
can_change_orders boolean
can_view_vouchers boolean
can_change_vouchers boolean
can_checkin_orders boolean
all_event_permissions bool Whether members of this team are granted all event-level
permissions, including future additions
limit_event_permissions list of strings The event-level permissions team members are granted
all_organizer_permissions bool Whether members of this team are granted all organizer-level
permissions, including future additions
all_organizer_permissions list of strings The organizer-level permissions team members are granted
can_create_events boolean **DEPRECATED**. Legacy interface, use ``limit_organizer_permissions``.
can_change_teams boolean **DEPRECATED**. Legacy interface, use ``limit_organizer_permissions``.
can_change_organizer_settings boolean **DEPRECATED**. Legacy interface, use ``limit_organizer_permissions``.
can_manage_customers boolean **DEPRECATED**. Legacy interface, use ``limit_organizer_permissions``.
can_manage_reusable_media boolean **DEPRECATED**. Legacy interface, use ``limit_organizer_permissions``.
can_manage_gift_cards boolean **DEPRECATED**. Legacy interface, use ``limit_organizer_permissions``.
can_change_event_settings boolean **DEPRECATED**. Legacy interface, use ``limit_event_permissions``.
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.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.plugins:write
event.settings.email.sender: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
--------------------
@@ -121,6 +158,10 @@ Team endpoints
"all_events": true,
"limit_events": [],
"require_2fa": true,
"all_event_permissions": true,
"limit_event_permissions": [],
"all_organizer_permissions": true,
"limit_organizer_permissions": [],
"can_create_events": true,
...
}
@@ -159,6 +200,10 @@ Team endpoints
"all_events": true,
"limit_events": [],
"require_2fa": true,
"all_event_permissions": true,
"limit_event_permissions": [],
"all_organizer_permissions": true,
"limit_organizer_permissions": [],
"can_create_events": true,
...
}
@@ -187,7 +232,10 @@ Team endpoints
"all_events": true,
"limit_events": [],
"require_2fa": true,
"can_create_events": true,
"all_event_permissions": true,
"limit_event_permissions": [],
"all_organizer_permissions": true,
"limit_organizer_permissions": [],
...
}
@@ -205,6 +253,10 @@ Team endpoints
"all_events": true,
"limit_events": [],
"require_2fa": true,
"all_event_permissions": true,
"limit_event_permissions": [],
"all_organizer_permissions": true,
"limit_organizer_permissions": [],
"can_create_events": true,
...
}
@@ -232,7 +284,8 @@ Team endpoints
Content-Length: 94
{
"can_create_events": true
"all_organizer_permissions": false,
"limit_organizer_permissions": ["organizer.events:create"]
}
**Example response**:
@@ -249,6 +302,10 @@ Team endpoints
"all_events": true,
"limit_events": [],
"require_2fa": true,
"all_event_permissions": true,
"limit_event_permissions": [],
"all_organizer_permissions": false,
"limit_organizer_permissions": ["organizer.events:create"],
"can_create_events": true,
...
}

View File

@@ -55,12 +55,12 @@ your views:
)
class AdminView(EventPermissionRequiredMixin, View):
permission = 'can_view_orders'
permission = 'event.orders:read'
...
@event_permission_required('can_view_orders')
@event_permission_required('event.orders:read')
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')
def navbar_info(sender, request, **kwargs):
url = resolve(request.path_info)
if not request.user.has_event_permission(request.organizer, request.event, 'can_change_vouchers'):
if not request.user.has_event_permission(request.organizer, request.event, 'event.vouchers:read'):
return []
return [{
'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):
model = Event
permission = 'can_change_settings'
permission = 'event.settings.general:write'
form_class = MySettingsForm
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
.event`` and ``request.organizer`` are available as usual.
To require a special permission like ``can_view_orders``, you do not need to inherit from a special ViewSet base
To require a special permission like ``event.orders:read``, you do not need to inherit from a special ViewSet base
class, you can just set the ``permission`` attribute on your viewset:
.. code-block:: python
class MyViewSet(ModelViewSet):
permission = 'can_view_orders'
permission = 'event.orders:read'
...
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
perm_holder = (request.auth if isinstance(request.auth, TeamAPIToken) else request.user)
if perm_holder.has_event_permission(request.event.organizer, request.event, 'can_view_orders'):
if perm_holder.has_event_permission(request.event.organizer, request.event, 'event.orders:read'):
...

View File

@@ -14,7 +14,8 @@ Core
: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,
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_permissions, register_organizer_permissions
Order events
""""""""""""

View File

@@ -196,7 +196,7 @@ A simple implementation could look like this:
.. code-block:: python
class MyNotificationType(NotificationType):
required_permission = "can_view_orders"
required_permission = "event.orders:read"
action_type = "pretix.event.order.paid"
verbose_name = _("Order has been paid")

View File

@@ -2,7 +2,7 @@ Permissions
===========
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 <user-teams>`_
The central concept here is the concept of *Teams*. You can read more on `configuring teams and permissions`_
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
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):
permission = 'can_change_organizer_settings'
# Only users with the permission ``can_change_organizer_settings`` on
permission = 'organizer.settings.general:write'
# Only users with the permission ``organizer.settings.general:write`` on
# 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
@organizer_permission_required('can_change_organizer_settings')
@organizer_permission_required('organizer.settings.general:write')
def my_orga_view(request, organizer, **kwargs):
# Only users with the permission ``can_change_organizer_settings`` on
# Only users with the permission ``organizer.settings.general:write`` on
# this organizer can access this
@@ -56,8 +56,8 @@ Of course, the same is available on event level:
class MyEventView(EventPermissionRequiredMixin, View):
permission = 'can_change_event_settings'
# Only users with the permission ``can_change_event_settings`` on
permission = 'event.settings.general:write'
# Only users with the permission ``event.settings.general:write`` on
# 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
@event_permission_required('can_change_event_settings')
@event_permission_required('event.settings.general:write')
def my_event_view(request, organizer, **kwargs):
# Only users with the permission ``can_change_event_settings`` on
# Only users with the permission ``event.settings.general:write`` on
# 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::
class MyModelViewSet(viewsets.ReadOnlyModelViewSet):
permission = 'can_view_orders'
permission = 'event.orders:read'
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::
>>> event.get_users_with_permission('can_change_event_settings')
>>> event.get_users_with_permission('event.orders:read')
<QuerySet: …>
Determine if a user has a certain permission for a specific event::
>>> user.has_event_permission(organizer, event, 'can_change_event_settings', request=request)
>>> user.has_event_permission(organizer, event, 'event.orders:read', request=request)
True
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::
>>> user.has_organizer_permission(organizer, 'can_change_event_settings', request=request)
>>> user.has_organizer_permission(organizer, 'event.orders:read', request=request)
True
Sometimes, it might be more useful to get the set of permissions at once::
>>> user.get_event_permission_set(organizer, event)
{'can_change_event_settings', 'can_view_orders', 'can_change_orders'}
{'event.settings.general:write', 'event.orders:read', 'event.orders:write'}
>>> user.get_organizer_permission_set(organizer, event)
{'can_change_organizer_settings', 'can_create_events'}
{'organizer.settings.general:write', 'organizer.events:create'}
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::
{% if "can_change_orders" in request.eventpermset %}
{% if "event.orders:write" in request.eventpermset %}
{% endif %}
You can also do the reverse to get any events a user has access to::
>>> user.get_events_with_permission('can_change_event_settings', request=request)
>>> user.get_events_with_permission('event.settings.general:write', request=request)
<QuerySet: …>
>>> user.get_events_with_any_permission(request=request)
@@ -195,3 +195,30 @@ 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,
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.
Adding permissions
------------------
Plugins can add permissions through the ``register_event_permissions`` and ``register_organizer_permission``.
We recommend to use this only for very significant permissions, as the system will become less usable with too many
permission levels, also because the team page will show all permission options, even those of disabled plugins.
We recommend to prefix the permission string with the plugin name and follow the ``<module>.<thing>:<action>`` pattern.
Example::
@receiver(register_event_permissions)
def register_default_event_permissions(sender, **kwargs):
return [
Permission("pretix_myplugin.resource:read", _("Read resources"),
"pretix_myplugin", _("Some helptext")),
]
@receiver(register_organizer_permissions)
def register_default_organizer_permissions(sender, **kwargs):
return [
Permission("pretix_myplugin.resource:read", _("Read resources"),
"pretix_myplugin", _("Some helptext")),
]
.. _configuring teams and permissions: https://docs.pretix.eu/guides/teams/

View File

@@ -3,7 +3,7 @@ name = "pretix"
dynamic = ["version"]
description = "Reinventing presales, one ticket at a time"
readme = "README.rst"
requires-python = ">=3.10"
requires-python = ">=3.9"
license = {file = "LICENSE"}
keywords = ["tickets", "web", "shop", "ecommerce"]
authors = [

View File

@@ -124,12 +124,12 @@ class EventCRUDPermission(EventPermission):
def has_permission(self, request, view):
if not super(EventCRUDPermission, self).has_permission(request, view):
return False
elif view.action == 'create' and 'can_create_events' not in request.orgapermset:
elif view.action == 'create' and 'organizer.events:create' not in request.orgapermset:
return False
elif view.action == 'destroy' and 'can_change_event_settings' not in request.eventpermset:
elif view.action == 'destroy' and 'event.settings.general:write' not in request.eventpermset:
return False
elif view.action in ['update', 'partial_update'] \
and 'can_change_event_settings' not in request.eventpermset:
and 'event.settings.general:write' not in request.eventpermset:
return False
return True

View File

@@ -300,7 +300,7 @@ class EventSerializer(SalesChannelMigrationMixin, I18nAwareModelSerializer):
def ignored_meta_properties(self):
perm_holder = (self.context['request'].auth if isinstance(self.context['request'].auth, (Device, TeamAPIToken))
else self.context['request'].user)
if perm_holder.has_organizer_permission(self.context['request'].organizer, 'can_change_organizer_settings', request=self.context['request']):
if perm_holder.has_organizer_permission(self.context['request'].organizer, 'organizer.settings.general:write', request=self.context['request']):
return []
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):
perm_holder = (self.context['request'].auth if isinstance(self.context['request'].auth, (Device, TeamAPIToken))
else self.context['request'].user)
if perm_holder.has_organizer_permission(self.context['request'].organizer, 'can_change_organizer_settings', request=self.context['request']):
if perm_holder.has_organizer_permission(self.context['request'].organizer, 'organizer.settings.general:write', request=self.context['request']):
return []
return [k for k, p in self.meta_properties.items() if p.protected]
@@ -1079,16 +1079,16 @@ class SeatSerializer(I18nAwareModelSerializer):
def prefetch_expanded_data(self, items, request, expand_fields):
if 'orderposition' in expand_fields:
if 'can_view_orders' not in request.eventpermset:
raise PermissionDenied('can_view_orders permission required for expand=orderposition')
if 'event.orders:read' not in request.eventpermset:
raise PermissionDenied('event.orders:read permission required for expand=orderposition')
prefetch_by_id(items, OrderPosition.objects.prefetch_related('order'), 'orderposition_id', 'orderposition')
if 'cartposition' in expand_fields:
if 'can_view_orders' not in request.eventpermset:
raise PermissionDenied('can_view_orders permission required for expand=cartposition')
if 'event.orders:read' not in request.eventpermset:
raise PermissionDenied('event.orders:read permission required for expand=cartposition')
prefetch_by_id(items, CartPosition.objects, 'cartposition_id', 'cartposition')
if 'voucher' in expand_fields:
if 'can_view_vouchers' not in request.eventpermset:
raise PermissionDenied('can_view_vouchers permission required for expand=voucher')
if 'event.vouchers:read' not in request.eventpermset:
raise PermissionDenied('event.vouchers:read permission required for expand=voucher')
prefetch_by_id(items, Voucher.objects, 'voucher_id', 'voucher')
def __init__(self, instance, *args, **kwargs):

View File

@@ -613,7 +613,7 @@ class OrderPositionSerializer(I18nAwareModelSerializer):
# /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
# layer to not set pdf_data=true in the first place.
request and hasattr(request, 'eventpermset') and 'can_view_orders' not in request.eventpermset
request and hasattr(request, 'eventpermset') and 'event.orders:read' not in request.eventpermset
)
if ('pdf_data' in self.context and not self.context['pdf_data']) or pdf_data_forbidden:
self.fields.pop('pdf_data', None)

View File

@@ -45,12 +45,19 @@ from pretix.base.models import (
SalesChannel, SeatingPlan, Team, TeamAPIToken, TeamInvite, User,
)
from pretix.base.models.seating import SeatingPlanLayoutValidator
from pretix.base.permissions import (
get_all_event_permissions, get_all_organizer_permissions,
)
from pretix.base.plugins import (
PLUGIN_LEVEL_EVENT, PLUGIN_LEVEL_EVENT_ORGANIZER_HYBRID,
PLUGIN_LEVEL_ORGANIZER,
)
from pretix.base.services.mail import SendMailException, mail
from pretix.base.settings import validate_organizer_settings
from pretix.helpers.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.multidomain.urlreverse import build_absolute_uri
@@ -306,21 +313,97 @@ class EventSlugField(serializers.SlugRelatedField):
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):
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:
model = Team
fields = (
'id', 'name', 'require_2fa', 'all_events', 'limit_events', 'can_create_events', 'can_change_teams',
'can_change_organizer_settings', 'can_manage_gift_cards', 'can_change_event_settings',
'can_change_items', 'can_view_orders', 'can_change_orders', 'can_view_vouchers',
'can_change_vouchers', 'can_checkin_orders', 'can_manage_customers', 'can_manage_reusable_media'
'id', 'name', 'require_2fa', 'all_events', 'limit_events', 'all_event_permissions', 'limit_event_permissions',
'all_organizer_permissions', 'limit_organizer_permissions', 'can_change_event_settings',
'can_change_items', 'can_view_orders', 'can_change_orders', 'can_checkin_orders', 'can_view_vouchers',
'can_change_vouchers', 'can_create_events', 'can_change_organizer_settings', 'can_change_teams',
'can_manage_gift_cards', 'can_manage_customers', 'can_manage_reusable_media'
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['limit_event_permissions'].choices = [(p.name, p.name) for p in get_all_event_permissions().values()]
self.fields['limit_organizer_permissions'].choices = [(p.name, p.name) for p in get_all_organizer_permissions().values()]
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):
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.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_plugable_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'):
raise ValidationError('Do not set both limit_events and all_events.')
return data

View File

@@ -52,8 +52,8 @@ class CartPositionViewSet(CreateModelMixin, DestroyModelMixin, viewsets.ReadOnly
ordering = ('datetime',)
ordering_fields = ('datetime', 'cart_id')
lookup_field = 'id'
permission = 'can_view_orders'
write_permission = 'can_change_orders'
permission = 'event.orders:read'
write_permission = 'event.orders:write'
def get_queryset(self):
return CartPosition.objects.filter(

View File

@@ -118,11 +118,11 @@ class CheckinListViewSet(viewsets.ModelViewSet):
def _get_permission_name(self, request):
if request.path.endswith('/failed_checkins/'):
return 'can_checkin_orders', 'can_change_orders'
return 'event.orders:checkin', 'event.orders:write'
elif request.method in SAFE_METHODS:
return 'can_view_orders', 'can_checkin_orders',
return 'event.orders:read', 'event.orders:checkin',
else:
return 'can_change_event_settings'
return 'event.settings.general:write'
def get_queryset(self):
qs = self.request.event.checkin_lists.prefetch_related(
@@ -457,7 +457,7 @@ def _redeem_process(*, checkinlists, raw_barcode, answers_data, datetime, force,
'event': op.order.event,
'pdf_data': pdf_data and (
user if user and user.is_authenticated else auth
).has_event_permission(request.organizer, event, 'can_view_orders', request),
).has_event_permission(request.organizer, event, 'event.orders:read', request),
}
common_checkin_args = dict(
@@ -822,8 +822,8 @@ class CheckinListPositionViewSet(viewsets.ReadOnlyModelViewSet):
}
filterset_class = CheckinOrderPositionFilter
permission = ('can_view_orders', 'can_checkin_orders')
write_permission = ('can_change_orders', 'can_checkin_orders')
permission = ('event.orders:read', 'event.orders:checkin')
write_permission = ('event.orders:write', 'event.orders:checkin')
def get_serializer_context(self):
ctx = super().get_serializer_context()
@@ -854,7 +854,7 @@ class CheckinListPositionViewSet(viewsets.ReadOnlyModelViewSet):
expand=self.request.query_params.getlist('expand'),
)
if 'pk' not in self.request.resolver_match.kwargs and 'can_view_orders' not in self.request.eventpermset \
if 'pk' not in self.request.resolver_match.kwargs and 'event.orders:read' not in self.request.eventpermset \
and len(self.request.query_params.get('search', '')) < 3:
qs = qs.none()
@@ -903,9 +903,9 @@ class CheckinListPositionViewSet(viewsets.ReadOnlyModelViewSet):
class CheckinRPCRedeemView(views.APIView):
def post(self, request, *args, **kwargs):
if isinstance(self.request.auth, (TeamAPIToken, Device)):
events = self.request.auth.get_events_with_permission(('can_change_orders', 'can_checkin_orders'))
events = self.request.auth.get_events_with_permission(('event.orders:write', 'event.orders:checkin'))
elif self.request.user.is_authenticated:
events = self.request.user.get_events_with_permission(('can_change_orders', 'can_checkin_orders'), self.request).filter(
events = self.request.user.get_events_with_permission(('event.orders:write', 'event.orders:checkin'), self.request).filter(
organizer=self.request.organizer
)
else:
@@ -972,9 +972,9 @@ class CheckinRPCSearchView(ListAPIView):
@cached_property
def lists(self):
if isinstance(self.request.auth, (TeamAPIToken, Device)):
events = self.request.auth.get_events_with_permission(('can_view_orders', 'can_checkin_orders'))
events = self.request.auth.get_events_with_permission(('event.orders:read', 'event.orders:checkin'))
elif self.request.user.is_authenticated:
events = self.request.user.get_events_with_permission(('can_view_orders', 'can_checkin_orders'), self.request).filter(
events = self.request.user.get_events_with_permission(('event.orders:read', 'event.orders:checkin'), self.request).filter(
organizer=self.request.organizer
)
else:
@@ -991,9 +991,9 @@ class CheckinRPCSearchView(ListAPIView):
@cached_property
def has_full_access_permission(self):
if isinstance(self.request.auth, (TeamAPIToken, Device)):
events = self.request.auth.get_events_with_permission('can_view_orders')
events = self.request.auth.get_events_with_permission('event.orders:read')
elif self.request.user.is_authenticated:
events = self.request.user.get_events_with_permission('can_view_orders', self.request).filter(
events = self.request.user.get_events_with_permission('event.orders:read', self.request).filter(
organizer=self.request.organizer
)
else:
@@ -1020,9 +1020,9 @@ class CheckinRPCSearchView(ListAPIView):
class CheckinRPCAnnulView(views.APIView):
def post(self, request, *args, **kwargs):
if isinstance(self.request.auth, (TeamAPIToken, Device)):
events = self.request.auth.get_events_with_permission(('can_change_orders', 'can_checkin_orders'))
events = self.request.auth.get_events_with_permission(('event.orders:write', 'event.orders:checkin'))
elif self.request.user.is_authenticated:
events = self.request.user.get_events_with_permission(('can_change_orders', 'can_checkin_orders'), self.request).filter(
events = self.request.user.get_events_with_permission(('event.orders:write', 'event.orders:checkin'), self.request).filter(
organizer=self.request.organizer
)
else:
@@ -1100,7 +1100,7 @@ class CheckinViewSet(viewsets.ReadOnlyModelViewSet):
filterset_class = CheckinFilter
ordering = ('created', 'id')
ordering_fields = ('created', 'datetime', 'id',)
permission = 'can_view_orders'
permission = 'event.orders:read'
def get_queryset(self):
qs = Checkin.all.filter().select_related(

View File

@@ -57,7 +57,7 @@ class DiscountViewSet(ConditionalListView, viewsets.ModelViewSet):
ordering_fields = ('id', 'position')
ordering = ('position', 'id')
permission = None
write_permission = 'can_change_items'
write_permission = 'event.items:write'
def get_queryset(self):
return self.request.event.discounts.prefetch_related(

View File

@@ -341,7 +341,7 @@ class CloneEventViewSet(viewsets.ModelViewSet):
lookup_field = 'slug'
lookup_url_kwarg = 'event'
http_method_names = ['post']
write_permission = 'can_create_events'
write_permission = 'event.settings.general:write'
def get_serializer_context(self):
ctx = super().get_serializer_context()
@@ -350,6 +350,12 @@ class CloneEventViewSet(viewsets.ModelViewSet):
return ctx
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.instance.log_action(
@@ -426,7 +432,7 @@ with scopes_disabled():
class SubEventViewSet(ConditionalListView, viewsets.ModelViewSet):
serializer_class = SubEventSerializer
queryset = SubEvent.objects.none()
write_permission = 'can_change_event_settings'
write_permission = 'event.settings.general:write'
filter_backends = (DjangoFilterBackend, TotalOrderingFilter)
ordering = ('date_from',)
ordering_fields = ('id', 'date_from', 'last_modified')
@@ -546,7 +552,7 @@ class SubEventViewSet(ConditionalListView, viewsets.ModelViewSet):
class TaxRuleViewSet(ConditionalListView, viewsets.ModelViewSet):
serializer_class = TaxRuleSerializer
queryset = TaxRule.objects.none()
write_permission = 'can_change_event_settings'
write_permission = 'event.settings.general:write'
def get_queryset(self):
return self.request.event.tax_rules.all()
@@ -589,7 +595,7 @@ class TaxRuleViewSet(ConditionalListView, viewsets.ModelViewSet):
class ItemMetaPropertiesViewSet(viewsets.ModelViewSet):
serializer_class = ItemMetaPropertiesSerializer
queryset = ItemMetaProperty.objects.none()
write_permission = 'can_change_event_settings'
write_permission = 'event.settings.general:write'
def get_queryset(self):
qs = self.request.event.item_meta_properties.all()
@@ -636,14 +642,14 @@ class ItemMetaPropertiesViewSet(viewsets.ModelViewSet):
class EventSettingsView(views.APIView):
permission = None
write_permission = 'can_change_event_settings'
write_permission = 'event.settings.general:write'
def get(self, request, *args, **kwargs):
if isinstance(request.auth, Device):
s = DeviceEventSettingsSerializer(instance=request.event.settings, event=request.event, context={
'request': request
})
elif 'can_change_event_settings' in request.eventpermset:
elif 'event.settings.general:write' in request.eventpermset:
s = EventSettingsSerializer(instance=request.event.settings, event=request.event, context={
'request': request
})
@@ -701,7 +707,7 @@ class SeatFilter(FilterSet):
class SeatViewSet(ConditionalListView, viewsets.ModelViewSet):
serializer_class = SeatSerializer
queryset = Seat.objects.none()
write_permission = 'can_change_event_settings'
write_permission = 'event.settings.general:write'
filter_backends = (DjangoFilterBackend, )
filterset_class = SeatFilter

View File

@@ -136,7 +136,7 @@ class ExportersMixin:
class EventExportersViewSet(ExportersMixin, viewsets.ViewSet):
permission = 'can_view_orders'
permission = 'event.orders:read'
def get_serializer_kwargs(self):
return {}
@@ -169,7 +169,7 @@ class OrganizerExportersViewSet(ExportersMixin, viewsets.ViewSet):
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(
events = perm_holder.get_events_with_permission('event.orders:read', request=self.request).filter(
organizer=self.request.organizer
)
responses = register_multievent_data_exporters.send(self.request.organizer)
@@ -196,7 +196,7 @@ class OrganizerExportersViewSet(ExportersMixin, viewsets.ViewSet):
else:
perm_holder = self.request.user
return {
'events': perm_holder.get_events_with_permission('can_view_orders', request=self.request).filter(
'events': perm_holder.get_events_with_permission('event.orders:read', request=self.request).filter(
organizer=self.request.organizer
)
}
@@ -222,11 +222,11 @@ class ScheduledExportersViewSet(viewsets.ModelViewSet):
class ScheduledEventExportViewSet(ScheduledExportersViewSet):
serializer_class = ScheduledEventExportSerializer
queryset = ScheduledEventExport.objects.none()
permission = 'can_view_orders'
permission = 'event.orders:read'
def get_queryset(self):
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, 'can_change_event_settings',
if not perm_holder.has_event_permission(self.request.organizer, self.request.event, 'event.settings.general:write',
request=self.request):
if self.request.user.is_authenticated:
qs = self.request.event.scheduled_exports.filter(owner=self.request.user)
@@ -291,7 +291,7 @@ class ScheduledOrganizerExportViewSet(ScheduledExportersViewSet):
def get_queryset(self):
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, 'can_change_organizer_settings',
if not perm_holder.has_organizer_permission(self.request.organizer, 'organizer.settings.general:write',
request=self.request):
if self.request.user.is_authenticated:
qs = self.request.organizer.scheduled_exports.filter(owner=self.request.user)
@@ -324,9 +324,9 @@ class ScheduledOrganizerExportViewSet(ScheduledExportersViewSet):
@cached_property
def events(self):
if isinstance(self.request.auth, (TeamAPIToken, Device)):
return self.request.auth.get_events_with_permission('can_view_orders')
return self.request.auth.get_events_with_permission('event.orders:read')
elif self.request.user.is_authenticated:
return self.request.user.get_events_with_permission('can_view_orders', self.request).filter(
return self.request.user.get_events_with_permission('event.orders:read', self.request).filter(
organizer=self.request.organizer
)

View File

@@ -99,7 +99,7 @@ class ItemViewSet(ConditionalListView, viewsets.ModelViewSet):
ordering = ('position', 'id')
filterset_class = ItemFilter
permission = None
write_permission = 'can_change_items'
write_permission = 'event.items:write'
def get_queryset(self):
return self.request.event.items.select_related('tax_rule').prefetch_related(
@@ -163,7 +163,7 @@ class ItemVariationViewSet(viewsets.ModelViewSet):
ordering_fields = ('id', 'position')
ordering = ('id',)
permission = None
write_permission = 'can_change_items'
write_permission = 'event.items:write'
@cached_property
def item(self):
@@ -234,7 +234,7 @@ class ItemBundleViewSet(viewsets.ModelViewSet):
ordering_fields = ('id',)
ordering = ('id',)
permission = None
write_permission = 'can_change_items'
write_permission = 'event.items:write'
@cached_property
def item(self):
@@ -286,7 +286,7 @@ class ItemProgramTimeViewSet(viewsets.ModelViewSet):
ordering_fields = ('id',)
ordering = ('id',)
permission = None
write_permission = 'can_change_items'
write_permission = 'event.items:write'
@cached_property
def item(self):
@@ -339,7 +339,7 @@ class ItemAddOnViewSet(viewsets.ModelViewSet):
ordering_fields = ('id', 'position')
ordering = ('id',)
permission = None
write_permission = 'can_change_items'
write_permission = 'event.items:write'
@cached_property
def item(self):
@@ -398,7 +398,7 @@ class ItemCategoryViewSet(ConditionalListView, viewsets.ModelViewSet):
ordering_fields = ('id', 'position')
ordering = ('position', 'id')
permission = None
write_permission = 'can_change_items'
write_permission = 'event.items:write'
def get_queryset(self):
return self.request.event.categories.all()
@@ -453,7 +453,7 @@ class QuestionViewSet(ConditionalListView, viewsets.ModelViewSet):
ordering_fields = ('id', 'position')
ordering = ('position', 'id')
permission = None
write_permission = 'can_change_items'
write_permission = 'event.items:write'
def get_queryset(self):
return self.request.event.questions.prefetch_related('options').all()
@@ -497,7 +497,7 @@ class QuestionOptionViewSet(viewsets.ModelViewSet):
ordering_fields = ('id', 'position')
ordering = ('position',)
permission = None
write_permission = 'can_change_items'
write_permission = 'event.items:write'
def get_queryset(self):
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 = ('id',)
permission = None
write_permission = 'can_change_items'
write_permission = 'event.items:write'
def get_queryset(self):
return self.request.event.quotas.select_related('subevent').prefetch_related('items', 'variations').all()

View File

@@ -62,8 +62,8 @@ with scopes_disabled():
class ReusableMediaViewSet(viewsets.ModelViewSet):
serializer_class = ReusableMediaSerializer
queryset = ReusableMedium.objects.none()
permission = 'can_manage_reusable_media'
write_permission = 'can_manage_reusable_media'
permission = 'organizer.reusablemedia:read'
write_permission = 'organizer.reusablemedia:write'
filter_backends = (DjangoFilterBackend, OrderingFilter)
ordering = ('-updated', '-id')
ordering_fields = ('created', 'updated', 'identifier', 'type', 'id')

View File

@@ -317,7 +317,7 @@ class OrderViewSetMixin:
class OrganizerOrderViewSet(OrderViewSetMixin, viewsets.ReadOnlyModelViewSet):
def get_base_queryset(self):
perm = "can_view_orders" if self.request.method in SAFE_METHODS else "can_change_orders"
perm = "event.orders:read" if self.request.method in SAFE_METHODS else "event.orders:write"
if isinstance(self.request.auth, (TeamAPIToken, Device)):
return Order.objects.filter(
event__organizer=self.request.organizer,
@@ -338,8 +338,8 @@ class OrganizerOrderViewSet(OrderViewSetMixin, viewsets.ReadOnlyModelViewSet):
class EventOrderViewSet(OrderViewSetMixin, viewsets.ModelViewSet):
permission = 'can_view_orders'
write_permission = 'can_change_orders'
permission = 'event.orders:read'
write_permission = 'event.orders:write'
def get_serializer_context(self):
ctx = super().get_serializer_context()
@@ -1078,8 +1078,8 @@ class OrderPositionViewSet(viewsets.ModelViewSet):
ordering = ('order__datetime', 'positionid')
ordering_fields = ('order__code', 'order__datetime', 'positionid', 'attendee_name', 'order__status',)
filterset_class = OrderPositionFilter
permission = 'can_view_orders'
write_permission = 'can_change_orders'
permission = 'event.orders:read'
write_permission = 'event.orders:write'
ordering_custom = {
'attendee_name': {
'_order': F('display_name').asc(nulls_first=True),
@@ -1580,8 +1580,8 @@ class OrderPositionViewSet(viewsets.ModelViewSet):
class PaymentViewSet(CreateModelMixin, viewsets.ReadOnlyModelViewSet):
serializer_class = OrderPaymentSerializer
queryset = OrderPayment.objects.none()
permission = 'can_view_orders'
write_permission = 'can_change_orders'
permission = 'event.orders:read'
write_permission = 'event.orders:write'
lookup_field = 'local_id'
def get_serializer_context(self):
@@ -1757,8 +1757,8 @@ class PaymentViewSet(CreateModelMixin, viewsets.ReadOnlyModelViewSet):
class RefundViewSet(CreateModelMixin, viewsets.ReadOnlyModelViewSet):
serializer_class = OrderRefundSerializer
queryset = OrderRefund.objects.none()
permission = 'can_view_orders'
write_permission = 'can_change_orders'
permission = 'event.orders:read'
write_permission = 'event.orders:write'
lookup_field = 'local_id'
def get_queryset(self):
@@ -1915,13 +1915,18 @@ class InvoiceViewSet(viewsets.ReadOnlyModelViewSet):
ordering = ('nr',)
ordering_fields = ('nr', 'date')
filterset_class = InvoiceFilter
permission = 'can_view_orders'
lookup_url_kwarg = 'number'
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):
perm = "can_view_orders" if self.request.method in SAFE_METHODS else "can_change_orders"
perm = "event.orders:read" if self.request.method in SAFE_METHODS else "event.orders:write"
if getattr(self.request, 'event', None):
qs = self.request.event.invoices
elif isinstance(self.request.auth, (TeamAPIToken, Device)):
@@ -2062,8 +2067,8 @@ class RevokedSecretViewSet(viewsets.ReadOnlyModelViewSet):
ordering = ('-created',)
ordering_fields = ('created', 'secret')
filterset_class = RevokedSecretFilter
permission = 'can_view_orders'
write_permission = 'can_change_orders'
permission = 'event.orders:read'
write_permission = 'event.orders:write'
def get_queryset(self):
return RevokedTicketSecret.objects.filter(event=self.request.event)
@@ -2084,8 +2089,8 @@ class BlockedSecretViewSet(viewsets.ReadOnlyModelViewSet):
filter_backends = (DjangoFilterBackend, TotalOrderingFilter)
ordering = ('-updated', '-pk')
filterset_class = BlockedSecretFilter
permission = 'can_view_orders'
write_permission = 'can_change_orders'
permission = 'event.orders:read'
write_permission = 'event.orders:write'
def get_queryset(self):
return BlockedTicketSecret.objects.filter(event=self.request.event)
@@ -2120,7 +2125,7 @@ class TransactionViewSet(viewsets.ReadOnlyModelViewSet):
ordering = ('datetime', 'pk')
ordering_fields = ('datetime', 'created', 'id',)
filterset_class = TransactionFilter
permission = 'can_view_orders'
permission = 'event.orders:read'
def get_queryset(self):
return Transaction.objects.filter(order__event=self.request.event).select_related("order")
@@ -2137,11 +2142,11 @@ class OrganizerTransactionViewSet(TransactionViewSet):
if isinstance(self.request.auth, (TeamAPIToken, Device)):
qs = qs.filter(
order__event__in=self.request.auth.get_events_with_permission("can_view_orders"),
order__event__in=self.request.auth.get_events_with_permission("event.orders:read"),
)
elif self.request.user.is_authenticated:
qs = qs.filter(
order__event__in=self.request.user.get_events_with_permission("can_view_orders", request=self.request)
order__event__in=self.request.user.get_events_with_permission("event.orders:read", request=self.request)
)
else:
raise PermissionDenied("Unknown authentication scheme")

View File

@@ -70,7 +70,7 @@ class OrganizerViewSet(mixins.UpdateModelMixin, viewsets.ReadOnlyModelViewSet):
filter_backends = (TotalOrderingFilter,)
ordering = ('slug',)
ordering_fields = ('name', 'slug')
write_permission = "can_change_organizer_settings"
write_permission = "organizer.settings.general:write"
def get_queryset(self):
if self.request.user.is_authenticated:
@@ -154,8 +154,8 @@ class OrganizerViewSet(mixins.UpdateModelMixin, viewsets.ReadOnlyModelViewSet):
class SeatingPlanViewSet(viewsets.ModelViewSet):
serializer_class = SeatingPlanSerializer
queryset = SeatingPlan.objects.none()
permission = 'can_change_organizer_settings'
write_permission = 'can_change_organizer_settings'
permission = 'organizer.settings.general:write'
write_permission = 'organizer.settings.general:write'
def get_queryset(self):
return self.request.organizer.seating_plans.order_by('name')
@@ -221,8 +221,8 @@ with scopes_disabled():
class GiftCardViewSet(viewsets.ModelViewSet):
serializer_class = GiftCardSerializer
queryset = GiftCard.objects.none()
permission = 'can_manage_gift_cards'
write_permission = 'can_manage_gift_cards'
permission = 'organizer.giftcards:write'
write_permission = 'organizer.giftcards:write'
filter_backends = (DjangoFilterBackend,)
filterset_class = GiftCardFilter
@@ -323,8 +323,8 @@ class GiftCardViewSet(viewsets.ModelViewSet):
class GiftCardTransactionViewSet(viewsets.ReadOnlyModelViewSet):
serializer_class = GiftCardTransactionSerializer
queryset = GiftCardTransaction.objects.none()
permission = 'can_manage_gift_cards'
write_permission = 'can_manage_gift_cards'
permission = 'organizer.giftcards:write'
write_permission = 'organizer.giftcards:write'
@cached_property
def giftcard(self):
@@ -341,8 +341,8 @@ class GiftCardTransactionViewSet(viewsets.ReadOnlyModelViewSet):
class TeamViewSet(viewsets.ModelViewSet):
serializer_class = TeamSerializer
queryset = Team.objects.none()
permission = 'can_change_teams'
write_permission = 'can_change_teams'
permission = 'organizer.teams:write'
write_permission = 'organizer.teams:write'
def get_queryset(self):
return self.request.organizer.teams.order_by('pk')
@@ -381,8 +381,8 @@ class TeamViewSet(viewsets.ModelViewSet):
class TeamMemberViewSet(DestroyModelMixin, viewsets.ReadOnlyModelViewSet):
serializer_class = TeamMemberSerializer
queryset = User.objects.none()
permission = 'can_change_teams'
write_permission = 'can_change_teams'
permission = 'organizer.teams:write'
write_permission = 'organizer.teams:write'
@cached_property
def team(self):
@@ -410,8 +410,8 @@ class TeamMemberViewSet(DestroyModelMixin, viewsets.ReadOnlyModelViewSet):
class TeamInviteViewSet(CreateModelMixin, DestroyModelMixin, viewsets.ReadOnlyModelViewSet):
serializer_class = TeamInviteSerializer
queryset = TeamInvite.objects.none()
permission = 'can_change_teams'
write_permission = 'can_change_teams'
permission = 'organizer.teams:write'
write_permission = 'organizer.teams:write'
@cached_property
def team(self):
@@ -447,8 +447,8 @@ class TeamInviteViewSet(CreateModelMixin, DestroyModelMixin, viewsets.ReadOnlyMo
class TeamAPITokenViewSet(CreateModelMixin, DestroyModelMixin, viewsets.ReadOnlyModelViewSet):
serializer_class = TeamAPITokenSerializer
queryset = TeamAPIToken.objects.none()
permission = 'can_change_teams'
write_permission = 'can_change_teams'
permission = 'organizer.teams:write'
write_permission = 'organizer.teams:write'
@cached_property
def team(self):
@@ -511,8 +511,8 @@ class DeviceViewSet(mixins.CreateModelMixin,
GenericViewSet):
serializer_class = DeviceSerializer
queryset = Device.objects.none()
permission = 'can_change_organizer_settings'
write_permission = 'can_change_organizer_settings'
permission = 'organizer.settings.general:write'
write_permission = 'organizer.settings.general:write'
lookup_field = 'device_id'
def get_queryset(self):
@@ -547,7 +547,7 @@ class DeviceViewSet(mixins.CreateModelMixin,
class OrganizerSettingsView(views.APIView):
permission = None
write_permission = 'can_change_organizer_settings'
write_permission = 'organizer.settings.general:write'
def get(self, request, *args, **kwargs):
s = OrganizerSettingsSerializer(instance=request.organizer.settings, organizer=request.organizer, context={
@@ -597,7 +597,7 @@ with scopes_disabled():
class CustomerViewSet(viewsets.ModelViewSet):
serializer_class = CustomerSerializer
queryset = Customer.objects.none()
permission = 'can_manage_customers'
permission = 'organizer.customers:write'
lookup_field = 'identifier'
filter_backends = (DjangoFilterBackend,)
filterset_class = CustomerFilter
@@ -657,7 +657,7 @@ class CustomerViewSet(viewsets.ModelViewSet):
class MembershipTypeViewSet(viewsets.ModelViewSet):
serializer_class = MembershipTypeSerializer
queryset = MembershipType.objects.none()
permission = 'can_change_organizer_settings'
permission = 'organizer.settings.general:write'
def get_queryset(self):
qs = self.request.organizer.membership_types.all()
@@ -714,7 +714,7 @@ with scopes_disabled():
class MembershipViewSet(viewsets.ModelViewSet):
serializer_class = MembershipSerializer
queryset = Membership.objects.none()
permission = 'can_manage_customers'
permission = 'organizer.customers:write'
filter_backends = (DjangoFilterBackend,)
filterset_class = MembershipFilter
@@ -764,8 +764,8 @@ with scopes_disabled():
class SalesChannelViewSet(viewsets.ModelViewSet):
serializer_class = SalesChannelSerializer
queryset = SalesChannel.objects.none()
permission = 'can_change_organizer_settings'
write_permission = 'can_change_organizer_settings'
permission = 'organizer.settings.general:write'
write_permission = 'organizer.settings.general:write'
filter_backends = (DjangoFilterBackend,)
filterset_class = SalesChannelFilter
lookup_field = 'identifier'

View File

@@ -204,7 +204,7 @@ class ShreddersMixin:
class EventShreddersViewSet(ShreddersMixin, viewsets.ViewSet):
permission = 'can_change_orders'
permission = 'event.orders:write'
def get_serializer_kwargs(self):
return {}

View File

@@ -62,8 +62,8 @@ class VoucherViewSet(viewsets.ModelViewSet):
ordering = ('id',)
ordering_fields = ('id', 'code', 'max_usages', 'valid_until', 'value')
filterset_class = VoucherFilter
permission = 'can_view_vouchers'
write_permission = 'can_change_vouchers'
permission = 'event.vouchers:read'
write_permission = 'event.vouchers:write'
@scopes_disabled() # we have an event check here, and we can save some performance on subqueries
def get_queryset(self):

View File

@@ -51,8 +51,8 @@ class WaitingListViewSet(viewsets.ModelViewSet):
ordering = ('created', 'pk',)
ordering_fields = ('id', 'created', 'email', 'item')
filterset_class = WaitingListFilter
permission = 'can_view_orders'
write_permission = 'can_change_orders'
permission = 'event.orders:read'
write_permission = 'event.orders:write'
def get_queryset(self):
return self.request.event.waitinglistentries.all()

View File

@@ -35,8 +35,8 @@ class WebhookFilter(FilterSet):
class WebHookViewSet(viewsets.ModelViewSet):
serializer_class = WebHookSerializer
queryset = WebHook.objects.none()
permission = 'can_change_organizer_settings'
write_permission = 'can_change_organizer_settings'
permission = 'organizer.settings.general:write'
write_permission = 'organizer.settings.general:write'
filter_backends = (DjangoFilterBackend,)
filterset_class = WebhookFilter

View File

@@ -224,7 +224,7 @@ class HistoryPasswordValidator:
).delete()
def has_event_access_permission(request, permission='can_change_event_settings'):
def has_event_access_permission(request, permission='event.settings.general:write'):
return (
request.user.is_authenticated and
request.user.has_event_permission(request.organizer, request.event, permission, request=request)

View File

@@ -184,7 +184,7 @@ class OrganizerLevelExportMixin:
The permission level required to use this exporter. Only useful for organizer-level exports,
not for event-level exports.
"""
return 'can_view_orders'
return 'event.orders:read'
class ListExporter(BaseExporter):

View File

@@ -47,7 +47,7 @@ from ..signals import register_multievent_data_exporters
class CustomerListExporter(OrganizerLevelExportMixin, ListExporter):
identifier = 'customerlist'
verbose_name = gettext_lazy('Customer accounts')
organizer_required_permission = 'can_manage_customers'
organizer_required_permission = 'organizer.customers:write'
category = pgettext_lazy('export_category', 'Customer accounts')
description = gettext_lazy('Download a spreadsheet of all currently registered customer accounts.')

View File

@@ -1200,7 +1200,7 @@ class QuotaListExporter(ListExporter):
class GiftcardTransactionListExporter(OrganizerLevelExportMixin, ListExporter):
identifier = 'giftcardtransactionlist'
verbose_name = gettext_lazy('Gift card transactions')
organizer_required_permission = 'can_manage_gift_cards'
organizer_required_permission = 'organizer.giftcards:write'
category = pgettext_lazy('export_category', 'Gift cards')
description = gettext_lazy('Download a spreadsheet of all gift card transactions.')
repeatable_read = False
@@ -1307,7 +1307,7 @@ class GiftcardRedemptionListExporter(ListExporter):
class GiftcardListExporter(OrganizerLevelExportMixin, ListExporter):
identifier = 'giftcardlist'
verbose_name = gettext_lazy('Gift cards')
organizer_required_permission = 'can_manage_gift_cards'
organizer_required_permission = 'organizer.giftcards:write'
category = pgettext_lazy('export_category', 'Gift cards')
description = gettext_lazy('Download a spreadsheet of all gift cards including their current value.')

View File

@@ -0,0 +1,129 @@
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",
),
]

View File

@@ -472,7 +472,7 @@ class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
:return: set
"""
teams = self._get_teams_for_event(organizer, event)
sets = [t.permission_set() for t in teams]
sets = [t.event_permission_set() for t in teams]
if sets:
return set.union(*sets)
else:
@@ -486,7 +486,7 @@ class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
:return: set
"""
teams = self._get_teams_for_organizer(organizer)
sets = [t.permission_set() for t in teams]
sets = [t.organizer_permission_set() for t in teams]
if sets:
return set.union(*sets)
else:
@@ -501,7 +501,7 @@ class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
:param organizer: The organizer of the event
:param event: The event to check
:param perm_name: The permission, e.g. ``can_change_teams``
:param perm_name: The permission, e.g. ``event.orders:read``
:param request: The current request (optional)
:param session_key: The current session key (optional)
:return: bool
@@ -513,8 +513,8 @@ class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
if teams:
self._teamcache['e{}'.format(event.pk)] = teams
if isinstance(perm_name, (tuple, list)):
return any([any(team.has_permission(p) for team in teams) for p in perm_name])
if not perm_name or any([team.has_permission(perm_name) for team in teams]):
return any([any(team.has_event_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]):
return True
return False
@@ -524,7 +524,7 @@ class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
to the organizer ``organizer``.
:param organizer: The organizer to check
:param perm_name: The permission, e.g. ``can_change_teams``
:param perm_name: The permission, e.g. ``organizer.events:create``
:param request: The current request (optional). Required to detect staff sessions properly.
:return: bool
"""
@@ -533,8 +533,8 @@ class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
teams = self._get_teams_for_organizer(organizer)
if teams:
if isinstance(perm_name, (tuple, list)):
return any([any(team.has_permission(p) for team in teams) for p in perm_name])
if not perm_name or any([team.has_permission(perm_name) for team in teams]):
return any([any(team.has_organizer_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]):
return True
return False
@@ -565,14 +565,15 @@ class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
:return: Iterable of Events
"""
from .event import Event
from .organizer import TeamQuerySet
if request and self.has_active_staff_session(request.session.session_key):
return Event.objects.all()
if isinstance(permission, (tuple, list)):
q = reduce(operator.or_, [Q(**{p: True}) for p in permission])
q = reduce(operator.or_, [TeamQuerySet.event_permission_q(p) for p in permission])
else:
q = Q(**{permission: True})
q = TeamQuerySet.event_permission_q(permission)
return Event.objects.filter(
Q(organizer_id__in=self.teams.filter(q, all_events=True).values_list('organizer', flat=True))
@@ -605,14 +606,13 @@ class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
:return: Iterable of Organizers
"""
from .event import Organizer
from .organizer import TeamQuerySet
if request and self.has_active_staff_session(request.session.session_key):
return Organizer.objects.all()
kwargs = {permission: True}
return Organizer.objects.filter(
id__in=self.teams.filter(**kwargs).values_list('organizer', flat=True)
id__in=self.teams.filter(TeamQuerySet.organizer_permission_q(permission)).values_list('organizer', flat=True)
)
def has_active_staff_session(self, session_key=None):

View File

@@ -189,13 +189,19 @@ class Device(LoggedModel):
kwargs['update_fields'] = {'device_id'}.union(kwargs['update_fields'])
super().save(*args, **kwargs)
def permission_set(self) -> set:
def _event_permission_set(self) -> set:
return {
'can_view_orders',
'can_change_orders',
'can_view_vouchers',
'can_manage_gift_cards',
'can_manage_reusable_media',
'event.orders:read',
'event.orders:write',
'event.vouchers:read',
}
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:
@@ -209,7 +215,7 @@ class Device(LoggedModel):
has_event_access = (self.all_events and organizer == self.organizer) or (
event in self.limit_events.all()
)
return self.permission_set() if has_event_access else set()
return self._event_permission_set() if has_event_access else set()
def get_organizer_permission_set(self, organizer) -> set:
"""
@@ -218,7 +224,7 @@ class Device(LoggedModel):
:param organizer: The organizer of the event
:return: set of permissions
"""
return self.permission_set() if self.organizer == organizer else set()
return self._organizer_permission_set() if self.organizer == organizer else set()
def has_event_permission(self, organizer, event, perm_name=None, request=None) -> bool:
"""
@@ -227,7 +233,7 @@ class Device(LoggedModel):
:param organizer: The organizer of the event
:param event: The event to check
:param perm_name: The permission, e.g. ``can_change_teams``
:param perm_name: The permission, e.g. ``event.orders:read``
:param request: This parameter is ignored and only defined for compatibility reasons.
:return: bool
"""
@@ -235,8 +241,8 @@ class Device(LoggedModel):
event in self.limit_events.all()
)
if isinstance(perm_name, (tuple, list)):
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.permission_set())
return has_event_access and any(p in self._event_permission_set() for p in perm_name)
return has_event_access and (not perm_name or perm_name in self._event_permission_set())
def has_organizer_permission(self, organizer, perm_name=None, request=None):
"""
@@ -244,13 +250,13 @@ class Device(LoggedModel):
to the organizer ``organizer``.
:param organizer: The organizer to check
:param perm_name: The permission, e.g. ``can_change_teams``
:param perm_name: The permission, e.g. ``organizer.events:create``
:param request: This parameter is ignored and only defined for compatibility reasons.
:return: bool
"""
if isinstance(perm_name, (tuple, list)):
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.permission_set())
return organizer == self.organizer and any(p in self._organizer_permission_set() for p in perm_name)
return organizer == self.organizer and (not perm_name or perm_name in self._organizer_permission_set())
def get_events_with_any_permission(self):
"""
@@ -271,8 +277,8 @@ class Device(LoggedModel):
:return: Iterable of Events
"""
if (
isinstance(permission, (list, tuple)) and any(p in self.permission_set() for p in permission)
) or (isinstance(permission, str) and permission in self.permission_set()):
isinstance(permission, (list, tuple)) and any(p in self._event_permission_set() for p in permission)
) or (isinstance(permission, str) and permission in self._event_permission_set()):
return self.get_events_with_any_permission()
else:
return self.organizer.events.none()

View File

@@ -1386,14 +1386,13 @@ class Event(EventMixin, LoggedModel):
from .auth import User
if permission:
kwargs = {permission: True}
qs = Team.objects.with_event_permission(permission)
else:
kwargs = {}
qs = Team.objects.all()
team_with_perm = Team.objects.filter(
team_with_perm = qs.filter(
members__pk=OuterRef('pk'),
organizer=self.organizer,
**kwargs
).filter(
Q(all_events=True) | Q(limit_events__pk=self.pk)
)

View File

@@ -31,9 +31,10 @@
# 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
# License for the specific language governing permissions and limitations under the License.
import operator
import string
from datetime import date, datetime, time
from functools import reduce
import pytz_deprecation_shim
from django.conf import settings
@@ -53,6 +54,10 @@ from i18nfield.strings import LazyI18nString
from pretix.base.models.base import LoggedModel
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 .auth import User
@@ -309,6 +314,32 @@ def generate_api_token():
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):
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]])
return (
Q(all_event_permissions=True) |
Q(**{f'limit_event_permissions__{perm_name}': True})
)
@classmethod
def organizer_permission_q(cls, perm_name):
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]])
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):
"""
A team is a collection of people given certain access rights to one or more events of an organizer.
@@ -321,36 +352,10 @@ class Team(LoggedModel):
:param all_events: Whether this team has access to all events of this organizer
:type all_events: bool
: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)
name = models.CharField(max_length=190, verbose_name=_("Team name"))
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(
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 "
@@ -358,62 +363,33 @@ class Team(LoggedModel):
"all users.")
)
can_create_events = models.BooleanField(
default=False,
verbose_name=_("Can create events"),
)
can_change_teams = models.BooleanField(
default=False,
verbose_name=_("Can change teams and permissions"),
)
can_change_organizer_settings = models.BooleanField(
default=False,
verbose_name=_("Can change organizer settings"),
help_text=_('Someone with this setting can get access to most data of all of your events, i.e. via privacy '
'reports, so be careful who you add to this team!')
)
can_manage_customers = models.BooleanField(
default=False,
verbose_name=_("Can manage customer accounts")
)
can_manage_reusable_media = models.BooleanField(
default=False,
verbose_name=_("Can manage reusable media")
)
can_manage_gift_cards = models.BooleanField(
default=False,
verbose_name=_("Can manage gift cards")
)
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")
)
# Scope
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)
# Permissions
# We store them as {key: True} instead of [key] because otherwise not all lookups we need are supported on SQLite
all_event_permissions = models.BooleanField(default=False, verbose_name=_("All event permissions"))
limit_event_permissions = models.JSONField(default=dict, verbose_name=_("Event permissions"))
all_organizer_permissions = models.BooleanField(default=False, verbose_name=_("All organizer permissions"))
limit_organizer_permissions = models.JSONField(default=dict, verbose_name=_("Organizer permissions"))
# Legacy lookups for plugin compatibility
can_change_event_settings = LegacyPermissionProperty()
can_change_items = LegacyPermissionProperty()
can_view_orders = LegacyPermissionProperty()
can_change_orders = LegacyPermissionProperty()
can_checkin_orders = LegacyPermissionProperty()
can_view_vouchers = LegacyPermissionProperty()
can_change_vouchers = LegacyPermissionProperty()
can_create_events = LegacyPermissionProperty()
can_change_organizer_settings = LegacyPermissionProperty()
can_change_teams = LegacyPermissionProperty()
can_manage_gift_cards = LegacyPermissionProperty()
can_manage_customers = LegacyPermissionProperty()
can_manage_reusable_media = LegacyPermissionProperty()
objects = TeamQuerySet.as_manager()
def __str__(self) -> str:
return _("%(name)s on %(object)s") % {
@@ -421,21 +397,51 @@ class Team(LoggedModel):
'object': str(self.organizer),
}
def permission_set(self) -> set:
attribs = dir(self)
return {
a for a in attribs if a.startswith('can_') and self.has_permission(a)
}
def event_permission_set(self, include_legacy=True) -> set:
from ..permissions import get_all_event_permissions
result = set()
for permission in get_all_event_permissions().keys():
if self.all_event_permissions or self.limit_event_permissions.get(permission):
result.add(permission)
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)
return result
def organizer_permission_set(self, include_legacy=True) -> set:
from ..permissions import get_all_organizer_permissions
result = set()
for permission in get_all_organizer_permissions().keys():
if self.all_organizer_permissions or self.limit_organizer_permissions.get(permission):
result.add(permission)
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
def can_change_settings(self): # Legacy compatiblilty
def can_change_settings(self): # Legacy compatibility
return self.can_change_event_settings
def has_permission(self, perm_name):
try:
def has_event_permission(self, perm_name):
if perm_name.startswith('can_') and hasattr(self, perm_name): # legacy
return getattr(self, perm_name)
except AttributeError:
raise ValueError('Invalid required permission: %s' % perm_name)
return self.all_event_permissions or self.limit_event_permissions.get(perm_name, False)
def has_organizer_permission(self, perm_name):
if perm_name.startswith('can_') and hasattr(self, perm_name): # legacy
return getattr(self, perm_name)
return self.all_organizer_permissions or self.limit_organizer_permissions.get(perm_name, False)
def permission_for_event(self, event):
if self.all_events:
@@ -447,6 +453,19 @@ class Team(LoggedModel):
def active_tokens(self):
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:
verbose_name = _("Team")
verbose_name_plural = _("Teams")
@@ -503,7 +522,7 @@ class TeamAPIToken(models.Model):
has_event_access = (self.team.all_events and organizer == self.team.organizer) or (
event in self.team.limit_events.all()
)
return self.team.permission_set() if has_event_access else set()
return self.team.event_permission_set() if has_event_access else set()
def get_organizer_permission_set(self, organizer) -> set:
"""
@@ -512,7 +531,7 @@ class TeamAPIToken(models.Model):
:param organizer: The organizer of the event
:return: set of permissions
"""
return self.team.permission_set() if self.team.organizer == organizer else set()
return self.team.organizer_permission_set() if self.team.organizer == organizer else set()
def has_event_permission(self, organizer, event, perm_name=None, request=None) -> bool:
"""
@@ -521,7 +540,7 @@ class TeamAPIToken(models.Model):
:param organizer: The organizer of the event
:param event: The event to check
:param perm_name: The permission, e.g. ``can_change_teams``
:param perm_name: The permission, e.g. ``event.orders:read``
:param request: This parameter is ignored and only defined for compatibility reasons.
:return: bool
"""
@@ -529,8 +548,8 @@ class TeamAPIToken(models.Model):
event in self.team.limit_events.all()
)
if isinstance(perm_name, (tuple, list)):
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_permission(perm_name))
return has_event_access and any(self.team.has_event_permission(p) for p in perm_name)
return has_event_access and (not perm_name or self.team.has_event_permission(perm_name))
def has_organizer_permission(self, organizer, perm_name=None, request=None):
"""
@@ -538,13 +557,13 @@ class TeamAPIToken(models.Model):
to the organizer ``organizer``.
:param organizer: The organizer to check
:param perm_name: The permission, e.g. ``can_change_teams``
:param perm_name: The permission, e.g. ``organizer:events.create``
:param request: This parameter is ignored and only defined for compatibility reasons.
:return: bool
"""
if isinstance(perm_name, (tuple, list)):
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_permission(perm_name))
return organizer == self.team.organizer and any(self.team.has_organizer_permission(p) for p in perm_name)
return organizer == self.team.organizer and (not perm_name or self.team.has_organizer_permission(perm_name))
def get_events_with_any_permission(self):
"""
@@ -565,8 +584,8 @@ class TeamAPIToken(models.Model):
:return: Iterable of Events
"""
if (
isinstance(permission, (list, tuple)) and any(getattr(self.team, p, False) for p in permission)
) or (isinstance(permission, str) and getattr(self.team, permission, False)):
isinstance(permission, (list, tuple)) and any(self.team.has_event_permission(p) for p in permission)
) or (isinstance(permission, str) and self.team.has_event_permission(permission)):
return self.get_events_with_any_permission()
else:
return self.team.organizer.events.none()

View File

@@ -151,7 +151,7 @@ def get_all_notification_types(event=None):
class ParametrizedOrderNotificationType(NotificationType):
required_permission = "can_view_orders"
required_permission = "event.orders:read"
def __init__(self, event, action_type, verbose_name, title):
self._action_type = action_type

View File

@@ -0,0 +1,113 @@
#
# 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
from collections import OrderedDict, namedtuple
from django.dispatch import receiver
from django.utils.translation import gettext_lazy as _, pgettext_lazy
from pretix.base.signals import (
register_event_permissions, register_organizer_permissions,
)
logger = logging.getLogger(__name__)
_ALL_EVENT_PERMISSIONS = None
_ALL_ORGANIZER_PERMISSIONS = None
Permission = namedtuple('Permission', ('name', 'label', 'plugin_name', 'help_text'))
def get_all_event_permissions():
global _ALL_EVENT_PERMISSIONS
if _ALL_EVENT_PERMISSIONS:
return _ALL_EVENT_PERMISSIONS
types = OrderedDict()
for recv, ret in register_event_permissions.send(None):
if isinstance(ret, (list, tuple)):
for r in ret:
types[r.name] = r
else:
types[ret.name] = ret
_ALL_EVENT_PERMISSIONS = types
return types
def get_all_organizer_permissions():
global _ALL_ORGANIZER_PERMISSIONS
if _ALL_ORGANIZER_PERMISSIONS:
return _ALL_ORGANIZER_PERMISSIONS
types = OrderedDict()
for recv, ret in register_organizer_permissions.send(None):
if isinstance(ret, (list, tuple)):
for r in ret:
types[r.name] = r
else:
types[ret.name] = ret
_ALL_ORGANIZER_PERMISSIONS = types
return types
@receiver(register_event_permissions, dispatch_uid="base_register_default_event_permissions")
def register_default_event_permissions(sender, **kwargs):
return [
Permission("event.settings.general:write", _("Change general settings"), None,
_("This includes access to all settings not listed explicitly below, including plugin settings.")),
Permission("event.settings.payment:write", _("Change payment settings"), None, None),
Permission("event.settings.plugins:write", _("Change plugin settings"), None, None),
Permission("event.settings.email.sender:write", _("Change email sending settings"), None, None),
Permission("event.settings.tax:write", _("Change tax rules"), None, None),
Permission("event.settings.invoicing:write", _("Change invoicing settings"), None, None),
Permission("event.subevents:write", pgettext_lazy("subevent", "Change event series dates"), None, None),
Permission("event.items:write", _("Change products and quotas"), None, None), # and questions but that might change?
Permission("event.orders:read", _("View orders"), None, None),
Permission("event.orders:write", _("Change orders"), None, _("This includes the ability to cancel and refund individual orders.")),
Permission("event.orders:checkin", _("Check-in orders"), None, None),
Permission("event.vouchers:read", _("View vouchers"), None, None),
Permission("event.vouchers:write", _("Change vouchers"), None, None),
Permission("event:cancel", pgettext_lazy("subevent", " entire event or date"), None, None),
]
@receiver(register_organizer_permissions, dispatch_uid="base_register_default_organizer_permissions")
def register_default_organizer_permissions(sender, **kwargs):
return [
Permission("organizer.events:create", _("Create events"), None, None),
Permission("organizer.settings.general:write", _("Change settings"), None,
_("This includes access to all organizer-level functionality not listed explicitly below, including plugin settings.")),
Permission("organizer.teams:write", _("Change teams"), None,
_("This includes the ability to give someone (including oneself) additional permissions.")),
Permission("organizer.giftcards:read", _("View gift cards"), None, None),
Permission("organizer.giftcards:write", _("Change gift cards"), None, None),
Permission("organizer.customers:read", _("View customer accounts"), None, None),
Permission("organizer.customers:write", _("Change customer accounts"), None, None),
Permission("organizer.reusablemedia:read", _("View reusable media"), None, None),
Permission("organizer.reusablemedia:write", _("Change reusable media"), None, None),
Permission("organizer.devices:read", _("View devices"), None, None),
Permission("organizer.devices:write", _("Change devices"), None,
_("This includes the ability to give access to events and data oneself does not have access to.")),
]

View File

@@ -106,7 +106,7 @@ def multiexport(self, organizer: Organizer, user: User, device: int, token: int,
device = Device.objects.get(pk=device)
if token:
device = TeamAPIToken.objects.get(pk=token)
allowed_events = (device or token or user).get_events_with_permission('can_view_orders')
allowed_events = (device or token or user).get_events_with_permission('event.orders:read')
if user and staff_session:
allowed_events = organizer.events.all()
@@ -291,7 +291,7 @@ def _run_scheduled_export(schedule, context: Union[Event, Organizer], exporter,
def scheduled_organizer_export(self, organizer: Organizer, schedule: int) -> None:
schedule = organizer.scheduled_exports.get(pk=schedule)
allowed_events = schedule.owner.get_events_with_permission('can_view_orders')
allowed_events = schedule.owner.get_events_with_permission('event.orders:read')
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):
events = allowed_events.filter(slug__in=schedule.export_form_data.get('events'), organizer=organizer)
@@ -346,7 +346,7 @@ def scheduled_event_export(self, event: Event, schedule: int) -> None:
exporter = ex
break
has_permission = schedule.owner.is_active and schedule.owner.has_event_permission(event.organizer, event, 'can_view_orders')
has_permission = schedule.owner.is_active and schedule.owner.has_event_permission(event.organizer, event, 'event.orders:read')
_run_scheduled_export(
schedule,

View File

@@ -561,6 +561,18 @@ however for this signal, the ``sender`` **may also be None** to allow creating t
notification settings!
"""
register_event_permissions = GlobalSignal()
"""
This signal is sent out to get all known permissions. Receivers should return an
instance of pretix.base.permissions.Permission or a list of such instances.
"""
register_organizer_permissions = GlobalSignal()
"""
This signal is sent out to get all known permissions. Receivers should return an
instance of pretix.base.permissions.Permission or a list of such instances.
"""
notification = EventPluginSignal()
"""
Arguments: ``logentry_id``, ``notification_type``

View File

@@ -102,7 +102,7 @@ def _default_context(request):
complain_testmode_orders = request.event.orders.filter(testmode=True).exists()
request.event.cache.set('complain_testmode_orders', complain_testmode_orders, 30)
ctx['complain_testmode_orders'] = complain_testmode_orders and request.user.has_event_permission(
request.organizer, request.event, 'can_view_orders', request=request
request.organizer, request.event, 'event.orders:read', request=request
)
else:
ctx['complain_testmode_orders'] = False

View File

@@ -62,6 +62,7 @@ from pretix.base.forms import (
)
from pretix.base.models import Event, Organizer, TaxRule, Team
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.reldate import RelativeDateField, RelativeDateTimeField
from pretix.base.services.placeholders import FormPlaceholderMixin
@@ -104,7 +105,7 @@ class EventWizardFoundationForm(forms.Form):
qs = Organizer.objects.all()
if not self.user.has_active_staff_session(self.session.session_key):
qs = qs.filter(
id__in=self.user.teams.filter(can_create_events=True).values_list('organizer', flat=True)
id__in=self.user.teams.filter(TeamQuerySet.organizer_permission_q("organizer.events:create")).values_list('organizer', flat=True)
)
self.fields['organizer'] = forms.ModelChoiceField(
label=_("Organizer"),
@@ -262,8 +263,12 @@ class EventWizardBasicsForm(I18nModelForm):
@staticmethod
def has_control_rights(user, organizer, session):
return user.teams.filter(
organizer=organizer, all_events=True, can_change_event_settings=True, can_change_items=True,
can_change_orders=True, can_change_vouchers=True
TeamQuerySet.event_permission_q("event.items:write"),
TeamQuerySet.event_permission_q("event.orders:write"),
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)
@@ -294,9 +299,14 @@ class EventWizardCopyForm(forms.Form):
return Event.objects.all()
return Event.objects.filter(
Q(organizer_id__in=user.teams.filter(
all_events=True, can_change_event_settings=True, can_change_items=True
# TODO: review these!
# 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(
can_change_event_settings=True, can_change_items=True
TeamQuerySet.event_permission_q("event.settings.general:write"),
TeamQuerySet.event_permission_q("event.items:write"),
).values_list('limit_events__id', flat=True))
)

View File

@@ -1110,7 +1110,7 @@ class OrderPaymentSearchFilterForm(forms.Form):
self.fields['organizer'].queryset = Organizer.objects.filter(
pk__in=self.request.user.teams.values_list('organizer', flat=True)
)
self.fields['event'].queryset = self.request.user.get_events_with_permission('can_view_orders')
self.fields['event'].queryset = self.request.user.get_events_with_permission('event.orders:read')
self.fields['provider'].choices += get_all_payment_providers()

View File

@@ -75,7 +75,10 @@ from pretix.base.models import (
ReusableMedium, SalesChannel, Team,
)
from pretix.base.models.customers import CustomerSSOClient, CustomerSSOProvider
from pretix.base.models.organizer import OrganizerFooterLink
from pretix.base.models.organizer import OrganizerFooterLink, TeamQuerySet
from pretix.base.permissions import (
get_all_event_permissions, get_all_organizer_permissions,
)
from pretix.base.settings import (
PERSON_NAME_SCHEMES, PERSON_NAME_TITLE_GROUPS, validate_organizer_settings,
)
@@ -297,6 +300,18 @@ class MembershipTypeForm(I18nModelForm):
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):
def __init__(self, *args, **kwargs):
@@ -305,16 +320,50 @@ class TeamForm(forms.ModelForm):
self.fields['limit_events'].queryset = organizer.events.all().order_by(
'-has_subevents', '-date_from'
)
self.fields['limit_event_permissions'] = PermissionMultipleChoiceField(
label=self.fields['limit_event_permissions'].label,
choices=[
(
p.name,
format_html(
'{} <span class="fa fa-info-circle text-muted" data-toggle="tooltip" title="{}"></span> (<code>{}</code>)',
p.label, p.help_text, p.name
) if p.help_text
else format_html('{} (<code>{}</code>)', p.label, p.name)
)
for p in get_all_event_permissions().values()
],
widget=forms.CheckboxSelectMultiple(attrs={
'data-inverse-dependency': '#id_all_event_permissions',
'class': 'scrolling-multiple-choice scrolling-multiple-choice-large',
}),
required=False,
)
self.fields['limit_organizer_permissions'] = PermissionMultipleChoiceField(
label=self.fields['limit_organizer_permissions'].label,
choices=[
(
p.name,
format_html(
'{} <span class="fa fa-info-circle text-muted" data-toggle="tooltip" title="{}"></span> (<code>{}</code>)',
p.label, p.help_text, p.name
) if p.help_text
else format_html('{} (<code>{}</code>)', p.label, p.name)
)
for p in get_all_organizer_permissions().values()
],
widget=forms.CheckboxSelectMultiple(attrs={
'data-inverse-dependency': '#id_all_organizer_permissions',
'class': 'scrolling-multiple-choice scrolling-multiple-choice-large',
}),
required=False,
)
class Meta:
model = Team
fields = ['name', 'require_2fa', 'all_events', 'limit_events', 'can_create_events',
'can_change_teams', 'can_change_organizer_settings',
'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']
fields = ['name', 'require_2fa', 'all_events', 'limit_events',
'all_event_permissions', 'limit_event_permissions',
'all_organizer_permissions', 'limit_organizer_permissions']
widgets = {
'limit_events': forms.CheckboxSelectMultiple(attrs={
'data-inverse-dependency': '#id_all_events',
@@ -327,9 +376,10 @@ class TeamForm(forms.ModelForm):
def clean(self):
data = super().clean()
if self.instance.pk and not data['can_change_teams']:
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(
can_change_teams=True, members__isnull=False
TeamQuerySet.organizer_permission_q("organizer.teams:write"),
members__isnull=False
).exists():
raise ValidationError(_('The changes could not be saved because there would be no remaining team with '
'the permission to change teams and permissions.'))

View File

@@ -43,7 +43,7 @@ def get_event_navigation(request: HttpRequest):
'icon': 'dashboard',
}
]
if 'can_change_event_settings' in request.eventpermset:
if 'event.settings.general:write' in request.eventpermset:
event_settings = [
{
'label': _('General'),
@@ -133,7 +133,7 @@ def get_event_navigation(request: HttpRequest):
'children': event_settings
})
if 'can_change_items' in request.eventpermset:
if 'event.items:write' in request.eventpermset:
nav.append({
'label': _('Products'),
'url': reverse('control:event.items', kwargs={
@@ -187,7 +187,7 @@ def get_event_navigation(request: HttpRequest):
]
})
if 'can_change_event_settings' in request.eventpermset:
if 'event.settings.general:write' in request.eventpermset:
if request.event.has_subevents:
nav.append({
'label': pgettext_lazy('subevent', 'Dates'),
@@ -199,7 +199,7 @@ def get_event_navigation(request: HttpRequest):
'icon': 'calendar',
})
if 'can_view_orders' in request.eventpermset:
if 'event.orders:read' in request.eventpermset:
children = [
{
'label': _('All orders'),
@@ -242,7 +242,7 @@ def get_event_navigation(request: HttpRequest):
'active': 'event.orders.waitinglist' in url.url_name,
},
]
if 'can_change_orders' in request.eventpermset:
if 'event.orders:write' in request.eventpermset:
children.append({
'label': _('Import'),
'url': reverse('control:event.orders.import', kwargs={
@@ -262,7 +262,7 @@ def get_event_navigation(request: HttpRequest):
'children': children
})
if 'can_view_vouchers' in request.eventpermset:
if 'event.vouchers:read' in request.eventpermset:
nav.append({
'label': _('Vouchers'),
'url': reverse('control:event.vouchers', kwargs={
@@ -291,7 +291,7 @@ def get_event_navigation(request: HttpRequest):
]
})
if 'can_view_orders' in request.eventpermset:
if 'event.orders:read' in request.eventpermset:
nav.append({
'label': pgettext_lazy('navigation', 'Check-in'),
'url': reverse('control:event.orders.checkinlists', kwargs={
@@ -480,7 +480,7 @@ def get_organizer_navigation(request):
'icon': 'calendar',
},
]
if 'can_change_organizer_settings' in request.orgapermset:
if 'organizer.settings.general:write' in request.orgapermset:
nav.append({
'label': _('Settings'),
'url': reverse('control:organizer.edit', kwargs={
@@ -534,7 +534,7 @@ def get_organizer_navigation(request):
]
})
if 'can_change_teams' in request.orgapermset:
if 'organizer.teams:write' in request.orgapermset:
nav.append({
'label': _('Teams'),
'url': reverse('control:organizer.teams', kwargs={
@@ -544,7 +544,7 @@ def get_organizer_navigation(request):
'icon': 'group',
})
if 'can_manage_gift_cards' in request.orgapermset:
if 'organizer.giftcards:write' in request.orgapermset:
children = []
children.append({
'label': _('Gift cards'),
@@ -554,7 +554,7 @@ def get_organizer_navigation(request):
'active': 'organizer.giftcard' in url.url_name and 'acceptance' not in url.url_name,
'children': children,
})
if 'can_change_organizer_settings' in request.orgapermset:
if 'organizer.settings.general:write' in request.orgapermset:
children.append(
{
'label': _('Acceptance'),
@@ -575,7 +575,7 @@ def get_organizer_navigation(request):
if request.organizer.settings.customer_accounts:
children = []
if 'can_manage_customers' in request.orgapermset:
if 'organizer.customers:write' in request.orgapermset:
children.append(
{
'label': _('Customers'),
@@ -585,7 +585,7 @@ def get_organizer_navigation(request):
'active': 'organizer.customer' in url.url_name,
}
)
if 'can_change_organizer_settings' in request.orgapermset:
if 'organizer.settings.general:write' in request.orgapermset:
children.append(
{
'label': _('Membership types'),
@@ -633,7 +633,7 @@ def get_organizer_navigation(request):
'active': 'organizer.reusable_medi' in url.url_name,
})
if 'can_change_organizer_settings' in request.orgapermset:
if 'organizer.settings.general:write' in request.orgapermset:
nav.append({
'label': _('Devices'),
'url': reverse('control:organizer.devices', kwargs={
@@ -667,7 +667,7 @@ def get_organizer_navigation(request):
'icon': 'download',
})
if 'can_change_organizer_settings' in request.orgapermset:
if 'organizer.settings.general:write' in request.orgapermset:
merge_in(nav, [{
'parent': reverse('control:organizer.export', kwargs={
'organizer': request.organizer.slug,

View File

@@ -53,9 +53,9 @@ def event_permission_required(permission):
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.
"""
if permission == 'can_change_settings':
if permission == 'event.settings.general:write':
# Legacy support
permission = 'can_change_event_settings'
permission = 'event.settings.general:write'
def decorator(function):
def wrapper(request, *args, **kw):
@@ -92,9 +92,9 @@ def organizer_permission_required(permission):
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.
"""
if permission == 'can_change_settings':
if permission == 'event.settings.general:write':
# Legacy support
permission = 'can_change_organizer_settings'
permission = 'organizer.settings.general:write'
def decorator(function):
def wrapper(request, *args, **kw):

View File

@@ -9,7 +9,7 @@
{% block content %}
<h1>
{% blocktrans with name=checkinlist.name %}Check-in list: {{ name }}{% endblocktrans %}
{% if 'can_change_event_settings' in request.eventpermset %}
{% if 'event.settings.general:write' in request.eventpermset %}
<a href="{% url "control:event.orders.checkinlists.edit" event=request.event.slug organizer=request.event.organizer.slug list=checkinlist.pk %}"
class="btn btn-default">
<span class="fa fa-wrench"></span>
@@ -87,7 +87,7 @@
<thead>
<tr>
<th>
{% if "can_change_orders" in request.eventpermset or "can_checkin_orders" in request.eventpermset %}
{% if "event.orders:write" in request.eventpermset or "event.orders:checkin" in request.eventpermset %}
<label aria-label="{% trans "select all rows for batch-operation" %}"
class="batch-select-label"><input type="checkbox" data-toggle-table/></label>
{% endif %}
@@ -132,7 +132,7 @@
{% for e in entries %}
<tr>
<td>
{% if "can_change_orders" in request.eventpermset or "can_checkin_orders" in request.eventpermset %}
{% if "event.orders:write" in request.eventpermset or "event.orders:checkin" in request.eventpermset %}
<input type="checkbox" name="checkin" id="id_checkin" class="" value="{{ e.pk }}"/>
{% endif %}
</td>
@@ -207,7 +207,7 @@
</table>
</div>
<div class="batch-select-actions">
{% if "can_change_orders" in request.eventpermset or "can_checkin_orders" in request.eventpermset %}
{% if "event.orders:write" in request.eventpermset or "event.orders:checkin" in request.eventpermset %}
<button type="submit" class="btn btn-primary btn-save">
<span class="fa fa-sign-in" aria-hidden="true"></span>
{% trans "Check-In selected attendees" %}
@@ -217,7 +217,7 @@
{% trans "Check-Out selected attendees" %}
</button>
{% endif %}
{% if "can_change_orders" in request.eventpermset %}
{% if "event.orders:write" in request.eventpermset %}
<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 %}"
data-no-asynctask

View File

@@ -63,7 +63,7 @@
{% endif %}
</p>
{% if "can_change_event_settings" in request.eventpermset %}
{% 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 %}"
class="btn btn-primary btn-lg"><i class="fa fa-plus"></i> {% trans "Create a new check-in list" %}
</a>
@@ -75,7 +75,7 @@
</div>
{% else %}
<p>
{% if "can_change_event_settings" in request.eventpermset %}
{% 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 %}"
class="btn btn-default"><i class="fa fa-plus"></i> {% trans "Create a new check-in list" %}</a>
{% endif %}
@@ -83,7 +83,7 @@
<a href="{% url "control:organizer.devices" organizer=request.organizer.slug %}"
class="btn btn-default"><i class="fa fa-tablet"></i> {% trans "Connected devices" %}</a>
{% endif %}
{% if "can_change_orders" in request.eventpermset %}
{% if "event.settings.general:write" in request.eventpermset %}
<a href="{% url "control:event.orders.checkinlists.reset" organizer=request.event.organizer.slug event=request.event.slug %}"
class="btn btn-default">
<span class="fa fa-repeat"></span>
@@ -158,7 +158,7 @@
<td class="text-right flip">
<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>
{% if "can_change_event_settings" in request.eventpermset %}
{% 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 }}"
class="btn btn-sm btn-default" title="{% trans "Clone" %}" data-toggle="tooltip">
<span class="fa fa-copy"></span>

View File

@@ -9,7 +9,7 @@
{% block inside %}
<h1>
{% blocktrans with name=checkinlist.name %}Check-in list: {{ name }}{% endblocktrans %}
{% if 'can_change_event_settings' in request.eventpermset %}
{% if 'event.settings.general:write' in request.eventpermset %}
<a href="{% url "control:event.orders.checkinlists.edit" event=request.event.slug organizer=request.event.organizer.slug list=checkinlist.pk %}"
class="btn btn-default">
<span class="fa fa-wrench"></span>

View File

@@ -7,7 +7,7 @@
{% block inside %}
<h1>
{% blocktrans with name=quota.name %}Quota: {{ name }}{% endblocktrans %}
{% if 'can_change_items' in request.eventpermset %}
{% if 'event.items:write' in request.eventpermset %}
<a href="{% url "control:event.items.quotas.edit" event=request.event.slug organizer=request.event.organizer.slug quota=quota.pk %}"
class="btn btn-default">
<span class="fa fa-edit"></span>

View File

@@ -26,7 +26,7 @@
{% endif %}
{% include "pretixcontrol/orders/fragment_order_status.html" with order=order class="pull-right flip" %}
</h1>
{% if 'can_change_orders' in request.eventpermset %}
{% if 'event.orders:write' in request.eventpermset %}
<form action="{% url "control:event.order.transition" event=request.event.slug organizer=request.event.organizer.slug code=order.code %}"
method="post">
{% csrf_token %}
@@ -193,7 +193,7 @@
<dt>{% trans "Order locale" %}</dt>
<dd>
{{ display_locale }}
{% if "can_change_orders" in request.eventpermset %}
{% if "event.orders:write" 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">
<span class="fa fa-edit"></span>
</a>
@@ -220,7 +220,7 @@
{{ order.customer.identifier }} {{ order.customer.email }}
</a>
{% endif %}
{% if "can_change_orders" in request.eventpermset %}
{% if "event.orders:write" 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">
<span class="fa fa-edit"></span>
</a>
@@ -233,7 +233,7 @@
{% 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>
{% endif %}
{% if "can_change_orders" in request.eventpermset %}
{% if "event.orders:write" 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">
<span class="fa fa-edit"></span>
</a>
@@ -257,7 +257,7 @@
<dt>{% trans "Phone number" %}</dt>
<dd>
{{ order.phone|default_if_none:""|phone_format }}
{% if "can_change_orders" in request.eventpermset %}
{% if "event.orders:write" 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">
<span class="fa fa-edit"></span>
</a>
@@ -371,7 +371,7 @@
<br/>
{% endif %}
{% endfor %}
{% if can_generate_invoice and 'can_change_orders' in request.eventpermset %}
{% if can_generate_invoice and 'event.orders:write' in request.eventpermset %}
<br/>
<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 %}">
@@ -382,7 +382,7 @@
</form>
{% endif %}
</dd>
{% elif can_generate_invoice and 'can_change_orders' in request.eventpermset %}
{% elif can_generate_invoice and 'event.orders:write' in request.eventpermset %}
<dt>{% trans "Invoices" %}</dt>
<dd>
<form class="form-inline helper-display-inline" method="post"
@@ -400,7 +400,7 @@
<div class="panel panel-default items">
<div class="panel-heading">
<div class="pull-right flip">
{% if 'can_change_orders' in request.eventpermset %}
{% if 'event.orders:write' in request.eventpermset %}
<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>
{% trans "Change answers" %}
@@ -893,7 +893,7 @@
{% endfor %}
</tbody>
</table>
{% if order.payment_refund_sum > 0 and "can_change_orders" in request.eventpermset %}
{% if order.payment_refund_sum > 0 and "event.orders:write" 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">
{% trans "Create a refund" %}
</a>
@@ -1012,7 +1012,7 @@
<div class="panel panel-default">
<div class="panel-heading">
<div class="pull-right flip">
{% if 'can_change_orders' in request.eventpermset %}
{% if 'event.orders:write' in request.eventpermset %}
<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>
{% trans "Change" %}
@@ -1088,7 +1088,7 @@
{% bootstrap_field comment_form.custom_followup_at %}
{% bootstrap_field comment_form.checkin_attention show_help=True show_label=False %}
{% bootstrap_field comment_form.checkin_text show_help=True show_label=False %}
{% if "can_change_orders" in request.eventpermset %}
{% if "event.orders:write" in request.eventpermset %}
<button class="btn btn-default">
{% trans "Update comment" %}
</button>

View File

@@ -122,7 +122,7 @@
<table class="table table-condensed table-hover table-orders">
<thead>
<tr>
{% if "can_change_orders" in request.eventpermset %}
{% if "event.orders:write" in request.eventpermset %}
<th>
<label aria-label="{% trans "select all rows for batch-operation" %}"
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>
</th>
</tr>
{% if page_obj.paginator.num_pages > 1 and "can_change_orders" in request.eventpermset %}
{% if page_obj.paginator.num_pages > 1 and "event.orders:write" in request.eventpermset %}
<tr class="table-select-all warning hidden">
<td>
<input type="checkbox" name="__ALL" id="__all"
@@ -171,7 +171,7 @@
<tbody>
{% for o in orders %}
<tr>
{% if "can_change_orders" in request.eventpermset %}
{% if "event.orders:write" in request.eventpermset %}
<td>
<label aria-label="{% trans "select row for batch-operation" %}"
class="batch-select-label"><input type="checkbox" name="order"
@@ -281,7 +281,7 @@
{% endif %}
</table>
</div>
{% if "can_change_orders" in request.eventpermset %}
{% if "event.orders:write" in request.eventpermset %}
<div class="batch-select-actions">
<div class="btn-group dropup">
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown"

View File

@@ -6,7 +6,7 @@
{% blocktrans with name=request.organizer.name %}Organizer: {{ name }}{% endblocktrans %}
</h1>
{% if events|length == 0 and not filter_form.filtered %}
{% if "can_create_events" in request.orgapermset %}
{% if "organizer.events:create" in request.orgapermset %}
<p>
<a href="{% url "control:events.add" %}?organizer={{ request.organizer.slug }}" class="btn btn-primary">
<span class="fa fa-plus"></span>
@@ -50,7 +50,7 @@
</div>
</form>
</div>
{% if "can_create_events" in request.orgapermset %}
{% if "organizer.events:create" in request.orgapermset %}
<p>
<a href="{% url "control:events.add" %}?organizer={{ request.organizer.slug }}" class="btn btn-primary">
<span class="fa fa-plus"></span>
@@ -125,7 +125,7 @@
data-toggle="tooltip">
<span class="fa fa-eye"></span>
</a>
{% if "can_create_events" in request.orgapermset %}
{% if "organizer.events:create" in request.orgapermset %}
<a href="{% url "control:events.add" %}?clone={{ e.pk }}" class="btn btn-sm btn-default"
title="{% trans "Clone event" %}" data-toggle="tooltip">
<span class="fa fa-copy"></span>

View File

@@ -22,25 +22,16 @@
</fieldset>
<fieldset>
<legend>{% trans "Organizer permissions" %}</legend>
{% bootstrap_field form.can_create_events layout="control" %}
{% bootstrap_field form.can_manage_gift_cards layout="control" %}
{% bootstrap_field form.can_manage_customers layout="control" %}
{% bootstrap_field form.can_manage_reusable_media layout="control" %}
{% bootstrap_field form.can_change_teams layout="control" %}
{% bootstrap_field form.can_change_organizer_settings layout="control" %}
{% bootstrap_field form.all_organizer_permissions layout="control" %}
{% bootstrap_field form.limit_organizer_permissions layout="control" %}
</fieldset>
<fieldset>
<legend>{% trans "Event permissions" %}</legend>
{% bootstrap_field form.all_events layout="control" %}
{% bootstrap_field form.limit_events layout="control" %}
{% bootstrap_field form.can_change_event_settings layout="control" %}
{% bootstrap_field form.can_change_items layout="control" %}
{% bootstrap_field form.can_view_orders layout="control" %}
{% bootstrap_field form.can_change_orders layout="control" %}
{% bootstrap_field form.can_checkin_orders layout="control" %}
{% bootstrap_field form.can_view_vouchers layout="control" %}
{% bootstrap_field form.can_change_vouchers layout="control" %}
{% bootstrap_field form.all_event_permissions layout="control" %}
{% bootstrap_field form.limit_event_permissions layout="control" %}
</fieldset>
<div class="form-group submit-group">
<button type="submit" class="btn btn-primary btn-save">

View File

@@ -65,7 +65,7 @@
</div>
</form>
</div>
{% if "can_change_event_settings" in request.eventpermset %}
{% if "event.subevents:write" in request.eventpermset %}
<p>
<a href="{% url "control:event.subevents.add" organizer=request.event.organizer.slug event=request.event.slug %}"
class="btn btn-default"><i class="fa fa-plus"></i>
@@ -85,7 +85,7 @@
<thead>
<tr>
<th>
{% if "can_change_event_settings" in request.eventpermset %}
{% if "event.subevents:write" in request.eventpermset %}
<label aria-label="{% trans "select all rows for batch-operation" %}" class="batch-select-label"><input type="checkbox" data-toggle-table/></label>
{% endif %}
</th>
@@ -107,7 +107,7 @@
</th>
<th></th>
</tr>
{% if "can_change_event_settings" in request.eventpermset and page_obj.paginator.num_pages > 1 %}
{% if "event.subevents:write" in request.eventpermset and page_obj.paginator.num_pages > 1 %}
<tr class="table-select-all warning hidden">
<td>
<input type="checkbox" name="__ALL" id="__all" data-results-total="{{ page_obj.paginator.count }}">
@@ -124,7 +124,7 @@
{% for s in subevents %}
<tr>
<td>
{% if "can_change_event_settings" in request.eventpermset %}
{% if "event.subevents:write" in request.eventpermset %}
<label aria-label="{% trans "select row for batch-operation" %}" class="batch-select-label"><input type="checkbox" name="subevent" class="batch-select-checkbox" value="{{ s.pk }}"/></label>
{% endif %}
</td>
@@ -201,7 +201,7 @@
</tbody>
</table>
</div>
{% if "can_change_event_settings" in request.eventpermset %}
{% if "event.subevents:write" in request.eventpermset %}
<div class="batch-select-actions">
<button type="submit" class="btn btn-danger btn-save" name="action" value="delete">
<i class="fa fa-trash"></i>{% trans "Delete selected" %}

View File

@@ -120,7 +120,7 @@
</div>
</div>
</div>
{% if "can_change_vouchers" in request.eventpermset %}
{% if "event.vouchers:write" in request.eventpermset %}
<div class="form-group submit-group">
<button type="submit" class="btn btn-primary btn-save">
{% trans "Save" %}

View File

@@ -72,7 +72,7 @@
{% endif %}
</p>
{% if "can_change_vouchers" in request.eventpermset %}
{% if "event.vouchers:write" in request.eventpermset %}
<a href="{% url "control:event.vouchers.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 voucher" %}</a>
<a href="{% url "control:event.vouchers.bulk" organizer=request.event.organizer.slug event=request.event.slug %}"
@@ -83,7 +83,7 @@
</div>
{% else %}
<p>
{% if "can_change_vouchers" in request.eventpermset %}
{% if "event.vouchers:write" in request.eventpermset %}
<a href="{% url "control:event.vouchers.add" organizer=request.event.organizer.slug event=request.event.slug %}"
class="btn btn-default"><i class="fa fa-plus"></i> {% trans "Create a new voucher" %}</a>
<a href="{% url "control:event.vouchers.bulk" organizer=request.event.organizer.slug event=request.event.slug %}"
@@ -103,7 +103,7 @@
<table class="table table-hover table-quotas">
<thead>
<tr>
{% if "can_change_vouchers" in request.eventpermset %}
{% if "event.vouchers:write" in request.eventpermset %}
<th>
<label aria-label="{% trans "select all rows for batch-operation" %}" class="batch-select-label">
<input type="checkbox" data-toggle-table />
@@ -148,7 +148,7 @@
<tbody>
{% for v in vouchers %}
<tr>
{% if "can_change_vouchers" in request.eventpermset %}
{% if "event.vouchers:write" in request.eventpermset %}
<td>
<label aria-label="{% trans "select row for batch-operation" %}" class="batch-select-label">
<input type="checkbox" name="voucher" class="batch-select-checkbox" value="{{ v.pk }}"/>
@@ -192,7 +192,7 @@
</td>
{% endif %}
<td class="text-right flip">
{% if "can_change_vouchers" in request.eventpermset %}
{% if "event.vouchers:write" in request.eventpermset %}
<a href="{% url "control:event.vouchers.bulk" organizer=request.event.organizer.slug event=request.event.slug %}?copy_from={{ v.id }}"
class="btn btn-sm btn-default" title="{% trans "Use as a template for new vouchers" %}" data-toggle="tooltip">
<span class="fa fa-copy"></span>
@@ -205,7 +205,7 @@
</tbody>
</table>
</div>
{% if "can_change_vouchers" in request.eventpermset %}
{% if "event.vouchers:write" in request.eventpermset %}
<div class="batch-select-actions">
<button type="submit" class="btn btn-danger" name="action" value="delete">
<i class="fa fa-trash" aria-hidden="true"></i>

View File

@@ -27,7 +27,7 @@
</div>
{% endif %}
<div class="row">
{% if 'can_change_orders' in request.eventpermset %}
{% if 'event.orders:write' in request.eventpermset %}
<form method="post" class="col-md-6"
action="{% url "control:event.orders.waitinglist.auto" event=request.event.slug organizer=request.organizer.slug %}"
data-asynctask>
@@ -80,7 +80,7 @@
</div>
</form>
{% endif %}
<div class="{% if 'can_change_orders' in request.eventpermset %}col-md-6{% else %}col-md-12{% endif %}">
<div class="{% if 'event.orders:write' in request.eventpermset %}col-md-6{% else %}col-md-12{% endif %}">
<div class="panel panel-default">
<div class="panel-heading">
{% trans "Sales estimate" %}
@@ -151,7 +151,7 @@
<thead>
<tr>
<th>
{% if "can_change_orders" in request.eventpermset %}
{% if "event.orders:write" in request.eventpermset %}
<label aria-label="{% trans "select all rows for batch-operation" %}" class="batch-select-label"><input type="checkbox" data-toggle-table/></label>
{% endif %}
</th>
@@ -171,7 +171,7 @@
<th>{% trans "Voucher" %}</th>
<th></th>
</tr>
{% if "can_change_orders" in request.eventpermset and page_obj.paginator.num_pages > 1 %}
{% if "event.orders:write" in request.eventpermset and page_obj.paginator.num_pages > 1 %}
<tr class="table-select-all warning hidden">
<td>
<input type="checkbox" name="__ALL" id="__all" data-results-total="{{ page_obj.paginator.count }}">
@@ -188,7 +188,7 @@
{% for e in entries %}
<tr>
<td>
{% if "can_change_orders" in request.eventpermset %}
{% if "event.orders:write" in request.eventpermset %}
<label aria-label="{% trans "select row for batch-operation" %}" class="batch-select-label"><input type="checkbox" name="entry" class="batch-select-checkbox" value="{{ e.pk }}"/></label>
{% endif %}
</td>
@@ -290,7 +290,7 @@
</tbody>
</table>
</div>
{% if "can_change_orders" in request.eventpermset %}
{% if "event.orders:write" in request.eventpermset %}
<div class="batch-select-actions">
<button type="submit" class="btn btn-danger btn-save" name="action" value="delete">
<i class="fa fa-trash"></i>

View File

@@ -150,7 +150,7 @@ class CheckInListShow(EventPermissionRequiredMixin, PaginationMixin, CheckInList
model = Checkin
context_object_name = 'entries'
template_name = 'pretixcontrol/checkin/index.html'
permission = 'can_view_orders'
permission = 'event.orders:read'
def dispatch(self, request, *args, **kwargs):
self.list = get_object_or_404(self.request.event.checkin_lists.all(), pk=kwargs.get("list"))
@@ -211,7 +211,7 @@ class CheckInListBulkRevertConfirmView(CheckInListQueryMixin, EventPermissionReq
class CheckInListBulkActionView(CheckInListQueryMixin, EventPermissionRequiredMixin, AsyncPostView):
permission = ('can_change_orders', 'can_checkin_orders')
permission = ('event.orders:write', 'event.orders:checkin')
def dispatch(self, request, *args, **kwargs):
self.list = get_object_or_404(self.request.event.checkin_lists.all(), pk=kwargs.get("list"))
@@ -228,7 +228,7 @@ class CheckInListBulkActionView(CheckInListQueryMixin, EventPermissionRequiredMi
self.list = get_object_or_404(request.event.checkin_lists.all(), pk=kwargs.get("list"))
positions = self.get_queryset()
if request.POST.get('revert') == 'true':
if not request.user.has_event_permission(request.organizer, request.event, 'can_change_orders', request=request):
if not request.user.has_event_permission(request.organizer, request.event, 'event.orders:write', request=request):
raise PermissionDenied()
for op in positions:
if op.order.status == Order.STATUS_PAID or (
@@ -295,7 +295,7 @@ class CheckInListBulkActionView(CheckInListQueryMixin, EventPermissionRequiredMi
class CheckinListList(EventPermissionRequiredMixin, PaginationMixin, ListView):
model = CheckinList
context_object_name = 'checkinlists'
permission = 'can_view_orders'
permission = 'event.orders:read'
template_name = 'pretixcontrol/checkin/lists.html'
ordering = ('subevent__date_from', 'name', 'pk')
@@ -319,7 +319,7 @@ class CheckinListList(EventPermissionRequiredMixin, PaginationMixin, ListView):
ctx['can_change_organizer_settings'] = self.request.user.has_organizer_permission(
self.request.organizer,
'can_change_organizer_settings',
'organizer.settings.general:write',
self.request
)
ctx['filter_form'] = self.filter_form
@@ -335,7 +335,7 @@ class CheckinListCreate(EventPermissionRequiredMixin, CreateView):
model = CheckinList
form_class = CheckinListForm
template_name = 'pretixcontrol/checkin/list_edit.html'
permission = 'can_change_event_settings'
permission = 'event.settings.general:write'
context_object_name = 'checkinlist'
def dispatch(self, request, *args, **kwargs):
@@ -386,7 +386,7 @@ class CheckinListUpdate(EventPermissionRequiredMixin, UpdateView):
model = CheckinList
form_class = CheckinListForm
template_name = 'pretixcontrol/checkin/list_edit.html'
permission = 'can_change_event_settings'
permission = 'event.settings.general:write'
context_object_name = 'checkinlist'
def dispatch(self, request, *args, **kwargs):
@@ -445,7 +445,7 @@ class CheckinListUpdate(EventPermissionRequiredMixin, UpdateView):
class CheckinListDelete(EventPermissionRequiredMixin, CompatDeleteView):
model = CheckinList
template_name = 'pretixcontrol/checkin/list_delete.html'
permission = 'can_change_event_settings'
permission = 'event.settings.general:write'
context_object_name = 'checkinlist'
def get_object(self, queryset=None) -> CheckinList:
@@ -476,7 +476,7 @@ class CheckinListDelete(EventPermissionRequiredMixin, CompatDeleteView):
class CheckinListView(EventPermissionRequiredMixin, PaginationMixin, ListView):
model = Checkin
context_object_name = 'checkins'
permission = 'can_view_orders'
permission = 'event.orders:read'
template_name = 'pretixcontrol/checkin/checkins.html'
ordering = ('-datetime', '-pk')
@@ -505,7 +505,7 @@ class CheckinListView(EventPermissionRequiredMixin, PaginationMixin, ListView):
class CheckInListSimulator(EventPermissionRequiredMixin, FormView):
template_name = 'pretixcontrol/checkin/simulator.html'
permission = 'can_view_orders'
permission = 'event.orders:read'
form_class = CheckinListSimulatorForm
def dispatch(self, request, *args, **kwargs):
@@ -575,7 +575,7 @@ class CheckInListSimulator(EventPermissionRequiredMixin, FormView):
class CheckInResetView(CheckInListQueryMixin, EventPermissionRequiredMixin, AsyncFormView):
form_class = CheckinResetForm
permission = "can_change_orders"
permission = "event.orders:write"
template_name = "pretixcontrol/checkin/reset.html"
def get_error_url(self, *args):

View File

@@ -66,6 +66,7 @@ from pretix.control.signals import (
from pretix.helpers.daterange import daterange
from ...base.models.orders import CancellationRequest
from ...base.models.organizer import TeamQuerySet
from ...base.templatetags.money import money_filter
from ..logdisplay import OVERVIEW_BANLIST
@@ -350,10 +351,10 @@ def event_index(request, organizer, event):
except SubEvent.DoesNotExist:
pass
can_view_orders = request.user.has_event_permission(request.organizer, request.event, 'can_view_orders',
can_view_orders = request.user.has_event_permission(request.organizer, request.event, 'event.orders:read',
request=request)
can_change_event_settings = request.user.has_event_permission(request.organizer, request.event,
'can_change_event_settings', request=request)
'event.settings.general:write', request=request)
widgets = []
if can_view_orders:
@@ -425,11 +426,11 @@ def event_index_log_lazy(request, organizer, event):
'device').order_by('-datetime')
qs = qs.exclude(action_type__in=OVERVIEW_BANLIST)
can_view_orders = request.user.has_event_permission(request.organizer, request.event, 'can_view_orders',
can_view_orders = request.user.has_event_permission(request.organizer, request.event, 'event.orders:read',
request=request)
can_change_event_settings = request.user.has_event_permission(request.organizer, request.event,
'can_change_event_settings', request=request)
can_view_vouchers = request.user.has_event_permission(request.organizer, request.event, 'can_view_vouchers',
'event.settings.general:write', request=request)
can_view_vouchers = request.user.has_event_permission(request.organizer, request.event, 'event.vouchers:read',
request=request)
if not can_view_orders:
@@ -441,7 +442,7 @@ def event_index_log_lazy(request, organizer, event):
ContentType.objects.get_for_model(Voucher),
ContentType.objects.get_for_model(Order)
]
if request.user.has_event_permission(request.organizer, request.event, 'can_change_items', request=request):
if request.user.has_event_permission(request.organizer, request.event, 'event.items:write', request=request):
allowed_types += [
ContentType.objects.get_for_model(Item),
ContentType.objects.get_for_model(ItemCategory),
@@ -491,8 +492,13 @@ def widgets_for_event_qs(request, qs, user, nmax, lazy=False):
# Get set of events where we have the permission to show the # of orders
if not lazy:
events_with_orders = set(qs.filter(
Q(organizer_id__in=user.teams.filter(all_events=True, can_view_orders=True).values_list('organizer', flat=True))
| Q(id__in=user.teams.filter(can_view_orders=True).values_list('limit_events__id', flat=True))
Q(organizer_id__in=user.teams.filter(
TeamQuerySet.event_permission_q("event.orders:read"),
all_events=True,
).values_list('organizer', flat=True))
| Q(id__in=user.teams.filter(
TeamQuerySet.event_permission_q("event.orders:read"),
).values_list('limit_events__id', flat=True))
).values_list('id', flat=True))
tpl = """
@@ -635,7 +641,7 @@ def user_index(request):
ctx = {
'widgets': rearrange(widgets),
'can_create_event': request.user.teams.filter(can_create_events=True).exists(),
'can_create_event': request.user.teams.with_organizer_permission("organizer:events.create").exists(),
'upcoming': widgets_for_event_qs(
request,
annotated_event_query(request, lazy=True).filter(

View File

@@ -72,7 +72,7 @@ def on_control_order_info(sender: Event, request, order: Order, **kwargs):
class ControlSyncJob(OrderView):
permission = 'can_change_orders'
permission = 'event.orders:write'
def post(self, request, provider, *args, **kwargs):
prov, meta = datasync_providers.get(active_in=self.request.event, identifier=provider)
@@ -154,7 +154,7 @@ class GlobalFailedSyncJobsView(AdministratorPermissionRequiredMixin, FailedSyncJ
class OrganizerFailedSyncJobsView(OrganizerPermissionRequiredMixin, FailedSyncJobsView):
permission = "can_change_organizer_settings"
permission = "organizer.settings.general:write"
def get_queryset(self):
return super().get_queryset().filter(
@@ -163,7 +163,7 @@ class OrganizerFailedSyncJobsView(OrganizerPermissionRequiredMixin, FailedSyncJo
class EventFailedSyncJobsView(EventPermissionRequiredMixin, FailedSyncJobsView):
permission = "can_change_event_settings"
permission = "event.settings.general:write"
def get_queryset(self):
return super().get_queryset().filter(

View File

@@ -50,7 +50,7 @@ from . import CreateView, PaginationMixin, UpdateView
class DiscountDelete(EventPermissionRequiredMixin, CompatDeleteView):
model = Discount
template_name = 'pretixcontrol/items/discount_delete.html'
permission = 'can_change_items'
permission = 'event.items:write'
context_object_name = 'discount'
def get_context_data(self, *args, **kwargs) -> dict:
@@ -96,7 +96,7 @@ class DiscountUpdate(EventPermissionRequiredMixin, UpdateView):
model = Discount
form_class = DiscountForm
template_name = 'pretixcontrol/items/discount.html'
permission = 'can_change_items'
permission = 'event.items:write'
context_object_name = 'discount'
def get_object(self, queryset=None) -> Discount:
@@ -139,7 +139,7 @@ class DiscountCreate(EventPermissionRequiredMixin, CreateView):
model = Discount
form_class = DiscountForm
template_name = 'pretixcontrol/items/discount.html'
permission = 'can_change_items'
permission = 'event.items:write'
context_object_name = 'discount'
def get_success_url(self) -> str:
@@ -227,7 +227,7 @@ def discount_move(request, discount, up=True):
messages.success(request, _('The order of discounts has been updated.'))
@event_permission_required("can_change_items")
@event_permission_required("event.items:write")
@require_http_methods(["POST"])
def discount_move_up(request, organizer, event, discount):
discount_move(request, discount, up=True)
@@ -236,7 +236,7 @@ def discount_move_up(request, organizer, event, discount):
event=request.event.slug)
@event_permission_required("can_change_items")
@event_permission_required("event.items:write")
@require_http_methods(["POST"])
def discount_move_down(request, organizer, event, discount):
discount_move(request, discount, up=False)
@@ -246,7 +246,7 @@ def discount_move_down(request, organizer, event, discount):
@transaction.atomic
@event_permission_required("can_change_items")
@event_permission_required("event.items:write")
@require_http_methods(["POST"])
def reorder_discounts(request, organizer, event):
try:

View File

@@ -155,7 +155,7 @@ class MetaDataEditorMixin:
property=p,
disabled=(
p.protected and
not self.request.user.has_organizer_permission(self.request.organizer, 'can_change_organizer_settings', request=self.request)
not self.request.user.has_organizer_permission(self.request.organizer, 'organizer.settings.general:write', request=self.request)
),
instance=val_instances.get(p.pk, self.meta_model(property=p, event=self.object)),
data=(self.request.POST if self.request.method == "POST" else None)
@@ -187,7 +187,7 @@ class EventUpdate(DecoupleMixin, EventSettingsViewMixin, EventPermissionRequired
model = Event
form_class = EventUpdateForm
template_name = 'pretixcontrol/event/settings.html'
permission = 'can_change_event_settings'
permission = 'event.settings.general:write'
@cached_property
def object(self) -> Event:
@@ -346,7 +346,7 @@ class EventUpdate(DecoupleMixin, EventSettingsViewMixin, EventPermissionRequired
class EventPlugins(EventSettingsViewMixin, EventPermissionRequiredMixin, TemplateView, SingleObjectMixin):
model = Event
context_object_name = 'event'
permission = 'can_change_event_settings'
permission = 'event.settings.general:write'
template_name = 'pretixcontrol/event/plugins.html'
def get_object(self, queryset=None) -> Event:
@@ -447,7 +447,7 @@ class EventPlugins(EventSettingsViewMixin, EventPermissionRequiredMixin, Templat
continue
if getattr(pluginmeta, 'level', PLUGIN_LEVEL_EVENT) == PLUGIN_LEVEL_EVENT_ORGANIZER_HYBRID:
if not request.user.has_organizer_permission(request.organizer, "can_change_organizer_settings", request):
if not request.user.has_organizer_permission(request.organizer, "organizer.settings.general:write", request):
messages.error(
request,
_("You do not have sufficient permission to enable plugins that need to be enabled "
@@ -502,7 +502,7 @@ class EventPlugins(EventSettingsViewMixin, EventPermissionRequiredMixin, Templat
class PaymentProviderSettings(EventSettingsViewMixin, EventPermissionRequiredMixin, TemplateView, SingleObjectMixin):
model = Event
context_object_name = 'event'
permission = 'can_change_event_settings'
permission = 'event.settings.general:write'
template_name = 'pretixcontrol/event/payment_provider.html'
def get_success_url(self) -> str:
@@ -581,7 +581,7 @@ class PaymentProviderSettings(EventSettingsViewMixin, EventPermissionRequiredMix
class EventSettingsFormView(EventPermissionRequiredMixin, DecoupleMixin, FormView):
model = Event
permission = 'can_change_event_settings'
permission = 'event.settings.general:write'
def get_context_data(self, *args, **kwargs) -> dict:
context = super().get_context_data(*args, **kwargs)
@@ -621,7 +621,7 @@ class EventSettingsFormView(EventPermissionRequiredMixin, DecoupleMixin, FormVie
class PaymentSettings(EventSettingsViewMixin, EventSettingsFormView):
template_name = 'pretixcontrol/event/payment.html'
form_class = PaymentSettingsForm
permission = 'can_change_event_settings'
permission = 'event.settings.general:write'
def get_success_url(self) -> str:
return reverse('control:event.settings.payment', kwargs={
@@ -650,7 +650,7 @@ class PaymentSettings(EventSettingsViewMixin, EventSettingsFormView):
class TaxSettings(EventSettingsViewMixin, EventSettingsFormView):
template_name = 'pretixcontrol/event/tax.html'
form_class = TaxSettingsForm
permission = 'can_change_event_settings'
permission = 'event.settings.general:write'
def get_success_url(self) -> str:
return reverse('control:event.settings.tax', kwargs={
@@ -670,7 +670,7 @@ class InvoiceSettings(EventSettingsViewMixin, EventSettingsFormView):
model = Event
form_class = InvoiceSettingsForm
template_name = 'pretixcontrol/event/invoicing.html'
permission = 'can_change_event_settings'
permission = 'event.settings.general:write'
def get_context_data(self, **kwargs):
types = get_transmission_types()
@@ -704,7 +704,7 @@ class CancelSettings(EventSettingsViewMixin, EventSettingsFormView):
model = Event
form_class = CancelSettingsForm
template_name = 'pretixcontrol/event/cancel.html'
permission = 'can_change_event_settings'
permission = 'event.settings.general:write'
def get_success_url(self) -> str:
return reverse('control:event.settings.cancel', kwargs={
@@ -738,7 +738,7 @@ class CancelSettings(EventSettingsViewMixin, EventSettingsFormView):
class InvoicePreview(EventPermissionRequiredMixin, View):
permission = 'can_change_event_settings'
permission = 'event.settings.general:write'
def get(self, request, *args, **kwargs):
fname, ftype, fcontent = build_preview_invoice_pdf(request.event)
@@ -753,7 +753,7 @@ class InvoicePreview(EventPermissionRequiredMixin, View):
class DangerZone(EventPermissionRequiredMixin, TemplateView):
permission = 'can_change_event_settings'
permission = 'event.settings.general:write'
template_name = 'pretixcontrol/event/dangerzone.html'
@@ -769,7 +769,7 @@ class MailSettings(EventSettingsViewMixin, EventSettingsFormView):
model = Event
form_class = MailSettingsForm
template_name = 'pretixcontrol/event/mail.html'
permission = 'can_change_event_settings'
permission = 'event.settings.general:write'
def get_success_url(self) -> str:
return reverse('control:event.settings.mail', kwargs={
@@ -801,7 +801,7 @@ class MailSettings(EventSettingsViewMixin, EventSettingsFormView):
class MailSettingsSetup(EventPermissionRequiredMixin, MailSettingsSetupView):
permission = 'can_change_event_settings'
permission = 'event.settings.general:write'
basetpl = 'pretixcontrol/event/base.html'
def get_success_url(self) -> str:
@@ -817,7 +817,7 @@ class MailSettingsSetup(EventPermissionRequiredMixin, MailSettingsSetupView):
class MailSettingsPreview(EventPermissionRequiredMixin, View):
permission = 'can_change_event_settings'
permission = 'event.settings.general:write'
# create index-language mapping
@cached_property
@@ -883,7 +883,7 @@ class MailSettingsPreview(EventPermissionRequiredMixin, View):
class MailSettingsRendererPreview(MailSettingsPreview):
permission = 'can_change_event_settings'
permission = 'event.settings.general:write'
def post(self, request, *args, **kwargs):
return HttpResponse(status=405)
@@ -931,7 +931,7 @@ class MailSettingsRendererPreview(MailSettingsPreview):
class TicketSettingsPreview(EventPermissionRequiredMixin, View):
permission = 'can_change_event_settings'
permission = 'event.settings.general:write'
@cached_property
def output(self):
@@ -963,7 +963,7 @@ class TicketSettings(EventSettingsViewMixin, EventPermissionRequiredMixin, FormV
model = Event
form_class = TicketSettingsForm
template_name = 'pretixcontrol/event/tickets.html'
permission = 'can_change_event_settings'
permission = 'event.settings.general:write'
def get_context_data(self, *args, **kwargs) -> dict:
context = super().get_context_data(*args, **kwargs)
@@ -1074,7 +1074,7 @@ class EventPermissions(EventSettingsViewMixin, EventPermissionRequiredMixin, Tem
class EventLive(EventPermissionRequiredMixin, TemplateView):
permission = 'can_change_event_settings'
permission = 'event.settings.general:write'
template_name = 'pretixcontrol/event/live.html'
def get_context_data(self, **kwargs):
@@ -1141,12 +1141,12 @@ class EventLive(EventPermissionRequiredMixin, TemplateView):
class EventTransferSession(EventPermissionRequiredMixin, TemplateView):
permission = 'can_change_event_settings'
permission = 'event.settings.general:write'
template_name = 'pretixcontrol/event/transfer_session.html'
class EventDelete(RecentAuthenticationRequiredMixin, EventPermissionRequiredMixin, FormView):
permission = 'can_change_event_settings'
permission = 'event.settings.general:write'
template_name = 'pretixcontrol/event/delete.html'
form_class = EventDeleteForm
@@ -1214,20 +1214,20 @@ class EventLog(EventPermissionRequiredMixin, PaginationMixin, ListView):
'user', 'content_type', 'api_token', 'oauth_application', 'device'
).order_by('-datetime', '-pk')
qs = qs.exclude(action_type__in=OVERVIEW_BANLIST)
if not self.request.user.has_event_permission(self.request.organizer, self.request.event, 'can_view_orders',
if not self.request.user.has_event_permission(self.request.organizer, self.request.event, 'event.orders:read',
request=self.request):
qs = qs.exclude(content_type=ContentType.objects.get_for_model(Order))
if not self.request.user.has_event_permission(self.request.organizer, self.request.event, 'can_view_vouchers',
if not self.request.user.has_event_permission(self.request.organizer, self.request.event, 'event.vouchers:read',
request=self.request):
qs = qs.exclude(content_type=ContentType.objects.get_for_model(Voucher))
if not self.request.user.has_event_permission(self.request.organizer, self.request.event,
'can_change_event_settings', request=self.request):
'event.settings.general:write', request=self.request):
allowed_types = [
ContentType.objects.get_for_model(Voucher),
ContentType.objects.get_for_model(Order)
]
if self.request.user.has_event_permission(self.request.organizer, self.request.event,
'can_change_items', request=self.request):
'event.items:write', request=self.request):
allowed_types += [
ContentType.objects.get_for_model(Item),
ContentType.objects.get_for_model(ItemCategory),
@@ -1264,7 +1264,7 @@ class EventLog(EventPermissionRequiredMixin, PaginationMixin, ListView):
class EventComment(EventPermissionRequiredMixin, View):
permission = 'can_change_event_settings'
permission = 'event.settings.general:write'
def post(self, *args, **kwargs):
form = CommentForm(self.request.POST)
@@ -1293,7 +1293,7 @@ class TaxCreate(EventSettingsViewMixin, EventPermissionRequiredMixin, CreateView
model = TaxRule
form_class = TaxRuleForm
template_name = 'pretixcontrol/event/tax_edit.html'
permission = 'can_change_event_settings'
permission = 'event.settings.general:write'
context_object_name = 'taxrule'
def get_success_url(self) -> str:
@@ -1354,7 +1354,7 @@ class TaxUpdate(EventSettingsViewMixin, EventPermissionRequiredMixin, UpdateView
model = TaxRule
form_class = TaxRuleForm
template_name = 'pretixcontrol/event/tax_edit.html'
permission = 'can_change_event_settings'
permission = 'event.settings.general:write'
context_object_name = 'rule'
def get_object(self, queryset=None) -> TaxRule:
@@ -1418,7 +1418,7 @@ class TaxUpdate(EventSettingsViewMixin, EventPermissionRequiredMixin, UpdateView
class TaxDefault(EventSettingsViewMixin, EventPermissionRequiredMixin, DetailView):
model = TaxRule
permission = 'can_change_event_settings'
permission = 'event.settings.general:write'
def get_object(self, queryset=None) -> TaxRule:
try:
@@ -1463,7 +1463,7 @@ class TaxDefault(EventSettingsViewMixin, EventPermissionRequiredMixin, DetailVie
class TaxDelete(EventSettingsViewMixin, EventPermissionRequiredMixin, CompatDeleteView):
model = TaxRule
template_name = 'pretixcontrol/event/tax_delete.html'
permission = 'can_change_event_settings'
permission = 'event.settings.general:write'
context_object_name = 'taxrule'
def get_object(self, queryset=None) -> TaxRule:
@@ -1500,7 +1500,7 @@ class TaxDelete(EventSettingsViewMixin, EventPermissionRequiredMixin, CompatDele
class WidgetSettings(EventSettingsViewMixin, EventPermissionRequiredMixin, FormView):
template_name = 'pretixcontrol/event/widget.html'
permission = 'can_change_event_settings'
permission = 'event.settings.general:write'
form_class = WidgetCodeForm
def get_form_kwargs(self):
@@ -1529,7 +1529,7 @@ class WidgetSettings(EventSettingsViewMixin, EventPermissionRequiredMixin, FormV
class QuickSetupView(FormView):
template_name = 'pretixcontrol/event/quick_setup.html'
permission = 'can_change_event_settings'
permission = 'event.settings.general:write'
form_class = QuickSetupForm
def dispatch(self, request, *args, **kwargs):

View File

@@ -159,7 +159,7 @@ def item_move(request, item, up=True):
messages.success(request, _('The order of items has been updated.'))
@event_permission_required("can_change_items")
@event_permission_required("event.items:write")
@require_http_methods(["POST"])
def item_move_up(request, organizer, event, item):
item_move(request, item, up=True)
@@ -168,7 +168,7 @@ def item_move_up(request, organizer, event, item):
event=request.event.slug)
@event_permission_required("can_change_items")
@event_permission_required("event.items:write")
@require_http_methods(["POST"])
def item_move_down(request, organizer, event, item):
item_move(request, item, up=False)
@@ -178,7 +178,7 @@ def item_move_down(request, organizer, event, item):
@transaction.atomic
@event_permission_required("can_change_items")
@event_permission_required("event.items:write")
@require_http_methods(["POST"])
def reorder_items(request, organizer, event, category):
try:
@@ -215,7 +215,7 @@ def reorder_items(request, organizer, event, category):
class CategoryDelete(EventPermissionRequiredMixin, CompatDeleteView):
model = ItemCategory
template_name = 'pretixcontrol/items/category_delete.html'
permission = 'can_change_items'
permission = 'event.items:write'
context_object_name = 'category'
def get_object(self, queryset=None) -> ItemCategory:
@@ -249,7 +249,7 @@ class CategoryUpdate(EventPermissionRequiredMixin, UpdateView):
model = ItemCategory
form_class = CategoryForm
template_name = 'pretixcontrol/items/category.html'
permission = 'can_change_items'
permission = 'event.items:write'
context_object_name = 'category'
def get_object(self, queryset=None) -> ItemCategory:
@@ -287,7 +287,7 @@ class CategoryCreate(EventPermissionRequiredMixin, CreateView):
model = ItemCategory
form_class = CategoryForm
template_name = 'pretixcontrol/items/category.html'
permission = 'can_change_items'
permission = 'event.items:write'
context_object_name = 'category'
def get_success_url(self) -> str:
@@ -371,7 +371,7 @@ def category_move(request, category, up=True):
messages.success(request, _('The order of categories has been updated.'))
@event_permission_required("can_change_items")
@event_permission_required("event.items:write")
@require_http_methods(["POST"])
def category_move_up(request, organizer, event, category):
category_move(request, category, up=True)
@@ -380,7 +380,7 @@ def category_move_up(request, organizer, event, category):
event=request.event.slug)
@event_permission_required("can_change_items")
@event_permission_required("event.items:write")
@require_http_methods(["POST"])
def category_move_down(request, organizer, event, category):
category_move(request, category, up=False)
@@ -390,7 +390,7 @@ def category_move_down(request, organizer, event, category):
@transaction.atomic
@event_permission_required("can_change_items")
@event_permission_required("event.items:write")
@require_http_methods(["POST"])
def reorder_categories(request, organizer, event):
try:
@@ -522,7 +522,7 @@ class QuestionList(ListView):
@transaction.atomic
@event_permission_required("can_change_items")
@event_permission_required("event.items:write")
@require_http_methods(["POST"])
def reorder_questions(request, organizer, event):
try:
@@ -570,7 +570,7 @@ def reorder_questions(request, organizer, event):
class QuestionDelete(EventPermissionRequiredMixin, CompatDeleteView):
model = Question
template_name = 'pretixcontrol/items/question_delete.html'
permission = 'can_change_items'
permission = 'event.items:write'
context_object_name = 'question'
def get_object(self, queryset=None) -> Question:
@@ -664,7 +664,7 @@ class QuestionMixin:
class QuestionView(EventPermissionRequiredMixin, ChartContainingView, DetailView):
model = Question
template_name = 'pretixcontrol/items/question.html'
permission = 'can_change_items'
permission = 'event.items:write'
template_name_field = 'question'
@cached_property
@@ -753,7 +753,7 @@ class QuestionUpdate(EventPermissionRequiredMixin, QuestionMixin, UpdateView):
model = Question
form_class = QuestionForm
template_name = 'pretixcontrol/items/question_edit.html'
permission = 'can_change_items'
permission = 'event.items:write'
context_object_name = 'question'
def get_object(self, queryset=None) -> Question:
@@ -794,7 +794,7 @@ class QuestionCreate(EventPermissionRequiredMixin, QuestionMixin, CreateView):
model = Question
form_class = QuestionForm
template_name = 'pretixcontrol/items/question_edit.html'
permission = 'can_change_items'
permission = 'event.items:write'
context_object_name = 'question'
def get_form_kwargs(self):
@@ -888,7 +888,7 @@ class QuotaCreate(EventPermissionRequiredMixin, CreateView):
model = Quota
form_class = QuotaForm
template_name = 'pretixcontrol/items/quota_edit.html'
permission = 'can_change_items'
permission = 'event.items:write'
context_object_name = 'quota'
def get_success_url(self) -> str:
@@ -1055,7 +1055,7 @@ class QuotaView(ChartContainingView, DetailView):
raise Http404(_("The requested quota does not exist."))
def post(self, request, *args, **kwargs):
if not request.user.has_event_permission(request.organizer, request.event, 'can_change_items', request):
if not request.user.has_event_permission(request.organizer, request.event, 'event.items:write', request):
raise PermissionDenied()
quota = self.get_object()
if 'reopen' in request.POST:
@@ -1085,7 +1085,7 @@ class QuotaUpdate(EventPermissionRequiredMixin, UpdateView):
model = Quota
form_class = QuotaForm
template_name = 'pretixcontrol/items/quota_edit.html'
permission = 'can_change_items'
permission = 'event.items:write'
context_object_name = 'quota'
def get_context_data(self, *args, **kwargs):
@@ -1143,7 +1143,7 @@ class QuotaUpdate(EventPermissionRequiredMixin, UpdateView):
class QuotaDelete(EventPermissionRequiredMixin, CompatDeleteView):
model = Quota
template_name = 'pretixcontrol/items/quota_delete.html'
permission = 'can_change_items'
permission = 'event.items:write'
context_object_name = 'quota'
def get_object(self, queryset=None) -> Quota:
@@ -1246,7 +1246,7 @@ class MetaDataEditorMixin:
class ItemCreate(EventPermissionRequiredMixin, MetaDataEditorMixin, CreateView):
form_class = ItemCreateForm
template_name = 'pretixcontrol/item/create.html'
permission = 'can_change_items'
permission = 'event.items:write'
def get_success_url(self) -> str:
return reverse('control:event.item', kwargs={
@@ -1322,7 +1322,7 @@ class ItemCreate(EventPermissionRequiredMixin, MetaDataEditorMixin, CreateView):
class ItemUpdateGeneral(ItemDetailMixin, EventPermissionRequiredMixin, MetaDataEditorMixin, UpdateView):
form_class = ItemUpdateForm
template_name = 'pretixcontrol/item/index.html'
permission = 'can_change_items'
permission = 'event.items:write'
@cached_property
def plugin_forms(self):
@@ -1584,7 +1584,7 @@ class ItemUpdateGeneral(ItemDetailMixin, EventPermissionRequiredMixin, MetaDataE
class ItemDelete(EventPermissionRequiredMixin, CompatDeleteView):
model = Item
template_name = 'pretixcontrol/item/delete.html'
permission = 'can_change_items'
permission = 'event.items:write'
context_object_name = 'item'
def get_context_data(self, *args, **kwargs) -> dict:

View File

@@ -51,6 +51,7 @@ from i18nfield.strings import LazyI18nString
from pretix.base.forms import SafeSessionWizardView
from pretix.base.i18n import language
from pretix.base.models import Event, EventMetaValue, Organizer, Quota, Team
from pretix.base.models.organizer import TeamQuerySet
from pretix.base.services.quotas import QuotaAvailability
from pretix.control.forms.event import (
EventWizardBasicsForm, EventWizardCopyForm, EventWizardFoundationForm,
@@ -190,7 +191,9 @@ class EventWizard(SafeSessionWizardView):
qs = Organizer.objects.all()
if not self.request.user.has_active_staff_session(self.request.session.session_key):
qs = qs.filter(
id__in=self.request.user.teams.filter(can_create_events=True).values_list('organizer', flat=True)
id__in=self.request.user.teams.filter(
TeamQuerySet.organizer_permission_q("organizer.events:create"),
).values_list('organizer', flat=True)
)
organizer = qs.get(slug=self.request.GET.get('organizer'))
initial['organizer'] = organizer
@@ -210,9 +213,9 @@ class EventWizard(SafeSessionWizardView):
else:
allow = (
request.user.has_event_permission(clone_from.organizer, clone_from,
'can_change_event_settings', request)
'event.settings.general:write', request)
and request.user.has_event_permission(clone_from.organizer, clone_from,
'can_change_items', request)
'event.items:write', request)
)
if not allow:
messages.error(self.request, _('You do not have permission to clone this event.'))
@@ -222,7 +225,7 @@ class EventWizard(SafeSessionWizardView):
def get_context_data(self, form, **kwargs):
ctx = super().get_context_data(form, **kwargs)
ctx['has_organizer'] = self.request.user.teams.filter(can_create_events=True).exists()
ctx['has_organizer'] = self.request.user.teams.filter(TeamQuerySet.organizer_permission_q("organizer.events:create")).exists()
if self.steps.current == 'basics':
ctx['organizer'] = self.get_cleaned_data_for_step('foundation').get('organizer')
return ctx
@@ -284,21 +287,16 @@ class EventWizard(SafeSessionWizardView):
name=_('Team {event}').format(
event=str(event.name)[:100] + "" if len(str(event.name)) > 100 else str(event.name)
),
can_change_event_settings=True, can_change_items=True,
can_view_orders=True, can_change_orders=True, can_view_vouchers=True,
can_change_vouchers=True
all_organizer_permissions=False,
all_event_permissions=True,
)
t.members.add(self.request.user)
t.limit_events.add(event)
t.log_action('pretix.team.created', user=self.request.user, data={
'_created_by_event_wizard': True,
'name': t.name,
'can_change_event_settings': True,
'can_change_items': True,
'can_view_orders': True,
'can_change_orders': True,
'can_view_vouchers': True,
'can_change_vouchers': True,
'all_organizer_permissions': False,
'all_event_permissions': True,
'limit_events': [event.pk],
})

View File

@@ -214,7 +214,7 @@ class BaseProcessView(AsyncAction, FormView):
class OrderImportView(EventPermissionRequiredMixin, BaseImportView):
template_name = 'pretixcontrol/orders/import_start.html'
permission = 'can_change_orders'
permission = 'event.orders:write'
def get_process_url(self, request, cf, charset):
return reverse('control:event.orders.import.process', kwargs={
@@ -225,7 +225,7 @@ class OrderImportView(EventPermissionRequiredMixin, BaseImportView):
class OrderProcessView(EventPermissionRequiredMixin, BaseProcessView):
permission = 'can_change_orders'
permission = 'event.orders:write'
template_name = 'pretixcontrol/orders/import_process.html'
form_class = OrdersProcessForm
task = import_orders
@@ -257,7 +257,7 @@ class OrderProcessView(EventPermissionRequiredMixin, BaseProcessView):
class VoucherImportView(EventPermissionRequiredMixin, BaseImportView):
template_name = 'pretixcontrol/vouchers/import_start.html'
permission = 'can_change_vouchers'
permission = 'event.vouchers:write'
def get_process_url(self, request, cf, charset):
return reverse('control:event.vouchers.import.process', kwargs={
@@ -268,7 +268,7 @@ class VoucherImportView(EventPermissionRequiredMixin, BaseImportView):
class VoucherProcessView(EventPermissionRequiredMixin, BaseProcessView):
permission = 'can_change_vouchers'
permission = 'event.vouchers:write'
template_name = 'pretixcontrol/vouchers/import_process.html'
form_class = VouchersProcessForm
task = import_vouchers

View File

@@ -170,7 +170,7 @@ class OrderSearchMixin:
class OrderSearch(OrderSearchMixin, EventPermissionRequiredMixin, TemplateView):
template_name = 'pretixcontrol/orders/search.html'
permission = 'can_view_orders'
permission = 'event.orders:read'
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
@@ -200,7 +200,7 @@ class OrderSearch(OrderSearchMixin, EventPermissionRequiredMixin, TemplateView):
class BaseOrderBulkActionView(OrderSearchMixin, EventPermissionRequiredMixin, AsyncFormView):
template_name = 'pretixcontrol/orders/bulk_action.html'
permission = 'can_change_orders'
permission = 'event.orders:write'
form_class = forms.Form
def get_queryset(self):
@@ -403,7 +403,7 @@ class OrderList(OrderSearchMixin, EventPermissionRequiredMixin, PaginationMixin,
model = Order
context_object_name = 'orders'
template_name = 'pretixcontrol/orders/index.html'
permission = 'can_view_orders'
permission = 'event.orders:read'
def get_queryset(self):
qs = Order.objects.filter(
@@ -526,7 +526,7 @@ class OrderView(EventPermissionRequiredMixin, DetailView):
class OrderDetail(OrderView):
template_name = 'pretixcontrol/order/index.html'
permission = 'can_view_orders'
permission = 'event.orders:read'
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
@@ -626,7 +626,7 @@ class OrderDetail(OrderView):
class OrderTransactions(OrderView):
template_name = 'pretixcontrol/order/transactions.html'
permission = 'can_view_orders'
permission = 'event.orders:read'
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
@@ -645,7 +645,7 @@ class OrderTransactions(OrderView):
class OrderDownload(AsyncAction, OrderView):
task = generate
permission = 'can_view_orders'
permission = 'event.orders:read'
def get_success_url(self, value):
return self.get_self_url()
@@ -744,7 +744,7 @@ class OrderDownload(AsyncAction, OrderView):
class OrderComment(OrderView):
permission = 'can_change_orders'
permission = 'event.orders:write'
def post(self, *args, **kwargs):
form = CommentForm(self.request.POST)
@@ -784,7 +784,7 @@ class OrderComment(OrderView):
class OrderApprove(OrderView):
permission = 'can_change_orders'
permission = 'event.orders:write'
def post(self, *args, **kwargs):
if self.order.require_approval:
@@ -803,7 +803,7 @@ class OrderApprove(OrderView):
class OrderDelete(OrderView):
permission = 'can_change_orders'
permission = 'event.orders:write'
def post(self, *args, **kwargs):
if self.order.testmode:
@@ -833,7 +833,7 @@ class OrderDelete(OrderView):
class OrderDeny(OrderView):
permission = 'can_change_orders'
permission = 'event.orders:write'
def post(self, request, *args, **kwargs):
if self.order.require_approval:
@@ -859,7 +859,7 @@ class OrderDeny(OrderView):
class OrderPaymentCancel(OrderView):
permission = 'can_change_orders'
permission = 'event.orders:write'
@cached_property
def payment(self):
@@ -898,7 +898,7 @@ class OrderPaymentCancel(OrderView):
class OrderRefundCancel(OrderView):
permission = 'can_change_orders'
permission = 'event.orders:write'
@cached_property
def refund(self):
@@ -928,7 +928,7 @@ class OrderRefundCancel(OrderView):
class OrderRefundProcess(OrderView):
permission = 'can_change_orders'
permission = 'event.orders:write'
@cached_property
def refund(self):
@@ -967,7 +967,7 @@ class OrderRefundProcess(OrderView):
class OrderRefundDone(OrderView):
permission = 'can_change_orders'
permission = 'event.orders:write'
@cached_property
def refund(self):
@@ -990,7 +990,7 @@ class OrderRefundDone(OrderView):
class OrderCancellationRequestDelete(OrderView):
permission = 'can_change_orders'
permission = 'event.orders:write'
@cached_property
def req(self):
@@ -1024,7 +1024,7 @@ class OrderCancellationRequestDelete(OrderView):
class OrderPaymentConfirm(OrderView):
permission = 'can_change_orders'
permission = 'event.orders:write'
@cached_property
def payment(self):
@@ -1082,7 +1082,7 @@ class OrderPaymentConfirm(OrderView):
class OrderRefundView(OrderView):
permission = 'can_change_orders'
permission = 'event.orders:write'
@cached_property
def start_form(self):
@@ -1427,7 +1427,7 @@ class OrderRefundView(OrderView):
class OrderTransition(OrderView):
permission = 'can_change_orders'
permission = 'event.orders:write'
@cached_property
def req(self):
@@ -1595,7 +1595,7 @@ class OrderTransition(OrderView):
class OrderInvoiceCreate(OrderView):
permission = 'can_change_orders'
permission = 'event.orders:write'
def post(self, *args, **kwargs):
with transaction.atomic():
@@ -1621,7 +1621,7 @@ class OrderInvoiceCreate(OrderView):
class OrderCheckVATID(OrderView):
permission = 'can_change_orders'
permission = 'event.orders:write'
def post(self, *args, **kwargs):
try:
@@ -1661,7 +1661,7 @@ class OrderCheckVATID(OrderView):
class OrderInvoiceRegenerate(OrderView):
permission = 'can_change_orders'
permission = 'event.orders:write'
def post(self, *args, **kwargs):
try:
@@ -1694,7 +1694,7 @@ class OrderInvoiceRegenerate(OrderView):
class OrderInvoiceRetransmit(OrderView):
permission = 'can_change_orders'
permission = 'event.orders:write'
def post(self, *args, **kwargs):
with transaction.atomic(durable=True):
@@ -1725,7 +1725,7 @@ class OrderInvoiceRetransmit(OrderView):
class OrderInvoiceReissue(OrderView):
permission = 'can_change_orders'
permission = 'event.orders:write'
def post(self, *args, **kwargs):
with transaction.atomic():
@@ -1775,7 +1775,7 @@ class OrderInvoiceInspect(AdministratorPermissionRequiredMixin, OrderView):
class OrderResendLink(OrderView):
permission = 'can_change_orders'
permission = 'event.orders:write'
def post(self, *args, **kwargs):
try:
@@ -1796,7 +1796,7 @@ class OrderResendLink(OrderView):
class InvoiceDownload(EventPermissionRequiredMixin, View):
permission = 'can_view_orders'
permission = 'event.orders:read'
def get_order_url(self):
return reverse('control:event.order', kwargs={
@@ -1840,7 +1840,7 @@ class InvoiceDownload(EventPermissionRequiredMixin, View):
class OrderExtend(OrderView):
permission = 'can_change_orders'
permission = 'event.orders:write'
def post(self, *args, **kwargs):
if self.form.is_valid():
@@ -1888,7 +1888,7 @@ class OrderExtend(OrderView):
class OrderReactivate(OrderView):
permission = 'can_change_orders'
permission = 'event.orders:write'
@cached_property
def reactivate_form(self):
@@ -1938,7 +1938,7 @@ class OrderReactivate(OrderView):
class OrderChange(OrderView):
permission = 'can_change_orders'
permission = 'event.orders:write'
template_name = 'pretixcontrol/order/change.html'
@cached_property
@@ -2195,7 +2195,7 @@ class OrderChange(OrderView):
class OrderModifyInformation(OrderQuestionsViewMixin, OrderView):
permission = 'can_change_orders'
permission = 'event.orders:write'
template_name = 'pretixcontrol/order/change_questions.html'
only_user_visible = False
all_optional = True
@@ -2248,7 +2248,7 @@ class OrderModifyInformation(OrderQuestionsViewMixin, OrderView):
class OrderContactChange(OrderView):
permission = 'can_change_orders'
permission = 'event.orders:write'
template_name = 'pretixcontrol/order/change_contact.html'
def get_context_data(self, **kwargs):
@@ -2263,7 +2263,7 @@ class OrderContactChange(OrderView):
data=self.request.POST if self.request.method == "POST" else None,
customers=self.request.organizer.settings.customer_accounts and (
self.request.user.has_organizer_permission(
self.request.organizer, 'can_manage_customers', request=self.request
self.request.organizer, 'organizer.customers:write', request=self.request
)
)
)
@@ -2332,7 +2332,7 @@ class OrderContactChange(OrderView):
class OrderLocaleChange(OrderView):
permission = 'can_change_orders'
permission = 'event.orders:write'
template_name = 'pretixcontrol/order/change_locale.html'
def get_context_data(self, **kwargs):
@@ -2388,7 +2388,7 @@ class OrderViewMixin:
class OrderSendMail(EventPermissionRequiredMixin, OrderViewMixin, FormView):
template_name = 'pretixcontrol/order/sendmail.html'
permission = 'can_change_orders'
permission = 'event.orders:write'
form_class = OrderMailForm
def get_form_kwargs(self):
@@ -2522,7 +2522,7 @@ class OrderPositionSendMail(OrderSendMail):
class OrderEmailHistory(EventPermissionRequiredMixin, OrderViewMixin, ListView):
template_name = 'pretixcontrol/order/mail_history.html'
permission = 'can_view_orders'
permission = 'event.orders:read'
model = LogEntry
context_object_name = 'logs'
paginate_by = 10
@@ -2559,7 +2559,7 @@ class OrderEmailHistory(EventPermissionRequiredMixin, OrderViewMixin, ListView):
class AnswerDownload(EventPermissionRequiredMixin, OrderViewMixin, ListView):
permission = 'can_view_orders'
permission = 'event.orders:read'
def get(self, request, *args, **kwargs):
answid = kwargs.get('answer')
@@ -2583,7 +2583,7 @@ class AnswerDownload(EventPermissionRequiredMixin, OrderViewMixin, ListView):
class OverView(EventPermissionRequiredMixin, TemplateView):
template_name = 'pretixcontrol/orders/overview.html'
permission = 'can_view_orders'
permission = 'event.orders:read'
@cached_property
def filter_form(self):
@@ -2622,7 +2622,7 @@ class OverView(EventPermissionRequiredMixin, TemplateView):
class OrderGo(EventPermissionRequiredMixin, View):
permission = 'can_view_orders'
permission = 'event.orders:read'
def get_order(self, code):
try:
@@ -2707,7 +2707,7 @@ class ExportMixin:
return ex
def get_scheduled_queryset(self):
if not self.request.user.has_event_permission(self.request.organizer, self.request.event, 'can_change_event_settings',
if not self.request.user.has_event_permission(self.request.organizer, self.request.event, 'event.settings.general:write',
request=self.request):
qs = self.request.event.scheduled_exports.filter(owner=self.request.user)
else:
@@ -2729,7 +2729,7 @@ class ExportMixin:
class ExportDoView(EventPermissionRequiredMixin, ExportMixin, AsyncAction, TemplateView):
permission = 'can_view_orders'
permission = 'event.orders:read'
known_errortypes = ['ExportError', 'ExportEmptyError']
task = export
template_name = 'pretixcontrol/orders/export_form.html'
@@ -2778,7 +2778,7 @@ class ExportDoView(EventPermissionRequiredMixin, ExportMixin, AsyncAction, Templ
class ExportView(EventPermissionRequiredMixin, ExportMixin, ListView):
permission = 'can_view_orders'
permission = 'event.orders:read'
paginate_by = 25
context_object_name = 'scheduled'
@@ -2864,7 +2864,7 @@ class ExportView(EventPermissionRequiredMixin, ExportMixin, ListView):
return self.get_scheduled_queryset()
def has_permission(self):
return self.request.user.has_event_permission(self.request.organizer, self.request.event, "can_view_orders")
return self.request.user.has_event_permission(self.request.organizer, self.request.event, "event.orders:read")
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
@@ -2890,7 +2890,7 @@ class ExportView(EventPermissionRequiredMixin, ExportMixin, ListView):
class DeleteScheduledExportView(EventPermissionRequiredMixin, ExportMixin, CompatDeleteView):
permission = 'can_view_orders'
permission = 'event.orders:read'
template_name = 'pretixcontrol/orders/export_delete.html'
context_object_name = 'export'
@@ -2939,7 +2939,7 @@ class RefundList(EventPermissionRequiredMixin, PaginationMixin, ListView):
model = OrderRefund
context_object_name = 'refunds'
template_name = 'pretixcontrol/orders/refunds.html'
permission = 'can_view_orders'
permission = 'event.orders:read'
def get_queryset(self):
qs = OrderRefund.objects.filter(
@@ -2964,7 +2964,7 @@ class RefundList(EventPermissionRequiredMixin, PaginationMixin, ListView):
class EventCancel(EventPermissionRequiredMixin, AsyncAction, FormView):
template_name = 'pretixcontrol/orders/cancel.html'
permission = 'can_change_orders'
permission = 'event.orders:write'
form_class = EventCancelForm
task = cancel_event
known_errortypes = ['OrderError']
@@ -3049,7 +3049,7 @@ class EventCancel(EventPermissionRequiredMixin, AsyncAction, FormView):
class EventCancelConfirm(EventPermissionRequiredMixin, AsyncAction, FormView):
template_name = 'pretixcontrol/orders/cancel_confirm.html'
permission = 'can_change_orders'
permission = 'event.orders:write'
form_class = EventCancelConfirmForm
task = cancel_event
known_errortypes = ['OrderError']

View File

@@ -95,7 +95,9 @@ from pretix.base.models.giftcards import (
GiftCardAcceptance, GiftCardTransaction, gen_giftcard_secret,
)
from pretix.base.models.orders import CancellationRequest
from pretix.base.models.organizer import SalesChannel, TeamAPIToken
from pretix.base.models.organizer import (
SalesChannel, TeamAPIToken, TeamQuerySet,
)
from pretix.base.payment import PaymentException
from pretix.base.plugins import (
PLUGIN_LEVEL_EVENT, PLUGIN_LEVEL_EVENT_ORGANIZER_HYBRID,
@@ -242,13 +244,13 @@ class OrganizerDetail(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin
class OrganizerTeamView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, DetailView):
model = Organizer
template_name = 'pretixcontrol/organizers/teams.html'
permission = 'can_change_permissions'
permission = 'organizer.teams:write'
context_object_name = 'organizer'
class OrganizerSettingsFormView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, FormView):
model = Organizer
permission = 'can_change_organizer_settings'
permission = 'organizer.settings.general:write'
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
@@ -279,7 +281,7 @@ class OrganizerSettingsFormView(OrganizerDetailViewMixin, OrganizerPermissionReq
class OrganizerMailSettings(OrganizerSettingsFormView):
form_class = MailSettingsForm
template_name = 'pretixcontrol/organizers/mail.html'
permission = 'can_change_organizer_settings'
permission = 'organizer.settings.general:write'
def get_success_url(self):
return reverse('control:organizer.settings.mail', kwargs={
@@ -305,7 +307,7 @@ class OrganizerMailSettings(OrganizerSettingsFormView):
class MailSettingsSetup(OrganizerPermissionRequiredMixin, MailSettingsSetupView):
permission = 'can_change_organizer_settings'
permission = 'organizer.settings.general:write'
basetpl = 'pretixcontrol/base.html'
def get_success_url(self):
@@ -320,7 +322,7 @@ class MailSettingsSetup(OrganizerPermissionRequiredMixin, MailSettingsSetupView)
class MailSettingsPreview(OrganizerPermissionRequiredMixin, View):
permission = 'can_change_organizer_settings'
permission = 'organizer.settings.general:write'
# return the origin text if key is missing in dict
class SafeDict(dict):
@@ -452,7 +454,7 @@ class OrganizerUpdate(OrganizerPermissionRequiredMixin, UpdateView):
model = Organizer
form_class = OrganizerUpdateForm
template_name = 'pretixcontrol/organizers/edit.html'
permission = 'can_change_organizer_settings'
permission = 'organizer.settings.general:write'
context_object_name = 'organizer'
@cached_property
@@ -580,10 +582,7 @@ class OrganizerCreate(CreateView):
ret = super().form_valid(form)
t = Team.objects.create(
organizer=form.instance, name=_('Administrators'),
all_events=True, can_create_events=True, can_change_teams=True, can_manage_gift_cards=True,
can_change_organizer_settings=True, can_change_event_settings=True, can_change_items=True,
can_manage_customers=True, can_manage_reusable_media=True,
can_view_orders=True, can_change_orders=True, can_view_vouchers=True, can_change_vouchers=True
all_events=True, all_event_permissions=True, all_organizer_permissions=True,
)
t.members.add(self.request.user)
return ret
@@ -597,7 +596,7 @@ class OrganizerCreate(CreateView):
class OrganizerPlugins(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, TemplateView, SingleObjectMixin):
model = Organizer
context_object_name = 'organizer'
permission = 'can_change_organizer_settings'
permission = 'organizer.settings.general:write'
template_name = 'pretixcontrol/organizers/plugins.html'
def get_object(self, queryset=None) -> Organizer:
@@ -769,14 +768,14 @@ class OrganizerPlugins(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixi
class OrganizerPluginEvents(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, FormView):
model = Organizer
context_object_name = 'organizer'
permission = 'can_change_organizer_settings'
permission = 'organizer.settings.general:write'
template_name = 'pretixcontrol/organizers/plugin_events.html'
form_class = OrganizerPluginEventsForm
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs["events"] = self.request.user.get_events_with_permission(
"can_change_event_settings", request=self.request
"event.settings.general:write", request=self.request
).filter(organizer=self.request.organizer)
kwargs["initial"] = {
"events": self.request.organizer.events.filter(plugins__regex='(^|,)' + self.plugin.module + '(,|$)')
@@ -854,7 +853,7 @@ class OrganizerPluginEvents(OrganizerDetailViewMixin, OrganizerPermissionRequire
class TeamListView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, PaginationMixin, ListView):
model = Team
template_name = 'pretixcontrol/organizers/teams.html'
permission = 'can_change_teams'
permission = 'organizer.teams:write'
context_object_name = 'teams'
def get_queryset(self):
@@ -880,7 +879,7 @@ class TeamListView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, P
class TeamCreateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, CreateView):
model = Team
template_name = 'pretixcontrol/organizers/team_edit.html'
permission = 'can_change_teams'
permission = 'organizer.teams:write'
form_class = TeamForm
def get_form_kwargs(self):
@@ -917,7 +916,7 @@ class TeamCreateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin,
class TeamUpdateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, UpdateView):
model = Team
template_name = 'pretixcontrol/organizers/team_edit.html'
permission = 'can_change_teams'
permission = 'organizer.teams:write'
context_object_name = 'team'
form_class = TeamForm
@@ -953,7 +952,7 @@ class TeamUpdateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin,
class TeamDeleteView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, CompatDeleteView):
model = Team
template_name = 'pretixcontrol/organizers/team_delete.html'
permission = 'can_change_teams'
permission = 'organizer.teams:write'
context_object_name = 'team'
def get_object(self, queryset=None):
@@ -971,7 +970,8 @@ class TeamDeleteView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin,
def is_allowed(self) -> bool:
return self.request.organizer.teams.exclude(pk=self.kwargs.get('team')).filter(
can_change_teams=True, members__isnull=False
TeamQuerySet.organizer_permission_q("organizer.teams:write"),
members__isnull=False
).exists() or self.request.user.has_active_staff_session(self.request.session.session_key)
@transaction.atomic
@@ -1012,7 +1012,7 @@ class TeamDeleteView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin,
class TeamMemberView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, DetailView):
template_name = 'pretixcontrol/organizers/team_members.html'
context_object_name = 'team'
permission = 'can_change_teams'
permission = 'organizer.teams:write'
model = Team
def get_object(self, queryset=None):
@@ -1066,9 +1066,10 @@ class TeamMemberView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin,
pass
else:
other_admin_teams = self.request.organizer.teams.exclude(pk=self.object.pk).filter(
can_change_teams=True, members__isnull=False
TeamQuerySet.organizer_permission_q("organizer.teams:write"),
members__isnull=False
).exists() or self.request.user.has_active_staff_session(self.request.session.session_key)
if not other_admin_teams and self.object.can_change_teams and self.object.members.count() == 1:
if not other_admin_teams and self.object.has_organizer_permission("organizer.teams:write") and self.object.members.count() == 1:
messages.error(self.request, _('You cannot remove the last member from this team as no one would '
'be left with the permission to change teams.'))
return redirect(self.get_success_url())
@@ -1232,7 +1233,7 @@ class DeviceQueryMixin:
class DeviceListView(DeviceQueryMixin, OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, ListView):
model = Device
template_name = 'pretixcontrol/organizers/devices.html'
permission = 'can_change_organizer_settings'
permission = 'organizer.settings.general:write'
context_object_name = 'devices'
paginate_by = 100
@@ -1245,7 +1246,7 @@ class DeviceListView(DeviceQueryMixin, OrganizerDetailViewMixin, OrganizerPermis
class DeviceCreateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, CreateView):
model = Device
template_name = 'pretixcontrol/organizers/device_edit.html'
permission = 'can_change_organizer_settings'
permission = 'organizer.settings.general:write'
form_class = DeviceForm
def get_form_kwargs(self):
@@ -1276,7 +1277,7 @@ class DeviceCreateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixi
class DeviceLogView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, ListView):
template_name = 'pretixcontrol/organizers/device_logs.html'
permission = 'can_change_organizer_settings'
permission = 'organizer.settings.general:write'
model = LogEntry
context_object_name = 'logs'
paginate_by = 20
@@ -1304,7 +1305,7 @@ class DeviceLogView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin,
class DeviceUpdateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, UpdateView):
model = Device
template_name = 'pretixcontrol/organizers/device_edit.html'
permission = 'can_change_organizer_settings'
permission = 'organizer.settings.general:write'
context_object_name = 'device'
form_class = DeviceForm
@@ -1347,7 +1348,7 @@ class DeviceUpdateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixi
class DeviceBulkUpdateView(DeviceQueryMixin, OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, FormView):
template_name = 'pretixcontrol/organizers/device_bulk_edit.html'
permission = 'can_change_organizer_settings'
permission = 'organizer.settings.general:write'
context_object_name = 'device'
form_class = DeviceBulkEditForm
@@ -1461,7 +1462,7 @@ class DeviceBulkUpdateView(DeviceQueryMixin, OrganizerDetailViewMixin, Organizer
class DeviceConnectView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, DetailView):
model = Device
template_name = 'pretixcontrol/organizers/device_connect.html'
permission = 'can_change_organizer_settings'
permission = 'organizer.settings.general:write'
context_object_name = 'device'
def get_object(self, queryset=None):
@@ -1493,7 +1494,7 @@ class DeviceConnectView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMix
class DeviceRevokeView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, DetailView):
model = Device
template_name = 'pretixcontrol/organizers/device_revoke.html'
permission = 'can_change_organizer_settings'
permission = 'organizer.settings.general:write'
context_object_name = 'device'
def get_object(self, queryset=None):
@@ -1523,7 +1524,7 @@ class DeviceRevokeView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixi
class WebHookListView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, ListView):
model = WebHook
template_name = 'pretixcontrol/organizers/webhooks.html'
permission = 'can_change_organizer_settings'
permission = 'organizer.settings.general:write'
context_object_name = 'webhooks'
def get_queryset(self):
@@ -1533,7 +1534,7 @@ class WebHookListView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin
class WebHookCreateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, CreateView):
model = WebHook
template_name = 'pretixcontrol/organizers/webhook_edit.html'
permission = 'can_change_organizer_settings'
permission = 'organizer.settings.general:write'
form_class = WebHookForm
def get_form_kwargs(self):
@@ -1567,7 +1568,7 @@ class WebHookCreateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMix
class WebHookUpdateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, UpdateView):
model = WebHook
template_name = 'pretixcontrol/organizers/webhook_edit.html'
permission = 'can_change_organizer_settings'
permission = 'organizer.settings.general:write'
context_object_name = 'webhook'
form_class = WebHookForm
@@ -1610,7 +1611,7 @@ class WebHookUpdateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMix
class WebHookLogsView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, ListView):
model = WebHook
template_name = 'pretixcontrol/organizers/webhook_logs.html'
permission = 'can_change_organizer_settings'
permission = 'organizer.settings.general:write'
context_object_name = 'calls'
paginate_by = 50
@@ -1652,7 +1653,7 @@ class WebHookLogsView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin
class GiftCardAcceptanceInviteView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, FormView):
model = GiftCardAcceptance
template_name = 'pretixcontrol/organizers/giftcard_acceptance_invite.html'
permission = 'can_change_organizer_settings'
permission = 'organizer.settings.general:write'
form_class = GiftCardAcceptanceInviteForm
def get_form_kwargs(self):
@@ -1682,7 +1683,7 @@ class GiftCardAcceptanceInviteView(OrganizerDetailViewMixin, OrganizerPermission
class GiftCardAcceptanceListView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, ListView):
model = GiftCardAcceptance
template_name = 'pretixcontrol/organizers/giftcard_acceptance_list.html'
permission = 'can_change_organizer_settings'
permission = 'organizer.settings.general:write'
context_object_name = 'acceptor_acceptance'
paginate_by = 50
@@ -1742,7 +1743,7 @@ class GiftCardAcceptanceListView(OrganizerDetailViewMixin, OrganizerPermissionRe
class GiftCardListView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, ListView):
model = GiftCard
template_name = 'pretixcontrol/organizers/giftcards.html'
permission = 'can_manage_gift_cards'
permission = 'organizer.giftcards:read'
context_object_name = 'giftcards'
paginate_by = 50
@@ -1765,7 +1766,7 @@ class GiftCardListView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixi
ctx = super().get_context_data(**kwargs)
ctx['filter_form'] = self.filter_form
ctx['other_organizers'] = self.request.user.get_organizers_with_permission(
'can_manage_gift_cards', self.request
'organizer.giftcards:write', self.request
).exclude(pk=self.request.organizer.pk)
return ctx
@@ -1776,7 +1777,7 @@ class GiftCardListView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixi
class GiftCardDetailView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, DetailView):
template_name = 'pretixcontrol/organizers/giftcard.html'
permission = 'can_manage_gift_cards'
permission = 'organizer.giftcards:read'
context_object_name = 'card'
def get_object(self, queryset=None) -> Organizer:
@@ -1787,6 +1788,8 @@ class GiftCardDetailView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMi
@transaction.atomic()
def post(self, request, *args, **kwargs):
if not request.user.has_organizer_permission(request.organizer, "organizer.giftcards:write", request=request):
raise PermissionDenied()
self.object = GiftCard.objects.select_for_update(of=OF_SELF).get(pk=self.get_object().pk)
if 'revert' in request.POST:
t = get_object_or_404(self.object.transactions.all(), pk=request.POST.get('revert'), order__isnull=False)
@@ -1866,7 +1869,7 @@ class GiftCardDetailView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMi
class GiftCardCreateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, CreateView):
template_name = 'pretixcontrol/organizers/giftcard_create.html'
permission = 'can_manage_gift_cards'
permission = 'organizer.giftcards:write'
form_class = GiftCardCreateForm
success_url = 'invalid'
@@ -1908,7 +1911,7 @@ class GiftCardCreateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMi
class GiftCardUpdateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, UpdateView):
template_name = 'pretixcontrol/organizers/giftcard_edit.html'
permission = 'can_manage_gift_cards'
permission = 'organizer.giftcards:write'
form_class = GiftCardUpdateForm
success_url = 'invalid'
context_object_name = 'card'
@@ -1999,7 +2002,7 @@ class ExportMixin:
@cached_property
def events(self):
return self.request.user.get_events_with_permission('can_view_orders', request=self.request).filter(
return self.request.user.get_events_with_permission('event.orders:read', request=self.request).filter(
organizer=self.request.organizer
)
@@ -2033,7 +2036,7 @@ class ExportMixin:
return ctx
def get_scheduled_queryset(self):
if not self.request.user.has_organizer_permission(self.request.organizer, 'can_change_organizer_settings',
if not self.request.user.has_organizer_permission(self.request.organizer, 'organizer.settings.general:write',
request=self.request):
qs = self.request.organizer.scheduled_exports.filter(owner=self.request.user)
else:
@@ -2267,7 +2270,7 @@ class RunScheduledExportView(OrganizerPermissionRequiredMixin, ExportMixin, View
class GateListView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, ListView):
model = Gate
template_name = 'pretixcontrol/organizers/gates.html'
permission = 'can_change_organizer_settings'
permission = 'organizer.settings.general:write'
context_object_name = 'gates'
def get_queryset(self):
@@ -2277,7 +2280,7 @@ class GateListView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, L
class GateCreateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, CreateView):
model = Gate
template_name = 'pretixcontrol/organizers/gate_edit.html'
permission = 'can_change_organizer_settings'
permission = 'organizer.settings.general:write'
form_class = GateForm
def get_form_kwargs(self):
@@ -2311,7 +2314,7 @@ class GateCreateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin,
class GateUpdateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, UpdateView):
model = Gate
template_name = 'pretixcontrol/organizers/gate_edit.html'
permission = 'can_change_organizer_settings'
permission = 'organizer.settings.general:write'
context_object_name = 'gate'
form_class = GateForm
@@ -2346,7 +2349,7 @@ class GateUpdateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin,
class GateDeleteView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, CompatDeleteView):
model = Gate
template_name = 'pretixcontrol/organizers/gate_delete.html'
permission = 'can_change_organizer_settings'
permission = 'organizer.settings.general:write'
context_object_name = 'gate'
def get_object(self, queryset=None):
@@ -2370,7 +2373,7 @@ class GateDeleteView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin,
class EventMetaPropertyListView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, ListView):
model = EventMetaProperty
template_name = 'pretixcontrol/organizers/properties.html'
permission = 'can_change_organizer_settings'
permission = 'organizer.settings.general:write'
context_object_name = 'properties'
def get_queryset(self):
@@ -2421,7 +2424,7 @@ class EventMetaPropertyEditorMixin:
class EventMetaPropertyCreateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, EventMetaPropertyEditorMixin, CreateView):
model = EventMetaProperty
permission = 'can_change_organizer_settings'
permission = 'organizer.settings.general:write'
def get_object(self, queryset=None):
return EventMetaProperty()
@@ -2451,7 +2454,7 @@ class EventMetaPropertyCreateView(OrganizerDetailViewMixin, OrganizerPermissionR
class EventMetaPropertyUpdateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, EventMetaPropertyEditorMixin, UpdateView):
model = EventMetaProperty
permission = 'can_change_organizer_settings'
permission = 'organizer.settings.general:write'
context_object_name = 'property'
def get_object(self, queryset=None):
@@ -2483,7 +2486,7 @@ class EventMetaPropertyUpdateView(OrganizerDetailViewMixin, OrganizerPermissionR
class EventMetaPropertyDeleteView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, CompatDeleteView):
model = EventMetaProperty
template_name = 'pretixcontrol/organizers/property_delete.html'
permission = 'can_change_organizer_settings'
permission = 'organizer.settings.general:write'
context_object_name = 'property'
def get_object(self, queryset=None):
@@ -2527,7 +2530,7 @@ def meta_property_move(request, property, up=True):
messages.success(request, _('The order of properties has been updated.'))
@organizer_permission_required("can_change_organizer_settings")
@organizer_permission_required("organizer.settings.general:write")
@require_http_methods(["POST"])
def meta_property_move_up(request, organizer, property):
meta_property_move(request, property, up=True)
@@ -2535,7 +2538,7 @@ def meta_property_move_up(request, organizer, property):
organizer=request.organizer.slug)
@organizer_permission_required("can_change_organizer_settings")
@organizer_permission_required("organizer.settings.general:write")
@require_http_methods(["POST"])
def meta_property_move_down(request, organizer, property):
meta_property_move(request, property, up=False)
@@ -2544,7 +2547,7 @@ def meta_property_move_down(request, organizer, property):
@transaction.atomic
@organizer_permission_required("can_change_organizer_settings")
@organizer_permission_required("organizer.settings.general:write")
@require_http_methods(["POST"])
def reorder_meta_properties(request, organizer):
try:
@@ -2576,7 +2579,7 @@ def reorder_meta_properties(request, organizer):
class LogView(OrganizerPermissionRequiredMixin, PaginationMixin, ListView):
template_name = 'pretixcontrol/organizers/logs.html'
permission = 'can_change_organizer_settings'
permission = 'organizer.settings.general:write'
model = LogEntry
context_object_name = 'logs'
@@ -2601,7 +2604,7 @@ class LogView(OrganizerPermissionRequiredMixin, PaginationMixin, ListView):
class MembershipTypeListView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, ListView):
model = MembershipType
template_name = 'pretixcontrol/organizers/membershiptypes.html'
permission = 'can_change_organizer_settings'
permission = 'organizer.settings.general:write'
context_object_name = 'types'
def get_queryset(self):
@@ -2611,7 +2614,7 @@ class MembershipTypeListView(OrganizerDetailViewMixin, OrganizerPermissionRequir
class MembershipTypeCreateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, CreateView):
model = MembershipType
template_name = 'pretixcontrol/organizers/membershiptype_edit.html'
permission = 'can_change_organizer_settings'
permission = 'organizer.settings.general:write'
form_class = MembershipTypeForm
def get_object(self, queryset=None):
@@ -2645,7 +2648,7 @@ class MembershipTypeCreateView(OrganizerDetailViewMixin, OrganizerPermissionRequ
class MembershipTypeUpdateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, UpdateView):
model = MembershipType
template_name = 'pretixcontrol/organizers/membershiptype_edit.html'
permission = 'can_change_organizer_settings'
permission = 'organizer.settings.general:write'
context_object_name = 'type'
form_class = MembershipTypeForm
@@ -2680,7 +2683,7 @@ class MembershipTypeUpdateView(OrganizerDetailViewMixin, OrganizerPermissionRequ
class MembershipTypeDeleteView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, CompatDeleteView):
model = MembershipType
template_name = 'pretixcontrol/organizers/membershiptype_delete.html'
permission = 'can_change_organizer_settings'
permission = 'organizer.settings.general:write'
context_object_name = 'type'
def get_object(self, queryset=None):
@@ -2710,7 +2713,7 @@ class MembershipTypeDeleteView(OrganizerDetailViewMixin, OrganizerPermissionRequ
class SSOProviderListView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, ListView):
model = CustomerSSOProvider
template_name = 'pretixcontrol/organizers/ssoproviders.html'
permission = 'can_change_organizer_settings'
permission = 'organizer.settings.general:write'
context_object_name = 'providers'
def get_queryset(self):
@@ -2720,7 +2723,7 @@ class SSOProviderListView(OrganizerDetailViewMixin, OrganizerPermissionRequiredM
class SSOProviderCreateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, CreateView):
model = CustomerSSOProvider
template_name = 'pretixcontrol/organizers/ssoprovider_edit.html'
permission = 'can_change_organizer_settings'
permission = 'organizer.settings.general:write'
form_class = SSOProviderForm
def get_object(self, queryset=None):
@@ -2754,7 +2757,7 @@ class SSOProviderCreateView(OrganizerDetailViewMixin, OrganizerPermissionRequire
class SSOProviderUpdateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, UpdateView):
model = CustomerSSOProvider
template_name = 'pretixcontrol/organizers/ssoprovider_edit.html'
permission = 'can_change_organizer_settings'
permission = 'organizer.settings.general:write'
context_object_name = 'provider'
form_class = SSOProviderForm
@@ -2796,7 +2799,7 @@ class SSOProviderUpdateView(OrganizerDetailViewMixin, OrganizerPermissionRequire
class SSOProviderDeleteView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, CompatDeleteView):
model = CustomerSSOProvider
template_name = 'pretixcontrol/organizers/ssoprovider_delete.html'
permission = 'can_change_organizer_settings'
permission = 'organizer.settings.general:write'
context_object_name = 'provider'
def get_object(self, queryset=None):
@@ -2826,7 +2829,7 @@ class SSOProviderDeleteView(OrganizerDetailViewMixin, OrganizerPermissionRequire
class SSOClientListView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, ListView):
model = CustomerSSOClient
template_name = 'pretixcontrol/organizers/ssoclients.html'
permission = 'can_change_organizer_settings'
permission = 'organizer.settings.general:write'
context_object_name = 'clients'
def get_queryset(self):
@@ -2836,7 +2839,7 @@ class SSOClientListView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMix
class SSOClientCreateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, CreateView):
model = CustomerSSOClient
template_name = 'pretixcontrol/organizers/ssoclient_edit.html'
permission = 'can_change_organizer_settings'
permission = 'organizer.settings.general:write'
form_class = SSOClientForm
def get_object(self, queryset=None):
@@ -2876,7 +2879,7 @@ class SSOClientCreateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredM
class SSOClientUpdateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, UpdateView):
model = CustomerSSOClient
template_name = 'pretixcontrol/organizers/ssoclient_edit.html'
permission = 'can_change_organizer_settings'
permission = 'organizer.settings.general:write'
context_object_name = 'client'
form_class = SSOClientForm
@@ -2926,7 +2929,7 @@ class SSOClientUpdateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredM
class SSOClientDeleteView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, CompatDeleteView):
model = CustomerSSOClient
template_name = 'pretixcontrol/organizers/ssoclient_delete.html'
permission = 'can_change_organizer_settings'
permission = 'organizer.settings.general:write'
context_object_name = 'client'
def get_object(self, queryset=None):
@@ -2956,7 +2959,7 @@ class SSOClientDeleteView(OrganizerDetailViewMixin, OrganizerPermissionRequiredM
class CustomerListView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, PaginationMixin, ListView):
model = Customer
template_name = 'pretixcontrol/organizers/customers.html'
permission = 'can_manage_customers'
permission = 'organizer.customers:write'
context_object_name = 'customers'
def get_queryset(self):
@@ -2977,7 +2980,7 @@ class CustomerListView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixi
class CustomerDetailView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, PaginationMixin, ListView):
template_name = 'pretixcontrol/organizers/customer.html'
permission = 'can_manage_customers'
permission = 'organizer.customers:write'
context_object_name = 'orders'
def get_queryset(self):
@@ -3092,7 +3095,7 @@ class CustomerDetailView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMi
class CustomerCreateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, CreateView):
template_name = 'pretixcontrol/organizers/customer_edit.html'
permission = 'can_manage_customers'
permission = 'organizer.customers:write'
context_object_name = 'customer'
form_class = CustomerCreateForm
@@ -3122,7 +3125,7 @@ class CustomerCreateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMi
class CustomerUpdateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, UpdateView):
template_name = 'pretixcontrol/organizers/customer_edit.html'
permission = 'can_manage_customers'
permission = 'organizer.customers:write'
context_object_name = 'customer'
form_class = CustomerUpdateForm
@@ -3151,7 +3154,7 @@ class CustomerUpdateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMi
class MembershipUpdateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, UpdateView):
template_name = 'pretixcontrol/organizers/customer_membership.html'
permission = 'can_manage_customers'
permission = 'organizer.customers:write'
context_object_name = 'membership'
form_class = MembershipUpdateForm
@@ -3191,7 +3194,7 @@ class MembershipUpdateView(OrganizerDetailViewMixin, OrganizerPermissionRequired
class MembershipDeleteView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, CompatDeleteView):
template_name = 'pretixcontrol/organizers/customer_membership_delete.html'
permission = 'can_manage_customers'
permission = 'organizer.customers:write'
context_object_name = 'membership'
def get_object(self, queryset=None):
@@ -3229,7 +3232,7 @@ class MembershipDeleteView(OrganizerDetailViewMixin, OrganizerPermissionRequired
class MembershipCreateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, CreateView):
template_name = 'pretixcontrol/organizers/customer_membership.html'
permission = 'can_manage_customers'
permission = 'organizer.customers:write'
context_object_name = 'membership'
form_class = MembershipUpdateForm
@@ -3268,7 +3271,7 @@ class MembershipCreateView(OrganizerDetailViewMixin, OrganizerPermissionRequired
class CustomerAnonymizeView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, DetailView):
template_name = 'pretixcontrol/organizers/customer_anonymize.html'
permission = 'can_manage_customers'
permission = 'organizer.customers:write'
context_object_name = 'customer'
def get_object(self, queryset=None):
@@ -3295,7 +3298,7 @@ class CustomerAnonymizeView(OrganizerDetailViewMixin, OrganizerPermissionRequire
class ReusableMediaListView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, PaginationMixin, ListView):
model = ReusableMedium
template_name = 'pretixcontrol/organizers/reusable_media.html'
permission = 'can_manage_reusable_media'
permission = 'organizer.reusablemedia:read'
context_object_name = 'media'
def get_queryset(self):
@@ -3319,7 +3322,7 @@ class ReusableMediaListView(OrganizerDetailViewMixin, OrganizerPermissionRequire
class ReusableMediumDetailView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, TemplateView):
template_name = 'pretixcontrol/organizers/reusable_medium.html'
permission = 'can_manage_reusable_media'
permission = 'organizer.reusablemedia:read'
@cached_property
def medium(self):
@@ -3336,7 +3339,7 @@ class ReusableMediumDetailView(OrganizerDetailViewMixin, OrganizerPermissionRequ
class ReusableMediumCreateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, CreateView):
template_name = 'pretixcontrol/organizers/reusable_medium_edit.html'
permission = 'can_manage_reusable_media'
permission = 'organizer.reusablemedia:write'
context_object_name = 'medium'
form_class = ReusableMediumCreateForm
@@ -3365,7 +3368,7 @@ class ReusableMediumCreateView(OrganizerDetailViewMixin, OrganizerPermissionRequ
class ReusableMediumUpdateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, UpdateView):
template_name = 'pretixcontrol/organizers/reusable_medium_edit.html'
permission = 'can_manage_reusable_media'
permission = 'organizer.reusablemedia:write'
context_object_name = 'medium'
form_class = ReusableMediumUpdateForm
@@ -3395,7 +3398,7 @@ class ReusableMediumUpdateView(OrganizerDetailViewMixin, OrganizerPermissionRequ
class ChannelListView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, ListView):
model = SalesChannel
template_name = 'pretixcontrol/organizers/channels.html'
permission = 'can_change_organizer_settings'
permission = 'organizer.settings.general:write'
context_object_name = 'channels'
def get_queryset(self):
@@ -3414,7 +3417,7 @@ class ChannelEditorMixin:
class ChannelCreateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, ChannelEditorMixin, CreateView):
model = SalesChannel
permission = 'can_change_organizer_settings'
permission = 'organizer.settings.general:write'
template_name = 'pretixcontrol/organizers/channel_add.html'
def get_object(self, queryset=None):
@@ -3486,7 +3489,7 @@ class ChannelCreateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMix
class ChannelUpdateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, ChannelEditorMixin, UpdateView):
model = SalesChannel
permission = 'can_change_organizer_settings'
permission = 'organizer.settings.general:write'
context_object_name = 'channel'
template_name = 'pretixcontrol/organizers/channel_edit.html'
@@ -3531,7 +3534,7 @@ class ChannelUpdateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMix
class ChannelDeleteView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, CompatDeleteView):
model = SalesChannel
template_name = 'pretixcontrol/organizers/channel_delete.html'
permission = 'can_change_organizer_settings'
permission = 'organizer.settings.general:write'
context_object_name = 'channel'
def get_object(self, queryset=None):
@@ -3587,7 +3590,7 @@ def channel_move(request, channel, up=True):
messages.success(request, _('The order of sales channels has been updated.'))
@organizer_permission_required("can_change_organizer_settings")
@organizer_permission_required("organizer.settings.general:write")
@require_http_methods(["POST"])
def channel_move_up(request, organizer, channel):
channel_move(request, channel, up=True)
@@ -3595,7 +3598,7 @@ def channel_move_up(request, organizer, channel):
organizer=request.organizer.slug)
@organizer_permission_required("can_change_organizer_settings")
@organizer_permission_required("organizer.settings.general:write")
@require_http_methods(["POST"])
def channel_move_down(request, organizer, channel):
channel_move(request, channel, up=False)
@@ -3604,7 +3607,7 @@ def channel_move_down(request, organizer, channel):
@transaction.atomic
@organizer_permission_required("can_change_organizer_settings")
@organizer_permission_required("organizer.settings.general:write")
@require_http_methods(["POST"])
def reorder_channels(request, organizer):
try:

View File

@@ -58,7 +58,7 @@ logger = logging.getLogger(__name__)
class BaseEditorView(EventPermissionRequiredMixin, TemplateView):
template_name = 'pretixcontrol/pdf/index.html'
permission = 'can_change_settings'
permission = 'event.settings.general:write'
accepted_formats = (
'application/pdf',
)

View File

@@ -85,7 +85,7 @@ class OrderSearch(PaginationMixin, ListView):
if not self.request.user.has_active_staff_session(self.request.session.session_key):
qs = qs.filter(
Q(event_id__in=self.request.user.get_events_with_permission('can_view_orders').values_list('id', flat=True))
Q(event_id__in=self.request.user.get_events_with_permission('event.orders:read').values_list('id', flat=True))
)
if self.filter_form.is_valid():
@@ -159,7 +159,7 @@ class PaymentSearch(PaginationMixin, ListView):
if not self.request.user.has_active_staff_session(self.request.session.session_key):
qs = qs.filter(
Q(order__event_id__in=self.request.user.get_events_with_permission('can_view_orders').values_list('id', flat=True))
Q(order__event_id__in=self.request.user.get_events_with_permission('event.orders:read').values_list('id', flat=True))
)
if self.filter_form.is_valid():

View File

@@ -76,7 +76,7 @@ class ShredderMixin:
class StartShredView(RecentAuthenticationRequiredMixin, EventPermissionRequiredMixin, ShredderMixin, TemplateView):
permission = 'can_change_orders'
permission = 'event.orders:write'
template_name = 'pretixcontrol/shredder/index.html'
def get_context_data(self, **kwargs):
@@ -87,7 +87,7 @@ class StartShredView(RecentAuthenticationRequiredMixin, EventPermissionRequiredM
class ShredDownloadView(RecentAuthenticationRequiredMixin, EventPermissionRequiredMixin, ShredderMixin, TemplateView):
permission = 'can_change_orders'
permission = 'event.orders:write'
template_name = 'pretixcontrol/shredder/download.html'
def get_context_data(self, **kwargs):
@@ -119,7 +119,7 @@ class ShredDownloadView(RecentAuthenticationRequiredMixin, EventPermissionRequir
class ShredExportView(RecentAuthenticationRequiredMixin, EventPermissionRequiredMixin, ShredderMixin, AsyncAction, View):
permission = 'can_change_orders'
permission = 'event.orders:write'
task = export
known_errortypes = ['ShredError']
@@ -148,7 +148,7 @@ class ShredExportView(RecentAuthenticationRequiredMixin, EventPermissionRequired
class ShredDoView(RecentAuthenticationRequiredMixin, EventPermissionRequiredMixin, ShredderMixin, AsyncAction, View):
permission = 'can_change_orders'
permission = 'event.orders:write'
task = shred
known_errortypes = ['ShredError']

View File

@@ -117,7 +117,7 @@ class SubEventList(EventPermissionRequiredMixin, PaginationMixin, SubEventQueryM
model = SubEvent
context_object_name = 'subevents'
template_name = 'pretixcontrol/subevents/index.html'
permission = 'can_change_settings'
permission = 'event.settings.general:write'
def get_queryset(self):
return super().get_queryset(True).prefetch_related(
@@ -156,7 +156,7 @@ class SubEventList(EventPermissionRequiredMixin, PaginationMixin, SubEventQueryM
class SubEventDelete(EventPermissionRequiredMixin, CompatDeleteView):
model = SubEvent
template_name = 'pretixcontrol/subevents/delete.html'
permission = 'can_change_settings'
permission = 'event.settings.general:write'
context_object_name = 'subevents'
def get_object(self, queryset=None) -> SubEvent:
@@ -241,7 +241,7 @@ class SubEventEditorMixin(MetaDataEditorMixin):
property=p,
disabled=(
p.protected and
not self.request.user.has_organizer_permission(self.request.organizer, 'can_change_organizer_settings', request=self.request)
not self.request.user.has_organizer_permission(self.request.organizer, 'organizer.settings.general:write', request=self.request)
),
default=self._default_meta.get(p.name, ''),
instance=val_instances.get(p.pk, self.meta_model(property=p, subevent=self.object)),
@@ -508,7 +508,7 @@ class SubEventEditorMixin(MetaDataEditorMixin):
class SubEventUpdate(EventPermissionRequiredMixin, SubEventEditorMixin, UpdateView):
model = SubEvent
template_name = 'pretixcontrol/subevents/detail.html'
permission = 'can_change_settings'
permission = 'event.settings.general:write'
context_object_name = 'subevent'
form_class = SubEventForm
@@ -575,7 +575,7 @@ class SubEventUpdate(EventPermissionRequiredMixin, SubEventEditorMixin, UpdateVi
class SubEventCreate(SubEventEditorMixin, EventPermissionRequiredMixin, CreateView):
model = SubEvent
template_name = 'pretixcontrol/subevents/detail.html'
permission = 'can_change_settings'
permission = 'event.settings.general:write'
context_object_name = 'subevent'
form_class = SubEventForm
@@ -669,7 +669,7 @@ class SubEventCreate(SubEventEditorMixin, EventPermissionRequiredMixin, CreateVi
class SubEventBulkAction(SubEventQueryMixin, EventPermissionRequiredMixin, View):
permission = 'can_change_settings'
permission = 'event.settings.general:write'
@transaction.atomic
def post(self, request, *args, **kwargs):
@@ -740,7 +740,7 @@ class SubEventBulkAction(SubEventQueryMixin, EventPermissionRequiredMixin, View)
class SubEventBulkCreate(SubEventEditorMixin, EventPermissionRequiredMixin, AsyncFormView):
model = SubEvent
template_name = 'pretixcontrol/subevents/bulk.html'
permission = 'can_change_settings'
permission = 'event.settings.general:write'
context_object_name = 'subevent'
form_class = SubEventBulkForm
itemformclass = BulkSubEventItemForm
@@ -1065,7 +1065,7 @@ class SubEventBulkCreate(SubEventEditorMixin, EventPermissionRequiredMixin, Asyn
class SubEventBulkEdit(SubEventQueryMixin, EventPermissionRequiredMixin, FormView):
permission = 'can_change_settings'
permission = 'event.settings.general:write'
form_class = SubEventBulkEditForm
template_name = 'pretixcontrol/subevents/bulk_edit.html'
context_object_name = 'subevent'

View File

@@ -51,6 +51,7 @@ from pretix.base.models import (
ItemVariation, ItemVariationMetaValue, Order, OrderPosition, Organizer,
SubEventMetaValue, User, Voucher,
)
from pretix.base.models.organizer import TeamQuerySet
from pretix.control.forms.event import EventWizardCopyForm
from pretix.control.permissions import (
event_permission_required, organizer_permission_required,
@@ -172,7 +173,7 @@ def event_list(request):
return JsonResponse(doc)
@organizer_permission_required(("can_manage_gift_cards", "can_manage_reusable_media"))
@organizer_permission_required(("organizer.giftcards:read", "organizer.reusablemedia:write"))
def giftcard_select2(request, **kwargs):
query = request.GET.get('query', '')
try:
@@ -180,7 +181,7 @@ def giftcard_select2(request, **kwargs):
except ValueError:
page = 1
if request.user.has_organizer_permission(request.organizer, 'can_manage_gift_cards', request):
if request.user.has_organizer_permission(request.organizer, 'organizer.giftcards:write', request):
qs = request.organizer.issued_gift_cards.filter(
Q(secret__icontains=query)
).order_by('secret')
@@ -210,7 +211,7 @@ def giftcard_select2(request, **kwargs):
return JsonResponse(doc)
@organizer_permission_required(("can_manage_reusable_media", "can_manage_gift_cards"))
@organizer_permission_required(("organizer.reusablemedia:write", "organizer.giftcards:write"))
def ticket_select2(request, **kwargs):
query = request.GET.get('query', '')
try:
@@ -240,8 +241,13 @@ def ticket_select2(request, **kwargs):
qs_orders = qs_orders.filter(
exact_match | (
soft_match & (
Q(order__event__organizer_id__in=request.user.teams.filter(all_events=True, can_view_orders=True).values_list('organizer', flat=True))
| Q(order__event_id__in=request.user.teams.filter(can_view_orders=True).values_list('limit_events__id', flat=True))
Q(order__event__organizer_id__in=request.user.teams.filter(
TeamQuerySet.event_permission_q("event.orders:read"),
all_events=True,
).values_list('organizer', flat=True))
| Q(order__event_id__in=request.user.teams.filter(
TeamQuerySet.event_permission_q("event.orders:read")
).values_list('limit_events__id', flat=True))
)
)
)
@@ -270,7 +276,7 @@ def ticket_select2(request, **kwargs):
return JsonResponse(doc)
@organizer_permission_required("can_manage_customers")
@organizer_permission_required("organizer.customers:write")
def customer_select2(request, **kwargs):
query = request.GET.get('query', '')
try:
@@ -337,9 +343,9 @@ def nav_context_list(request):
if not request.user.has_active_staff_session(request.session.session_key):
qs_orders = qs_orders.filter(
Q(event__organizer_id__in=request.user.teams.filter(
all_events=True, can_view_orders=True).values_list('organizer', flat=True))
TeamQuerySet.event_permission_q("event.orders:read"), all_events=True).values_list('organizer', flat=True))
| Q(event_id__in=request.user.teams.filter(
can_view_orders=True).values_list('limit_events__id', flat=True))
TeamQuerySet.event_permission_q("event.orders:read")).values_list('limit_events__id', flat=True))
)
qs_vouchers = Voucher.objects.filter(
@@ -348,9 +354,9 @@ def nav_context_list(request):
if not request.user.has_active_staff_session(request.session.session_key):
qs_vouchers = qs_vouchers.filter(
Q(event__organizer_id__in=request.user.teams.filter(
all_events=True, can_view_vouchers=True).values_list('organizer', flat=True))
TeamQuerySet.event_permission_q("event.vouchers:read"), all_events=True).values_list('organizer', flat=True))
| Q(event_id__in=request.user.teams.filter(
can_view_vouchers=True).values_list('limit_events__id', flat=True))
TeamQuerySet.event_permission_q("event.vouchers:read")).values_list('limit_events__id', flat=True))
)
else:
qs_vouchers = Voucher.objects.none()
@@ -813,7 +819,7 @@ def organizer_select2(request):
qs = qs.filter(Q(name__icontains=term) | Q(slug__icontains=term))
if not request.user.has_active_staff_session(request.session.session_key):
if 'can_create' in request.GET:
qs = qs.filter(pk__in=request.user.teams.filter(can_create_events=True).values_list('organizer', flat=True))
qs = qs.filter(pk__in=request.user.teams.filter(TeamQuerySet.organizer_permission_q("organizer.events:create")).values_list('organizer', flat=True))
else:
qs = qs.filter(pk__in=request.user.teams.values_list('organizer', flat=True))
@@ -976,21 +982,21 @@ def item_meta_values(request, organizer, event):
var_matches = var_matches.filter(variation__item__event__organizer_id=organizer.pk)
all_access = (
request.user.has_active_staff_session(request.session.session_key)
or request.user.teams.filter(all_events=True, organizer=organizer, can_change_items=True).exists()
or request.user.teams.filter(TeamQuerySet.event_permission_q("event.items:write"), all_events=True, organizer=organizer).exists()
)
if not all_access:
defaults = defaults.filter(
event__id__in=request.user.teams.filter(can_change_items=True).values_list(
event__id__in=request.user.teams.filter(TeamQuerySet.event_permission_q("event.items:write")).values_list(
'limit_events__id', flat=True
)
)
matches = matches.filter(
item__event__id__in=request.user.teams.filter(can_change_items=True).values_list(
item__event__id__in=request.user.teams.filter(TeamQuerySet.event_permission_q("event.items:write")).values_list(
'limit_events__id', flat=True
)
)
var_matches = var_matches.filter(
variation__item__event__id__in=request.user.teams.filter(can_change_items=True).values_list(
variation__item__event__id__in=request.user.teams.filter(TeamQuerySet.event_permission_q("event.items:write")).values_list(
'limit_events__id', flat=True
)
)
@@ -1007,7 +1013,7 @@ def item_meta_values(request, organizer, event):
})
@organizer_permission_required(("can_view_orders", "can_change_organizer_settings"))
@organizer_permission_required(("event.orders:read", "organizer.settings.general:write"))
# This decorator is a bit of a hack since this is not technically an organizer permission, but it does the job here --
# anyone who can see orders for any event can see the check-in log view where this is used as a filter
def devices_select2(request, **kwargs):
@@ -1045,7 +1051,7 @@ def devices_select2(request, **kwargs):
return JsonResponse(doc)
@organizer_permission_required(("can_view_orders", "can_change_event_settings", "can_change_organizer_settings"))
@organizer_permission_required(("event.orders:read", "event.settings.general:write", "organizer.settings.general:write"))
# This decorator is a bit of a hack since this is not technically an organizer permission, but it does the job here --
# anyone who can see orders for any event can see the check-in log view where this is used as a filter
def gate_select2(request, **kwargs):

View File

@@ -83,7 +83,7 @@ class VoucherList(PaginationMixin, EventPermissionRequiredMixin, ListView):
model = Voucher
context_object_name = 'vouchers'
template_name = 'pretixcontrol/vouchers/index.html'
permission = 'can_view_vouchers'
permission = 'event.vouchers:read'
@scopes_disabled() # we have an event check here, and we can save some performance on subqueries
def get_queryset(self):
@@ -155,7 +155,7 @@ class VoucherList(PaginationMixin, EventPermissionRequiredMixin, ListView):
class VoucherTags(EventPermissionRequiredMixin, TemplateView):
template_name = 'pretixcontrol/vouchers/tags.html'
permission = 'can_view_vouchers'
permission = 'event.vouchers:read'
def get_queryset(self):
qs = self.request.event.vouchers.order_by('tag').filter(
@@ -196,7 +196,7 @@ class VoucherTags(EventPermissionRequiredMixin, TemplateView):
class VoucherDeleteCarts(EventPermissionRequiredMixin, CompatDeleteView):
model = Voucher
template_name = 'pretixcontrol/vouchers/delete_carts.html'
permission = 'can_change_vouchers'
permission = 'event.vouchers:write'
context_object_name = 'voucher'
def get_object(self, queryset=None) -> Voucher:
@@ -228,7 +228,7 @@ class VoucherDeleteCarts(EventPermissionRequiredMixin, CompatDeleteView):
class VoucherDelete(EventPermissionRequiredMixin, CompatDeleteView):
model = Voucher
template_name = 'pretixcontrol/vouchers/delete.html'
permission = 'can_change_vouchers'
permission = 'event.vouchers:write'
context_object_name = 'voucher'
def get_object(self, queryset=None) -> Voucher:
@@ -270,7 +270,7 @@ class VoucherDelete(EventPermissionRequiredMixin, CompatDeleteView):
class VoucherUpdate(EventPermissionRequiredMixin, UpdateView):
model = Voucher
template_name = 'pretixcontrol/vouchers/detail.html'
permission = ('can_change_vouchers', 'can_view_vouchers')
permission = ('event.vouchers:write', 'event.vouchers:read')
context_object_name = 'voucher'
def form_invalid(self, form):
@@ -286,7 +286,7 @@ class VoucherUpdate(EventPermissionRequiredMixin, UpdateView):
def get_form(self, form_class=None):
form = super().get_form(form_class)
if not self.request.user.has_event_permission(self.request.organizer, self.request.event, 'can_change_vouchers',
if not self.request.user.has_event_permission(self.request.organizer, self.request.event, 'event.vouchers:write',
request=self.request):
for f in form.fields.values():
f.disabled = True
@@ -313,7 +313,7 @@ class VoucherUpdate(EventPermissionRequiredMixin, UpdateView):
@transaction.atomic
def post(self, request, *args, **kwargs):
if not request.user.has_event_permission(request.organizer, request.event, 'can_change_vouchers',
if not request.user.has_event_permission(request.organizer, request.event, 'event.vouchers:write',
request=request):
raise PermissionDenied()
return super().post(request, *args, **kwargs)
@@ -344,7 +344,7 @@ class VoucherUpdate(EventPermissionRequiredMixin, UpdateView):
class VoucherCreate(EventPermissionRequiredMixin, CreateView):
model = Voucher
template_name = 'pretixcontrol/vouchers/detail.html'
permission = 'can_change_vouchers'
permission = 'event.vouchers:write'
context_object_name = 'voucher'
def form_invalid(self, form):
@@ -389,7 +389,7 @@ class VoucherCreate(EventPermissionRequiredMixin, CreateView):
class VoucherGo(EventPermissionRequiredMixin, View):
permission = 'can_view_vouchers'
permission = 'event.vouchers:read'
def get_voucher(self, code):
return Voucher.objects.get(code__iexact=code, event=self.request.event)
@@ -408,7 +408,7 @@ class VoucherGo(EventPermissionRequiredMixin, View):
class VoucherBulkCreate(EventPermissionRequiredMixin, AsyncFormView):
model = Voucher
template_name = 'pretixcontrol/vouchers/bulk.html'
permission = 'can_change_vouchers'
permission = 'event.vouchers:write'
context_object_name = 'voucher'
atomic_execute = True
@@ -540,7 +540,7 @@ class VoucherBulkCreate(EventPermissionRequiredMixin, AsyncFormView):
class VoucherBulkMailPreview(EventPermissionRequiredMixin, View):
permission = 'can_change_vouchers'
permission = 'event.vouchers:write'
# return the origin text if key is missing in dict
class SafeDict(dict):
@@ -579,7 +579,7 @@ class VoucherBulkMailPreview(EventPermissionRequiredMixin, View):
class VoucherRNG(EventPermissionRequiredMixin, View):
permission = 'can_change_vouchers'
permission = 'event.vouchers:write'
def get(self, request, *args, **kwargs):
try:
@@ -603,7 +603,7 @@ class VoucherRNG(EventPermissionRequiredMixin, View):
class VoucherBulkAction(EventPermissionRequiredMixin, View):
permission = 'can_change_vouchers'
permission = 'event.vouchers:write'
@cached_property
def objects(self):

View File

@@ -64,7 +64,7 @@ from . import UpdateView
class AutoAssign(EventPermissionRequiredMixin, AsyncAction, View):
task = assign_automatically
known_errortypes = ['WaitingListError']
permission = 'can_change_orders'
permission = 'event.orders:write'
def get_success_message(self, value):
return _('{num} vouchers have been created and sent out via email.').format(num=value)
@@ -143,7 +143,7 @@ class WaitingListQuerySetMixin:
class WaitingListActionView(EventPermissionRequiredMixin, WaitingListQuerySetMixin, View):
model = WaitingListEntry
permission = 'can_change_orders'
permission = 'event.orders:write'
def _redirect_back(self):
if "next" in self.request.GET and url_has_allowed_host_and_scheme(self.request.GET.get("next"), allowed_hosts=None):
@@ -233,7 +233,7 @@ class WaitingListView(EventPermissionRequiredMixin, WaitingListQuerySetMixin, Pa
model = WaitingListEntry
context_object_name = 'entries'
template_name = 'pretixcontrol/waitinglist/index.html'
permission = 'can_view_orders'
permission = 'event.orders:read'
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
@@ -360,7 +360,7 @@ class WaitingListView(EventPermissionRequiredMixin, WaitingListQuerySetMixin, Pa
class EntryDelete(EventPermissionRequiredMixin, CompatDeleteView):
model = WaitingListEntry
template_name = 'pretixcontrol/waitinglist/delete.html'
permission = 'can_change_orders'
permission = 'event.orders:write'
context_object_name = 'entry'
def get_object(self, queryset=None) -> WaitingListEntry:
@@ -393,7 +393,7 @@ class EntryDelete(EventPermissionRequiredMixin, CompatDeleteView):
class EntryTransfer(EventPermissionRequiredMixin, UpdateView):
model = WaitingListEntry
template_name = 'pretixcontrol/waitinglist/transfer.html'
permission = 'can_change_orders'
permission = 'event.orders:write'
form_class = WaitingListEntryTransferForm
context_object_name = 'entry'

View File

@@ -0,0 +1,96 @@
#
# 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 warnings
# The first two mapping tables are used to migrate *configuration*, i.e. existing Team objects or Team objects
# created through old-style API calls. These need to be "complete on both sides", i.e. all new permissions are assigned
# to existing users in some scenario.
OLD_TO_NEW_EVENT_MIGRATION = {
"can_change_event_settings": [
"event.settings.general:write",
"event.settings.payment:write",
"event.settings.plugins:write",
"event.settings.email.sender:write",
"event.settings.tax:write",
"event.settings.invoicing:write",
"event.subevents:write",
],
"can_change_items": ["event.items:write"],
"can_view_orders": ["event.orders:read"],
"can_change_orders": ["event.orders:write", "event:cancel"],
"can_checkin_orders": ["event.orders:checkin"],
"can_view_vouchers": ["event.vouchers:read"],
"can_change_vouchers": ["event.vouchers:write"],
}
OLD_TO_NEW_ORGANIZER_MIGRATION = {
"can_create_events": ["organizer.events:create"],
"can_change_organizer_settings": ["organizer.settings.general:write", "organizer.devices:read",
"organizer.devices:write"],
"can_change_teams": ["organizer.teams:write"],
"can_manage_gift_cards": ["organizer.giftcards:read", "organizer.giftcards:write"],
"can_manage_customers": ["organizer.customers:read", "organizer.customers:write"],
"can_manage_reusable_media": ["organizer.reusablemedia:read", "organizer.reusablemedia:write"],
}
# The second two mapping tables are used to migrate permission *checks*, i.e. they define which permissions a user needs
# to have to fulfill the "old-style condition". These are shorter than the migration mappings, since it does not
# make sense to check e.g. for event.settings.tax:write on every plugin that checks for can_change_event_settings
OLD_TO_NEW_EVENT_COMPAT = {
"can_change_event_settings": ["event.settings.general:write"],
"can_change_items": ["event.items:write"],
"can_view_orders": ["event.orders:read"],
"can_change_orders": ["event.orders:write"],
"can_checkin_orders": ["event.orders:checkin"],
"can_view_vouchers": ["event.vouchers:read"],
"can_change_vouchers": ["event.vouchers:write"],
}
OLD_TO_NEW_ORGANIZER_COMPAT = {
"can_create_events": ["organizer.events:create"],
"can_change_organizer_settings": ["organizer.settings.general:write"],
"can_change_teams": ["organizer.teams:write"],
"can_manage_gift_cards": ["organizer.giftcards:read", "organizer.giftcards:write"],
"can_manage_customers": ["organizer.customers:read", "organizer.customers:write"],
"can_manage_reusable_media": ["organizer.reusablemedia:read", "organizer.reusablemedia:write"],
}
class LegacyPermissionProperty:
name = None
def __set_name__(self, owner, name):
self.name = name
def __get__(self, instance, owner=None):
if instance is None:
return self
warnings.warn("Legacy permission attribute used", DeprecationWarning, stacklevel=2)
if self.name in OLD_TO_NEW_EVENT_COMPAT:
return instance.all_event_permissions or all(
kk in instance.limit_event_permissions for kk in OLD_TO_NEW_EVENT_COMPAT[self.name]
)
if self.name in OLD_TO_NEW_ORGANIZER_COMPAT:
return instance.all_organizer_permissions or all(
kk in instance.limit_organizer_permissions for kk in OLD_TO_NEW_ORGANIZER_COMPAT[self.name]
)
raise AttributeError("Unknown legacy attribute")

View File

@@ -108,7 +108,7 @@ class RuleViewSet(viewsets.ModelViewSet):
filter_backends = (DjangoFilterBackend, TotalOrderingFilter)
ordering = ("id",)
ordering_fields = ("id",)
permission = "can_change_event_settings"
permission = "event.settings.general:write"
def get_queryset(self):
return AutoCheckinRule.objects.filter(event=self.request.event)

View File

@@ -39,7 +39,7 @@ from pretix.plugins.autocheckin.models import AutoCheckinRule
def nav_event_receiver(sender, request, **kwargs):
url = request.resolver_match
if not request.user.has_event_permission(
request.organizer, request.event, "can_change_event_settings", request=request
request.organizer, request.event, "event.settings.general:write", request=request
):
return []
return [

View File

@@ -83,7 +83,7 @@
{% endif %}
</td>
<td class="text-right flip">
{% if "can_change_event_settings" in request.eventpermset %}
{% if "event.settings.general:write" in request.eventpermset %}
<a href="{% url "plugins:autocheckin:edit" organizer=request.event.organizer.slug event=request.event.slug rule=r.id %}" class="btn btn-default btn-sm"><i class="fa fa-edit"></i></a>
<a href="{% url "plugins:autocheckin:add" organizer=request.event.organizer.slug event=request.event.slug %}?copy_from={{ r.id }}"
class="btn btn-default btn-sm" title="{% trans "Clone" %}" data-toggle="tooltip"><i class="fa fa-copy"></i></a>

View File

@@ -35,7 +35,7 @@ from pretix.plugins.autocheckin.models import AutoCheckinRule
class IndexView(EventPermissionRequiredMixin, ListView):
permission = "can_change_event_settings"
permission = "event.settings.general:write"
template_name = "pretixplugins/autocheckin/index.html"
paginate_by = 50
context_object_name = "rules"
@@ -70,7 +70,7 @@ class IndexView(EventPermissionRequiredMixin, ListView):
class RuleAddView(EventPermissionRequiredMixin, CreateView):
template_name = "pretixplugins/autocheckin/add.html"
permission = "can_change_event_settings"
permission = "event.settings.general:write"
form_class = AutoCheckinRuleForm
model = AutoCheckinRule
@@ -140,7 +140,7 @@ class RuleEditView(EventPermissionRequiredMixin, UpdateView):
model = AutoCheckinRule
form_class = AutoCheckinRuleForm
template_name = "pretixplugins/autocheckin/edit.html"
permission = "can_change_event_settings"
permission = "event.settings.general:write"
def get_object(self, queryset=None) -> AutoCheckinRule:
return get_object_or_404(
@@ -178,7 +178,7 @@ class RuleEditView(EventPermissionRequiredMixin, UpdateView):
class RuleDeleteView(EventPermissionRequiredMixin, DeleteView):
model = AutoCheckinRule
permission = "can_change_event_settings"
permission = "event.settings.general:write"
template_name = "pretixplugins/autocheckin/delete.html"
context_object_name = "rule"

View File

@@ -44,8 +44,8 @@ from pretix.plugins.badges.models import BadgeItem, BadgeLayout
def control_nav_import(sender, request=None, **kwargs):
url = resolve(request.path_info)
p = (
request.user.has_event_permission(request.organizer, request.event, 'can_change_settings', request)
or request.user.has_event_permission(request.organizer, request.event, 'can_view_orders', request)
request.user.has_event_permission(request.organizer, request.event, 'event.settings.general:write', request)
or request.user.has_event_permission(request.organizer, request.event, 'event.orders:read', request)
)
if not p:
return []

View File

@@ -12,7 +12,7 @@
{% endblocktrans %}
</p>
{% if "can_change_event_settings" in request.eventpermset %}
{% if "event.settings.general:write" in request.eventpermset %}
<a href="{% url "plugins:badges: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 badge layout" %}
</a>
@@ -20,7 +20,7 @@
</div>
{% else %}
<p>
{% if "can_change_event_settings" in request.eventpermset %}
{% if "event.settings.general:write" in request.eventpermset %}
<a href="{% url "plugins:badges:add" organizer=request.event.organizer.slug event=request.event.slug %}" class="btn btn-default"><i class="fa fa-plus"></i> {% trans "Create a new badge layout" %}
</a>
{% endif %}
@@ -40,7 +40,7 @@
{% for l in layouts %}
<tr>
<td>
{% if "can_change_event_settings" in request.eventpermset %}
{% if "event.settings.general:write" in request.eventpermset %}
<strong><a href="{% url "plugins:badges:edit" organizer=request.event.organizer.slug event=request.event.slug layout=l.id %}">
{{ l.name }}
</a></strong>
@@ -54,7 +54,7 @@
<span class="fa fa-check"></span>
{% trans "Default" %}
</span>
{% elif "can_change_event_settings" in request.eventpermset %}
{% elif "event.settings.general:write" in request.eventpermset %}
<form class="form-inline" method="post"
action="{% url "plugins:badges:default" organizer=request.event.organizer.slug event=request.event.slug layout=l.id %}"
>
@@ -66,7 +66,7 @@
{% endif %}
</td>
<td class="text-right flip">
{% if "can_change_event_settings" in request.eventpermset %}
{% if "event.settings.general:write" in request.eventpermset %}
<a href="{% url "plugins:badges:edit" organizer=request.event.organizer.slug event=request.event.slug layout=l.id %}" class="btn btn-default btn-sm"><i class="fa fa-edit"></i></a>
<a href="{% url "plugins:badges:add" organizer=request.event.organizer.slug event=request.event.slug %}?copy_from={{ l.id }}"
class="btn btn-default btn-sm" title="{% trans "Clone" %}" data-toggle="tooltip"><i class="fa fa-copy"></i></a>

View File

@@ -59,7 +59,7 @@ from .templates import TEMPLATES
class LayoutListView(EventPermissionRequiredMixin, ListView):
model = BadgeLayout
permission = ('can_change_event_settings', 'can_view_orders')
permission = ('event.settings.general:write', 'event.orders:read')
template_name = 'pretixplugins/badges/index.html'
context_object_name = 'layouts'
@@ -71,7 +71,7 @@ class LayoutCreate(EventPermissionRequiredMixin, CreateView):
model = BadgeLayout
form_class = BadgeLayoutForm
template_name = 'pretixplugins/badges/edit.html'
permission = 'can_change_event_settings'
permission = 'event.settings.general:write'
context_object_name = 'layout'
success_url = '/ignored'
@@ -139,7 +139,7 @@ class LayoutCreate(EventPermissionRequiredMixin, CreateView):
class LayoutSetDefault(EventPermissionRequiredMixin, DetailView):
model = BadgeLayout
permission = 'can_change_event_settings'
permission = 'event.settings.general:write'
def get_object(self, queryset=None) -> BadgeLayout:
try:
@@ -171,7 +171,7 @@ class LayoutSetDefault(EventPermissionRequiredMixin, DetailView):
class LayoutDelete(EventPermissionRequiredMixin, CompatDeleteView):
model = BadgeLayout
template_name = 'pretixplugins/badges/delete.html'
permission = 'can_change_event_settings'
permission = 'event.settings.general:write'
context_object_name = 'layout'
def get_object(self, queryset=None) -> BadgeLayout:
@@ -269,7 +269,7 @@ class LayoutEditorView(BaseEditorView):
class OrderPrintDo(EventPermissionRequiredMixin, AsyncAction, View):
task = badges_create_pdf
permission = 'can_view_orders'
permission = 'event.orders:read'
known_errortypes = ['OrderError', 'ExportError']
def get_success_message(self, value):

View File

@@ -97,7 +97,6 @@ class BankImportJobViewSet(CreateModelMixin, viewsets.ReadOnlyModelViewSet):
queryset = BankImportJob.objects.none()
filter_backends = (DjangoFilterBackend,)
filterset_class = JobFilter
permission = 'can_view_orders'
def get_queryset(self):
return BankImportJob.objects.filter(organizer=self.request.organizer)
@@ -105,9 +104,30 @@ class BankImportJobViewSet(CreateModelMixin, viewsets.ReadOnlyModelViewSet):
def perform_create(self, serializer):
return serializer.save()
def retrieve(self, request, *args, **kwargs):
perm_holder = (request.auth if isinstance(request.auth, (Device, TeamAPIToken)) else request.user)
has_any_event_perm = perm_holder.get_events_with_permission(
"event.orders:read", request=request
).filter(organizer=request.organizer).exists()
if not has_any_event_perm:
raise PermissionDenied('Invalid set of permissions')
return super().retrieve(request, *args, **kwargs)
def list(self, request, *args, **kwargs):
perm_holder = (request.auth if isinstance(request.auth, (Device, TeamAPIToken)) else request.user)
has_any_event_perm = perm_holder.get_events_with_permission(
"event.orders:read", request=request
).filter(organizer=request.organizer).exists()
if not has_any_event_perm:
raise PermissionDenied('Invalid set of permissions')
return super().list(request, *args, **kwargs)
def create(self, request, *args, **kwargs):
perm_holder = (request.auth if isinstance(request.auth, (Device, TeamAPIToken)) else request.user)
if not perm_holder.has_organizer_permission(request.organizer, 'can_change_orders'):
has_any_event_perm = perm_holder.get_events_with_permission(
"event.orders:write", request=request
).filter(organizer=request.organizer).exists()
if not has_any_event_perm:
raise PermissionDenied('Invalid set of permissions')
if BankImportJob.objects.filter(Q(organizer=request.organizer)).filter(

View File

@@ -41,7 +41,7 @@ def register_payment_provider(sender, **kwargs):
@receiver(nav_event, dispatch_uid="payment_banktransfer_nav")
def control_nav_import(sender, request=None, **kwargs):
url = resolve(request.path_info)
if not request.user.has_event_permission(request.organizer, request.event, 'can_change_orders', request=request):
if not request.user.has_event_permission(request.organizer, request.event, 'event.orders:write', request=request):
return []
return [
{
@@ -76,7 +76,10 @@ def control_nav_import(sender, request=None, **kwargs):
@receiver(nav_organizer, dispatch_uid="payment_banktransfer_organav")
def control_nav_orga_import(sender, request=None, **kwargs):
url = resolve(request.path_info)
if not request.user.has_organizer_permission(request.organizer, 'can_change_orders', request=request):
has_any_event_perm = request.user.get_events_with_permission(
"event.orders:write", request=request
).filter(organizer=request.organizer).exists()
if not has_any_event_perm:
return []
return [
{

View File

@@ -44,6 +44,7 @@ from typing import Set
from django import forms
from django.contrib import messages
from django.core.exceptions import PermissionDenied
from django.db import transaction
from django.db.models import Count, Q, QuerySet
from django.http import FileResponse, JsonResponse
@@ -58,12 +59,11 @@ from localflavor.generic.forms import BICFormField, IBANFormField
from pretix.base.forms.widgets import DatePickerWidget
from pretix.base.models import Event, Order, OrderPayment, OrderRefund, Quota
from pretix.base.models.organizer import TeamQuerySet
from pretix.base.services.mail import SendMailException
from pretix.base.settings import SettingsSandbox
from pretix.base.templatetags.money import money_filter
from pretix.control.permissions import (
EventPermissionRequiredMixin, OrganizerPermissionRequiredMixin,
)
from pretix.control.permissions import EventPermissionRequiredMixin
from pretix.control.views.organizer import OrganizerDetailViewMixin
from pretix.helpers.json import CustomJSONEncoder
from pretix.plugins.banktransfer import camtimport, csvimport, mt940import
@@ -80,7 +80,7 @@ logger = logging.getLogger('pretix.plugins.banktransfer')
class ActionView(View):
permission = 'can_change_orders'
permission = 'event.orders:write'
def _discard(self, trans):
trans.state = BankTransaction.STATE_DISCARDED
@@ -285,7 +285,7 @@ class ActionView(View):
class JobDetailView(DetailView):
template_name = 'pretixplugins/banktransfer/job_detail.html'
permission = 'can_change_orders'
permission = 'event.orders:write'
context_objectname = 'job'
def redirect_form(self):
@@ -374,7 +374,7 @@ class BankTransactionFilterForm(forms.Form):
class ImportView(ListView):
template_name = 'pretixplugins/banktransfer/import_form.html'
permission = 'can_change_orders'
permission = 'event.orders:write'
context_object_name = 'transactions_unhandled'
paginate_by = 30
@@ -631,44 +631,54 @@ class ImportView(ListView):
class OrganizerBanktransferView:
def dispatch(self, request, *args, **kwargs):
has_any_event_perm = request.user.get_events_with_permission(
"event.orders:write", request=request
).filter(organizer=request.organizer).exists()
if not has_any_event_perm:
raise PermissionDenied()
return super().dispatch(request, *args, **kwargs)
class EventImportView(EventPermissionRequiredMixin, ImportView):
permission = 'can_change_orders'
permission = 'event.orders:write'
class OrganizerImportView(OrganizerBanktransferView, OrganizerPermissionRequiredMixin, OrganizerDetailViewMixin,
class OrganizerImportView(OrganizerBanktransferView, OrganizerDetailViewMixin,
ImportView):
permission = 'can_change_orders'
pass
class EventJobDetailView(EventPermissionRequiredMixin, JobDetailView):
permission = 'can_change_orders'
permission = 'event.orders:write'
class OrganizerJobDetailView(OrganizerBanktransferView, OrganizerPermissionRequiredMixin, OrganizerDetailViewMixin,
class OrganizerJobDetailView(OrganizerBanktransferView, OrganizerDetailViewMixin,
JobDetailView):
permission = 'can_change_orders'
pass
class EventActionView(EventPermissionRequiredMixin, ActionView):
permission = 'can_change_orders'
permission = 'event.orders:write'
class OrganizerActionView(OrganizerBanktransferView, OrganizerPermissionRequiredMixin, OrganizerDetailViewMixin,
class OrganizerActionView(OrganizerBanktransferView, OrganizerDetailViewMixin,
ActionView):
permission = 'can_change_orders'
def order_qs(self):
all = self.request.user.teams.filter(organizer=self.request.organizer, can_change_orders=True,
can_view_orders=True, all_events=True).exists()
all = self.request.user.teams.filter(
TeamQuerySet.event_permission_q("event.orders:read"),
TeamQuerySet.event_permission_q("event.orders:write"),
all_events=True,
organizer=self.request.organizer,
).exists()
if self.request.user.has_active_staff_session(self.request.session.session_key) or all:
return Order.objects.filter(event__organizer=self.request.organizer)
else:
return Order.objects.filter(
event_id__in=self.request.user.teams.filter(
organizer=self.request.organizer, can_change_orders=True, can_view_orders=True
TeamQuerySet.event_permission_q("event.orders:read"),
TeamQuerySet.event_permission_q("event.orders:write"),
organizer=self.request.organizer,
).values_list('limit_events__id', flat=True)
)
@@ -761,7 +771,7 @@ class RefundExportListView(ListView):
class EventRefundExportListView(EventPermissionRequiredMixin, RefundExportListView):
permission = 'can_change_orders'
permission = 'event.orders:write'
def get_success_url(self):
return reverse('plugins:banktransfer:refunds.list', kwargs={
@@ -783,8 +793,7 @@ class EventRefundExportListView(EventPermissionRequiredMixin, RefundExportListVi
)
class OrganizerRefundExportListView(OrganizerPermissionRequiredMixin, RefundExportListView):
permission = 'can_change_orders'
class OrganizerRefundExportListView(OrganizerBanktransferView, RefundExportListView):
def get_success_url(self):
return reverse('plugins:banktransfer:refunds.list', kwargs={
@@ -817,7 +826,7 @@ class DownloadRefundExportView(DetailView):
class EventDownloadRefundExportView(EventPermissionRequiredMixin, DownloadRefundExportView):
permission = 'can_change_orders'
permission = 'event.orders:write'
def get_object(self, *args, **kwargs):
return get_object_or_404(
@@ -827,8 +836,7 @@ class EventDownloadRefundExportView(EventPermissionRequiredMixin, DownloadRefund
)
class OrganizerDownloadRefundExportView(OrganizerPermissionRequiredMixin, OrganizerDetailViewMixin, DownloadRefundExportView):
permission = 'can_change_orders'
class OrganizerDownloadRefundExportView(OrganizerBanktransferView, OrganizerDetailViewMixin, DownloadRefundExportView):
def get_object(self, *args, **kwargs):
return get_object_or_404(
@@ -856,9 +864,9 @@ class SepaXMLExportView(SingleObjectMixin, FormView):
template_name = 'pretixplugins/banktransfer/sepa_export.html'
context_object_name = "export"
def setup(self, request, *args, **kwargs):
super().setup(request, *args, **kwargs)
def dispatch(self, request, *args, **kwargs):
self.object: RefundExport = self.get_object()
return super().dispatch(request, *args, **kwargs)
def form_valid(self, form):
self.object.downloaded = True
@@ -875,7 +883,7 @@ class SepaXMLExportView(SingleObjectMixin, FormView):
class EventSepaXMLExportView(EventPermissionRequiredMixin, SepaXMLExportView):
permission = 'can_change_orders'
permission = 'event.orders:write'
def get_object(self, *args, **kwargs):
return get_object_or_404(
@@ -890,8 +898,7 @@ class EventSepaXMLExportView(EventPermissionRequiredMixin, SepaXMLExportView):
return form
class OrganizerSepaXMLExportView(OrganizerPermissionRequiredMixin, OrganizerDetailViewMixin, SepaXMLExportView):
permission = 'can_change_orders'
class OrganizerSepaXMLExportView(OrganizerBanktransferView, OrganizerDetailViewMixin, SepaXMLExportView):
def get_object(self, *args, **kwargs):
return get_object_or_404(

View File

@@ -246,7 +246,7 @@ def webhook(request, *args, **kwargs):
return HttpResponse(status=200)
@event_permission_required('can_change_event_settings')
@event_permission_required('event.settings.general:write')
@require_POST
def oauth_disconnect(request, **kwargs):
del request.event.settings.payment_paypal_connect_refresh_token

View File

@@ -216,7 +216,7 @@ class PayView(PaypalOrderView, TemplateView):
@scopes_disabled()
@event_permission_required('can_change_event_settings')
@event_permission_required('event.settings.general:write')
def isu_return(request, *args, **kwargs):
getparams = ['merchantId', 'merchantIdInPayPal', 'permissionsGranted', 'accountStatus', 'consentStatus', 'productIntentID', 'isEmailConfirmed']
sessionparams = ['payment_paypal_isu_event', 'payment_paypal_isu_tracking_id']
@@ -526,7 +526,7 @@ def webhook(request, *args, **kwargs):
return HttpResponse(status=200)
@event_permission_required('can_change_event_settings')
@event_permission_required('event.settings.general:write')
@require_POST
def isu_disconnect(request, **kwargs):
del request.event.settings.payment_paypal_connect_refresh_token

View File

@@ -83,7 +83,7 @@ def check_against_prefix_list(u, allowlist):
@receiver(nav_event_settings, dispatch_uid='returnurl_nav')
def navbar_info(sender, request, **kwargs):
url = resolve(request.path_info)
if not request.user.has_event_permission(request.organizer, request.event, 'can_change_event_settings',
if not request.user.has_event_permission(request.organizer, request.event, 'event.settings.general:write',
request=request):
return []
return [{

View File

@@ -48,7 +48,7 @@ class ReturnSettings(EventSettingsViewMixin, EventSettingsFormView):
model = Event
form_class = ReturnSettingsForm
template_name = 'returnurl/settings.html'
permission = 'can_change_settings'
permission = 'event.settings.general:write'
def get_success_url(self) -> str:
return reverse('plugins:returnurl:settings', kwargs={

View File

@@ -113,7 +113,7 @@ class RuleViewSet(viewsets.ModelViewSet):
filterset_class = RuleFilter
ordering = ('id',)
ordering_fields = ('id',)
permission = 'can_change_event_settings'
permission = 'event.settings.general:write'
def get_queryset(self):
return Rule.objects.filter(event=self.request.event)

View File

@@ -79,7 +79,7 @@ def scheduled_mail_create(sender, **kwargs):
@receiver(nav_event, dispatch_uid="sendmail_nav")
def control_nav_import(sender, request=None, **kwargs):
url = resolve(request.path_info)
if not request.user.has_event_permission(request.organizer, request.event, 'can_change_orders', request=request):
if not request.user.has_event_permission(request.organizer, request.event, 'event.orders:write', request=request):
return []
return [
{

View File

@@ -72,7 +72,7 @@ logger = logging.getLogger('pretix.plugins.sendmail')
class IndexView(EventPermissionRequiredMixin, TemplateView):
template_name = 'pretixplugins/sendmail/index.html'
permission = 'can_change_orders'
permission = 'event.orders:write'
def get_context_data(self, **kwargs):
from .signals import sendmail_view_classes
@@ -94,7 +94,7 @@ class IndexView(EventPermissionRequiredMixin, TemplateView):
class BaseSenderView(EventPermissionRequiredMixin, FormView):
# These parameters usually SHOULD NOT be overridden
template_name = 'pretixplugins/sendmail/send_form.html'
permission = 'can_change_orders'
permission = 'event.orders:write'
# These parameters MUST be overridden by subclasses
form_fragment_name = None
@@ -523,7 +523,7 @@ class WaitinglistSendView(BaseSenderView):
class EmailHistoryView(EventPermissionRequiredMixin, ListView):
template_name = 'pretixplugins/sendmail/history.html'
permission = 'can_change_orders'
permission = 'event.orders:write'
model = LogEntry
context_object_name = 'logs'
paginate_by = 5
@@ -571,7 +571,7 @@ class EmailHistoryView(EventPermissionRequiredMixin, ListView):
class CreateRule(EventPermissionRequiredMixin, CreateView):
template_name = 'pretixplugins/sendmail/rule_create.html'
permission = 'can_change_event_settings'
permission = 'event.settings.general:write'
form_class = forms.RuleForm
model = Rule
@@ -621,7 +621,7 @@ class UpdateRule(EventPermissionRequiredMixin, UpdateView):
model = Rule
form_class = forms.RuleForm
template_name = 'pretixplugins/sendmail/rule_update.html'
permission = 'can_change_event_settings'
permission = 'event.settings.general:write'
def get_object(self, queryset=None) -> Rule:
return get_object_or_404(
@@ -701,7 +701,7 @@ class ListRules(EventPermissionRequiredMixin, PaginationMixin, ListView):
class DeleteRule(EventPermissionRequiredMixin, DeleteView):
model = Rule
permission = 'can_change_event_settings'
permission = 'event.settings.general:write'
template_name = 'pretixplugins/sendmail/rule_delete.html'
context_object_name = 'rule'

View File

@@ -30,7 +30,7 @@ from pretix.control.signals import nav_event
@receiver(nav_event, dispatch_uid="statistics_nav")
def control_nav_import(sender, request=None, **kwargs):
url = resolve(request.path_info)
if not request.user.has_event_permission(request.organizer, request.event, 'can_view_orders', request=request):
if not request.user.has_event_permission(request.organizer, request.event, 'event.orders:read', request=request):
return []
return [
{

View File

@@ -52,7 +52,7 @@ from pretix.plugins.statistics.signals import clear_cache
class IndexView(EventPermissionRequiredMixin, ChartContainingView, TemplateView):
template_name = 'pretixplugins/statistics/index.html'
permission = 'can_view_orders'
permission = 'event.orders:read'
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)

View File

@@ -473,7 +473,7 @@ def paymentintent_webhook(event, event_json, paymentintent_id, rso):
return HttpResponse(status=200)
@event_permission_required('can_change_event_settings')
@event_permission_required('event.settings.general:write')
def oauth_disconnect(request, **kwargs):
if request.method != "POST":
return render(request, 'pretixplugins/stripe/oauth_disconnect.html', {})
@@ -671,7 +671,7 @@ class ScaReturnView(StripeOrderView, View):
class OrganizerSettingsFormView(DecoupleMixin, OrganizerDetailViewMixin, AdministratorPermissionRequiredMixin, FormView):
model = Organizer
permission = 'can_change_organizer_settings'
permission = 'organizer.settings.general:write'
form_class = OrganizerStripeSettingsForm
template_name = 'pretixplugins/stripe/organizer_stripe.html'

View File

@@ -206,7 +206,7 @@ class RenderJobSerializer(serializers.Serializer):
class TicketRendererViewSet(viewsets.ViewSet):
permission = 'can_view_orders'
permission = 'event.orders:read'
def get_serializer_kwargs(self):
return {}

View File

@@ -12,7 +12,7 @@
{% endblocktrans %}
</p>
{% if "can_change_event_settings" in request.eventpermset %}
{% if "event.settings.general:write" in request.eventpermset %}
<a href="{% url "plugins:ticketoutputpdf: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 layout" %}
</a>
@@ -20,7 +20,7 @@
</div>
{% else %}
<p>
{% if "can_change_event_settings" in request.eventpermset %}
{% if "event.settings.general:write" in request.eventpermset %}
<a href="{% url "plugins:ticketoutputpdf:add" organizer=request.event.organizer.slug event=request.event.slug %}" class="btn btn-default"><i class="fa fa-plus"></i> {% trans "Create a new layout" %}
</a>
{% endif %}
@@ -38,7 +38,7 @@
{% for l in layouts %}
<tr>
<td>
{% if "can_change_event_settings" in request.eventpermset %}
{% if "event.settings.general:write" in request.eventpermset %}
<strong><a href="{% url "plugins:ticketoutputpdf:edit" organizer=request.event.organizer.slug event=request.event.slug layout=l.id %}">
{{ l.name }}
</a></strong>
@@ -52,7 +52,7 @@
<span class="fa fa-check"></span>
{% trans "Default" %}
</span>
{% elif "can_change_event_settings" in request.eventpermset %}
{% elif "event.settings.general:write" in request.eventpermset %}
<form class="form-inline" method="post"
action="{% url "plugins:ticketoutputpdf:default" organizer=request.event.organizer.slug event=request.event.slug layout=l.id %}">
{% csrf_token %}
@@ -63,7 +63,7 @@
{% endif %}
</td>
<td class="text-right flip">
{% if "can_change_event_settings" in request.eventpermset %}
{% if "event.settings.general:write" in request.eventpermset %}
<a href="{% url "plugins:ticketoutputpdf:edit" organizer=request.event.organizer.slug event=request.event.slug layout=l.id %}" class="btn btn-default btn-sm"><i class="fa fa-edit"></i></a>
<a href="{% url "plugins:ticketoutputpdf:add" organizer=request.event.organizer.slug event=request.event.slug %}?copy_from={{ l.id }}"
class="btn btn-default btn-sm" title="{% trans "Clone" %}" data-toggle="tooltip"><i class="fa fa-copy"></i></a>

View File

@@ -95,7 +95,7 @@ class EditorView(BaseEditorView):
class LayoutListView(EventPermissionRequiredMixin, ListView):
model = TicketLayout
permission = ('can_change_event_settings')
permission = ('event.settings.general:write')
template_name = 'pretixplugins/ticketoutputpdf/index.html'
context_object_name = 'layouts'
@@ -107,7 +107,7 @@ class LayoutCreate(EventPermissionRequiredMixin, CreateView):
model = TicketLayout
form_class = TicketLayoutForm
template_name = 'pretixplugins/ticketoutputpdf/edit.html'
permission = 'can_change_event_settings'
permission = 'event.settings.general:write'
context_object_name = 'layout'
success_url = '/ignored'
@@ -157,7 +157,7 @@ class LayoutCreate(EventPermissionRequiredMixin, CreateView):
class LayoutSetDefault(EventPermissionRequiredMixin, DetailView):
model = TicketLayout
permission = 'can_change_event_settings'
permission = 'event.settings.general:write'
def get_object(self, queryset=None) -> TicketLayout:
try:
@@ -186,7 +186,7 @@ class LayoutSetDefault(EventPermissionRequiredMixin, DetailView):
class LayoutDelete(EventPermissionRequiredMixin, CompatDeleteView):
model = TicketLayout
template_name = 'pretixplugins/ticketoutputpdf/delete.html'
permission = 'can_change_event_settings'
permission = 'event.settings.general:write'
context_object_name = 'layout'
def get_object(self, queryset=None) -> TicketLayout:
@@ -218,7 +218,7 @@ class LayoutDelete(EventPermissionRequiredMixin, CompatDeleteView):
class LayoutGetDefault(EventPermissionRequiredMixin, View):
permission = 'can_change_event_settings'
permission = 'event.settings.general:write'
def get(self, request, *args, **kwargs):
layout = self.request.event.ticket_layouts.get_or_create(
@@ -304,7 +304,7 @@ class LayoutEditorView(BaseEditorView):
class OrderPrintDo(EventPermissionRequiredMixin, AsyncAction, View):
task = tickets_create_pdf
permission = 'can_view_orders'
permission = 'event.orders:read'
known_errortypes = ['OrderError', 'ExportError']
def get_success_message(self, value):

View File

@@ -30,7 +30,7 @@ from pretix.control.signals import nav_event
@receiver(nav_event, dispatch_uid='webcheckin_nav_event')
def navbar_entry(sender, request, **kwargs):
url = request.resolver_match
if not request.user.has_event_permission(request.organizer, request.event, ('can_change_orders', 'can_checkin_orders'), request=request):
if not request.user.has_event_permission(request.organizer, request.event, ('event.orders:write', 'event.orders:checkin'), request=request):
return []
return [{
'label': mark_safe(_('Web Check-in') + ' <span class="label label-success">beta</span>'),

View File

@@ -26,7 +26,7 @@ from pretix.helpers.countries import CachedCountries
class IndexView(EventPermissionRequiredMixin, TemplateView):
permission = ('can_change_orders', 'can_checkin_orders')
permission = ('event.orders:write', 'event.orders:checkin')
template_name = 'pretixplugins/webcheckin/index.html'
def get_context_data(self, **kwargs):

View File

@@ -208,7 +208,7 @@ class TicketPageMixin:
ctx['download_buttons'] = self.download_buttons
ctx['backend_user'] = has_event_access_permission(self.request, 'can_view_orders')
ctx['backend_user'] = has_event_access_permission(self.request, 'event.orders:read')
return ctx

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