Use new permissions, remove inconsistencies

This commit is contained in:
Raphael Michel
2026-01-12 18:30:39 +01:00
parent e566ab3405
commit 8d5bb273c6
61 changed files with 1214 additions and 780 deletions

View File

@@ -30,7 +30,7 @@ software_brand string Device software
software_version string Device software version (read-only) software_version string Device software version (read-only)
created datetime Creation time created datetime Creation time
initialized datetime Time of initialization (or ``null``) initialized datetime Time of initialization (or ``null``)
initialization_token string Token for initialization initialization_token string Token for initialization (field invisible without without write permission)
revoked boolean Whether this device no longer has access revoked boolean Whether this device no longer has access
security_profile string The name of a supported security profile restricting API access security_profile string The name of a supported security profile restricting API access
===================================== ========================== ======================================================= ===================================== ========================== =======================================================

View File

@@ -65,8 +65,6 @@ Endpoints
Returns a list of all events within a given organizer the authenticated user/token has access to. Returns a list of all events within a given organizer the authenticated user/token has access to.
Permission required: "Can change event settings"
**Example request**: **Example request**:
.. sourcecode:: http .. sourcecode:: http
@@ -161,8 +159,6 @@ Endpoints
Returns information on one event, identified by its slug. Returns information on one event, identified by its slug.
Permission required: "Can change event settings"
**Example request**: **Example request**:
.. sourcecode:: http .. sourcecode:: http
@@ -234,8 +230,6 @@ Endpoints
Please note that events cannot be created as 'live' using this endpoint. Quotas and payment must be added to the Please note that events cannot be created as 'live' using this endpoint. Quotas and payment must be added to the
event before sales can go live. event before sales can go live.
Permission required: "Can create events"
**Example request**: **Example request**:
.. sourcecode:: http .. sourcecode:: http
@@ -338,8 +332,6 @@ Endpoints
Please note that you can only copy from events under the same organizer this way. Use the ``clone_from`` parameter Please note that you can only copy from events under the same organizer this way. Use the ``clone_from`` parameter
when creating a new event for this instead. when creating a new event for this instead.
Permission required: "Can create events"
**Example request**: **Example request**:
.. sourcecode:: http .. sourcecode:: http
@@ -433,8 +425,6 @@ Endpoints
Updates an event Updates an event
Permission required: "Can change event settings"
**Example request**: **Example request**:
.. sourcecode:: http .. sourcecode:: http
@@ -510,8 +500,6 @@ Endpoints
Delete an event. Note that events with orders cannot be deleted to ensure data integrity. Delete an event. Note that events with orders cannot be deleted to ensure data integrity.
Permission required: "Can change event settings"
**Example request**: **Example request**:
.. sourcecode:: http .. sourcecode:: http
@@ -561,8 +549,6 @@ organizer level.
Get current values of event settings. Get current values of event settings.
Permission required: "Can change event settings" (Exception: with device auth, *some* settings can always be *read*.)
**Example request**: **Example request**:
.. sourcecode:: http .. sourcecode:: http
@@ -615,6 +601,8 @@ organizer level.
Updates event settings. Note that ``PUT`` is not allowed here, only ``PATCH``. Updates event settings. Note that ``PUT`` is not allowed here, only ``PATCH``.
Permission "Can change event settings" is always required. Some keys requrie additional permissions.
.. warning:: .. warning::
Settings can be stored at different levels in pretix. If a value is not set on event level, a default setting Settings can be stored at different levels in pretix. If a value is not set on event level, a default setting

View File

@@ -110,8 +110,6 @@ Endpoints
Updates an organizer. Currently only the ``plugins`` field may be updated. Updates an organizer. Currently only the ``plugins`` field may be updated.
Permission required: "Can change organizer settings"
**Example request**: **Example request**:
.. sourcecode:: http .. sourcecode:: http
@@ -172,8 +170,6 @@ information about the properties.
Get current values of organizer settings. Get current values of organizer settings.
Permission required: "Can change organizer settings"
**Example request**: **Example request**:
.. sourcecode:: http .. sourcecode:: http

View File

@@ -154,7 +154,7 @@ Endpoints
.. http:post:: /api/v1/organizers/(organizer)/reusablemedia/lookup/ .. http:post:: /api/v1/organizers/(organizer)/reusablemedia/lookup/
Look up a new reusable medium by its identifier. In some cases, this might lead to the automatic creation of a new Look up a new reusable medium by its identifier. In some cases, this might lead to the automatic creation of a new
medium behind the scenes. medium behind the scenes, therefore this endpoint requires write permissions.
This endpoint, and this endpoint only, might return media from a different organizer if there is a cross-acceptance This endpoint, and this endpoint only, might return media from a different organizer if there is a cross-acceptance
agreement. In this case, only linked gift cards will be returned, no order position or customer records, agreement. In this case, only linked gift cards will be returned, no order position or customer records,

View File

@@ -154,8 +154,6 @@ Endpoints
Creates a new subevent. Creates a new subevent.
Permission required: "Can create events"
**Example request**: **Example request**:
.. sourcecode:: http .. sourcecode:: http
@@ -300,8 +298,6 @@ Endpoints
provide all fields of the resource, other fields will be reset to default. With ``PATCH``, you only need to provide provide all fields of the resource, other fields will be reset to default. With ``PATCH``, you only need to provide
the fields that you want to change. the fields that you want to change.
Permission required: "Can change event settings"
**Example request**: **Example request**:
.. sourcecode:: http .. sourcecode:: http
@@ -373,8 +369,6 @@ Endpoints
Delete a sub-event. Note that events with orders cannot be deleted to ensure data integrity. Delete a sub-event. Note that events with orders cannot be deleted to ensure data integrity.
Permission required: "Can change event settings"
**Example request**: **Example request**:
.. sourcecode:: http .. sourcecode:: http

View File

@@ -63,8 +63,6 @@ Possible values for ``limit_event_permissions`` defined in the core pretix syste
event.settings.general:write event.settings.general:write
event.settings.payment:write event.settings.payment:write
event.settings.plugins:write
event.settings.email.sender:write
event.settings.tax:write event.settings.tax:write
event.settings.invoicing:write event.settings.invoicing:write
event.subevents:write event.subevents:write

View File

@@ -707,7 +707,10 @@ class TaxRuleSerializer(CountryFieldMixin, I18nAwareModelSerializer):
class EventSettingsSerializer(SettingsSerializer): class EventSettingsSerializer(SettingsSerializer):
default_write_permission = 'event.settings.general:write'
default_fields = [ default_fields = [
# These are readable for all users with access to the events, therefore secrets made in the settings store
# should not be included!
'imprint_url', 'imprint_url',
'checkout_email_helptext', 'checkout_email_helptext',
'presale_has_ended_text', 'presale_has_ended_text',

View File

@@ -24,7 +24,7 @@ from decimal import Decimal
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from rest_framework import serializers from rest_framework import serializers
from rest_framework.exceptions import ValidationError from rest_framework.exceptions import PermissionDenied, ValidationError
from pretix.api.serializers.i18n import I18nAwareModelSerializer from pretix.api.serializers.i18n import I18nAwareModelSerializer
from pretix.api.serializers.order import OrderPositionSerializer from pretix.api.serializers.order import OrderPositionSerializer
@@ -66,6 +66,9 @@ class ReusableMediaSerializer(I18nAwareModelSerializer):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
if 'linked_giftcard' in self.context['request'].query_params.getlist('expand'): if 'linked_giftcard' in self.context['request'].query_params.getlist('expand'):
if not self.context["can_read_giftcards"]:
raise PermissionDenied("No permission to access gift card details.")
self.fields['linked_giftcard'] = NestedGiftCardSerializer(read_only=True, context=self.context) self.fields['linked_giftcard'] = NestedGiftCardSerializer(read_only=True, context=self.context)
if 'linked_giftcard.owner_ticket' in self.context['request'].query_params.getlist('expand'): if 'linked_giftcard.owner_ticket' in self.context['request'].query_params.getlist('expand'):
self.fields['linked_giftcard'].fields['owner_ticket'] = NestedOrderPositionSerializer(read_only=True, context=self.context) self.fields['linked_giftcard'].fields['owner_ticket'] = NestedOrderPositionSerializer(read_only=True, context=self.context)
@@ -77,6 +80,8 @@ class ReusableMediaSerializer(I18nAwareModelSerializer):
) )
if 'linked_orderposition' in self.context['request'].query_params.getlist('expand'): if 'linked_orderposition' in self.context['request'].query_params.getlist('expand'):
# No additional permission check performed, documented limitation of the permission system
# Would get to complex/unusable otherwise since the permission depends on the event
self.fields['linked_orderposition'] = NestedOrderPositionSerializer(read_only=True) self.fields['linked_orderposition'] = NestedOrderPositionSerializer(read_only=True)
else: else:
self.fields['linked_orderposition'] = serializers.PrimaryKeyRelatedField( self.fields['linked_orderposition'] = serializers.PrimaryKeyRelatedField(
@@ -86,6 +91,9 @@ class ReusableMediaSerializer(I18nAwareModelSerializer):
) )
if 'customer' in self.context['request'].query_params.getlist('expand'): if 'customer' in self.context['request'].query_params.getlist('expand'):
if not self.context["can_read_customers"]:
raise PermissionDenied("No permission to access customer details.")
self.fields['customer'] = CustomerSerializer(read_only=True) self.fields['customer'] = CustomerSerializer(read_only=True)
else: else:
self.fields['customer'] = serializers.SlugRelatedField( self.fields['customer'] = serializers.SlugRelatedField(

View File

@@ -422,7 +422,7 @@ class DeviceSerializer(serializers.ModelSerializer):
created = serializers.DateTimeField(read_only=True) created = serializers.DateTimeField(read_only=True)
revoked = serializers.BooleanField(read_only=True) revoked = serializers.BooleanField(read_only=True)
initialized = serializers.DateTimeField(read_only=True) initialized = serializers.DateTimeField(read_only=True)
initialization_token = serializers.DateTimeField(read_only=True) initialization_token = serializers.CharField(read_only=True)
security_profile = serializers.ChoiceField(choices=[], required=False, default="full") security_profile = serializers.ChoiceField(choices=[], required=False, default="full")
class Meta: class Meta:
@@ -436,6 +436,8 @@ class DeviceSerializer(serializers.ModelSerializer):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.fields['security_profile'].choices = [(k, v.verbose_name) for k, v in get_all_security_profiles().items()] self.fields['security_profile'].choices = [(k, v.verbose_name) for k, v in get_all_security_profiles().items()]
if not self.context['can_see_tokens']:
del self.fields['initialization_token']
class TeamInviteSerializer(serializers.ModelSerializer): class TeamInviteSerializer(serializers.ModelSerializer):
@@ -520,7 +522,10 @@ class TeamMemberSerializer(serializers.ModelSerializer):
class OrganizerSettingsSerializer(SettingsSerializer): class OrganizerSettingsSerializer(SettingsSerializer):
default_write_permission = 'organizer.settings.general:write'
default_fields = [ default_fields = [
# These are readable for all users with access to the events, therefore secrets made in the settings store
# should not be included!
'customer_accounts', 'customer_accounts',
'customer_accounts_native', 'customer_accounts_native',
'customer_accounts_link_by_email', 'customer_accounts_link_by_email',

View File

@@ -37,6 +37,8 @@ logger = logging.getLogger(__name__)
class SettingsSerializer(serializers.Serializer): class SettingsSerializer(serializers.Serializer):
default_fields = [] default_fields = []
readonly_fields = [] readonly_fields = []
default_write_permission = 'organizer.settings.general:write'
write_permission_required = {}
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.changed_data = [] self.changed_data = []
@@ -58,9 +60,17 @@ class SettingsSerializer(serializers.Serializer):
f._label = str(form_kwargs.get('label', fname)) f._label = str(form_kwargs.get('label', fname))
f._help_text = str(form_kwargs.get('help_text')) f._help_text = str(form_kwargs.get('help_text'))
f.parent = self f.parent = self
self.write_permission_required[fname] = DEFAULTS[fname].get('write_permission', self.default_write_permission)
self.fields[fname] = f self.fields[fname] = f
def validate(self, attrs): def validate(self, attrs):
for k in attrs.keys():
p = self.write_permission_required.get(k, self.default_write_permission)
if p not in self.context["permissions"]:
raise ValidationError({k: f"Setting this field requires permission {p}"})
return {k: v for k, v in attrs.items() if k not in self.readonly_fields} return {k: v for k, v in attrs.items() if k not in self.readonly_fields}
def update(self, instance: HierarkeyProxy, validated_data): def update(self, instance: HierarkeyProxy, validated_data):

View File

@@ -432,7 +432,7 @@ with scopes_disabled():
class SubEventViewSet(ConditionalListView, viewsets.ModelViewSet): class SubEventViewSet(ConditionalListView, viewsets.ModelViewSet):
serializer_class = SubEventSerializer serializer_class = SubEventSerializer
queryset = SubEvent.objects.none() queryset = SubEvent.objects.none()
write_permission = 'event.settings.general:write' write_permission = 'event.subevents:write'
filter_backends = (DjangoFilterBackend, TotalOrderingFilter) filter_backends = (DjangoFilterBackend, TotalOrderingFilter)
ordering = ('date_from',) ordering = ('date_from',)
ordering_fields = ('id', 'date_from', 'last_modified') ordering_fields = ('id', 'date_from', 'last_modified')
@@ -552,7 +552,7 @@ class SubEventViewSet(ConditionalListView, viewsets.ModelViewSet):
class TaxRuleViewSet(ConditionalListView, viewsets.ModelViewSet): class TaxRuleViewSet(ConditionalListView, viewsets.ModelViewSet):
serializer_class = TaxRuleSerializer serializer_class = TaxRuleSerializer
queryset = TaxRule.objects.none() queryset = TaxRule.objects.none()
write_permission = 'event.settings.general:write' write_permission = 'event.settings.tax:write'
def get_queryset(self): def get_queryset(self):
return self.request.event.tax_rules.all() return self.request.event.tax_rules.all()
@@ -647,14 +647,13 @@ class EventSettingsView(views.APIView):
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
if isinstance(request.auth, Device): if isinstance(request.auth, Device):
s = DeviceEventSettingsSerializer(instance=request.event.settings, event=request.event, context={ s = DeviceEventSettingsSerializer(instance=request.event.settings, event=request.event, context={
'request': request 'request': request, 'permissions': request.eventpermset
})
elif 'event.settings.general:write' in request.eventpermset:
s = EventSettingsSerializer(instance=request.event.settings, event=request.event, context={
'request': request
}) })
else: else:
raise PermissionDenied() s = EventSettingsSerializer(instance=request.event.settings, event=request.event, context={
'request': request, 'permissions': request.eventpermset,
})
if 'explain' in request.GET: if 'explain' in request.GET:
return Response({ return Response({
fname: { fname: {
@@ -668,7 +667,7 @@ class EventSettingsView(views.APIView):
def patch(self, request, *wargs, **kwargs): def patch(self, request, *wargs, **kwargs):
s = EventSettingsSerializer(instance=request.event.settings, data=request.data, partial=True, s = EventSettingsSerializer(instance=request.event.settings, data=request.data, partial=True,
event=request.event, context={'request': request}) event=request.event, context={'request': request, 'permissions': request.eventpermset})
s.is_valid(raise_exception=True) s.is_valid(raise_exception=True)
with transaction.atomic(): with transaction.atomic():
s.save() s.save()
@@ -680,7 +679,7 @@ class EventSettingsView(views.APIView):
) )
s = EventSettingsSerializer( s = EventSettingsSerializer(
instance=request.event.settings, event=request.event, context={ instance=request.event.settings, event=request.event, context={
'request': request 'request': request, 'permissions': request.eventpermset
}) })
return Response(s.data) return Response(s.data)

View File

@@ -95,6 +95,8 @@ class ReusableMediaViewSet(viewsets.ModelViewSet):
def get_serializer_context(self): def get_serializer_context(self):
ctx = super().get_serializer_context() ctx = super().get_serializer_context()
ctx['organizer'] = self.request.organizer ctx['organizer'] = self.request.organizer
ctx['can_read_giftcards'] = 'organizer.giftcards:read' in self.request.orgapermset
ctx['can_read_customers'] = 'organizer.customers:read' in self.request.orgapermset
return ctx return ctx
@transaction.atomic() @transaction.atomic()

View File

@@ -221,7 +221,7 @@ with scopes_disabled():
class GiftCardViewSet(viewsets.ModelViewSet): class GiftCardViewSet(viewsets.ModelViewSet):
serializer_class = GiftCardSerializer serializer_class = GiftCardSerializer
queryset = GiftCard.objects.none() queryset = GiftCard.objects.none()
permission = 'organizer.giftcards:write' permission = 'organizer.giftcards:read'
write_permission = 'organizer.giftcards:write' write_permission = 'organizer.giftcards:write'
filter_backends = (DjangoFilterBackend,) filter_backends = (DjangoFilterBackend,)
filterset_class = GiftCardFilter filterset_class = GiftCardFilter
@@ -344,7 +344,7 @@ class GiftCardViewSet(viewsets.ModelViewSet):
class GiftCardTransactionViewSet(viewsets.ReadOnlyModelViewSet): class GiftCardTransactionViewSet(viewsets.ReadOnlyModelViewSet):
serializer_class = GiftCardTransactionSerializer serializer_class = GiftCardTransactionSerializer
queryset = GiftCardTransaction.objects.none() queryset = GiftCardTransaction.objects.none()
permission = 'organizer.giftcards:write' permission = 'organizer.giftcards:read'
write_permission = 'organizer.giftcards:write' write_permission = 'organizer.giftcards:write'
@cached_property @cached_property
@@ -532,8 +532,8 @@ class DeviceViewSet(mixins.CreateModelMixin,
GenericViewSet): GenericViewSet):
serializer_class = DeviceSerializer serializer_class = DeviceSerializer
queryset = Device.objects.none() queryset = Device.objects.none()
permission = 'organizer.settings.general:write' permission = 'organizer.devices:read'
write_permission = 'organizer.settings.general:write' write_permission = 'organizer.devices:write'
lookup_field = 'device_id' lookup_field = 'device_id'
def get_queryset(self): def get_queryset(self):
@@ -542,6 +542,9 @@ class DeviceViewSet(mixins.CreateModelMixin,
def get_serializer_context(self): def get_serializer_context(self):
ctx = super().get_serializer_context() ctx = super().get_serializer_context()
ctx['organizer'] = self.request.organizer ctx['organizer'] = self.request.organizer
ctx['can_see_tokens'] = (
self.request.user if self.request.user and self.request.user.is_authenticated else self.request.auth
).has_organizer_permission(self.request.organizer, 'organizer.devices:write', request=self.request)
return ctx return ctx
@transaction.atomic() @transaction.atomic()
@@ -572,7 +575,7 @@ class OrganizerSettingsView(views.APIView):
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
s = OrganizerSettingsSerializer(instance=request.organizer.settings, organizer=request.organizer, context={ s = OrganizerSettingsSerializer(instance=request.organizer.settings, organizer=request.organizer, context={
'request': request 'request': request, 'permissions': request.orgapermset
}) })
if 'explain' in request.GET: if 'explain' in request.GET:
return Response({ return Response({
@@ -589,7 +592,7 @@ class OrganizerSettingsView(views.APIView):
s = OrganizerSettingsSerializer( s = OrganizerSettingsSerializer(
instance=request.organizer.settings, data=request.data, partial=True, instance=request.organizer.settings, data=request.data, partial=True,
organizer=request.organizer, context={ organizer=request.organizer, context={
'request': request 'request': request, 'permissions': request.orgapermset
} }
) )
s.is_valid(raise_exception=True) s.is_valid(raise_exception=True)
@@ -601,7 +604,7 @@ class OrganizerSettingsView(views.APIView):
} }
) )
s = OrganizerSettingsSerializer(instance=request.organizer.settings, organizer=request.organizer, context={ s = OrganizerSettingsSerializer(instance=request.organizer.settings, organizer=request.organizer, context={
'request': request 'request': request, 'permissions': request.orgapermset
}) })
return Response(s.data) return Response(s.data)
@@ -618,7 +621,8 @@ with scopes_disabled():
class CustomerViewSet(viewsets.ModelViewSet): class CustomerViewSet(viewsets.ModelViewSet):
serializer_class = CustomerSerializer serializer_class = CustomerSerializer
queryset = Customer.objects.none() queryset = Customer.objects.none()
permission = 'organizer.customers:write' permission = 'organizer.customers:read'
write_permission = 'organizer.customers:write'
lookup_field = 'identifier' lookup_field = 'identifier'
filter_backends = (DjangoFilterBackend,) filter_backends = (DjangoFilterBackend,)
filterset_class = CustomerFilter filterset_class = CustomerFilter
@@ -735,7 +739,8 @@ with scopes_disabled():
class MembershipViewSet(viewsets.ModelViewSet): class MembershipViewSet(viewsets.ModelViewSet):
serializer_class = MembershipSerializer serializer_class = MembershipSerializer
queryset = Membership.objects.none() queryset = Membership.objects.none()
permission = 'organizer.customers:write' permission = 'organizer.customers:read'
write_permission = 'organizer.customers:write'
filter_backends = (DjangoFilterBackend,) filter_backends = (DjangoFilterBackend,)
filterset_class = MembershipFilter filterset_class = MembershipFilter

View File

@@ -78,18 +78,18 @@ def register_default_event_permissions(sender, **kwargs):
Permission("event.settings.general:write", _("Change general settings"), None, Permission("event.settings.general:write", _("Change general settings"), None,
_("This includes access to all settings not listed explicitly below, including plugin settings.")), _("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.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.tax:write", _("Change tax rules"), None, None),
Permission("event.settings.invoicing:write", _("Change invoicing settings"), 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.subevents:write", pgettext_lazy("subevent", "Change event series dates"), None,
Permission("event.items:write", _("Change products and quotas"), None, None), # and questions but that might change? _("Read access is granted to all teams with access to the event.")),
Permission("event.items:write", _("Change products, quotas, and questions"), None,
_("Also includes related objects like categories or discounts. Read access is granted to all teams with access to the event.")),
Permission("event.orders:read", _("View orders"), None, None), 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: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.orders:checkin", _("Check-in orders"), None, None),
Permission("event.vouchers:read", _("View vouchers"), None, None), Permission("event.vouchers:read", _("View vouchers"), None, None),
Permission("event.vouchers:write", _("Change vouchers"), None, None), Permission("event.vouchers:write", _("Change vouchers"), None, None),
Permission("event:cancel", pgettext_lazy("subevent", " entire event or date"), None, None), Permission("event:cancel", pgettext_lazy("subevent", "Cancel entire event or date"), None, None),
] ]
@@ -100,14 +100,16 @@ def register_default_organizer_permissions(sender, **kwargs):
Permission("organizer.settings.general:write", _("Change settings"), None, Permission("organizer.settings.general:write", _("Change settings"), None,
_("This includes access to all organizer-level functionality not listed explicitly below, including plugin settings.")), _("This includes access to all organizer-level functionality not listed explicitly below, including plugin settings.")),
Permission("organizer.teams:write", _("Change teams"), None, Permission("organizer.teams:write", _("Change teams"), None,
_("This includes the ability to give someone (including oneself) additional permissions.")), _("This includes the ability to give someone (including oneself) additional permissions. Read access "
"is implicitly granted to the same team.")),
Permission("organizer.giftcards:read", _("View gift cards"), None, None), Permission("organizer.giftcards:read", _("View gift cards"), None, None),
Permission("organizer.giftcards:write", _("Change 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:read", _("View customer accounts"), None, None),
Permission("organizer.customers:write", _("Change 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:read", _("View reusable media"), None,
_("This includes access to data of tickets connected to reusable media.")),
Permission("organizer.reusablemedia:write", _("Change reusable media"), None, None), Permission("organizer.reusablemedia:write", _("Change reusable media"), None, None),
Permission("organizer.devices:read", _("View devices"), None, None), Permission("organizer.devices:read", _("View devices and gates"), None, None),
Permission("organizer.devices:write", _("Change devices"), None, Permission("organizer.devices:write", _("Change devices and gates"), None,
_("This includes the ability to give access to events and data oneself does not have access to.")), _("This includes the ability to give access to events and data oneself does not have access to.")),
] ]

View File

@@ -345,6 +345,7 @@ DEFAULTS = {
'type': bool, 'type': bool,
'form_class': forms.BooleanField, 'form_class': forms.BooleanField,
'serializer_class': serializers.BooleanField, 'serializer_class': serializers.BooleanField,
'write_permission': 'event.settings.tax:write',
'form_kwargs': dict( 'form_kwargs': dict(
label=_("Show net prices instead of gross prices in the product list"), label=_("Show net prices instead of gross prices in the product list"),
help_text=_("Independent of your choice, the cart will show gross prices as this is the price that needs to be " help_text=_("Independent of your choice, the cart will show gross prices as this is the price that needs to be "
@@ -492,6 +493,7 @@ DEFAULTS = {
'type': str, 'type': str,
'form_class': forms.ChoiceField, 'form_class': forms.ChoiceField,
'serializer_class': serializers.ChoiceField, 'serializer_class': serializers.ChoiceField,
'write_permission': 'event.settings.tax:write',
'form_kwargs': dict( 'form_kwargs': dict(
label=_("Rounding of taxes"), label=_("Rounding of taxes"),
widget=forms.RadioSelect, widget=forms.RadioSelect,
@@ -511,15 +513,17 @@ DEFAULTS = {
'type': bool, 'type': bool,
'form_class': forms.BooleanField, 'form_class': forms.BooleanField,
'serializer_class': serializers.BooleanField, 'serializer_class': serializers.BooleanField,
'write_permission': 'event.settings.invoicing:write',
'form_kwargs': dict( 'form_kwargs': dict(
label=_("Ask for invoice address"), label=_("Ask for invoice address"),
) ),
}, },
'invoice_address_not_asked_free': { 'invoice_address_not_asked_free': {
'default': 'False', 'default': 'False',
'type': bool, 'type': bool,
'form_class': forms.BooleanField, 'form_class': forms.BooleanField,
'serializer_class': serializers.BooleanField, 'serializer_class': serializers.BooleanField,
'write_permission': 'event.settings.invoicing:write',
'form_kwargs': dict( 'form_kwargs': dict(
label=_('Do not ask for invoice address if an order is free'), label=_('Do not ask for invoice address if an order is free'),
) )
@@ -529,6 +533,7 @@ DEFAULTS = {
'type': bool, 'type': bool,
'form_class': forms.BooleanField, 'form_class': forms.BooleanField,
'serializer_class': serializers.BooleanField, 'serializer_class': serializers.BooleanField,
'write_permission': 'event.settings.invoicing:write',
'form_kwargs': dict( 'form_kwargs': dict(
label=_("Require customer name"), label=_("Require customer name"),
) )
@@ -538,6 +543,7 @@ DEFAULTS = {
'type': bool, 'type': bool,
'form_class': forms.BooleanField, 'form_class': forms.BooleanField,
'serializer_class': serializers.BooleanField, 'serializer_class': serializers.BooleanField,
'write_permission': 'event.settings.invoicing:write',
'form_kwargs': dict( 'form_kwargs': dict(
label=_("Show attendee names on invoices"), label=_("Show attendee names on invoices"),
) )
@@ -547,6 +553,7 @@ DEFAULTS = {
'type': bool, 'type': bool,
'form_class': forms.BooleanField, 'form_class': forms.BooleanField,
'serializer_class': serializers.BooleanField, 'serializer_class': serializers.BooleanField,
'write_permission': 'event.settings.invoicing:write',
'form_kwargs': dict( 'form_kwargs': dict(
label=_("Show event location on invoices"), label=_("Show event location on invoices"),
help_text=_("The event location will be shown below the list of products if it is the same for all " help_text=_("The event location will be shown below the list of products if it is the same for all "
@@ -558,6 +565,7 @@ DEFAULTS = {
'type': str, 'type': str,
'form_class': forms.ChoiceField, 'form_class': forms.ChoiceField,
'serializer_class': serializers.ChoiceField, 'serializer_class': serializers.ChoiceField,
'write_permission': 'event.settings.invoicing:write',
'form_kwargs': dict( 'form_kwargs': dict(
label=_("Show exchange rates"), label=_("Show exchange rates"),
widget=forms.RadioSelect, widget=forms.RadioSelect,
@@ -581,6 +589,7 @@ DEFAULTS = {
'default': 'False', 'default': 'False',
'form_class': forms.BooleanField, 'form_class': forms.BooleanField,
'serializer_class': serializers.BooleanField, 'serializer_class': serializers.BooleanField,
'write_permission': 'event.settings.invoicing:write',
'type': bool, 'type': bool,
'form_kwargs': dict( 'form_kwargs': dict(
label=_("Require invoice address"), label=_("Require invoice address"),
@@ -591,6 +600,7 @@ DEFAULTS = {
'default': 'False', 'default': 'False',
'form_class': forms.BooleanField, 'form_class': forms.BooleanField,
'serializer_class': serializers.BooleanField, 'serializer_class': serializers.BooleanField,
'write_permission': 'event.settings.invoicing:write',
'type': bool, 'type': bool,
'form_kwargs': dict( 'form_kwargs': dict(
label=_("Require a business address"), label=_("Require a business address"),
@@ -603,6 +613,7 @@ DEFAULTS = {
'type': bool, 'type': bool,
'form_class': forms.BooleanField, 'form_class': forms.BooleanField,
'serializer_class': serializers.BooleanField, 'serializer_class': serializers.BooleanField,
'write_permission': 'event.settings.invoicing:write',
'form_kwargs': dict( 'form_kwargs': dict(
label=_("Ask for beneficiary"), label=_("Ask for beneficiary"),
widget=forms.CheckboxInput(attrs={'data-checkbox-dependency': '#id_invoice_address_asked'}), widget=forms.CheckboxInput(attrs={'data-checkbox-dependency': '#id_invoice_address_asked'}),
@@ -613,6 +624,7 @@ DEFAULTS = {
'type': LazyI18nString, 'type': LazyI18nString,
'form_class': I18nFormField, 'form_class': I18nFormField,
'serializer_class': I18nField, 'serializer_class': I18nField,
'write_permission': 'event.settings.invoicing:write',
'form_kwargs': dict( 'form_kwargs': dict(
label=_("Custom recipient field label"), label=_("Custom recipient field label"),
widget=I18nTextInput, widget=I18nTextInput,
@@ -628,6 +640,7 @@ DEFAULTS = {
'type': LazyI18nString, 'type': LazyI18nString,
'form_class': I18nFormField, 'form_class': I18nFormField,
'serializer_class': I18nField, 'serializer_class': I18nField,
'write_permission': 'event.settings.invoicing:write',
'form_kwargs': dict( 'form_kwargs': dict(
label=_("Custom recipient field help text"), label=_("Custom recipient field help text"),
widget=I18nTextInput, widget=I18nTextInput,
@@ -640,6 +653,7 @@ DEFAULTS = {
'type': bool, 'type': bool,
'form_class': forms.BooleanField, 'form_class': forms.BooleanField,
'serializer_class': serializers.BooleanField, 'serializer_class': serializers.BooleanField,
'write_permission': 'event.settings.invoicing:write',
'form_kwargs': dict( 'form_kwargs': dict(
label=_("Ask for VAT ID"), label=_("Ask for VAT ID"),
help_text=format_lazy( help_text=format_lazy(
@@ -655,6 +669,7 @@ DEFAULTS = {
'type': list, 'type': list,
'form_class': forms.MultipleChoiceField, 'form_class': forms.MultipleChoiceField,
'serializer_class': serializers.MultipleChoiceField, 'serializer_class': serializers.MultipleChoiceField,
'write_permission': 'event.settings.invoicing:write',
'serializer_kwargs': dict( 'serializer_kwargs': dict(
choices=lazy( choices=lazy(
lambda *args: sorted([(cc, gettext(Country(cc).name)) for cc in VAT_ID_COUNTRIES], key=lambda c: c[1]), lambda *args: sorted([(cc, gettext(Country(cc).name)) for cc in VAT_ID_COUNTRIES], key=lambda c: c[1]),
@@ -682,6 +697,7 @@ DEFAULTS = {
'type': LazyI18nString, 'type': LazyI18nString,
'form_class': I18nFormField, 'form_class': I18nFormField,
'serializer_class': I18nField, 'serializer_class': I18nField,
'write_permission': 'event.settings.invoicing:write',
'form_kwargs': dict( 'form_kwargs': dict(
label=_("Invoice address explanation"), label=_("Invoice address explanation"),
widget=I18nMarkdownTextarea, widget=I18nMarkdownTextarea,
@@ -694,6 +710,7 @@ DEFAULTS = {
'type': bool, 'type': bool,
'form_class': forms.BooleanField, 'form_class': forms.BooleanField,
'serializer_class': serializers.BooleanField, 'serializer_class': serializers.BooleanField,
'write_permission': 'event.settings.invoicing:write',
'form_kwargs': dict( 'form_kwargs': dict(
label=_("Show paid amount on partially paid invoices"), label=_("Show paid amount on partially paid invoices"),
help_text=_("If an invoice has already been paid partially, this option will add the paid and pending " help_text=_("If an invoice has already been paid partially, this option will add the paid and pending "
@@ -705,6 +722,7 @@ DEFAULTS = {
'type': bool, 'type': bool,
'form_class': forms.BooleanField, 'form_class': forms.BooleanField,
'serializer_class': serializers.BooleanField, 'serializer_class': serializers.BooleanField,
'write_permission': 'event.settings.invoicing:write',
'form_kwargs': dict( 'form_kwargs': dict(
label=_("Show free products on invoices"), label=_("Show free products on invoices"),
help_text=_("Note that invoices will never be generated for orders that contain only free " help_text=_("Note that invoices will never be generated for orders that contain only free "
@@ -716,6 +734,7 @@ DEFAULTS = {
'type': bool, 'type': bool,
'form_class': forms.BooleanField, 'form_class': forms.BooleanField,
'serializer_class': serializers.BooleanField, 'serializer_class': serializers.BooleanField,
'write_permission': 'event.settings.invoicing:write',
'form_kwargs': dict( 'form_kwargs': dict(
label=_("Show expiration date of order"), label=_("Show expiration date of order"),
help_text=_("The expiration date will not be shown if the invoice is generated after the order is paid."), help_text=_("The expiration date will not be shown if the invoice is generated after the order is paid."),
@@ -727,6 +746,7 @@ DEFAULTS = {
'form_class': forms.IntegerField, 'form_class': forms.IntegerField,
'serializer_class': serializers.IntegerField, 'serializer_class': serializers.IntegerField,
'serializer_kwargs': dict(), 'serializer_kwargs': dict(),
'write_permission': 'event.settings.invoicing:write',
'form_kwargs': dict( 'form_kwargs': dict(
label=_("Minimum length of invoice number after prefix"), label=_("Minimum length of invoice number after prefix"),
help_text=_("The part of your invoice number after your prefix will be filled up with leading zeros up to this length, e.g. INV-001 or INV-00001."), help_text=_("The part of your invoice number after your prefix will be filled up with leading zeros up to this length, e.g. INV-001 or INV-00001."),
@@ -740,6 +760,7 @@ DEFAULTS = {
'type': bool, 'type': bool,
'form_class': forms.BooleanField, 'form_class': forms.BooleanField,
'serializer_class': serializers.BooleanField, 'serializer_class': serializers.BooleanField,
'write_permission': 'event.settings.invoicing:write',
'form_kwargs': dict( 'form_kwargs': dict(
label=_("Generate invoices with consecutive numbers"), label=_("Generate invoices with consecutive numbers"),
help_text=_("If deactivated, the order code will be used in the invoice number."), help_text=_("If deactivated, the order code will be used in the invoice number."),
@@ -750,6 +771,7 @@ DEFAULTS = {
'type': str, 'type': str,
'form_class': forms.CharField, 'form_class': forms.CharField,
'serializer_class': serializers.CharField, 'serializer_class': serializers.CharField,
'write_permission': 'event.settings.invoicing:write',
'form_kwargs': dict( 'form_kwargs': dict(
label=_("Invoice number prefix"), label=_("Invoice number prefix"),
help_text=_("This will be prepended to invoice numbers. If you leave this field empty, your event slug will " help_text=_("This will be prepended to invoice numbers. If you leave this field empty, your event slug will "
@@ -777,6 +799,7 @@ DEFAULTS = {
'type': str, 'type': str,
'form_class': forms.CharField, 'form_class': forms.CharField,
'serializer_class': serializers.CharField, 'serializer_class': serializers.CharField,
'write_permission': 'event.settings.invoicing:write',
'form_kwargs': dict( 'form_kwargs': dict(
label=_("Invoice number prefix for cancellations"), label=_("Invoice number prefix for cancellations"),
help_text=_("This will be prepended to invoice numbers of cancellations. If you leave this field empty, " help_text=_("This will be prepended to invoice numbers of cancellations. If you leave this field empty, "
@@ -800,6 +823,7 @@ DEFAULTS = {
'type': bool, 'type': bool,
'form_class': forms.BooleanField, 'form_class': forms.BooleanField,
'serializer_class': serializers.BooleanField, 'serializer_class': serializers.BooleanField,
'write_permission': 'event.settings.invoicing:write',
'form_kwargs': dict( 'form_kwargs': dict(
label=_("Highlight order code to make it stand out visibly"), label=_("Highlight order code to make it stand out visibly"),
help_text=_("Only respected by some invoice renderers."), help_text=_("Only respected by some invoice renderers."),
@@ -811,6 +835,7 @@ DEFAULTS = {
'form_class': forms.ChoiceField, 'form_class': forms.ChoiceField,
'serializer_class': serializers.ChoiceField, 'serializer_class': serializers.ChoiceField,
'serializer_kwargs': lambda: dict(**invoice_font_kwargs()), 'serializer_kwargs': lambda: dict(**invoice_font_kwargs()),
'write_permission': 'event.settings.invoicing:write',
'form_kwargs': lambda: dict( 'form_kwargs': lambda: dict(
label=_('Font'), label=_('Font'),
help_text=_("Only respected by some invoice renderers."), help_text=_("Only respected by some invoice renderers."),
@@ -821,6 +846,7 @@ DEFAULTS = {
'invoice_renderer': { 'invoice_renderer': {
'default': 'classic', # default for new events is 'modern1' 'default': 'classic', # default for new events is 'modern1'
'type': str, 'type': str,
'write_permission': 'event.settings.invoicing:write',
}, },
'ticket_secret_generator': { 'ticket_secret_generator': {
'default': 'random', 'default': 'random',
@@ -897,6 +923,7 @@ DEFAULTS = {
'type': LazyI18nString, 'type': LazyI18nString,
'form_class': I18nFormField, 'form_class': I18nFormField,
'serializer_class': I18nField, 'serializer_class': I18nField,
'write_permission': 'event.settings.payment:write',
'form_kwargs': dict( 'form_kwargs': dict(
widget=I18nMarkdownTextarea, widget=I18nMarkdownTextarea,
widget_kwargs={'attrs': { widget_kwargs={'attrs': {
@@ -918,6 +945,7 @@ DEFAULTS = {
('minutes', _("in minutes")) ('minutes', _("in minutes"))
), ),
), ),
'write_permission': 'event.settings.payment:write',
'form_kwargs': dict( 'form_kwargs': dict(
label=_("Set payment term"), label=_("Set payment term"),
widget=forms.RadioSelect, widget=forms.RadioSelect,
@@ -935,6 +963,7 @@ DEFAULTS = {
'type': int, 'type': int,
'form_class': forms.IntegerField, 'form_class': forms.IntegerField,
'serializer_class': serializers.IntegerField, 'serializer_class': serializers.IntegerField,
'write_permission': 'event.settings.payment:write',
'form_kwargs': dict( 'form_kwargs': dict(
label=_('Payment term in days'), label=_('Payment term in days'),
widget=forms.NumberInput( widget=forms.NumberInput(
@@ -960,6 +989,7 @@ DEFAULTS = {
'type': bool, 'type': bool,
'form_class': forms.BooleanField, 'form_class': forms.BooleanField,
'serializer_class': serializers.BooleanField, 'serializer_class': serializers.BooleanField,
'write_permission': 'event.settings.payment:write',
'form_kwargs': dict( 'form_kwargs': dict(
label=_('Only end payment terms on weekdays'), label=_('Only end payment terms on weekdays'),
help_text=_("If this is activated and the payment term of any order ends on a Saturday or Sunday, it will be " help_text=_("If this is activated and the payment term of any order ends on a Saturday or Sunday, it will be "
@@ -977,6 +1007,7 @@ DEFAULTS = {
'type': int, 'type': int,
'form_class': forms.IntegerField, 'form_class': forms.IntegerField,
'serializer_class': serializers.IntegerField, 'serializer_class': serializers.IntegerField,
'write_permission': 'event.settings.payment:write',
'form_kwargs': dict( 'form_kwargs': dict(
label=_('Payment term in minutes'), label=_('Payment term in minutes'),
help_text=_("The number of minutes after placing an order the user has to pay to preserve their reservation. " help_text=_("The number of minutes after placing an order the user has to pay to preserve their reservation. "
@@ -1001,6 +1032,7 @@ DEFAULTS = {
'type': RelativeDateWrapper, 'type': RelativeDateWrapper,
'form_class': RelativeDateField, 'form_class': RelativeDateField,
'serializer_class': SerializerRelativeDateField, 'serializer_class': SerializerRelativeDateField,
'write_permission': 'event.settings.payment:write',
'form_kwargs': dict( 'form_kwargs': dict(
label=_('Last date of payments'), label=_('Last date of payments'),
help_text=_("The last date any payments are accepted. This has precedence over the terms " help_text=_("The last date any payments are accepted. This has precedence over the terms "
@@ -1013,6 +1045,7 @@ DEFAULTS = {
'type': bool, 'type': bool,
'form_class': forms.BooleanField, 'form_class': forms.BooleanField,
'serializer_class': serializers.BooleanField, 'serializer_class': serializers.BooleanField,
'write_permission': 'event.settings.payment:write',
'form_kwargs': dict( 'form_kwargs': dict(
label=_('Automatically expire unpaid orders'), label=_('Automatically expire unpaid orders'),
help_text=_("If checked, all unpaid orders will automatically go from 'pending' to 'expired' " help_text=_("If checked, all unpaid orders will automatically go from 'pending' to 'expired' "
@@ -1025,6 +1058,7 @@ DEFAULTS = {
'type': int, 'type': int,
'form_class': forms.IntegerField, 'form_class': forms.IntegerField,
'serializer_class': serializers.IntegerField, 'serializer_class': serializers.IntegerField,
'write_permission': 'event.settings.payment:write',
'form_kwargs': dict( 'form_kwargs': dict(
label=_('Expiration delay'), label=_('Expiration delay'),
help_text=_("The order will only actually expire this many days after the expiration date communicated " help_text=_("The order will only actually expire this many days after the expiration date communicated "
@@ -1047,6 +1081,7 @@ DEFAULTS = {
'type': bool, 'type': bool,
'form_class': forms.BooleanField, 'form_class': forms.BooleanField,
'serializer_class': serializers.BooleanField, 'serializer_class': serializers.BooleanField,
'write_permission': 'event.settings.payment:write',
'form_kwargs': dict( 'form_kwargs': dict(
label=_('Hide "payment pending" state on customer-facing pages'), label=_('Hide "payment pending" state on customer-facing pages'),
help_text=_("The payment instructions panel will still be shown to the primary customer, but no indication " help_text=_("The payment instructions panel will still be shown to the primary customer, but no indication "
@@ -1058,9 +1093,11 @@ DEFAULTS = {
'default': 'True', 'default': 'True',
'type': bool, 'type': bool,
'serializer_class': serializers.BooleanField, 'serializer_class': serializers.BooleanField,
'write_permission': 'event.settings.payment:write',
}, },
'payment_giftcard_public_name': { 'payment_giftcard_public_name': {
'default': LazyI18nString.from_gettext(gettext_noop('Gift card')), 'default': LazyI18nString.from_gettext(gettext_noop('Gift card')),
'write_permission': 'event.settings.payment:write',
'type': LazyI18nString 'type': LazyI18nString
}, },
'payment_giftcard_public_description': { 'payment_giftcard_public_description': {
@@ -1069,10 +1106,12 @@ DEFAULTS = {
'enough credit to pay for the full order, you will be shown this page again and you can either ' 'enough credit to pay for the full order, you will be shown this page again and you can either '
'redeem another gift card or select a different payment method for the difference.' 'redeem another gift card or select a different payment method for the difference.'
)), )),
'write_permission': 'event.settings.payment:write',
'type': LazyI18nString 'type': LazyI18nString
}, },
'payment_resellers__restrict_to_sales_channels': { 'payment_resellers__restrict_to_sales_channels': {
'default': ['resellers'], 'default': ['resellers'],
'write_permission': 'event.settings.payment:write',
'type': list 'type': list
}, },
'payment_term_accept_late': { 'payment_term_accept_late': {
@@ -1080,6 +1119,7 @@ DEFAULTS = {
'type': bool, 'type': bool,
'form_class': forms.BooleanField, 'form_class': forms.BooleanField,
'serializer_class': serializers.BooleanField, 'serializer_class': serializers.BooleanField,
'write_permission': 'event.settings.payment:write',
'form_kwargs': dict( 'form_kwargs': dict(
label=_('Accept late payments'), label=_('Accept late payments'),
help_text=_("Accept payments for orders even when they are in 'expired' state as long as enough " help_text=_("Accept payments for orders even when they are in 'expired' state as long as enough "
@@ -1109,6 +1149,7 @@ DEFAULTS = {
('none', _('Charge no taxes')), ('none', _('Charge no taxes')),
), ),
), ),
'write_permission': 'event.settings.payment:write',
'form_kwargs': dict( 'form_kwargs': dict(
label=_("Tax handling on payment fees"), label=_("Tax handling on payment fees"),
widget=forms.RadioSelect, widget=forms.RadioSelect,
@@ -1155,6 +1196,7 @@ DEFAULTS = {
('paid', _('Automatically on payment or when required by payment method')), ('paid', _('Automatically on payment or when required by payment method')),
), ),
), ),
'write_permission': 'event.settings.invoicing:write',
'form_kwargs': dict( 'form_kwargs': dict(
label=_("Generate invoices"), label=_("Generate invoices"),
widget=forms.RadioSelect, widget=forms.RadioSelect,
@@ -1183,6 +1225,7 @@ DEFAULTS = {
('invoice_date', _('Invoice date')), ('invoice_date', _('Invoice date')),
), ),
), ),
'write_permission': 'event.settings.invoicing:write',
'form_kwargs': dict( 'form_kwargs': dict(
label=_("Date of service"), label=_("Date of service"),
widget=forms.RadioSelect, widget=forms.RadioSelect,
@@ -1203,6 +1246,7 @@ DEFAULTS = {
'type': bool, 'type': bool,
'form_class': forms.BooleanField, 'form_class': forms.BooleanField,
'serializer_class': serializers.BooleanField, 'serializer_class': serializers.BooleanField,
'write_permission': 'event.settings.invoicing:write',
'form_kwargs': dict( 'form_kwargs': dict(
label=_("Automatically cancel and reissue invoice on address changes"), label=_("Automatically cancel and reissue invoice on address changes"),
help_text=_("If customers change their invoice address on an existing order, the invoice will " help_text=_("If customers change their invoice address on an existing order, the invoice will "
@@ -1215,6 +1259,7 @@ DEFAULTS = {
'type': bool, 'type': bool,
'form_class': forms.BooleanField, 'form_class': forms.BooleanField,
'serializer_class': serializers.BooleanField, 'serializer_class': serializers.BooleanField,
'write_permission': 'event.settings.invoicing:write',
'form_kwargs': dict( 'form_kwargs': dict(
label=_("Allow to update existing invoices"), label=_("Allow to update existing invoices"),
help_text=_("By default, invoices can never again be changed once they are issued. In most countries, we " help_text=_("By default, invoices can never again be changed once they are issued. In most countries, we "
@@ -1224,6 +1269,7 @@ DEFAULTS = {
}, },
'invoice_generate_sales_channels': { 'invoice_generate_sales_channels': {
'default': json.dumps(['web']), 'default': json.dumps(['web']),
'write_permission': 'event.settings.invoicing:write',
'type': list 'type': list
}, },
'invoice_generate_only_business': { 'invoice_generate_only_business': {
@@ -1240,6 +1286,7 @@ DEFAULTS = {
'type': str, 'type': str,
'form_class': forms.CharField, 'form_class': forms.CharField,
'serializer_class': serializers.CharField, 'serializer_class': serializers.CharField,
'write_permission': 'event.settings.invoicing:write',
'form_kwargs': dict( 'form_kwargs': dict(
label=_("Address line"), label=_("Address line"),
widget=forms.Textarea(attrs={ widget=forms.Textarea(attrs={
@@ -1255,6 +1302,7 @@ DEFAULTS = {
'type': str, 'type': str,
'form_class': forms.CharField, 'form_class': forms.CharField,
'serializer_class': serializers.CharField, 'serializer_class': serializers.CharField,
'write_permission': 'event.settings.invoicing:write',
'form_kwargs': dict( 'form_kwargs': dict(
max_length=190, max_length=190,
label=_("Company name"), label=_("Company name"),
@@ -1265,6 +1313,7 @@ DEFAULTS = {
'type': str, 'type': str,
'form_class': forms.CharField, 'form_class': forms.CharField,
'serializer_class': serializers.CharField, 'serializer_class': serializers.CharField,
'write_permission': 'event.settings.invoicing:write',
'form_kwargs': dict( 'form_kwargs': dict(
widget=forms.TextInput(attrs={ widget=forms.TextInput(attrs={
'placeholder': '12345' 'placeholder': '12345'
@@ -1278,6 +1327,7 @@ DEFAULTS = {
'type': str, 'type': str,
'form_class': forms.CharField, 'form_class': forms.CharField,
'serializer_class': serializers.CharField, 'serializer_class': serializers.CharField,
'write_permission': 'event.settings.invoicing:write',
'form_kwargs': dict( 'form_kwargs': dict(
widget=forms.TextInput(attrs={ widget=forms.TextInput(attrs={
'placeholder': _('Random City') 'placeholder': _('Random City')
@@ -1294,6 +1344,7 @@ DEFAULTS = {
'serializer_kwargs': { 'serializer_kwargs': {
'choices': [('', '')], 'choices': [('', '')],
}, },
'write_permission': 'event.settings.invoicing:write',
'form_kwargs': { 'form_kwargs': {
"label": pgettext_lazy('address', 'State'), "label": pgettext_lazy('address', 'State'),
'choices': [('', '')], 'choices': [('', '')],
@@ -1305,6 +1356,7 @@ DEFAULTS = {
'form_class': forms.ChoiceField, 'form_class': forms.ChoiceField,
'serializer_class': serializers.ChoiceField, 'serializer_class': serializers.ChoiceField,
'serializer_kwargs': lambda: dict(**country_choice_kwargs()), 'serializer_kwargs': lambda: dict(**country_choice_kwargs()),
'write_permission': 'event.settings.invoicing:write',
'form_kwargs': lambda: dict( 'form_kwargs': lambda: dict(
label=_('Country'), label=_('Country'),
widget=forms.Select(attrs={ widget=forms.Select(attrs={
@@ -1318,6 +1370,7 @@ DEFAULTS = {
'type': str, 'type': str,
'form_class': forms.CharField, 'form_class': forms.CharField,
'serializer_class': serializers.CharField, 'serializer_class': serializers.CharField,
'write_permission': 'event.settings.invoicing:write',
'form_kwargs': dict( 'form_kwargs': dict(
label=_("Domestic tax ID"), label=_("Domestic tax ID"),
help_text=_("e.g. tax number in Germany, ABN in Australia, …"), help_text=_("e.g. tax number in Germany, ABN in Australia, …"),
@@ -1329,6 +1382,7 @@ DEFAULTS = {
'type': str, 'type': str,
'form_class': forms.CharField, 'form_class': forms.CharField,
'serializer_class': serializers.CharField, 'serializer_class': serializers.CharField,
'write_permission': 'event.settings.invoicing:write',
'form_kwargs': dict( 'form_kwargs': dict(
label=_("EU VAT ID"), label=_("EU VAT ID"),
max_length=190, max_length=190,
@@ -1339,6 +1393,7 @@ DEFAULTS = {
'type': LazyI18nString, 'type': LazyI18nString,
'form_class': I18nFormField, 'form_class': I18nFormField,
'serializer_class': I18nField, 'serializer_class': I18nField,
'write_permission': 'event.settings.invoicing:write',
'form_kwargs': dict( 'form_kwargs': dict(
widget=I18nTextarea, widget=I18nTextarea,
widget_kwargs={'attrs': { widget_kwargs={'attrs': {
@@ -1356,6 +1411,7 @@ DEFAULTS = {
'type': LazyI18nString, 'type': LazyI18nString,
'form_class': I18nFormField, 'form_class': I18nFormField,
'serializer_class': I18nField, 'serializer_class': I18nField,
'write_permission': 'event.settings.invoicing:write',
'form_kwargs': dict( 'form_kwargs': dict(
widget=I18nTextarea, widget=I18nTextarea,
widget_kwargs={'attrs': { widget_kwargs={'attrs': {
@@ -1373,6 +1429,7 @@ DEFAULTS = {
'type': LazyI18nString, 'type': LazyI18nString,
'form_class': I18nFormField, 'form_class': I18nFormField,
'serializer_class': I18nField, 'serializer_class': I18nField,
'write_permission': 'event.settings.invoicing:write',
'form_kwargs': dict( 'form_kwargs': dict(
widget=I18nTextarea, widget=I18nTextarea,
widget_kwargs={'attrs': { widget_kwargs={'attrs': {
@@ -1387,6 +1444,7 @@ DEFAULTS = {
}, },
'invoice_language': { 'invoice_language': {
'default': '__user__', 'default': '__user__',
'write_permission': 'event.settings.invoicing:write',
'type': str 'type': str
}, },
'invoice_email_attachment': { 'invoice_email_attachment': {
@@ -1394,6 +1452,7 @@ DEFAULTS = {
'type': bool, 'type': bool,
'form_class': forms.BooleanField, 'form_class': forms.BooleanField,
'serializer_class': serializers.BooleanField, 'serializer_class': serializers.BooleanField,
'write_permission': 'event.settings.invoicing:write',
'form_kwargs': dict( 'form_kwargs': dict(
label=_("Attach invoices to emails"), label=_("Attach invoices to emails"),
help_text=_("If invoices are automatically generated for all orders, they will be attached to the order " help_text=_("If invoices are automatically generated for all orders, they will be attached to the order "
@@ -1407,6 +1466,7 @@ DEFAULTS = {
'type': str, 'type': str,
'form_class': forms.CharField, 'form_class': forms.CharField,
'serializer_class': serializers.CharField, 'serializer_class': serializers.CharField,
'write_permission': 'event.settings.invoicing:write',
'form_kwargs': dict( 'form_kwargs': dict(
label=_("Email address to receive a copy of each invoice"), label=_("Email address to receive a copy of each invoice"),
help_text=_("Each newly created invoice will be sent to this email address shortly after creation. You can " help_text=_("Each newly created invoice will be sent to this email address shortly after creation. You can "
@@ -3260,7 +3320,8 @@ Your {organizer} team""")) # noqa: W291
'image/png', 'image/jpeg', 'image/gif' 'image/png', 'image/jpeg', 'image/gif'
], ],
max_size=settings.FILE_UPLOAD_MAX_SIZE_IMAGE, max_size=settings.FILE_UPLOAD_MAX_SIZE_IMAGE,
) ),
'write_permission': 'event.settings.invoicing:write',
}, },
'frontpage_text': { 'frontpage_text': {
'default': '', 'default': '',

View File

@@ -1118,6 +1118,9 @@ api_event_settings_fields = EventPluginSignal()
This signal is sent out to collect serializable settings fields for the API. You are expected to This signal is sent out to collect serializable settings fields for the API. You are expected to
return a dictionary mapping names of attributes in the settings store to DRF serializer field instances. return a dictionary mapping names of attributes in the settings store to DRF serializer field instances.
These are readable for all users with access to the events, therefore secrets made in the settings store
should not be included!
As with all event-plugin signals, the ``sender`` keyword argument will contain the event. As with all event-plugin signals, the ``sender`` keyword argument will contain the event.
""" """

View File

@@ -32,7 +32,11 @@ from pretix.base.models import ItemVariation
from pretix.base.reldate import RelativeDateWrapper from pretix.base.reldate import RelativeDateWrapper
from pretix.base.signals import timeline_events from pretix.base.signals import timeline_events
TimelineEvent = namedtuple('TimelineEvent', ('event', 'subevent', 'datetime', 'description', 'edit_url')) TimelineEvent = namedtuple(
'TimelineEvent',
('event', 'subevent', 'datetime', 'description', 'edit_url', 'edit_permission'),
defaults=(None, None, None, None, None, 'event.settings.general:write')
)
def timeline_for_event(event, subevent=None): def timeline_for_event(event, subevent=None):
@@ -46,6 +50,7 @@ def timeline_for_event(event, subevent=None):
'subevent': subevent.pk 'subevent': subevent.pk
} }
) )
ev_edit_permission = 'event.subevents:write'
else: else:
ev_edit_url = reverse( ev_edit_url = reverse(
'control:event.settings', kwargs={ 'control:event.settings', kwargs={
@@ -53,12 +58,14 @@ def timeline_for_event(event, subevent=None):
'organizer': event.organizer.slug 'organizer': event.organizer.slug
} }
) )
ev_edit_permission = 'event.settings.general:write'
tl.append(TimelineEvent( tl.append(TimelineEvent(
event=event, subevent=subevent, event=event, subevent=subevent,
datetime=ev.date_from, datetime=ev.date_from,
description=pgettext_lazy('timeline', 'Your event starts'), description=pgettext_lazy('timeline', 'Your event starts'),
edit_url=ev_edit_url + '#id_date_from_0' edit_url=ev_edit_url + '#id_date_from_0',
edit_permission=ev_edit_permission,
)) ))
if ev.date_to: if ev.date_to:
@@ -66,7 +73,8 @@ def timeline_for_event(event, subevent=None):
event=event, subevent=subevent, event=event, subevent=subevent,
datetime=ev.date_to, datetime=ev.date_to,
description=pgettext_lazy('timeline', 'Your event ends'), description=pgettext_lazy('timeline', 'Your event ends'),
edit_url=ev_edit_url + '#id_date_to_0' edit_url=ev_edit_url + '#id_date_to_0',
edit_permission=ev_edit_permission,
)) ))
if ev.date_admission: if ev.date_admission:
@@ -74,7 +82,8 @@ def timeline_for_event(event, subevent=None):
event=event, subevent=subevent, event=event, subevent=subevent,
datetime=ev.date_admission, datetime=ev.date_admission,
description=pgettext_lazy('timeline', 'Admissions for your event start'), description=pgettext_lazy('timeline', 'Admissions for your event start'),
edit_url=ev_edit_url + '#id_date_admission_0' edit_url=ev_edit_url + '#id_date_admission_0',
edit_permission=ev_edit_permission,
)) ))
if ev.presale_start: if ev.presale_start:
@@ -82,7 +91,8 @@ def timeline_for_event(event, subevent=None):
event=event, subevent=subevent, event=event, subevent=subevent,
datetime=ev.presale_start, datetime=ev.presale_start,
description=pgettext_lazy('timeline', 'Start of ticket sales'), description=pgettext_lazy('timeline', 'Start of ticket sales'),
edit_url=ev_edit_url + '#id_presale_start_0' edit_url=ev_edit_url + '#id_presale_start_0',
edit_permission=ev_edit_permission,
)) ))
tl.append(TimelineEvent( tl.append(TimelineEvent(
@@ -97,7 +107,8 @@ def timeline_for_event(event, subevent=None):
) if not ev.presale_end else ( ) if not ev.presale_end else (
pgettext_lazy('timeline', 'End of ticket sales') pgettext_lazy('timeline', 'End of ticket sales')
), ),
edit_url=ev_edit_url + '#id_presale_end_0' edit_url=ev_edit_url + '#id_presale_end_0',
edit_permission=ev_edit_permission,
)) ))
rd = event.settings.get('last_order_modification_date', as_type=RelativeDateWrapper) rd = event.settings.get('last_order_modification_date', as_type=RelativeDateWrapper)
@@ -106,7 +117,8 @@ def timeline_for_event(event, subevent=None):
event=event, subevent=subevent, event=event, subevent=subevent,
datetime=rd.datetime(ev), datetime=rd.datetime(ev),
description=pgettext_lazy('timeline', 'Customers can no longer modify their order information'), description=pgettext_lazy('timeline', 'Customers can no longer modify their order information'),
edit_url=ev_edit_url + '#id_settings-last_order_modification_date_0_0' edit_url=ev_edit_url + '#id_settings-last_order_modification_date_0_0',
edit_permission='event.settings.general:write',
)) ))
rd = event.settings.get('payment_term_last', as_type=RelativeDateWrapper) rd = event.settings.get('payment_term_last', as_type=RelativeDateWrapper)
@@ -122,7 +134,8 @@ def timeline_for_event(event, subevent=None):
edit_url=reverse('control:event.settings.payment', kwargs={ edit_url=reverse('control:event.settings.payment', kwargs={
'event': event.slug, 'event': event.slug,
'organizer': event.organizer.slug 'organizer': event.organizer.slug
}) }),
edit_permission='event.settings.payment:write',
)) ))
rd = event.settings.get('ticket_download_date', as_type=RelativeDateWrapper) rd = event.settings.get('ticket_download_date', as_type=RelativeDateWrapper)
@@ -134,7 +147,8 @@ def timeline_for_event(event, subevent=None):
edit_url=reverse('control:event.settings.tickets', kwargs={ edit_url=reverse('control:event.settings.tickets', kwargs={
'event': event.slug, 'event': event.slug,
'organizer': event.organizer.slug 'organizer': event.organizer.slug
}) }),
edit_permission='event.settings.general:write',
)) ))
rd = event.settings.get('cancel_allow_user_until', as_type=RelativeDateWrapper) rd = event.settings.get('cancel_allow_user_until', as_type=RelativeDateWrapper)
@@ -146,7 +160,8 @@ def timeline_for_event(event, subevent=None):
edit_url=reverse('control:event.settings.cancel', kwargs={ edit_url=reverse('control:event.settings.cancel', kwargs={
'event': event.slug, 'event': event.slug,
'organizer': event.organizer.slug 'organizer': event.organizer.slug
}) }),
edit_permission='event.settings.general:write',
)) ))
rd = event.settings.get('cancel_allow_user_paid_until', as_type=RelativeDateWrapper) rd = event.settings.get('cancel_allow_user_paid_until', as_type=RelativeDateWrapper)
@@ -158,7 +173,8 @@ def timeline_for_event(event, subevent=None):
edit_url=reverse('control:event.settings.cancel', kwargs={ edit_url=reverse('control:event.settings.cancel', kwargs={
'event': event.slug, 'event': event.slug,
'organizer': event.organizer.slug 'organizer': event.organizer.slug
}) }),
edit_permission='event.settings.general:write',
)) ))
rd = event.settings.get('change_allow_user_until', as_type=RelativeDateWrapper) rd = event.settings.get('change_allow_user_until', as_type=RelativeDateWrapper)
@@ -170,7 +186,8 @@ def timeline_for_event(event, subevent=None):
edit_url=reverse('control:event.settings.cancel', kwargs={ edit_url=reverse('control:event.settings.cancel', kwargs={
'event': event.slug, 'event': event.slug,
'organizer': event.organizer.slug 'organizer': event.organizer.slug
}) }),
edit_permission='event.settings.general:write',
)) ))
rd = event.settings.get('waiting_list_auto_disable', as_type=RelativeDateWrapper) rd = event.settings.get('waiting_list_auto_disable', as_type=RelativeDateWrapper)
@@ -182,7 +199,8 @@ def timeline_for_event(event, subevent=None):
edit_url=reverse('control:event.settings', kwargs={ edit_url=reverse('control:event.settings', kwargs={
'event': event.slug, 'event': event.slug,
'organizer': event.organizer.slug 'organizer': event.organizer.slug
}) + '#waiting-list-open' }) + '#waiting-list-open',
edit_permission='event.settings.general:write',
)) ))
if not event.has_subevents: if not event.has_subevents:
@@ -196,7 +214,8 @@ def timeline_for_event(event, subevent=None):
edit_url=reverse('control:event.settings.mail', kwargs={ edit_url=reverse('control:event.settings.mail', kwargs={
'event': event.slug, 'event': event.slug,
'organizer': event.organizer.slug 'organizer': event.organizer.slug
}) }),
edit_permission='event.settings.general:write',
)) ))
if subevent: if subevent:
@@ -210,7 +229,8 @@ def timeline_for_event(event, subevent=None):
'event': event.slug, 'event': event.slug,
'organizer': event.organizer.slug, 'organizer': event.organizer.slug,
'subevent': subevent.pk, 'subevent': subevent.pk,
}) }),
edit_permission='event.subevents:write',
)) ))
if sei.available_until: if sei.available_until:
tl.append(TimelineEvent( tl.append(TimelineEvent(
@@ -221,7 +241,8 @@ def timeline_for_event(event, subevent=None):
'event': event.slug, 'event': event.slug,
'organizer': event.organizer.slug, 'organizer': event.organizer.slug,
'subevent': subevent.pk, 'subevent': subevent.pk,
}) }),
edit_permission='event.subevents:write',
)) ))
for sei in subevent.var_overrides.values(): for sei in subevent.var_overrides.values():
if sei.available_from: if sei.available_from:
@@ -234,7 +255,8 @@ def timeline_for_event(event, subevent=None):
'event': event.slug, 'event': event.slug,
'organizer': event.organizer.slug, 'organizer': event.organizer.slug,
'subevent': subevent.pk, 'subevent': subevent.pk,
}) }),
edit_permission='event.subevents:write',
)) ))
if sei.available_until: if sei.available_until:
tl.append(TimelineEvent( tl.append(TimelineEvent(
@@ -246,7 +268,8 @@ def timeline_for_event(event, subevent=None):
'event': event.slug, 'event': event.slug,
'organizer': event.organizer.slug, 'organizer': event.organizer.slug,
'subevent': subevent.pk, 'subevent': subevent.pk,
}) }),
edit_permission='event.subevents:write',
)) ))
for d in event.discounts.filter(Q(available_from__isnull=False) | Q(available_until__isnull=False)): for d in event.discounts.filter(Q(available_from__isnull=False) | Q(available_until__isnull=False)):
@@ -259,7 +282,8 @@ def timeline_for_event(event, subevent=None):
'event': event.slug, 'event': event.slug,
'organizer': event.organizer.slug, 'organizer': event.organizer.slug,
'discount': d.pk, 'discount': d.pk,
}) }),
edit_permission='event.items:write',
)) ))
if d.available_until: if d.available_until:
tl.append(TimelineEvent( tl.append(TimelineEvent(
@@ -270,7 +294,8 @@ def timeline_for_event(event, subevent=None):
'event': event.slug, 'event': event.slug,
'organizer': event.organizer.slug, 'organizer': event.organizer.slug,
'discount': d.pk, 'discount': d.pk,
}) }),
edit_permission='event.items:write',
)) ))
for p in event.items.filter(Q(available_from__isnull=False) | Q(available_until__isnull=False)): for p in event.items.filter(Q(available_from__isnull=False) | Q(available_until__isnull=False)):
@@ -283,7 +308,8 @@ def timeline_for_event(event, subevent=None):
'event': event.slug, 'event': event.slug,
'organizer': event.organizer.slug, 'organizer': event.organizer.slug,
'item': p.pk, 'item': p.pk,
}) + '#id_available_from_0' }) + '#id_available_from_0',
edit_permission='event.items:write',
)) ))
if p.available_until: if p.available_until:
tl.append(TimelineEvent( tl.append(TimelineEvent(
@@ -294,7 +320,8 @@ def timeline_for_event(event, subevent=None):
'event': event.slug, 'event': event.slug,
'organizer': event.organizer.slug, 'organizer': event.organizer.slug,
'item': p.pk, 'item': p.pk,
}) + '#id_available_until_0' }) + '#id_available_until_0',
edit_permission='event.items:write',
)) ))
for v in ItemVariation.objects.filter( for v in ItemVariation.objects.filter(
@@ -313,7 +340,8 @@ def timeline_for_event(event, subevent=None):
'event': event.slug, 'event': event.slug,
'organizer': event.organizer.slug, 'organizer': event.organizer.slug,
'item': v.item.pk, 'item': v.item.pk,
}) + '#tab-0-3-open' }) + '#tab-0-3-open',
edit_permission='event.items:write',
)) ))
if v.available_until: if v.available_until:
tl.append(TimelineEvent( tl.append(TimelineEvent(
@@ -327,7 +355,8 @@ def timeline_for_event(event, subevent=None):
'event': event.slug, 'event': event.slug,
'organizer': event.organizer.slug, 'organizer': event.organizer.slug,
'item': v.item.pk, 'item': v.item.pk,
}) + '#tab-0-3-open' }) + '#tab-0-3-open',
edit_permission='event.items:write',
)) ))
pprovs = event.get_payment_providers() pprovs = event.get_payment_providers()
@@ -357,7 +386,8 @@ def timeline_for_event(event, subevent=None):
'event': event.slug, 'event': event.slug,
'organizer': event.organizer.slug, 'organizer': event.organizer.slug,
'provider': pprov.identifier, 'provider': pprov.identifier,
}) }),
edit_permission='event.settings.payment:write',
)) ))
availability_date = pprov.settings.get('_availability_date', as_type=RelativeDateWrapper) availability_date = pprov.settings.get('_availability_date', as_type=RelativeDateWrapper)
if availability_date: if availability_date:
@@ -375,7 +405,8 @@ def timeline_for_event(event, subevent=None):
'event': event.slug, 'event': event.slug,
'organizer': event.organizer.slug, 'organizer': event.organizer.slug,
'provider': pprov.identifier, 'provider': pprov.identifier,
}) }),
edit_permission='event.settings.payment:write',
)) ))
for recv, resp in timeline_events.send(sender=event, subevent=subevent): for recv, resp in timeline_events.send(sender=event, subevent=subevent):

View File

@@ -43,24 +43,29 @@ def get_event_navigation(request: HttpRequest):
'icon': 'dashboard', 'icon': 'dashboard',
} }
] ]
if 'event.settings.general:write' in request.eventpermset: event_settings = []
event_settings = [ if "event.settings.general:write" in request.eventpermset:
{ event_settings.append({
'label': _('General'), 'label': _('General'),
'url': reverse('control:event.settings', kwargs={ 'url': reverse('control:event.settings', kwargs={
'event': request.event.slug, 'event': request.event.slug,
'organizer': request.event.organizer.slug, 'organizer': request.event.organizer.slug,
}), }),
'active': url.url_name == 'event.settings', 'active': url.url_name == 'event.settings',
}, })
{
'label': _('Payment'), if "event.settings.payment:write" in request.eventpermset or "event.settings.general:write" in request.eventpermset:
'url': reverse('control:event.settings.payment', kwargs={ event_settings.append({
'event': request.event.slug, 'label': _('Payment'),
'organizer': request.event.organizer.slug, 'url': reverse('control:event.settings.payment', kwargs={
}), 'event': request.event.slug,
'active': url.url_name in ('event.settings.payment', 'event.settings.payment.provider'), 'organizer': request.event.organizer.slug,
}, }),
'active': url.url_name in ('event.settings.payment', 'event.settings.payment.provider'),
})
if "event.settings.general:write" in request.eventpermset:
event_settings += [
{ {
'label': _('Plugins'), 'label': _('Plugins'),
'url': reverse('control:event.settings.plugins', kwargs={ 'url': reverse('control:event.settings.plugins', kwargs={
@@ -84,23 +89,31 @@ def get_event_navigation(request: HttpRequest):
'organizer': request.event.organizer.slug, 'organizer': request.event.organizer.slug,
}), }),
'active': url.url_name == 'event.settings.mail', 'active': url.url_name == 'event.settings.mail',
}, }
{ ]
'label': _('Taxes'),
'url': reverse('control:event.settings.tax', kwargs={ if "event.settings.tax:write" in request.eventpermset or "event.settings.general:write" in request.eventpermset:
'event': request.event.slug, event_settings.append({
'organizer': request.event.organizer.slug, 'label': _('Taxes'),
}), 'url': reverse('control:event.settings.tax', kwargs={
'active': url.url_name.startswith('event.settings.tax'), 'event': request.event.slug,
}, 'organizer': request.event.organizer.slug,
{ }),
'label': _('Invoicing'), 'active': url.url_name.startswith('event.settings.tax'),
'url': reverse('control:event.settings.invoice', kwargs={ })
'event': request.event.slug,
'organizer': request.event.organizer.slug, if "event.settings.invoicing:write" in request.eventpermset or "event.settings.general:write" in request.eventpermset:
}), event_settings.append({
'active': url.url_name == 'event.settings.invoice', 'label': _('Invoicing'),
}, 'url': reverse('control:event.settings.invoice', kwargs={
'event': request.event.slug,
'organizer': request.event.organizer.slug,
}),
'active': url.url_name == 'event.settings.invoice',
})
if "event.settings.general:write" in request.eventpermset:
event_settings += [
{ {
'label': pgettext_lazy('action', 'Cancellation'), 'label': pgettext_lazy('action', 'Cancellation'),
'url': reverse('control:event.settings.cancel', kwargs={ 'url': reverse('control:event.settings.cancel', kwargs={
@@ -118,86 +131,85 @@ def get_event_navigation(request: HttpRequest):
'active': url.url_name == 'event.settings.widget', 'active': url.url_name == 'event.settings.widget',
}, },
] ]
# It would be better to allow plugins to handle the permission themselves, but for backwards compatibility
# we need to have it in the "if" statement
event_settings += sorted( event_settings += sorted(
sum((list(a[1]) for a in nav_event_settings.send(request.event, request=request)), []), sum((list(a[1]) for a in nav_event_settings.send(request.event, request=request)), []),
key=lambda r: r['label'] key=lambda r: r['label']
) )
if event_settings:
nav.append({ nav.append({
'label': _('Settings'), 'label': _('Settings'),
'url': reverse('control:event.settings', kwargs={ 'url': event_settings[0]["url"],
'event': request.event.slug,
'organizer': request.event.organizer.slug,
}),
'active': False, 'active': False,
'icon': 'wrench', 'icon': 'wrench',
'children': event_settings 'children': event_settings
}) })
if 'event.items:write' in request.eventpermset: nav.append({
nav.append({ 'label': _('Products'),
'label': _('Products'), 'url': reverse('control:event.items', kwargs={
'url': reverse('control:event.items', kwargs={ 'event': request.event.slug,
'event': request.event.slug, 'organizer': request.event.organizer.slug,
'organizer': request.event.organizer.slug, }),
}), 'active': False,
'active': False, 'icon': 'ticket',
'icon': 'ticket', 'children': [
'children': [ {
{ 'label': _('Products'),
'label': _('Products'), 'url': reverse('control:event.items', kwargs={
'url': reverse('control:event.items', kwargs={
'event': request.event.slug,
'organizer': request.event.organizer.slug,
}),
'active': url.url_name in (
'event.item', 'event.items.add', 'event.items') or "event.item." in url.url_name,
},
{
'label': _('Quotas'),
'url': reverse('control:event.items.quotas', kwargs={
'event': request.event.slug,
'organizer': request.event.organizer.slug,
}),
'active': 'event.items.quota' in url.url_name,
},
{
'label': _('Categories'),
'url': reverse('control:event.items.categories', kwargs={
'event': request.event.slug,
'organizer': request.event.organizer.slug,
}),
'active': 'event.items.categories' in url.url_name,
},
{
'label': _('Questions'),
'url': reverse('control:event.items.questions', kwargs={
'event': request.event.slug,
'organizer': request.event.organizer.slug,
}),
'active': 'event.items.questions' in url.url_name,
},
{
'label': _('Discounts'),
'url': reverse('control:event.items.discounts', kwargs={
'event': request.event.slug,
'organizer': request.event.organizer.slug,
}),
'active': 'event.items.discounts' in url.url_name,
},
]
})
if 'event.settings.general:write' in request.eventpermset:
if request.event.has_subevents:
nav.append({
'label': pgettext_lazy('subevent', 'Dates'),
'url': reverse('control:event.subevents', kwargs={
'event': request.event.slug, 'event': request.event.slug,
'organizer': request.event.organizer.slug, 'organizer': request.event.organizer.slug,
}), }),
'active': ('event.subevent' in url.url_name), 'active': url.url_name in (
'icon': 'calendar', 'event.item', 'event.items.add', 'event.items') or "event.item." in url.url_name,
}) },
{
'label': _('Quotas'),
'url': reverse('control:event.items.quotas', kwargs={
'event': request.event.slug,
'organizer': request.event.organizer.slug,
}),
'active': 'event.items.quota' in url.url_name,
},
{
'label': _('Categories'),
'url': reverse('control:event.items.categories', kwargs={
'event': request.event.slug,
'organizer': request.event.organizer.slug,
}),
'active': 'event.items.categories' in url.url_name,
},
{
'label': _('Questions'),
'url': reverse('control:event.items.questions', kwargs={
'event': request.event.slug,
'organizer': request.event.organizer.slug,
}),
'active': 'event.items.questions' in url.url_name,
},
{
'label': _('Discounts'),
'url': reverse('control:event.items.discounts', kwargs={
'event': request.event.slug,
'organizer': request.event.organizer.slug,
}),
'active': 'event.items.discounts' in url.url_name,
},
]
})
if request.event.has_subevents:
nav.append({
'label': pgettext_lazy('subevent', 'Dates'),
'url': reverse('control:event.subevents', kwargs={
'event': request.event.slug,
'organizer': request.event.organizer.slug,
}),
'active': ('event.subevent' in url.url_name),
'icon': 'calendar',
})
if 'event.orders:read' in request.eventpermset: if 'event.orders:read' in request.eventpermset:
children = [ children = [
@@ -291,7 +303,7 @@ def get_event_navigation(request: HttpRequest):
] ]
}) })
if 'event.orders:read' in request.eventpermset: if 'event.orders:read' in request.eventpermset or 'event.settings.general:write' in request.eventpermset:
nav.append({ nav.append({
'label': pgettext_lazy('navigation', 'Check-in'), 'label': pgettext_lazy('navigation', 'Check-in'),
'url': reverse('control:event.orders.checkinlists', kwargs={ 'url': reverse('control:event.orders.checkinlists', kwargs={
@@ -544,7 +556,7 @@ def get_organizer_navigation(request):
'icon': 'group', 'icon': 'group',
}) })
if 'organizer.giftcards:write' in request.orgapermset: if 'organizer.giftcards:read' in request.orgapermset or 'organizer.giftcards:write' in request.orgapermset:
children = [] children = []
children.append({ children.append({
'label': _('Gift cards'), 'label': _('Gift cards'),
@@ -575,7 +587,7 @@ def get_organizer_navigation(request):
if request.organizer.settings.customer_accounts: if request.organizer.settings.customer_accounts:
children = [] children = []
if 'organizer.customers:write' in request.orgapermset: if 'organizer.customers:read' in request.orgapermset or 'organizer.customers:write' in request.orgapermset:
children.append( children.append(
{ {
'label': _('Customers'), 'label': _('Customers'),
@@ -624,16 +636,17 @@ def get_organizer_navigation(request):
}) })
if request.organizer.settings.reusable_media_active: if request.organizer.settings.reusable_media_active:
nav.append({ if 'organizer.reusablemedia:read' in request.orgapermset or 'organizer.reusablemedia:write' in request.orgapermset:
'label': _('Reusable media'), nav.append({
'url': reverse('control:organizer.reusable_media', kwargs={ 'label': _('Reusable media'),
'organizer': request.organizer.slug 'url': reverse('control:organizer.reusable_media', kwargs={
}), 'organizer': request.organizer.slug
'icon': 'key', }),
'active': 'organizer.reusable_medi' in url.url_name, 'icon': 'key',
}) 'active': 'organizer.reusable_medi' in url.url_name,
})
if 'organizer.settings.general:write' in request.orgapermset: if 'organizer.devices:read' in request.orgapermset or 'organizer.devices:write' in request.orgapermset:
nav.append({ nav.append({
'label': _('Devices'), 'label': _('Devices'),
'url': reverse('control:organizer.devices', kwargs={ 'url': reverse('control:organizer.devices', kwargs={

View File

@@ -53,7 +53,7 @@ def event_permission_required(permission):
This view decorator rejects all requests with a 403 response which are not from This view decorator rejects all requests with a 403 response which are not from
users having the given permission for the event the request is associated with. users having the given permission for the event the request is associated with.
""" """
if permission == 'event.settings.general:write': if permission == 'can_change_settings':
# Legacy support # Legacy support
permission = 'event.settings.general:write' permission = 'event.settings.general:write'

View File

@@ -68,7 +68,7 @@
class="btn btn-primary btn-lg"><i class="fa fa-plus"></i> {% trans "Create a new check-in list" %} class="btn btn-primary btn-lg"><i class="fa fa-plus"></i> {% trans "Create a new check-in list" %}
</a> </a>
{% endif %} {% endif %}
{% if can_change_organizer_settings %} {% if link_device_settings %}
<a href="{% url "control:organizer.devices" organizer=request.organizer.slug %}" <a href="{% url "control:organizer.devices" organizer=request.organizer.slug %}"
class="btn btn-default btn-lg"><i class="fa fa-tablet"></i> {% trans "Connected devices" %}</a> class="btn btn-default btn-lg"><i class="fa fa-tablet"></i> {% trans "Connected devices" %}</a>
{% endif %} {% endif %}
@@ -79,11 +79,11 @@
<a href="{% url "control:event.orders.checkinlists.add" organizer=request.event.organizer.slug event=request.event.slug %}" <a href="{% url "control:event.orders.checkinlists.add" organizer=request.event.organizer.slug event=request.event.slug %}"
class="btn btn-default"><i class="fa fa-plus"></i> {% trans "Create a new check-in list" %}</a> class="btn btn-default"><i class="fa fa-plus"></i> {% trans "Create a new check-in list" %}</a>
{% endif %} {% endif %}
{% if can_change_organizer_settings %} {% if link_device_settings %}
<a href="{% url "control:organizer.devices" organizer=request.organizer.slug %}" <a href="{% url "control:organizer.devices" organizer=request.organizer.slug %}"
class="btn btn-default"><i class="fa fa-tablet"></i> {% trans "Connected devices" %}</a> class="btn btn-default"><i class="fa fa-tablet"></i> {% trans "Connected devices" %}</a>
{% endif %} {% endif %}
{% if "event.settings.general:write" in request.eventpermset %} {% if "event.settings.general:write" in request.eventpermset and "event.orders:write" in request.eventpermset %}
<a href="{% url "control:event.orders.checkinlists.reset" organizer=request.event.organizer.slug event=request.event.slug %}" <a href="{% url "control:event.orders.checkinlists.reset" organizer=request.event.organizer.slug event=request.event.slug %}"
class="btn btn-default"> class="btn btn-default">
<span class="fa fa-repeat"></span> <span class="fa fa-repeat"></span>
@@ -100,7 +100,9 @@
<a href="?{% url_replace request 'ordering' '-name' %}"><i class="fa fa-caret-down"></i></a> <a href="?{% url_replace request 'ordering' '-name' %}"><i class="fa fa-caret-down"></i></a>
<a href="?{% url_replace request 'ordering' 'name' %}"><i class="fa fa-caret-up"></i></a> <a href="?{% url_replace request 'ordering' 'name' %}"><i class="fa fa-caret-up"></i></a>
</th> </th>
<th>{% trans "Checked in" %}</th> {% if "event.orders:read" in request.eventpermset %}
<th>{% trans "Checked in" %}</th>
{% endif %}
{% if request.event.has_subevents %} {% if request.event.has_subevents %}
<th> <th>
{% trans "Date" context "subevent" %} {% trans "Date" context "subevent" %}
@@ -119,18 +121,20 @@
<strong><a <strong><a
href="{% url "control:event.orders.checkinlists.show" organizer=request.event.organizer.slug event=request.event.slug list=cl.id %}">{{ cl.name }}</a></strong> href="{% url "control:event.orders.checkinlists.show" organizer=request.event.organizer.slug event=request.event.slug list=cl.id %}">{{ cl.name }}</a></strong>
</td> </td>
<td> {% if "event.orders:read" in request.eventpermset %}
<div class="quotabox availability"> <td>
<div class="progress"> <div class="quotabox availability">
<div class="progress-bar progress-bar-success progress-bar-{{ cl.percent }}"> <div class="progress">
<div class="progress-bar progress-bar-success progress-bar-{{ cl.percent }}">
</div>
</div>
<div class="numbers">
{{ cl.checkin_count|default_if_none:"0" }} /
{{ cl.position_count|default_if_none:"0" }}
</div> </div>
</div> </div>
<div class="numbers"> </td>
{{ cl.checkin_count|default_if_none:"0" }} / {% endif %}
{{ cl.position_count|default_if_none:"0" }}
</div>
</div>
</td>
{% if request.event.has_subevents %} {% if request.event.has_subevents %}
{% if cl.subevent %} {% if cl.subevent %}
<td> <td>
@@ -156,16 +160,18 @@
{% endif %} {% endif %}
</td> </td>
<td class="text-right flip"> <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 %}" {% if "event.orders:read" in request.eventpermset %}
class="btn btn-default btn-sm"><i class="fa fa-eye"></i></a> <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>
<a href="{% url "control:event.orders.checkinlists.simulator" organizer=request.event.organizer.slug event=request.event.slug list=cl.id %}"
title="{% trans "Check-in simulator" %}" data-toggle="tooltip"
class="btn btn-default btn-sm"><i class="fa fa-flask"></i></a>
{% endif %}
{% if "event.settings.general:write" in request.eventpermset %} {% if "event.settings.general:write" in request.eventpermset %}
<a href="{% url "control:event.orders.checkinlists.add" organizer=request.event.organizer.slug event=request.event.slug %}?copy_from={{ cl.id }}" <a href="{% url "control:event.orders.checkinlists.add" organizer=request.event.organizer.slug event=request.event.slug %}?copy_from={{ cl.id }}"
class="btn btn-sm btn-default" title="{% trans "Clone" %}" data-toggle="tooltip"> class="btn btn-sm btn-default" title="{% trans "Clone" %}" data-toggle="tooltip">
<span class="fa fa-copy"></span> <span class="fa fa-copy"></span>
</a> </a>
<a href="{% url "control:event.orders.checkinlists.simulator" organizer=request.event.organizer.slug event=request.event.slug list=cl.id %}"
title="{% trans "Check-in simulator" %}" data-toggle="tooltip"
class="btn btn-default btn-sm"><i class="fa fa-flask"></i></a>
<a href="{% url "control:event.orders.checkinlists.edit" organizer=request.event.organizer.slug event=request.event.slug list=cl.id %}" <a href="{% url "control:event.orders.checkinlists.edit" organizer=request.event.organizer.slug event=request.event.slug list=cl.id %}"
class="btn btn-default btn-sm"><i class="fa fa-wrench"></i></a> class="btn btn-default btn-sm"><i class="fa fa-wrench"></i></a>
<a href="{% url "control:event.orders.checkinlists.delete" organizer=request.event.organizer.slug event=request.event.slug list=cl.id %}" <a href="{% url "control:event.orders.checkinlists.delete" organizer=request.event.organizer.slug event=request.event.slug list=cl.id %}"

View File

@@ -11,18 +11,20 @@
<ul class="list-group"> <ul class="list-group">
{% for identifier, display_name, pending, objects in providers %} {% for identifier, display_name, pending, objects in providers %}
<li class="list-group-item"> <li class="list-group-item">
<form action="{% url "control:event.order.sync_job" organizer=event.organizer.slug event=event.slug code=order.code provider=identifier %}" method="post" class="form-inline pull-right"> {% if "event.orders:write" in request.eventpermset %}
{% csrf_token %} <form action="{% url "control:event.order.sync_job" organizer=event.organizer.slug event=event.slug code=order.code provider=identifier %}" method="post" class="form-inline pull-right">
{% if pending %} {% csrf_token %}
{% if pending.not_before > now or pending.need_manual_retry %} {% if pending %}
<button type="submit" name="run_job_now" value="{{ pending.pk }}" class="btn btn-default"><i class="fa fa-refresh"></i> {% trans "Retry now" %}</button> {% if pending.not_before > now or pending.need_manual_retry %}
<button type="submit" name="run_job_now" value="{{ pending.pk }}" class="btn btn-default"><i class="fa fa-refresh"></i> {% trans "Retry now" %}</button>
{% endif %}
<button type="submit" name="cancel_job" value="{{ pending.pk }}" class="btn btn-danger"><i class="fa fa-times"></i> {% trans "Cancel" %}</button>
{% else %}
<button type="submit" class="btn btn-default"><i class="fa fa-refresh"></i> {% trans "Sync now" %}</button>
<input type="hidden" name="queue_sync" value="true">
{% endif %} {% endif %}
<button type="submit" name="cancel_job" value="{{ pending.pk }}" class="btn btn-danger"><i class="fa fa-times"></i> {% trans "Cancel" %}</button> </form>
{% else %} {% endif %}
<button type="submit" class="btn btn-default"><i class="fa fa-refresh"></i> {% trans "Sync now" %}</button>
<input type="hidden" name="queue_sync" value="true">
{% endif %}
</form>
<p><b>{{ display_name }}</b></p> <p><b>{{ display_name }}</b></p>
{% if pending %} {% if pending %}
<p> <p>

View File

@@ -40,12 +40,16 @@
this option. this option.
{% endblocktrans %} {% endblocktrans %}
</div> </div>
<div class="col-sm-12 col-md-3"> <div class="col-sm-12 col-md-3 text-center">
<a href="{% url "control:event.cancel" organizer=request.organizer.slug event=request.event.slug %}" {% if "event:cancel" in request.eventpermset %}
class="btn btn-danger btn-block btn-lg"> <a href="{% url "control:event.cancel" organizer=request.organizer.slug event=request.event.slug %}"
<span class="fa fa-ban"></span> class="btn btn-danger btn-block btn-lg">
{% trans "Cancel event" %} <span class="fa fa-ban"></span>
</a> {% trans "Cancel event" %}
</a>
{% else %}
{% trans "No permission" %}
{% endif %}
</div> </div>
</div> </div>
</div> </div>

View File

@@ -19,7 +19,7 @@
<span class="{% if e.time < nearly_now %}text-muted{% endif %}"> <span class="{% if e.time < nearly_now %}text-muted{% endif %}">
{{ e.entry.description }} {{ e.entry.description }}
</span> </span>
{% if e.entry.edit_url %} {% if e.entry.edit_url and e.entry.edit_permission in request.eventpermset %}
&nbsp; &nbsp;
<a href="{{ e.entry.edit_url }}" class="text-muted"> <a href="{{ e.entry.edit_url }}" class="text-muted">
<span class="fa fa-edit"></span> <span class="fa fa-edit"></span>

View File

@@ -155,22 +155,24 @@
</form> </form>
</div> </div>
</div> </div>
<div class="panel panel-default"> {% if "event.orders:read" in request.eventpermset or "event.orders:write" in request.eventpermset or "event.settings.general:write" in request.eventpermset or "event.items:write" in request.eventpermset %}
<div class="panel-heading"> <div class="panel panel-default">
<h3 class="panel-title"> <div class="panel-heading">
{% trans "Event logs" %} <h3 class="panel-title">
</h3> {% trans "Event logs" %}
</div> </h3>
<ul class="list-group" id="logs_target"> </div>
<div class="logs-lazy-loading"> <ul class="list-group" id="logs_target">
<span class="fa fa-cog fa-4x"></span> <div class="logs-lazy-loading">
<span class="fa fa-cog fa-4x"></span>
</div>
</ul>
<div class="panel-footer">
<a href="{% url "control:event.log" event=request.event.slug organizer=request.event.organizer.slug %}">
{% trans "Show more logs" %}
</a>
</div> </div>
</ul>
<div class="panel-footer">
<a href="{% url "control:event.log" event=request.event.slug organizer=request.event.organizer.slug %}">
{% trans "Show more logs" %}
</a>
</div> </div>
</div> {% endif %}
{% endblock %} {% endblock %}

View File

@@ -165,13 +165,15 @@
</p> </p>
</fieldset> </fieldset>
</div> </div>
<div class="form-group submit-group"> {% if "event.settings.invoicing:write" in request.eventpermset %}
<button type="submit" class="btn btn-default btn-lg" name="preview" value="preview" formtarget="_blank"> <div class="form-group submit-group">
{% trans "Save and show preview" %} <button type="submit" class="btn btn-default btn-lg" name="preview" value="preview" formtarget="_blank">
</button> {% trans "Save and show preview" %}
<button type="submit" class="btn btn-primary btn-save"> </button>
{% trans "Save" %} <button type="submit" class="btn btn-primary btn-save">
</button> {% trans "Save" %}
</div> </button>
</div>
{% endif %}
</form> </form>
{% endblock %} {% endblock %}

View File

@@ -41,14 +41,17 @@
{% endfor %} {% endfor %}
</td> </td>
<td class="text-right flip"> <td class="text-right flip">
<a href="{% url 'control:event.settings.payment.provider' event=request.event.slug organizer=request.organizer.slug provider=provider.identifier %}" {% if "event.settings.payment:write" in request.eventpermset %}
class="btn btn-default"> <a href="{% url 'control:event.settings.payment.provider' event=request.event.slug organizer=request.organizer.slug provider=provider.identifier %}"
<span class="fa fa-cog"></span> class="btn btn-default">
{% trans "Settings" %} <span class="fa fa-cog"></span>
</a> {% trans "Settings" %}
</a>
{% endif %}
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}
{% if "event.settings.general:write" in request.eventpermset %}
<tr> <tr>
<td colspan="4"> <td colspan="4">
<br> <br>
@@ -58,6 +61,7 @@
</a> </a>
</td> </td>
</tr> </tr>
{% endif %}
</tbody> </tbody>
</table> </table>
@@ -83,10 +87,12 @@
{% bootstrap_field form.payment_explanation layout="control" %} {% bootstrap_field form.payment_explanation layout="control" %}
</fieldset> </fieldset>
</div> </div>
<div class="form-group submit-group"> {% if "event.settings.payment:write" in request.eventpermset %}
<button type="submit" class="btn btn-primary btn-save"> <div class="form-group submit-group">
{% trans "Save" %} <button type="submit" class="btn btn-primary btn-save">
</button> {% trans "Save" %}
</div> </button>
</div>
{% endif %}
</form> </form>
{% endblock %} {% endblock %}

View File

@@ -23,8 +23,10 @@
{% endblocktrans %} {% endblocktrans %}
</p> </p>
<a href="{% url "control:event.settings.tax.add" organizer=request.event.organizer.slug event=request.event.slug %}" {% if "event.settings.tax:write" in request.eventpermset %}
class="btn btn-primary btn-lg"><i class="fa fa-plus"></i> {% trans "Create a new tax rule" %}</a> <a href="{% url "control:event.settings.tax.add" organizer=request.event.organizer.slug event=request.event.slug %}"
class="btn btn-primary btn-lg"><i class="fa fa-plus"></i> {% trans "Create a new tax rule" %}</a>
{% endif %}
</div> </div>
{% else %} {% else %}
<div class="table-responsive"> <div class="table-responsive">
@@ -42,10 +44,14 @@
{% for tr in taxrules %} {% for tr in taxrules %}
<tr> <tr>
<td> <td>
<strong><a {% if "event.settings.tax:write" in request.eventpermset %}
href="{% url "control:event.settings.tax.edit" organizer=request.event.organizer.slug event=request.event.slug rule=tr.id %}"> <strong><a
{{ tr.internal_name|default:tr.name }} href="{% url "control:event.settings.tax.edit" organizer=request.event.organizer.slug event=request.event.slug rule=tr.id %}">
</a></strong> {{ tr.internal_name|default:tr.name }}
</a></strong>
{% else %}
<strong>{{ tr.internal_name|default:tr.name }}</strong>
{% endif %}
</td> </td>
<td> <td>
{% if tr.default %} {% if tr.default %}
@@ -53,7 +59,7 @@
<span class="fa fa-check"></span> <span class="fa fa-check"></span>
{% trans "Default" %} {% trans "Default" %}
</span> </span>
{% else %} {% elif "event.settings.tax:write" in request.eventpermset %}
<form class="form-inline" method="post" <form class="form-inline" method="post"
action="{% url "control:event.settings.tax.default" organizer=request.event.organizer.slug event=request.event.slug rule=tr.id %}"> action="{% url "control:event.settings.tax.default" organizer=request.event.organizer.slug event=request.event.slug rule=tr.id %}">
{% csrf_token %} {% csrf_token %}
@@ -83,10 +89,12 @@
{% endif %} {% endif %}
</td> </td>
<td class="text-right flip"> <td class="text-right flip">
<a href="{% url "control:event.settings.tax.edit" organizer=request.event.organizer.slug event=request.event.slug rule=tr.id %}" {% if "event.settings.tax:write" in request.eventpermset %}
class="btn btn-default btn-sm"><i class="fa fa-edit"></i></a> <a href="{% url "control:event.settings.tax.edit" organizer=request.event.organizer.slug event=request.event.slug rule=tr.id %}"
<a href="{% url "control:event.settings.tax.delete" organizer=request.event.organizer.slug event=request.event.slug rule=tr.id %}" class="btn btn-default btn-sm"><i class="fa fa-edit"></i></a>
class="btn btn-danger btn-sm"><i class="fa fa-trash"></i></a> <a href="{% url "control:event.settings.tax.delete" organizer=request.event.organizer.slug event=request.event.slug rule=tr.id %}"
class="btn btn-danger btn-sm"><i class="fa fa-trash"></i></a>
{% endif %}
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}
@@ -94,9 +102,11 @@
<tfoot> <tfoot>
<tr> <tr>
<td colspan="5"> <td colspan="5">
<a href="{% url "control:event.settings.tax.add" organizer=request.event.organizer.slug event=request.event.slug %}" {% if "event.settings.tax:write" in request.eventpermset %}
class="btn btn-default"><i class="fa fa-plus"></i> {% trans "Create a new tax rule" %} <a href="{% url "control:event.settings.tax.add" organizer=request.event.organizer.slug event=request.event.slug %}"
</a> class="btn btn-default"><i class="fa fa-plus"></i> {% trans "Create a new tax rule" %}
</a>
{% endif %}
</td> </td>
</tr> </tr>
</tfoot> </tfoot>
@@ -111,10 +121,12 @@
{% bootstrap_field form.tax_rounding layout="control" %} {% bootstrap_field form.tax_rounding layout="control" %}
{% bootstrap_field form.display_net_prices layout="control" %} {% bootstrap_field form.display_net_prices layout="control" %}
</fieldset> </fieldset>
<div class="form-group submit-group"> {% if "event.settings.tax:write" in request.eventpermset %}
<button type="submit" class="btn btn-primary btn-save"> <div class="form-group submit-group">
{% trans "Save" %} <button type="submit" class="btn btn-primary btn-save">
</button> {% trans "Save" %}
</div> </button>
</div>
{% endif %}
</form> </form>
{% endblock %} {% endblock %}

View File

@@ -16,14 +16,18 @@
{% endblocktrans %} {% endblocktrans %}
</p> </p>
<a href="{% url "control:event.items.categories.add" organizer=request.event.organizer.slug event=request.event.slug %}" {% if 'event.items:write' in request.eventpermset %}
class="btn btn-primary btn-lg"><i class="fa fa-plus"></i> {% trans "Create a new category" %}</a> <a href="{% url "control:event.items.categories.add" organizer=request.event.organizer.slug event=request.event.slug %}"
class="btn btn-primary btn-lg"><i class="fa fa-plus"></i> {% trans "Create a new category" %}</a>
{% endif %}
</div> </div>
{% else %} {% else %}
<p> {% if 'event.items:write' in request.eventpermset %}
<a href="{% url "control:event.items.categories.add" organizer=request.event.organizer.slug event=request.event.slug %}" class="btn btn-default"><i class="fa fa-plus"></i> {% trans "Create a new category" %} <p>
</a> <a href="{% url "control:event.items.categories.add" organizer=request.event.organizer.slug event=request.event.slug %}" class="btn btn-default"><i class="fa fa-plus"></i> {% trans "Create a new category" %}
</p> </a>
</p>
{% endif %}
<form method="post"> <form method="post">
{% csrf_token %} {% csrf_token %}
<div class="table-responsive"> <div class="table-responsive">
@@ -39,7 +43,11 @@
{% for c in categories %} {% for c in categories %}
<tr data-dnd-id="{{ c.id }}"> <tr data-dnd-id="{{ c.id }}">
<td> <td>
<strong><a href="{% url "control:event.items.categories.edit" organizer=request.event.organizer.slug event=request.event.slug category=c.id %}">{{ c.internal_name|default:c.name }}</a></strong> {% if 'event.items:write' in request.eventpermset %}
<strong><a href="{% url "control:event.items.categories.edit" organizer=request.event.organizer.slug event=request.event.slug category=c.id %}">{{ c.internal_name|default:c.name }}</a></strong>
{% else %}
<strong>{{ c.internal_name|default:c.name }}</strong>
{% endif %}
<br> <br>
<small class="text-muted"> <small class="text-muted">
#{{ c.pk }} #{{ c.pk }}
@@ -49,15 +57,17 @@
{{ c.get_category_type_display }} {{ c.get_category_type_display }}
</td> </td>
<td class="text-right flip"> <td class="text-right flip">
<button title="{% trans "Move up" %}" formaction="{% url "control:event.items.categories.up" organizer=request.event.organizer.slug event=request.event.slug category=c.id %}" class="btn btn-default btn-sm sortable-up"{% if forloop.counter0 == 0 and not page_obj.has_previous %} disabled{% endif %}><i class="fa fa-arrow-up"></i></button> {% if 'event.items:write' in request.eventpermset %}
<button title="{% trans "Move down" %}" formaction="{% url "control:event.items.categories.down" organizer=request.event.organizer.slug event=request.event.slug category=c.id %}" class="btn btn-default btn-sm sortable-down"{% if forloop.revcounter0 == 0 and not page_obj.has_next %} disabled{% endif %}><i class="fa fa-arrow-down"></i></button> <button title="{% trans "Move up" %}" formaction="{% url "control:event.items.categories.up" organizer=request.event.organizer.slug event=request.event.slug category=c.id %}" class="btn btn-default btn-sm sortable-up"{% if forloop.counter0 == 0 and not page_obj.has_previous %} disabled{% endif %}><i class="fa fa-arrow-up"></i></button>
<span class="dnd-container" title="{% trans "Click and drag this button to reorder. Double click to show buttons for reordering." %}"></span> <button title="{% trans "Move down" %}" formaction="{% url "control:event.items.categories.down" organizer=request.event.organizer.slug event=request.event.slug category=c.id %}" class="btn btn-default btn-sm sortable-down"{% if forloop.revcounter0 == 0 and not page_obj.has_next %} disabled{% endif %}><i class="fa fa-arrow-down"></i></button>
<a title="{% trans "Edit" %}" href="{% url "control:event.items.categories.edit" organizer=request.event.organizer.slug event=request.event.slug category=c.id %}" class="btn btn-default btn-sm"><i class="fa fa-edit"></i></a> <span class="dnd-container" title="{% trans "Click and drag this button to reorder. Double click to show buttons for reordering." %}"></span>
<a href="{% url "control:event.items.categories.add" organizer=request.event.organizer.slug event=request.event.slug %}?copy_from={{ c.id }}" <a title="{% trans "Edit" %}" href="{% url "control:event.items.categories.edit" organizer=request.event.organizer.slug event=request.event.slug category=c.id %}" class="btn btn-default btn-sm"><i class="fa fa-edit"></i></a>
class="btn btn-sm btn-default" title="{% trans "Clone" %}" data-toggle="tooltip"> <a href="{% url "control:event.items.categories.add" organizer=request.event.organizer.slug event=request.event.slug %}?copy_from={{ c.id }}"
<span class="fa fa-copy"></span> class="btn btn-sm btn-default" title="{% trans "Clone" %}" data-toggle="tooltip">
</a> <span class="fa fa-copy"></span>
<a title="{% trans "Delete" %}" href="{% url "control:event.items.categories.delete" organizer=request.event.organizer.slug event=request.event.slug category=c.id %}" class="btn btn-danger btn-sm"><i class="fa fa-trash"></i></a> </a>
<a title="{% trans "Delete" %}" href="{% url "control:event.items.categories.delete" organizer=request.event.organizer.slug event=request.event.slug category=c.id %}" class="btn btn-danger btn-sm"><i class="fa fa-trash"></i></a>
{% endif %}
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}

View File

@@ -39,15 +39,19 @@
{% endblocktrans %} {% endblocktrans %}
</p> </p>
<a href="{% url "control:event.items.discounts.add" organizer=request.event.organizer.slug event=request.event.slug %}" {% if 'event.items:write' in request.eventpermset %}
class="btn btn-primary btn-lg"><i class="fa fa-plus"></i> {% trans "Create a new discount" %}</a> <a href="{% url "control:event.items.discounts.add" organizer=request.event.organizer.slug event=request.event.slug %}"
class="btn btn-primary btn-lg"><i class="fa fa-plus"></i> {% trans "Create a new discount" %}</a>
{% endif %}
</div> </div>
{% else %} {% else %}
<p> {% if 'event.items:write' in request.eventpermset %}
<a href="{% url "control:event.items.discounts.add" organizer=request.event.organizer.slug event=request.event.slug %}" <p>
class="btn btn-default"><i class="fa fa-plus"></i> {% trans "Create a new discount" %} <a href="{% url "control:event.items.discounts.add" organizer=request.event.organizer.slug event=request.event.slug %}"
</a> class="btn btn-default"><i class="fa fa-plus"></i> {% trans "Create a new discount" %}
</p> </a>
</p>
{% endif %}
<form method="post"> <form method="post">
{% csrf_token %} {% csrf_token %}
<div class="table-responsive"> <div class="table-responsive">
@@ -70,8 +74,12 @@
{% else %} {% else %}
<del> <del>
{% endif %} {% endif %}
<a href="{% url "control:event.items.discounts.edit" organizer=request.event.organizer.slug event=request.event.slug discount=d.id %}"> {% if 'event.items:write' in request.eventpermset %}
<a href="{% url "control:event.items.discounts.edit" organizer=request.event.organizer.slug event=request.event.slug discount=d.id %}">
{{ d.internal_name }}</a> {{ d.internal_name }}</a>
{% else %}
{{ d.internal_name }}
{% endif %}
{% if d.active %} {% if d.active %}
</strong> </strong>
{% else %} {% else %}
@@ -134,23 +142,25 @@
</td> </td>
{% endif %} {% endif %}
<td class="text-right flip"> <td class="text-right flip">
<button formaction="{% url "control:event.items.discounts.up" organizer=request.event.organizer.slug event=request.event.slug discount=d.id %}" {% if 'event.items:write' in request.eventpermset %}
class="btn btn-default btn-sm sortable-up" title="{% trans "Move up" %}" <button formaction="{% url "control:event.items.discounts.up" organizer=request.event.organizer.slug event=request.event.slug discount=d.id %}"
{% if forloop.counter0 == 0 and not page_obj.has_previous %} class="btn btn-default btn-sm sortable-up" title="{% trans "Move up" %}"
disabled{% endif %}><i class="fa fa-arrow-up"></i></button> {% if forloop.counter0 == 0 and not page_obj.has_previous %}
<button formaction="{% url "control:event.items.discounts.down" organizer=request.event.organizer.slug event=request.event.slug discount=d.id %}" disabled{% endif %}><i class="fa fa-arrow-up"></i></button>
class="btn btn-default btn-sm sortable-down" title="{% trans "Move down" %}" <button formaction="{% url "control:event.items.discounts.down" organizer=request.event.organizer.slug event=request.event.slug discount=d.id %}"
{% if forloop.revcounter0 == 0 and not page_obj.has_next %} disabled{% endif %}> class="btn btn-default btn-sm sortable-down" title="{% trans "Move down" %}"
<i class="fa fa-arrow-down"></i></button> {% if forloop.revcounter0 == 0 and not page_obj.has_next %} disabled{% endif %}>
<span class="dnd-container" title="{% trans "Click and drag this button to reorder. Double click to show buttons for reordering." %}"></span> <i class="fa fa-arrow-down"></i></button>
<a href="{% url "control:event.items.discounts.edit" organizer=request.event.organizer.slug event=request.event.slug discount=d.id %}" <span class="dnd-container" title="{% trans "Click and drag this button to reorder. Double click to show buttons for reordering." %}"></span>
class="btn btn-default btn-sm"><i class="fa fa-edit"></i></a> <a href="{% url "control:event.items.discounts.edit" organizer=request.event.organizer.slug event=request.event.slug discount=d.id %}"
<a href="{% url "control:event.items.discounts.add" organizer=request.event.organizer.slug event=request.event.slug %}?copy_from={{ d.id }}" class="btn btn-default btn-sm"><i class="fa fa-edit"></i></a>
class="btn btn-sm btn-default" title="{% trans "Clone" %}" data-toggle="tooltip"> <a href="{% url "control:event.items.discounts.add" organizer=request.event.organizer.slug event=request.event.slug %}?copy_from={{ d.id }}"
<span class="fa fa-copy"></span> class="btn btn-sm btn-default" title="{% trans "Clone" %}" data-toggle="tooltip">
</a> <span class="fa fa-copy"></span>
<a href="{% url "control:event.items.discounts.delete" organizer=request.event.organizer.slug event=request.event.slug discount=d.id %}" </a>
class="btn btn-danger btn-sm"><i class="fa fa-trash"></i></a> <a href="{% url "control:event.items.discounts.delete" organizer=request.event.organizer.slug event=request.event.slug discount=d.id %}"
class="btn btn-danger btn-sm"><i class="fa fa-trash"></i></a>
{% endif %}
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}

View File

@@ -21,14 +21,18 @@
{% endblocktrans %} {% endblocktrans %}
</p> </p>
<a href="{% url "control:event.items.add" organizer=request.event.organizer.slug event=request.event.slug %}" {% if 'event.items:write' in request.eventpermset %}
class="btn btn-primary btn-lg"><i class="fa fa-plus"></i> {% trans "Create a new product" %}</a> <a href="{% url "control:event.items.add" organizer=request.event.organizer.slug event=request.event.slug %}"
class="btn btn-primary btn-lg"><i class="fa fa-plus"></i> {% trans "Create a new product" %}</a>
{% endif %}
</div> </div>
{% else %} {% else %}
<p> {% if 'event.items:write' in request.eventpermset %}
<a href="{% url "control:event.items.add" organizer=request.event.organizer.slug event=request.event.slug %}" <p>
class="btn btn-default"><i class="fa fa-plus"></i> {% trans "Create a new product" %}</a> <a href="{% url "control:event.items.add" organizer=request.event.organizer.slug event=request.event.slug %}"
</p> class="btn btn-default"><i class="fa fa-plus"></i> {% trans "Create a new product" %}</a>
</p>
{% endif %}
<form method="post"> <form method="post">
{% csrf_token %} {% csrf_token %}
<div class="table-responsive"> <div class="table-responsive">
@@ -51,7 +55,9 @@
<tbody> <tbody>
<tr class="sortable-disabled"><th colspan="9" scope="colgroup" class="text-muted"> <tr class="sortable-disabled"><th colspan="9" scope="colgroup" class="text-muted">
{{ c.internal_name|default:c.name }}{% if c.category_type != "normal" %} <span class="font-normal">({{ c.get_category_type_display }})</span>{% endif %} {{ c.internal_name|default:c.name }}{% if c.category_type != "normal" %} <span class="font-normal">({{ c.get_category_type_display }})</span>{% endif %}
<a href="{% url "control:event.items.categories.edit" organizer=request.event.organizer.slug event=request.event.slug category=c.id %}" title="{% trans "Edit" %}"><span class="fa fa-edit fa-fw"></span></a> {% if 'event.items:write' in request.eventpermset %}
<a href="{% url "control:event.items.categories.edit" organizer=request.event.organizer.slug event=request.event.slug category=c.id %}" title="{% trans "Edit" %}"><span class="fa fa-edit fa-fw"></span></a>
{% endif %}
</th></tr> </th></tr>
</tbody> </tbody>
{% endif %} {% endif %}
@@ -62,7 +68,11 @@
<tr data-dnd-id="{{ i.id }}" {% if not i.active %}class="row-muted"{% endif %}> <tr data-dnd-id="{{ i.id }}" {% if not i.active %}class="row-muted"{% endif %}>
<td><strong> <td><strong>
{% if not i.active %}<strike>{% endif %} {% if not i.active %}<strike>{% endif %}
<a href="{% url "control:event.item" organizer=request.event.organizer.slug event=request.event.slug item=i.id %}">{{ i }}</a> {% if 'event.items:write' in request.eventpermset %}
<a href="{% url "control:event.item" organizer=request.event.organizer.slug event=request.event.slug item=i.id %}">{{ i }}</a>
{% else %}
{{ i }}
{% endif %}
{% if not i.active %}</strike>{% endif %} {% if not i.active %}</strike>{% endif %}
</strong> </strong>
<br> <br>
@@ -158,12 +168,14 @@
{% endif %} {% endif %}
</td> </td>
<td class="text-right flip col-actions"> <td class="text-right flip col-actions">
<button title="{% trans "Move up" %}" formaction="{% url "control:event.items.up" organizer=request.event.organizer.slug event=request.event.slug item=i.id %}" class="btn btn-default btn-sm sortable-up"{% if forloop.counter0 == 0 %} disabled{% endif %}><i class="fa fa-arrow-up"></i></button> {% if 'event.items:write' in request.eventpermset %}
<button title="{% trans "Move down" %}" formaction="{% url "control:event.items.down" organizer=request.event.organizer.slug event=request.event.slug item=i.id %}" class="btn btn-default btn-sm sortable-down"{% if forloop.revcounter0 == 0 %} disabled{% endif %}><i class="fa fa-arrow-down"></i></button> <button title="{% trans "Move up" %}" formaction="{% url "control:event.items.up" organizer=request.event.organizer.slug event=request.event.slug item=i.id %}" class="btn btn-default btn-sm sortable-up"{% if forloop.counter0 == 0 %} disabled{% endif %}><i class="fa fa-arrow-up"></i></button>
<span class="dnd-container" title="{% trans "Click and drag this button to reorder. Double click to show buttons for reordering." %}"></span> <button title="{% trans "Move down" %}" formaction="{% url "control:event.items.down" organizer=request.event.organizer.slug event=request.event.slug item=i.id %}" class="btn btn-default btn-sm sortable-down"{% if forloop.revcounter0 == 0 %} disabled{% endif %}><i class="fa fa-arrow-down"></i></button>
<a href="{% url "control:event.item" organizer=request.event.organizer.slug event=request.event.slug item=i.id %}" class="btn btn-default btn-sm" title="{% trans "Edit" %}"><i class="fa fa-edit"></i></a> <span class="dnd-container" title="{% trans "Click and drag this button to reorder. Double click to show buttons for reordering." %}"></span>
<a href="{% url "control:event.items.add" organizer=request.event.organizer.slug event=request.event.slug %}?copy_from={{ i.id }}" class="btn btn-default btn-sm" title="{% trans "Clone" %}" data-toggle="tooltip"><i class="fa fa-copy"></i></a> <a href="{% url "control:event.item" organizer=request.event.organizer.slug event=request.event.slug item=i.id %}" class="btn btn-default btn-sm" title="{% trans "Edit" %}"><i class="fa fa-edit"></i></a>
<a href="{% url "control:event.items.delete" organizer=request.event.organizer.slug event=request.event.slug item=i.id %}" class="btn btn-danger btn-sm" title="{% trans "Delete" %}"><i class="fa fa-trash"></i></a> <a href="{% url "control:event.items.add" organizer=request.event.organizer.slug event=request.event.slug %}?copy_from={{ i.id }}" class="btn btn-default btn-sm" title="{% trans "Clone" %}" data-toggle="tooltip"><i class="fa fa-copy"></i></a>
<a href="{% url "control:event.items.delete" organizer=request.event.organizer.slug event=request.event.slug item=i.id %}" class="btn btn-danger btn-sm" title="{% trans "Delete" %}"><i class="fa fa-trash"></i></a>
{% endif %}
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}

View File

@@ -7,45 +7,57 @@
{% block inside %} {% block inside %}
<h1> <h1>
{% blocktrans with name=question.question %}Question: {{ name }}{% endblocktrans %} {% blocktrans with name=question.question %}Question: {{ name }}{% endblocktrans %}
<a href="{% url "control:event.items.questions.edit" event=request.event.slug organizer=request.event.organizer.slug question=question.pk %}" {% if 'event.items:write' in request.eventpermset %}
class="btn btn-default"> <a href="{% url "control:event.items.questions.edit" event=request.event.slug organizer=request.event.organizer.slug question=question.pk %}"
<span class="fa fa-edit"></span> class="btn btn-default">
{% trans "Edit question" %} <span class="fa fa-edit"></span>
</a> {% trans "Edit question" %}
</a>
{% endif %}
</h1> </h1>
<div class="panel panel-default"> {% if 'event.orders:read' in request.eventpermset %}
<div class="panel-heading"> <div class="panel panel-default">
<h3 class="panel-title">{% trans "Filter" %}</h3> <div class="panel-heading">
<h3 class="panel-title">{% trans "Filter" %}</h3>
</div>
<form class="panel-body filter-form" action="" method="get">
<div class="row">
<div class="col-md-2 col-xs-6">
{% bootstrap_field form.status %}
</div>
<div class="col-md-3 col-xs-6">
{% bootstrap_field form.item %}
</div>
{% if has_subevents %}
<div class="col-md-3 col-xs-6">
{% bootstrap_field form.subevent %}
</div>
<div class="col-md-4 col-xs-6">
{% bootstrap_field form.date_range %}
</div>
{% endif %}
</div>
<div class="text-right">
<button class="btn btn-primary btn-lg" type="submit">
<span class="fa fa-filter"></span>
{% trans "Filter" %}
</button>
</div>
</form>
</div> </div>
<form class="panel-body filter-form" action="" method="get"> {% endif %}
<div class="row">
<div class="col-md-2 col-xs-6">
{% bootstrap_field form.status %}
</div>
<div class="col-md-3 col-xs-6">
{% bootstrap_field form.item %}
</div>
{% if has_subevents %}
<div class="col-md-3 col-xs-6">
{% bootstrap_field form.subevent %}
</div>
<div class="col-md-4 col-xs-6">
{% bootstrap_field form.date_range %}
</div>
{% endif %}
</div>
<div class="text-right">
<button class="btn btn-primary btn-lg" type="submit">
<span class="fa fa-filter"></span>
{% trans "Filter" %}
</button>
</div>
</form>
</div>
<div class="row"> <div class="row">
{% if not stats %} {% if 'event.orders:read' not in request.eventpermset %}
<div class="empty-collection col-md-10 col-xs-12">
<p>
{% blocktrans trimmed %}
No permission to view answers.
{% endblocktrans %}
</p>
</div>
{% elif not stats %}
<div class="empty-collection col-md-10 col-xs-12"> <div class="empty-collection col-md-10 col-xs-12">
<p> <p>
{% blocktrans trimmed %} {% blocktrans trimmed %}

View File

@@ -10,10 +10,12 @@
{% endblocktrans %} {% endblocktrans %}
</p> </p>
{% csrf_token %} {% csrf_token %}
<p> {% if 'event.items:write' in request.eventpermset %}
<a href="{% url "control:event.items.questions.add" organizer=request.event.organizer.slug event=request.event.slug %}" class="btn btn-default"><i class="fa fa-plus"></i> {% trans "Create a new question" %} <p>
</a> <a href="{% url "control:event.items.questions.add" organizer=request.event.organizer.slug event=request.event.slug %}" class="btn btn-default"><i class="fa fa-plus"></i> {% trans "Create a new question" %}
</p> </a>
</p>
{% endif %}
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-hover table-quotas"> <table class="table table-hover table-quotas">
<thead> <thead>
@@ -24,7 +26,9 @@
<th class="iconcol"></th> <th class="iconcol"></th>
<th class="iconcol"></th> <th class="iconcol"></th>
<th>{% trans "Products" %}</th> <th>{% trans "Products" %}</th>
<th class="action-col-2"></th> {% if 'event.items:write' in request.eventpermset %}
<th class="action-col-2"></th>
{% endif %}
<th class="action-col-2"></th> <th class="action-col-2"></th>
</tr> </tr>
</thead> </thead>
@@ -79,16 +83,22 @@
<small>{% trans "All personalized products" %}</small> <small>{% trans "All personalized products" %}</small>
{% endif %} {% endif %}
</td> </td>
<td class="dnd-container"> {% if 'event.items:write' in request.eventpermset %}
</td> <td class="dnd-container">
</td>
{% endif %}
<td class="text-right flip"> <td class="text-right flip">
{% if q.pk %} {% if q.pk %}
<a href="{% url "control:event.items.questions.show" organizer=request.event.organizer.slug event=request.event.slug question=q.id %}" class="btn btn-default btn-sm"><i class="fa fa-bar-chart"></i></a> <a href="{% url "control:event.items.questions.show" organizer=request.event.organizer.slug event=request.event.slug question=q.id %}" class="btn btn-default btn-sm"><i class="fa fa-bar-chart"></i></a>
<a href="{% url "control:event.items.questions.edit" organizer=request.event.organizer.slug event=request.event.slug question=q.id %}" class="btn btn-default btn-sm"><i class="fa fa-edit"></i></a> {% if 'event.items:write' in request.eventpermset %}
<a href="{% url "control:event.items.questions.delete" organizer=request.event.organizer.slug event=request.event.slug question=q.id %}" class="btn btn-danger btn-sm"><i class="fa fa-trash"></i></a> <a href="{% url "control:event.items.questions.edit" organizer=request.event.organizer.slug event=request.event.slug question=q.id %}" class="btn btn-default btn-sm"><i class="fa fa-edit"></i></a>
<a href="{% url "control:event.items.questions.delete" organizer=request.event.organizer.slug event=request.event.slug question=q.id %}" class="btn btn-danger btn-sm"><i class="fa fa-trash"></i></a>
{% endif %}
{% else %} {% else %}
<a href="{% url "control:event.settings" organizer=request.event.organizer.slug event=request.event.slug %}#tab-0-2-open" {% if 'event.settings.general:write' in request.eventpermset %}
class="btn btn-default btn-sm"><i class="fa fa-wrench"></i></a> <a href="{% url "control:event.settings" organizer=request.event.organizer.slug event=request.event.slug %}#tab-0-2-open"
class="btn btn-default btn-sm"><i class="fa fa-wrench"></i></a>
{% endif %}
{% endif %} {% endif %}
</td> </td>
</tr> </tr>

View File

@@ -30,14 +30,18 @@
{% endif %} {% endif %}
</p> </p>
<a href="{% url "control:event.items.quotas.add" organizer=request.event.organizer.slug event=request.event.slug %}" {% if 'event.items:write' in request.eventpermset %}
class="btn btn-primary btn-lg"><i class="fa fa-plus"></i> {% trans "Create a new quota" %}</a> <a href="{% url "control:event.items.quotas.add" organizer=request.event.organizer.slug event=request.event.slug %}"
class="btn btn-primary btn-lg"><i class="fa fa-plus"></i> {% trans "Create a new quota" %}</a>
{% endif %}
</div> </div>
{% else %} {% else %}
<p> {% if 'event.items:write' in request.eventpermset %}
<a href="{% url "control:event.items.quotas.add" organizer=request.event.organizer.slug event=request.event.slug %}" class="btn btn-default"><i class="fa fa-plus"></i> {% trans "Create a new quota" %} <p>
</a> <a href="{% url "control:event.items.quotas.add" organizer=request.event.organizer.slug event=request.event.slug %}" class="btn btn-default"><i class="fa fa-plus"></i> {% trans "Create a new quota" %}
</p> </a>
</p>
{% endif %}
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-hover table-quotas"> <table class="table table-hover table-quotas">
<thead> <thead>
@@ -91,12 +95,14 @@
<td>{% if q.size == None %}Unlimited{% else %}{{ q.size }}{% endif %}</td> <td>{% if q.size == None %}Unlimited{% else %}{{ q.size }}{% endif %}</td>
<td>{% include "pretixcontrol/items/fragment_quota_availability.html" with availability=q.cached_avail closed=q.closed %}</td> <td>{% include "pretixcontrol/items/fragment_quota_availability.html" with availability=q.cached_avail closed=q.closed %}</td>
<td class="text-right flip"> <td class="text-right flip">
<a href="{% url "control:event.items.quotas.edit" organizer=request.event.organizer.slug event=request.event.slug quota=q.id %}" class="btn btn-default btn-sm"><i class="fa fa-edit"></i></a> {% if 'event.items:write' in request.eventpermset %}
<a href="{% url "control:event.items.quotas.add" organizer=request.event.organizer.slug event=request.event.slug %}?copy_from={{ q.id }}" <a href="{% url "control:event.items.quotas.edit" organizer=request.event.organizer.slug event=request.event.slug quota=q.id %}" class="btn btn-default btn-sm"><i class="fa fa-edit"></i></a>
class="btn btn-sm btn-default" title="{% trans "Clone" %}" data-toggle="tooltip"> <a href="{% url "control:event.items.quotas.add" organizer=request.event.organizer.slug event=request.event.slug %}?copy_from={{ q.id }}"
<span class="fa fa-copy"></span> class="btn btn-sm btn-default" title="{% trans "Clone" %}" data-toggle="tooltip">
</a> <span class="fa fa-copy"></span>
<a href="{% url "control:event.items.quotas.delete" organizer=request.event.organizer.slug event=request.event.slug quota=q.id %}" class="btn btn-danger btn-sm"><i class="fa fa-trash"></i></a> </a>
<a href="{% url "control:event.items.quotas.delete" organizer=request.event.organizer.slug event=request.event.slug quota=q.id %}" class="btn btn-danger btn-sm"><i class="fa fa-trash"></i></a>
{% endif %}
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}

View File

@@ -319,7 +319,7 @@
<span class="fa fa-check text-success fa-stack-1x fa-stack-shifted"></span> <span class="fa fa-check text-success fa-stack-1x fa-stack-shifted"></span>
</span> </span>
{% endif %} {% endif %}
{% if i.transmission_status != "inflight" %} {% if i.transmission_status != "inflight" and "event.orders:write" in request.eventpermset %}
<form class="form-inline helper-display-inline" method="post" <form class="form-inline helper-display-inline" method="post"
action="{% url "control:event.order.retransmitinvoice" event=request.event.slug organizer=request.event.organizer.slug code=order.code id=i.pk %}"> action="{% url "control:event.order.retransmitinvoice" event=request.event.slug organizer=request.event.organizer.slug code=order.code id=i.pk %}">
{% csrf_token %} {% csrf_token %}
@@ -334,7 +334,7 @@
</form> </form>
{% endif %} {% endif %}
{% if not i.canceled %} {% if not i.canceled %}
{% if i.regenerate_allowed %} {% if i.regenerate_allowed and "event.orders:write" in request.eventpermset %}
<form class="form-inline helper-display-inline" method="post" <form class="form-inline helper-display-inline" method="post"
action="{% url "control:event.order.regeninvoice" event=request.event.slug organizer=request.event.organizer.slug code=order.code id=i.pk %}"> action="{% url "control:event.order.regeninvoice" event=request.event.slug organizer=request.event.organizer.slug code=order.code id=i.pk %}">
{% csrf_token %} {% csrf_token %}
@@ -344,7 +344,7 @@
</button> </button>
</form> </form>
{% endif %} {% endif %}
{% if not i.is_cancellation %} {% if not i.is_cancellation and "event.orders:write" in request.eventpermset %}
<form class="form-inline helper-display-inline" method="post" <form class="form-inline helper-display-inline" method="post"
action="{% url "control:event.order.reissueinvoice" event=request.event.slug organizer=request.event.organizer.slug code=order.code id=i.pk %}"> action="{% url "control:event.order.reissueinvoice" event=request.event.slug organizer=request.event.organizer.slug code=order.code id=i.pk %}">
{% csrf_token %} {% csrf_token %}

View File

@@ -100,28 +100,30 @@
{{ r.amount|money:request.event.currency }} {{ r.amount|money:request.event.currency }}
</td> </td>
<td class="text-right flip"> <td class="text-right flip">
{% if r.state == "transit" or r.state == "created" %} {% if "event.orders:write" in request.eventpermset %}
<a href="{% url "control:event.order.refunds.cancel" event=request.event.slug organizer=request.event.organizer.slug code=r.order.code refund=r.pk %}?next={{ request.get_full_path|urlencode }}" {% if r.state == "transit" or r.state == "created" %}
class="btn btn-danger btn-xs" data-toggle="tooltip"> <a href="{% url "control:event.order.refunds.cancel" event=request.event.slug organizer=request.event.organizer.slug code=r.order.code refund=r.pk %}?next={{ request.get_full_path|urlencode }}"
<span class="fa fa-times"></span> class="btn btn-danger btn-xs" data-toggle="tooltip">
{% trans "Cancel" %} <span class="fa fa-times"></span>
</a> {% trans "Cancel" %}
<a href="{% url "control:event.order.refunds.done" event=request.event.slug organizer=request.event.organizer.slug code=r.order.code refund=r.pk %}?next={{ request.get_full_path|urlencode }}" </a>
class="btn btn-primary btn-xs" data-toggle="tooltip"> <a href="{% url "control:event.order.refunds.done" event=request.event.slug organizer=request.event.organizer.slug code=r.order.code refund=r.pk %}?next={{ request.get_full_path|urlencode }}"
<span class="fa fa-check"></span> class="btn btn-primary btn-xs" data-toggle="tooltip">
{% trans "Confirm as done" %} <span class="fa fa-check"></span>
</a> {% trans "Confirm as done" %}
</a>
{% elif r.state == "external" %} {% elif r.state == "external" %}
<a href="{% url "control:event.order.refunds.cancel" event=request.event.slug organizer=request.event.organizer.slug code=r.order.code refund=r.pk %}?next={{ request.get_full_path|urlencode }}" <a href="{% url "control:event.order.refunds.cancel" event=request.event.slug organizer=request.event.organizer.slug code=r.order.code refund=r.pk %}?next={{ request.get_full_path|urlencode }}"
class="btn btn-default btn-xs" data-toggle="tooltip"> class="btn btn-default btn-xs" data-toggle="tooltip">
<span class="fa fa-times"></span> <span class="fa fa-times"></span>
{% trans "Ignore" %} {% trans "Ignore" %}
</a> </a>
<a href="{% url "control:event.order.refunds.process" event=request.event.slug organizer=request.event.organizer.slug code=r.order.code refund=r.pk %}?next={{ request.get_full_path|urlencode }}" <a href="{% url "control:event.order.refunds.process" event=request.event.slug organizer=request.event.organizer.slug code=r.order.code refund=r.pk %}?next={{ request.get_full_path|urlencode }}"
class="btn btn-primary btn-xs" data-toggle="tooltip"> class="btn btn-primary btn-xs" data-toggle="tooltip">
<span class="fa fa-check"></span> <span class="fa fa-check"></span>
{% trans "Process refund" %} {% trans "Process refund" %}
</a> </a>
{% endif %}
{% endif %} {% endif %}
</td> </td>
</tr> </tr>

View File

@@ -93,16 +93,18 @@
{% endif %} {% endif %}
</dl> </dl>
</form> </form>
<div class="text-right"> {% if "organizer.customers:write" in request.orgapermset %}
<a href="{% url "control:organizer.customer.edit" organizer=request.organizer.slug customer=customer.identifier %}" <div class="text-right">
class="btn btn-default"> <a href="{% url "control:organizer.customer.edit" organizer=request.organizer.slug customer=customer.identifier %}"
<i class="fa fa-edit"></i> {% trans "Edit" %} class="btn btn-default">
</a> <i class="fa fa-edit"></i> {% trans "Edit" %}
<a href="{% url "control:organizer.customer.anonymize" organizer=request.organizer.slug customer=customer.identifier %}" </a>
class="btn btn-danger"> <a href="{% url "control:organizer.customer.anonymize" organizer=request.organizer.slug customer=customer.identifier %}"
<i class="fa fa-trash"></i> {% trans "Anonymize" %} class="btn btn-danger">
</a> <i class="fa fa-trash"></i> {% trans "Anonymize" %}
</div> </a>
</div>
{% endif %}
</div> </div>
</div> </div>
<div class="panel panel-default items"> <div class="panel panel-default items">
@@ -162,35 +164,39 @@
</div> </div>
</td> </td>
<td class="text-right flip"> <td class="text-right flip">
<a href="{% url "control:organizer.customer.membership.edit" organizer=request.organizer.slug customer=customer.identifier id=m.pk %}" {% if "organizer.customers:write" in request.orgapermset %}
data-toggle="tooltip" <a href="{% url "control:organizer.customer.membership.edit" organizer=request.organizer.slug customer=customer.identifier id=m.pk %}"
title="{% trans "Edit" %}"
class="btn btn-default">
<i class="fa fa-edit"></i>
</a>
{% if m.testmode %}
<a href="{% url "control:organizer.customer.membership.delete" organizer=request.organizer.slug customer=customer.identifier id=m.pk %}"
data-toggle="tooltip" data-toggle="tooltip"
title="{% trans "Delete" %}" title="{% trans "Edit" %}"
class="btn btn-danger"> class="btn btn-default">
<i class="fa fa-trash"></i> <i class="fa fa-edit"></i>
</a> </a>
{% if m.testmode %}
<a href="{% url "control:organizer.customer.membership.delete" organizer=request.organizer.slug customer=customer.identifier id=m.pk %}"
data-toggle="tooltip"
title="{% trans "Delete" %}"
class="btn btn-danger">
<i class="fa fa-trash"></i>
</a>
{% endif %}
{% endif %} {% endif %}
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
<tfoot> {% if "organizer.customers:write" in request.orgapermset %}
<tr> <tfoot>
<td colspan="7"> <tr>
<a href="{% url "control:organizer.customer.membership.add" organizer=request.organizer.slug customer=customer.identifier %}" <td colspan="7">
class="btn btn-default"> <a href="{% url "control:organizer.customer.membership.add" organizer=request.organizer.slug customer=customer.identifier %}"
<i class="fa fa-plus"></i> class="btn btn-default">
{% trans "Add membership" %} <i class="fa fa-plus"></i>
</a> {% trans "Add membership" %}
</td> </a>
</tr> </td>
</tfoot> </tr>
</tfoot>
{% endif %}
</table> </table>
</div> </div>
<div class="panel panel-default items"> <div class="panel panel-default items">
@@ -300,14 +306,18 @@
{% for gc in gift_cards %} {% for gc in gift_cards %}
<tr> <tr>
<td> <td>
<a href="{% url "control:organizer.giftcard" organizer=organizer.slug giftcard=gc.id %}"> {% if "organizer.giftcards:read" in request.orgapermset %}
<strong>{{ gc.secret }}</strong></a> <a href="{% url "control:organizer.giftcard" organizer=organizer.slug giftcard=gc.id %}">
{% if gc.testmode %} <strong>{{ gc.secret }}</strong></a>
<span class="label label-warning">{% trans "TEST MODE" %}</span> {% else %}
{% endif %} <strong>{{ gc.secret|slice:":3" }}…</strong>
{% if gc.expired %} {% endif %}
<span class="label label-danger">{% trans "Expired" %}</span> {% if gc.testmode %}
{% endif %} <span class="label label-warning">{% trans "TEST MODE" %}</span>
{% endif %}
{% if gc.expired %}
<span class="label label-danger">{% trans "Expired" %}</span>
{% endif %}
</td> </td>
<td>{{ gc.issuance|date:"SHORT_DATETIME_FORMAT" }}</td> <td>{{ gc.issuance|date:"SHORT_DATETIME_FORMAT" }}</td>
<td>{% if gc.expires %}{{ gc.expires|date:"SHORT_DATETIME_FORMAT" }}{% endif %}</td> <td>{% if gc.expires %}{{ gc.expires|date:"SHORT_DATETIME_FORMAT" }}{% endif %}</td>
@@ -316,10 +326,12 @@
<p class="text-right">{{ gc.value|money:gc.currency }}</p> <p class="text-right">{{ gc.value|money:gc.currency }}</p>
</td> </td>
<td class="text-right"> <td class="text-right">
<a href="{% url "control:organizer.giftcard" organizer=organizer.slug giftcard=gc.id %}" {% if "organizer.giftcards:read" in request.orgapermset %}
class="btn btn-default btn-sm" data-toggle="tooltip" title="{% trans "Details" %}"> <a href="{% url "control:organizer.giftcard" organizer=organizer.slug giftcard=gc.id %}"
<i class="fa fa-eye"></i> class="btn btn-default btn-sm" data-toggle="tooltip" title="{% trans "Details" %}">
</a> <i class="fa fa-eye"></i>
</a>
{% endif %}
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}

View File

@@ -15,8 +15,10 @@
No customer accounts have been created yet. No customer accounts have been created yet.
{% endblocktrans %} {% endblocktrans %}
</p> </p>
<a href="{% url "control:organizer.customer.create" organizer=request.organizer.slug %}" {% if "organizer.customers:write" in request.orgapermset %}
class="btn btn-primary btn-lg"><i class="fa fa-plus"></i> {% trans "Create a new customer" %}</a> <a href="{% url "control:organizer.customer.create" organizer=request.organizer.slug %}"
class="btn btn-primary btn-lg"><i class="fa fa-plus"></i> {% trans "Create a new customer" %}</a>
{% endif %}
</div> </div>
{% else %} {% else %}
<div class="panel panel-default"> <div class="panel panel-default">
@@ -43,10 +45,12 @@
</div> </div>
</form> </form>
</div> </div>
<p> {% if "organizer.customers:write" in request.orgapermset %}
<a href="{% url "control:organizer.customer.create" organizer=request.organizer.slug %}" <p>
class="btn btn-default"><i class="fa fa-plus"></i> {% trans "Create a new customer" %}</a> <a href="{% url "control:organizer.customer.create" organizer=request.organizer.slug %}"
</p> class="btn btn-default"><i class="fa fa-plus"></i> {% trans "Create a new customer" %}</a>
</p>
{% endif %}
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-condensed table-hover"> <table class="table table-condensed table-hover">
<thead> <thead>

View File

@@ -51,10 +51,12 @@
</div> </div>
</form> </form>
</div> </div>
<p> {% if "organizer.devices:write" in request.orgapermset %}
<a href="{% url "control:organizer.device.add" organizer=request.organizer.slug %}" <p>
class="btn btn-default"><i class="fa fa-plus"></i> {% trans "Connect a device" %}</a> <a href="{% url "control:organizer.device.add" organizer=request.organizer.slug %}"
</p> class="btn btn-default"><i class="fa fa-plus"></i> {% trans "Connect a device" %}</a>
</p>
{% endif %}
<form action="{% url "control:organizer.device.bulk_edit" organizer=request.organizer.slug %}" method="post"> <form action="{% url "control:organizer.device.bulk_edit" organizer=request.organizer.slug %}" method="post">
{% csrf_token %} {% csrf_token %}
{% for field in filter_form %} {% for field in filter_form %}
@@ -64,10 +66,12 @@
<table class="table table-condensed table-hover table-quotas"> <table class="table table-condensed table-hover table-quotas">
<thead> <thead>
<tr> <tr>
<th> {% if "organizer.devices:write" in request.orgapermset %}
<label aria-label="{% trans "select all rows for batch-operation" %}" <th>
class="batch-select-label"><input type="checkbox" data-toggle-table/></label> <label aria-label="{% trans "select all rows for batch-operation" %}"
</th> class="batch-select-label"><input type="checkbox" data-toggle-table/></label>
</th>
{% endif %}
<th>{% trans "Device ID" %} <th>{% trans "Device ID" %}
<a href="?{% url_replace request 'ordering' '-device_id' %}"><i <a href="?{% url_replace request 'ordering' '-device_id' %}"><i
class="fa fa-caret-down"></i></a> class="fa fa-caret-down"></i></a>
@@ -105,12 +109,14 @@
<tbody> <tbody>
{% for d in devices %} {% for d in devices %}
<tr {% if d.revoked %}class="text-muted"{% endif %}> <tr {% if d.revoked %}class="text-muted"{% endif %}>
<td> {% if "organizer.devices:write" in request.orgapermset %}
<label aria-label="{% trans "select row for batch-operation" %}" <td>
class="batch-select-label"><input type="checkbox" name="device" <label aria-label="{% trans "select row for batch-operation" %}"
class="batch-select-checkbox" class="batch-select-label"><input type="checkbox" name="device"
value="{{ d.pk }}"/></label> class="batch-select-checkbox"
</td> value="{{ d.pk }}"/></label>
</td>
{% endif %}
<td> <td>
{{ d.device_id }} {{ d.device_id }}
</td> </td>
@@ -158,15 +164,17 @@
{% endif %} {% endif %}
</td> </td>
<td class="text-right flip"> <td class="text-right flip">
{% if not d.initialized %} {% if "organizer.devices:write" in request.orgapermset %}
<a href="{% url "control:organizer.device.connect" organizer=request.organizer.slug device=d.id %}" {% if not d.initialized %}
class="btn btn-primary btn-sm"><i class="fa fa-link"></i> <a href="{% url "control:organizer.device.connect" organizer=request.organizer.slug device=d.id %}"
{% trans "Connect" %}</a> class="btn btn-primary btn-sm"><i class="fa fa-link"></i>
{% endif %} {% trans "Connect" %}</a>
{% if not d.initialized or d.api_token %} {% endif %}
<a href="{% url "control:organizer.device.revoke" organizer=request.organizer.slug device=d.id %}" {% if not d.initialized or d.api_token %}
class="btn btn-default btn-sm"> <a href="{% url "control:organizer.device.revoke" organizer=request.organizer.slug device=d.id %}"
{% trans "Revoke access" %}</a> class="btn btn-default btn-sm">
{% trans "Revoke access" %}</a>
{% endif %}
{% endif %} {% endif %}
{% if d.initialized %} {% if d.initialized %}
<a href="{% url "control:organizer.device.logs" organizer=request.organizer.slug device=d.id %}" <a href="{% url "control:organizer.device.logs" organizer=request.organizer.slug device=d.id %}"
@@ -175,19 +183,23 @@
{% trans "Logs" %} {% trans "Logs" %}
</a> </a>
{% endif %} {% endif %}
<a href="{% url "control:organizer.device.edit" organizer=request.organizer.slug device=d.id %}" {% if "organizer.devices:write" in request.orgapermset %}
class="btn btn-default btn-sm"><i class="fa fa-edit"></i></a> <a href="{% url "control:organizer.device.edit" organizer=request.organizer.slug device=d.id %}"
class="btn btn-default btn-sm"><i class="fa fa-edit"></i></a>
{% endif %}
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
</div> </div>
<div class="batch-select-actions"> {% if "organizer.devices:write" in request.orgapermset %}
<button type="submit" class="btn btn-primary btn-save" name="action" value="edit"> <div class="batch-select-actions">
<i class="fa fa-edit"></i>{% trans "Edit selected" %} <button type="submit" class="btn btn-primary btn-save" name="action" value="edit">
</button> <i class="fa fa-edit"></i>{% trans "Edit selected" %}
</div> </button>
</div>
{% endif %}
</form> </form>
{% include "pretixcontrol/pagination.html" %} {% include "pretixcontrol/pagination.html" %}
{% endif %} {% endif %}

View File

@@ -6,10 +6,12 @@
<p> <p>
{% trans "The list below shows gates that you can use to group check-in devices." %} {% trans "The list below shows gates that you can use to group check-in devices." %}
</p> </p>
<a href="{% url "control:organizer.gate.add" organizer=request.organizer.slug %}" class="btn btn-default"> {% if "organizer.devices:write" in request.orgapermset %}
<span class="fa fa-plus"></span> <a href="{% url "control:organizer.gate.add" organizer=request.organizer.slug %}" class="btn btn-default">
{% trans "Create a new gate" %} <span class="fa fa-plus"></span>
</a> {% trans "Create a new gate" %}
</a>
{% endif %}
<table class="table table-condensed table-hover"> <table class="table table-condensed table-hover">
<thead> <thead>
<tr> <tr>
@@ -21,15 +23,21 @@
{% for g in gates %} {% for g in gates %}
<tr> <tr>
<td><strong> <td><strong>
<a href="{% url "control:organizer.gate.edit" organizer=request.organizer.slug gate=g.id %}"> {% if "organizer.devices:write" in request.orgapermset %}
<a href="{% url "control:organizer.gate.edit" organizer=request.organizer.slug gate=g.id %}">
{{ g.name }}
</a>
{% else %}
{{ g.name }} {{ g.name }}
</a> {% endif %}
</strong></td> </strong></td>
<td class="text-right flip"> <td class="text-right flip">
<a href="{% url "control:organizer.gate.edit" organizer=request.organizer.slug gate=g.id %}" {% if "organizer.devices:write" in request.orgapermset %}
class="btn btn-default btn-sm"><i class="fa fa-edit"></i></a> <a href="{% url "control:organizer.gate.edit" organizer=request.organizer.slug gate=g.id %}"
<a href="{% url "control:organizer.gate.delete" organizer=request.organizer.slug gate=g.id %}" class="btn btn-default btn-sm"><i class="fa fa-edit"></i></a>
class="btn btn-danger btn-sm"><i class="fa fa-trash"></i></a> <a href="{% url "control:organizer.gate.delete" organizer=request.organizer.slug gate=g.id %}"
class="btn btn-danger btn-sm"><i class="fa fa-trash"></i></a>
{% endif %}
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}

View File

@@ -10,10 +10,12 @@
{% if card.testmode %} {% if card.testmode %}
<span class="label label-warning">{% trans "TEST MODE" %}</span> <span class="label label-warning">{% trans "TEST MODE" %}</span>
{% endif %} {% endif %}
<a href="{% url "control:organizer.giftcard.edit" organizer=request.organizer.slug giftcard=card.id %}" {% if "organizer.giftcards:write" in request.orgapermset %}
class="btn btn-default"> <a href="{% url "control:organizer.giftcard.edit" organizer=request.organizer.slug giftcard=card.id %}"
<i class="fa fa-edit"></i> {% trans "Edit" %} class="btn btn-default">
</a> <i class="fa fa-edit"></i> {% trans "Edit" %}
</a>
{% endif %}
</h1> </h1>
<div class="row"> <div class="row">
<div class="col-md-10 col-xs-12"> <div class="col-md-10 col-xs-12">
@@ -112,22 +114,24 @@
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
<tfoot> {% if "organizer.giftcards:write" in request.orgapermset %}
<tr> <tfoot>
<td></td> <tr>
<td> <td></td>
<input type="text" class="form-control helper-display-block" placeholder="{% trans "Text" %}" <td>
name="text"> <input type="text" class="form-control helper-display-block" placeholder="{% trans "Text" %}"
</td> name="text">
<td class="text-right form-inline"> </td>
<input type="text" class="form-control input-sm" placeholder="{% trans "Value" %}" name="value"> <td class="text-right form-inline">
<button type="submit" class="btn btn-primary"> <input type="text" class="form-control input-sm" placeholder="{% trans "Value" %}" name="value">
<span class="fa fa-plus"></span> <button type="submit" class="btn btn-primary">
</button> <span class="fa fa-plus"></span>
</td> </button>
</td>
</tr> </tr>
</tfoot> </tfoot>
{% endif %}
</table> </table>
</form> </form>
</div> </div>

View File

@@ -15,10 +15,11 @@
or you can manually issue gift cards. or you can manually issue gift cards.
{% endblocktrans %} {% endblocktrans %}
</p> </p>
{% if "organizer.giftcards:write" in request.orgapermset %}
<a href="{% url "control:organizer.giftcard.add" organizer=request.organizer.slug %}" <a href="{% url "control:organizer.giftcard.add" organizer=request.organizer.slug %}"
class="btn btn-default btn-lg"><i class="fa fa-plus"></i> {% trans "Manually issue a gift card" %} class="btn btn-default btn-lg"><i class="fa fa-plus"></i> {% trans "Manually issue a gift card" %}
</a> </a>
{% endif %}
</div> </div>
{% else %} {% else %}
<div class="panel panel-default"> <div class="panel panel-default">
@@ -45,10 +46,12 @@
</div> </div>
</form> </form>
</div> </div>
<p> {% if "organizer.giftcards:write" in request.orgapermset %}
<a href="{% url "control:organizer.giftcard.add" organizer=request.organizer.slug %}" <p>
class="btn btn-default"><i class="fa fa-plus"></i> {% trans "Manually issue a gift card" %}</a> <a href="{% url "control:organizer.giftcard.add" organizer=request.organizer.slug %}"
</p> class="btn btn-default"><i class="fa fa-plus"></i> {% trans "Manually issue a gift card" %}</a>
</p>
{% endif %}
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-condensed table-hover"> <table class="table table-condensed table-hover">
<thead> <thead>

View File

@@ -15,8 +15,10 @@
No media have been created yet. No media have been created yet.
{% endblocktrans %} {% endblocktrans %}
</p> </p>
<a href="{% url "control:organizer.reusable_medium.create" organizer=request.organizer.slug %}" {% if "organizer.reusablemedia:write" in request.orgapermset %}
class="btn btn-primary btn-lg"><i class="fa fa-plus"></i> {% trans "Create a new medium" %}</a> <a href="{% url "control:organizer.reusable_medium.create" organizer=request.organizer.slug %}"
class="btn btn-primary btn-lg"><i class="fa fa-plus"></i> {% trans "Create a new medium" %}</a>
{% endif %}
</div> </div>
{% else %} {% else %}
<div class="panel panel-default"> <div class="panel panel-default">
@@ -40,10 +42,12 @@
</div> </div>
</form> </form>
</div> </div>
<p> {% if "organizer.reusablemedia:write" in request.orgapermset %}
<a href="{% url "control:organizer.reusable_medium.create" organizer=request.organizer.slug %}" <p>
class="btn btn-default"><i class="fa fa-plus"></i> {% trans "Create a new medium" %}</a> <a href="{% url "control:organizer.reusable_medium.create" organizer=request.organizer.slug %}"
</p> class="btn btn-default"><i class="fa fa-plus"></i> {% trans "Create a new medium" %}</a>
</p>
{% endif %}
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-condensed table-hover"> <table class="table table-condensed table-hover">
<thead> <thead>
@@ -77,9 +81,13 @@
{% if m.customer %} {% if m.customer %}
<span class="helper-display-block"> <span class="helper-display-block">
<span class="fa fa-user fa-fw"></span> <span class="fa fa-user fa-fw"></span>
<a href="{% url "control:organizer.customer" organizer=request.organizer.slug customer=m.customer.identifier %}"> {% if "organizer.customers:read" in request.orgapermset %}
<a href="{% url "control:organizer.customer" organizer=request.organizer.slug customer=m.customer.identifier %}">
{{ m.customer }}
</a>
{% else %}
{{ m.customer }} {{ m.customer }}
</a> {% endif %}
</span> </span>
{% endif %} {% endif %}
{% if m.linked_orderposition %} {% if m.linked_orderposition %}
@@ -92,8 +100,12 @@
{% if m.linked_giftcard %} {% if m.linked_giftcard %}
<span class="helper-display-block"> <span class="helper-display-block">
<span class="fa fa-credit-card fa-fw"></span> <span class="fa fa-credit-card fa-fw"></span>
<a href="{% url "control:organizer.giftcard" organizer=request.organizer.slug giftcard=m.linked_giftcard.id %}"> {% if "organizer.giftcards:read" in request.orgapermset %}
{{ m.linked_giftcard.secret }}</a> <a href="{% url "control:organizer.giftcard" organizer=request.organizer.slug giftcard=m.linked_giftcard.id %}">
{{ m.linked_giftcard.secret }}</a>
{% else %}
{{ m.linked_giftcard.secret|slice:":3" }}…
{% endif %}
</span> </span>
{% endif %} {% endif %}
</td> </td>

View File

@@ -22,60 +22,69 @@
</h3> </h3>
</div> </div>
<div class="panel-body"> <div class="panel-body">
<form action="" method="post"> {% csrf_token %}
{% csrf_token %} <dl class="dl-horizontal">
<dl class="dl-horizontal"> <dt>{% trans "Media type" context "reusable_media" %}</dt>
<dt>{% trans "Media type" context "reusable_media" %}</dt> <dd>{{ medium.get_type_display }}</dd>
<dd>{{ medium.get_type_display }}</dd> <dt>{% trans "Identifier" context "reusable_media" %}</dt>
<dt>{% trans "Identifier" context "reusable_media" %}</dt> <dd><code>{{ medium.identifier }}</code></dd>
<dd><code>{{ medium.identifier }}</code></dd> <dt>{% trans "Status" %}</dt>
<dt>{% trans "Status" %}</dt> <dd>
<dd> {% if not medium.active %}
{% if not medium.active %} {% trans "disabled" %}
{% trans "disabled" %} {% elif medium.is_expired %}
{% elif medium.is_expired %} {% trans "expired" %}
{% trans "expired" %} {% else %}
{% else %} {% trans "active" %}
{% trans "active" %} {% endif %}
{% endif %} </dd>
</dd> <dt>{% trans "Connections" context "reusable_media" %}</dt>
<dt>{% trans "Connections" context "reusable_media" %}</dt> <dd>
<dd> {% if medium.customer %}
{% if medium.customer %} <span class="helper-display-block">
<span class="helper-display-block"> <span class="fa fa-user fa-fw"></span>
<span class="fa fa-user fa-fw"></span> {% if "organizer.customers:read" in request.orgapermset %}
<a href="{% url "control:organizer.customer" organizer=request.organizer.slug customer=medium.customer.identifier %}"> <a href="{% url "control:organizer.customer" organizer=request.organizer.slug customer=medium.customer.identifier %}">
{{ medium.customer }} {{ medium.customer }}
</a> </a>
</span> {% else %}
{{ medium.customer }}
{% endif %} {% endif %}
{% if medium.linked_orderposition %} </span>
<span class="helper-display-block">
<span class="fa fa-ticket fa-fw"></span>
<a href="{% url "control:event.order" event=medium.linked_orderposition.order.event.slug organizer=request.organizer.slug code=medium.linked_orderposition.order.code %}">
{{ medium.linked_orderposition.order.code }}</a>-{{ medium.linked_orderposition.positionid }}
</span>
{% endif %}
{% if medium.linked_giftcard %}
<span class="helper-display-block">
<span class="fa fa-credit-card fa-fw"></span>
<a href="{% url "control:organizer.giftcard" organizer=request.organizer.slug giftcard=medium.linked_giftcard.id %}">
{{ medium.linked_giftcard.secret }}</a>
</span>
{% endif %}
</dd>
{% if medium.notes %}
<dt>{% trans "Notes" %}</dt>
<dd>{{ medium.notes }}</dd>
{% endif %} {% endif %}
</dl> {% if medium.linked_orderposition %}
</form> <span class="helper-display-block">
<div class="text-right"> <span class="fa fa-ticket fa-fw"></span>
<a href="{% url "control:organizer.reusable_medium.edit" organizer=request.organizer.slug pk=medium.pk %}" <a href="{% url "control:event.order" event=medium.linked_orderposition.order.event.slug organizer=request.organizer.slug code=medium.linked_orderposition.order.code %}">
class="btn btn-default"> {{ medium.linked_orderposition.order.code }}</a>-{{ medium.linked_orderposition.positionid }}
<i class="fa fa-edit"></i> {% trans "Edit" %} </span>
</a> {% endif %}
</div> {% if medium.linked_giftcard %}
<span class="helper-display-block">
<span class="fa fa-credit-card fa-fw"></span>
{% if "organizer.giftcards:read" in request.orgapermset %}
<a href="{% url "control:organizer.giftcard" organizer=request.organizer.slug giftcard=medium.linked_giftcard.id %}">
{{ medium.linked_giftcard.secret }}
</a>
{% else %}
{{ medium.linked_giftcard.secret|slice:":3" }}…
{% endif %}
</span>
{% endif %}
</dd>
{% if medium.notes %}
<dt>{% trans "Notes" %}</dt>
<dd>{{ medium.notes }}</dd>
{% endif %}
</dl>
{% if "organizer.reusablemedia:write" in request.orgapermset %}
<div class="text-right">
<a href="{% url "control:organizer.reusable_medium.edit" organizer=request.organizer.slug pk=medium.pk %}"
class="btn btn-default">
<i class="fa fa-edit"></i> {% trans "Edit" %}
</a>
</div>
{% endif %}
</div> </div>
</div> </div>
</div> </div>

View File

@@ -13,12 +13,14 @@
{% endblocktrans %} {% endblocktrans %}
</p> </p>
<a href="{% url "control:event.subevents.add" organizer=request.event.organizer.slug event=request.event.slug %}" {% if "event.subevents:write" in request.eventpermset %}
class="btn btn-primary btn-lg"><i class="fa fa-plus"></i> <a href="{% url "control:event.subevents.add" organizer=request.event.organizer.slug event=request.event.slug %}"
{% trans "Create a new date" context "subevent" %}</a> class="btn btn-primary btn-lg"><i class="fa fa-plus"></i>
<a href="{% url "control:event.subevents.bulk" organizer=request.event.organizer.slug event=request.event.slug %}" {% trans "Create a new date" context "subevent" %}</a>
class="btn btn-primary btn-lg"><i class="fa fa-plus"></i> <a href="{% url "control:event.subevents.bulk" organizer=request.event.organizer.slug event=request.event.slug %}"
{% trans "Create many new dates" context "subevent" %}</a> class="btn btn-primary btn-lg"><i class="fa fa-plus"></i>
{% trans "Create many new dates" context "subevent" %}</a>
{% endif %}
</div> </div>
{% else %} {% else %}
<div class="panel panel-default"> <div class="panel panel-default">
@@ -84,11 +86,13 @@
<table class="table table-hover table-quotas"> <table class="table table-hover table-quotas">
<thead> <thead>
<tr> <tr>
<th> {% if "event.subevents:write" in request.eventpermset %}
{% if "event.subevents: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> {% if "event.subevents:write" in request.eventpermset %}
{% endif %} <label aria-label="{% trans "select all rows for batch-operation" %}" class="batch-select-label"><input type="checkbox" data-toggle-table/></label>
</th> {% endif %}
</th>
{% endif %}
<th> <th>
{% trans "Name" %} {% trans "Name" %}
</th> </th>
@@ -123,11 +127,11 @@
<tbody> <tbody>
{% for s in subevents %} {% for s in subevents %}
<tr> <tr>
<td> {% if "event.subevents:write" in request.eventpermset %}
{% if "event.subevents:write" in request.eventpermset %} <td>
<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> <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>
</td> {% endif %}
<td> <td>
<strong><a href="{% url "control:event.subevent" organizer=request.event.organizer.slug event=request.event.slug subevent=s.id %}?returnto={{ request.GET.urlencode|urlencode }}"> <strong><a href="{% url "control:event.subevent" organizer=request.event.organizer.slug event=request.event.slug subevent=s.id %}?returnto={{ request.GET.urlencode|urlencode }}">
{{ s.name }}</a></strong><br> {{ s.name }}</a></strong><br>
@@ -173,28 +177,32 @@
{% endif %} {% endif %}
</td> </td>
<td class="text-right flip"> <td class="text-right flip">
<a href="{% url "control:event.orders" organizer=request.event.organizer.slug event=request.event.slug %}?subevent={{ s.id }}" class="btn btn-default btn-sm" title="{% trans "Show orders" %}"><i class="fa fa-shopping-cart" aria-hidden="true"></i></a> {% if "event.orders:read" in request.eventpermset %}
<a href="{% url "control:event.orders" organizer=request.event.organizer.slug event=request.event.slug %}?subevent={{ s.id }}" class="btn btn-default btn-sm" title="{% trans "Show orders" %}"><i class="fa fa-shopping-cart" aria-hidden="true"></i></a>
{% endif %}
<a href="{% url "control:event.subevent" organizer=request.event.organizer.slug event=request.event.slug subevent=s.id %}?returnto={{ request.GET.urlencode|urlencode }}" class="btn btn-default btn-sm"><i class="fa fa-edit"></i></a> {% if "event.subevents:write" in request.eventpermset %}
<div class="btn-group {% if forloop.revcounter0 < 2 %}dropup{% endif %}"> <a href="{% url "control:event.subevent" organizer=request.event.organizer.slug event=request.event.slug subevent=s.id %}?returnto={{ request.GET.urlencode|urlencode }}" class="btn btn-default btn-sm"><i class="fa fa-edit"></i></a>
<button type="button" class="btn btn-default btn-sm dropdown-toggle" <div class="btn-group {% if forloop.revcounter0 < 2 %}dropup{% endif %}">
data-toggle="dropdown"> <button type="button" class="btn btn-default btn-sm dropdown-toggle"
<span class="fa fa-copy"></span> data-toggle="dropdown">
</button> <span class="fa fa-copy"></span>
<ul class="dropdown-menu dropdown-menu-right"> </button>
<li> <ul class="dropdown-menu dropdown-menu-right">
<a href="{% url "control:event.subevents.add" organizer=request.event.organizer.slug event=request.event.slug %}?copy_from={{ s.id }}"> <li>
{% trans "Use as a template for a new date" context "subevent" %} <a href="{% url "control:event.subevents.add" organizer=request.event.organizer.slug event=request.event.slug %}?copy_from={{ s.id }}">
</a> {% trans "Use as a template for a new date" context "subevent" %}
</li> </a>
<li> </li>
<a href="{% url "control:event.subevents.bulk" organizer=request.event.organizer.slug event=request.event.slug %}?copy_from={{ s.id }}"> <li>
{% trans "Use as a template for many new dates" context "subevent" %} <a href="{% url "control:event.subevents.bulk" organizer=request.event.organizer.slug event=request.event.slug %}?copy_from={{ s.id }}">
</a> {% trans "Use as a template for many new dates" context "subevent" %}
</li> </a>
</ul> </li>
</div> </ul>
<a href="{% url "control:event.subevent.delete" organizer=request.event.organizer.slug event=request.event.slug subevent=s.id %}?returnto={{ request.GET.urlencode|urlencode }}" class="btn btn-danger btn-sm"><i class="fa fa-trash"></i></a> </div>
<a href="{% url "control:event.subevent.delete" organizer=request.event.organizer.slug event=request.event.slug subevent=s.id %}?returnto={{ request.GET.urlencode|urlencode }}" class="btn btn-danger btn-sm"><i class="fa fa-trash"></i></a>
{% endif %}
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}

View File

@@ -7,10 +7,12 @@
{% block content %} {% block content %}
<h1> <h1>
{% trans "Waiting list" %} {% trans "Waiting list" %}
<a href="{% url "control:event.settings" event=request.event.slug organizer=request.organizer.slug %}#waiting-list-open" class="btn btn-default"> {% if "event.settings.general:write" in request.eventpermset %}
<span class="fa fa-cog"></span> <a href="{% url "control:event.settings" event=request.event.slug organizer=request.organizer.slug %}#waiting-list-open" class="btn btn-default">
{% trans "Settings" %} <span class="fa fa-cog"></span>
</a> {% trans "Settings" %}
</a>
{% endif %}
</h1> </h1>
{% if not request.event.settings.waiting_list_enabled %} {% if not request.event.settings.waiting_list_enabled %}
<div class="alert alert-danger"> <div class="alert alert-danger">
@@ -259,31 +261,34 @@
{% endif %} {% endif %}
</td> </td>
<td class="text-right flip"> <td class="text-right flip">
{% if not e.voucher %} {% if 'event.orders:write' in request.eventpermset %}
<button name="move_top" value="{{ e.pk }}" class="btn btn-default btn-sm" {% if not e.voucher %}
data-toggle="tooltip" title="{% trans "Move to the top of the list" %}"> <button name="move_top" value="{{ e.pk }}" class="btn btn-default btn-sm"
<span class="fa fa-thumbs-up"></span> data-toggle="tooltip" title="{% trans "Move to the top of the list" %}">
</button> <span class="fa fa-thumbs-up"></span>
<button name="move_end" value="{{ e.pk }}" class="btn btn-default btn-sm" </button>
data-toggle="tooltip" title="{% trans "Move to the end of the list" %}"> <button name="move_end" value="{{ e.pk }}" class="btn btn-default btn-sm"
<span class="fa fa-thumbs-down"></span> data-toggle="tooltip" title="{% trans "Move to the end of the list" %}">
</button> <span class="fa fa-thumbs-down"></span>
</button>
<a href="{% url "control:event.orders.waitinglist.edit" organizer=request.event.organizer.slug event=request.event.slug entry=e.id %}" <a href="{% url "control:event.orders.waitinglist.edit" organizer=request.event.organizer.slug event=request.event.slug entry=e.id %}"
class="btn btn-default btn-sm" title="{% trans "Edit entry" %}" class="btn btn-default btn-sm" title="{% trans "Edit entry" %}"
data-toggle="tooltip"> data-toggle="tooltip">
<i class="fa fa-edit" aria-hidden="true"></i> <i class="fa fa-edit" aria-hidden="true"></i>
</a> </a>
<a href="{% url "control:event.orders.waitinglist.delete" organizer=request.event.organizer.slug event=request.event.slug entry=e.id %}?next={{ request.get_full_path|urlencode }}" class="btn btn-danger btn-sm"><i class="fa fa-trash"></i></a> <a href="{% url "control:event.orders.waitinglist.delete" organizer=request.event.organizer.slug event=request.event.slug entry=e.id %}?next={{ request.get_full_path|urlencode }}"
{% else %} class="btn btn-danger btn-sm"><i class="fa fa-trash"></i></a>
<button class="btn btn-default btn-sm disabled"> {% else %}
<span class="fa fa-thumbs-up"></span> <button class="btn btn-default btn-sm disabled">
</button> <span class="fa fa-thumbs-up"></span>
<button class="btn btn-default btn-sm disabled"> </button>
<span class="fa fa-thumbs-down"></span> <button class="btn btn-default btn-sm disabled">
</button> <span class="fa fa-thumbs-down"></span>
<span class="btn btn-danger btn-sm disabled"><i class="fa fa-trash"></i></span> </button>
<span class="btn btn-danger btn-sm disabled"><i class="fa fa-trash"></i></span>
{% endif %}
{% endif %} {% endif %}
</td> </td>
</tr> </tr>

View File

@@ -295,7 +295,7 @@ class CheckInListBulkActionView(CheckInListQueryMixin, EventPermissionRequiredMi
class CheckinListList(EventPermissionRequiredMixin, PaginationMixin, ListView): class CheckinListList(EventPermissionRequiredMixin, PaginationMixin, ListView):
model = CheckinList model = CheckinList
context_object_name = 'checkinlists' context_object_name = 'checkinlists'
permission = 'event.orders:read' permission = ('event.orders:read', 'event.settings.general:write')
template_name = 'pretixcontrol/checkin/lists.html' template_name = 'pretixcontrol/checkin/lists.html'
ordering = ('subevent__date_from', 'name', 'pk') ordering = ('subevent__date_from', 'name', 'pk')
@@ -317,9 +317,9 @@ class CheckinListList(EventPermissionRequiredMixin, PaginationMixin, ListView):
cl.subevent.event = self.request.event # re-use same event object to make sure settings are cached cl.subevent.event = self.request.event # re-use same event object to make sure settings are cached
ctx['checkinlists'] = clists ctx['checkinlists'] = clists
ctx['can_change_organizer_settings'] = self.request.user.has_organizer_permission( ctx['link_device_settings'] = self.request.user.has_organizer_permission(
self.request.organizer, self.request.organizer,
'organizer.settings.general:write', 'organizer.devices:read',
self.request self.request
) )
ctx['filter_form'] = self.filter_form ctx['filter_form'] = self.filter_form
@@ -578,6 +578,12 @@ class CheckInResetView(CheckInListQueryMixin, EventPermissionRequiredMixin, Asyn
permission = "event.orders:write" permission = "event.orders:write"
template_name = "pretixcontrol/checkin/reset.html" template_name = "pretixcontrol/checkin/reset.html"
def dispatch(self, request, *args, **kwargs):
# Special case, we want two permissions to be set
if not request.user.has_event_permission(request.organizer, request.event, "event.settings.general:write", request=request):
raise PermissionDenied()
return super().dispatch(request, *args, **kwargs)
def get_error_url(self, *args): def get_error_url(self, *args):
return reverse( return reverse(
"control:event.orders.checkinlists", "control:event.orders.checkinlists",

View File

@@ -502,7 +502,7 @@ class EventPlugins(EventSettingsViewMixin, EventPermissionRequiredMixin, Templat
class PaymentProviderSettings(EventSettingsViewMixin, EventPermissionRequiredMixin, TemplateView, SingleObjectMixin): class PaymentProviderSettings(EventSettingsViewMixin, EventPermissionRequiredMixin, TemplateView, SingleObjectMixin):
model = Event model = Event
context_object_name = 'event' context_object_name = 'event'
permission = 'event.settings.general:write' permission = 'event.settings.payment:write'
template_name = 'pretixcontrol/event/payment_provider.html' template_name = 'pretixcontrol/event/payment_provider.html'
def get_success_url(self) -> str: def get_success_url(self) -> str:
@@ -618,10 +618,28 @@ class EventSettingsFormView(EventPermissionRequiredMixin, DecoupleMixin, FormVie
return self.render_to_response(self.get_context_data(form=form)) return self.render_to_response(self.get_context_data(form=form))
class PaymentSettings(EventSettingsViewMixin, EventSettingsFormView): class WritePermissionMixin:
def post(self, request, *args, **kwargs):
# Special case, we want to allow different access for read and write
if not request.user.has_event_permission(request.organizer, request.event, self.write_permission,
request=request):
raise PermissionDenied()
return super().post(request, *args, **kwargs)
def get_form(self, *args, **kwargs):
form = super().get_form(*args, **kwargs)
if not self.request.user.has_event_permission(
self.request.organizer, self.request.event, self.write_permission, request=self.request):
for f in form.fields.values():
f.disabled = True
return form
class PaymentSettings(WritePermissionMixin, EventSettingsViewMixin, EventSettingsFormView):
template_name = 'pretixcontrol/event/payment.html' template_name = 'pretixcontrol/event/payment.html'
form_class = PaymentSettingsForm form_class = PaymentSettingsForm
permission = 'event.settings.general:write' permission = ('event.settings.payment:write', 'event.settings.general:write')
write_permission = 'event.settings.payment:write'
def get_success_url(self) -> str: def get_success_url(self) -> str:
return reverse('control:event.settings.payment', kwargs={ return reverse('control:event.settings.payment', kwargs={
@@ -647,10 +665,11 @@ class PaymentSettings(EventSettingsViewMixin, EventSettingsFormView):
return context return context
class TaxSettings(EventSettingsViewMixin, EventSettingsFormView): class TaxSettings(WritePermissionMixin, EventSettingsViewMixin, EventSettingsFormView):
template_name = 'pretixcontrol/event/tax.html' template_name = 'pretixcontrol/event/tax.html'
form_class = TaxSettingsForm form_class = TaxSettingsForm
permission = 'event.settings.general:write' permission = ('event.settings.tax:write', 'event.settings.general:write')
write_permission = 'event.settings.tax:write'
def get_success_url(self) -> str: def get_success_url(self) -> str:
return reverse('control:event.settings.tax', kwargs={ return reverse('control:event.settings.tax', kwargs={
@@ -666,11 +685,12 @@ class TaxSettings(EventSettingsViewMixin, EventSettingsFormView):
return context return context
class InvoiceSettings(EventSettingsViewMixin, EventSettingsFormView): class InvoiceSettings(WritePermissionMixin, EventSettingsViewMixin, EventSettingsFormView):
model = Event model = Event
form_class = InvoiceSettingsForm form_class = InvoiceSettingsForm
template_name = 'pretixcontrol/event/invoicing.html' template_name = 'pretixcontrol/event/invoicing.html'
permission = 'event.settings.general:write' permission = ('event.settings.invoicing:write', 'event.settings.general:write')
write_permission = 'event.settings.invoicing:write'
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
types = get_transmission_types() types = get_transmission_types()
@@ -738,7 +758,7 @@ class CancelSettings(EventSettingsViewMixin, EventSettingsFormView):
class InvoicePreview(EventPermissionRequiredMixin, View): class InvoicePreview(EventPermissionRequiredMixin, View):
permission = 'event.settings.general:write' permission = 'event.settings.invoicing:write'
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
fname, ftype, fcontent = build_preview_invoice_pdf(request.event) fname, ftype, fcontent = build_preview_invoice_pdf(request.event)
@@ -1297,7 +1317,7 @@ class TaxCreate(EventSettingsViewMixin, EventPermissionRequiredMixin, CreateView
model = TaxRule model = TaxRule
form_class = TaxRuleForm form_class = TaxRuleForm
template_name = 'pretixcontrol/event/tax_edit.html' template_name = 'pretixcontrol/event/tax_edit.html'
permission = 'event.settings.general:write' permission = 'event.settings.tax:write'
context_object_name = 'taxrule' context_object_name = 'taxrule'
def get_success_url(self) -> str: def get_success_url(self) -> str:
@@ -1358,7 +1378,7 @@ class TaxUpdate(EventSettingsViewMixin, EventPermissionRequiredMixin, UpdateView
model = TaxRule model = TaxRule
form_class = TaxRuleForm form_class = TaxRuleForm
template_name = 'pretixcontrol/event/tax_edit.html' template_name = 'pretixcontrol/event/tax_edit.html'
permission = 'event.settings.general:write' permission = 'event.settings.tax:write'
context_object_name = 'rule' context_object_name = 'rule'
def get_object(self, queryset=None) -> TaxRule: def get_object(self, queryset=None) -> TaxRule:
@@ -1422,7 +1442,7 @@ class TaxUpdate(EventSettingsViewMixin, EventPermissionRequiredMixin, UpdateView
class TaxDefault(EventSettingsViewMixin, EventPermissionRequiredMixin, DetailView): class TaxDefault(EventSettingsViewMixin, EventPermissionRequiredMixin, DetailView):
model = TaxRule model = TaxRule
permission = 'event.settings.general:write' permission = 'event.settings.tax:write'
def get_object(self, queryset=None) -> TaxRule: def get_object(self, queryset=None) -> TaxRule:
try: try:
@@ -1467,7 +1487,7 @@ class TaxDefault(EventSettingsViewMixin, EventPermissionRequiredMixin, DetailVie
class TaxDelete(EventSettingsViewMixin, EventPermissionRequiredMixin, CompatDeleteView): class TaxDelete(EventSettingsViewMixin, EventPermissionRequiredMixin, CompatDeleteView):
model = TaxRule model = TaxRule
template_name = 'pretixcontrol/event/tax_delete.html' template_name = 'pretixcontrol/event/tax_delete.html'
permission = 'event.settings.general:write' permission = 'event.settings.tax:write'
context_object_name = 'taxrule' context_object_name = 'taxrule'
def get_object(self, queryset=None) -> TaxRule: def get_object(self, queryset=None) -> TaxRule:

View File

@@ -664,7 +664,7 @@ class QuestionMixin:
class QuestionView(EventPermissionRequiredMixin, ChartContainingView, DetailView): class QuestionView(EventPermissionRequiredMixin, ChartContainingView, DetailView):
model = Question model = Question
template_name = 'pretixcontrol/items/question.html' template_name = 'pretixcontrol/items/question.html'
permission = 'event.items:write' permission = None
template_name_field = 'question' template_name_field = 'question'
@cached_property @cached_property

View File

@@ -2969,7 +2969,7 @@ class RefundList(EventPermissionRequiredMixin, PaginationMixin, ListView):
class EventCancel(EventPermissionRequiredMixin, AsyncAction, FormView): class EventCancel(EventPermissionRequiredMixin, AsyncAction, FormView):
template_name = 'pretixcontrol/orders/cancel.html' template_name = 'pretixcontrol/orders/cancel.html'
permission = 'event.orders:write' permission = 'event:cancel'
form_class = EventCancelForm form_class = EventCancelForm
task = cancel_event task = cancel_event
known_errortypes = ['OrderError'] known_errortypes = ['OrderError']

View File

@@ -1232,7 +1232,7 @@ class DeviceQueryMixin:
class DeviceListView(DeviceQueryMixin, OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, ListView): class DeviceListView(DeviceQueryMixin, OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, ListView):
model = Device model = Device
template_name = 'pretixcontrol/organizers/devices.html' template_name = 'pretixcontrol/organizers/devices.html'
permission = 'organizer.settings.general:write' permission = 'organizer.devices:read'
context_object_name = 'devices' context_object_name = 'devices'
paginate_by = 100 paginate_by = 100
@@ -1245,7 +1245,7 @@ class DeviceListView(DeviceQueryMixin, OrganizerDetailViewMixin, OrganizerPermis
class DeviceCreateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, CreateView): class DeviceCreateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, CreateView):
model = Device model = Device
template_name = 'pretixcontrol/organizers/device_edit.html' template_name = 'pretixcontrol/organizers/device_edit.html'
permission = 'organizer.settings.general:write' permission = 'organizer.devices:write'
form_class = DeviceForm form_class = DeviceForm
def get_form_kwargs(self): def get_form_kwargs(self):
@@ -1276,7 +1276,7 @@ class DeviceCreateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixi
class DeviceLogView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, ListView): class DeviceLogView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, ListView):
template_name = 'pretixcontrol/organizers/device_logs.html' template_name = 'pretixcontrol/organizers/device_logs.html'
permission = 'organizer.settings.general:write' permission = 'organizer.devices:read'
model = LogEntry model = LogEntry
context_object_name = 'logs' context_object_name = 'logs'
paginate_by = 20 paginate_by = 20
@@ -1304,7 +1304,7 @@ class DeviceLogView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin,
class DeviceUpdateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, UpdateView): class DeviceUpdateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, UpdateView):
model = Device model = Device
template_name = 'pretixcontrol/organizers/device_edit.html' template_name = 'pretixcontrol/organizers/device_edit.html'
permission = 'organizer.settings.general:write' permission = 'organizer.devices:write'
context_object_name = 'device' context_object_name = 'device'
form_class = DeviceForm form_class = DeviceForm
@@ -1347,7 +1347,7 @@ class DeviceUpdateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixi
class DeviceBulkUpdateView(DeviceQueryMixin, OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, FormView): class DeviceBulkUpdateView(DeviceQueryMixin, OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, FormView):
template_name = 'pretixcontrol/organizers/device_bulk_edit.html' template_name = 'pretixcontrol/organizers/device_bulk_edit.html'
permission = 'organizer.settings.general:write' permission = 'organizer.devices:write'
context_object_name = 'device' context_object_name = 'device'
form_class = DeviceBulkEditForm form_class = DeviceBulkEditForm
@@ -1461,7 +1461,7 @@ class DeviceBulkUpdateView(DeviceQueryMixin, OrganizerDetailViewMixin, Organizer
class DeviceConnectView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, DetailView): class DeviceConnectView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, DetailView):
model = Device model = Device
template_name = 'pretixcontrol/organizers/device_connect.html' template_name = 'pretixcontrol/organizers/device_connect.html'
permission = 'organizer.settings.general:write' permission = 'organizer.devices:write'
context_object_name = 'device' context_object_name = 'device'
def get_object(self, queryset=None): def get_object(self, queryset=None):
@@ -1493,7 +1493,7 @@ class DeviceConnectView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMix
class DeviceRevokeView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, DetailView): class DeviceRevokeView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, DetailView):
model = Device model = Device
template_name = 'pretixcontrol/organizers/device_revoke.html' template_name = 'pretixcontrol/organizers/device_revoke.html'
permission = 'organizer.settings.general:write' permission = 'organizer.devices:write'
context_object_name = 'device' context_object_name = 'device'
def get_object(self, queryset=None): def get_object(self, queryset=None):
@@ -2308,7 +2308,7 @@ class RunScheduledExportView(OrganizerPermissionRequiredMixin, ExportMixin, View
class GateListView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, ListView): class GateListView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, ListView):
model = Gate model = Gate
template_name = 'pretixcontrol/organizers/gates.html' template_name = 'pretixcontrol/organizers/gates.html'
permission = 'organizer.settings.general:write' permission = 'organizer.devices:read'
context_object_name = 'gates' context_object_name = 'gates'
def get_queryset(self): def get_queryset(self):
@@ -2318,7 +2318,7 @@ class GateListView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, L
class GateCreateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, CreateView): class GateCreateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, CreateView):
model = Gate model = Gate
template_name = 'pretixcontrol/organizers/gate_edit.html' template_name = 'pretixcontrol/organizers/gate_edit.html'
permission = 'organizer.settings.general:write' permission = 'organizer.devices:write'
form_class = GateForm form_class = GateForm
def get_form_kwargs(self): def get_form_kwargs(self):
@@ -2352,7 +2352,7 @@ class GateCreateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin,
class GateUpdateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, UpdateView): class GateUpdateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, UpdateView):
model = Gate model = Gate
template_name = 'pretixcontrol/organizers/gate_edit.html' template_name = 'pretixcontrol/organizers/gate_edit.html'
permission = 'organizer.settings.general:write' permission = 'organizer.devices:write'
context_object_name = 'gate' context_object_name = 'gate'
form_class = GateForm form_class = GateForm
@@ -2387,7 +2387,7 @@ class GateUpdateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin,
class GateDeleteView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, CompatDeleteView): class GateDeleteView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, CompatDeleteView):
model = Gate model = Gate
template_name = 'pretixcontrol/organizers/gate_delete.html' template_name = 'pretixcontrol/organizers/gate_delete.html'
permission = 'organizer.settings.general:write' permission = 'organizer.devices:write'
context_object_name = 'gate' context_object_name = 'gate'
def get_object(self, queryset=None): def get_object(self, queryset=None):
@@ -2997,7 +2997,7 @@ class SSOClientDeleteView(OrganizerDetailViewMixin, OrganizerPermissionRequiredM
class CustomerListView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, PaginationMixin, ListView): class CustomerListView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, PaginationMixin, ListView):
model = Customer model = Customer
template_name = 'pretixcontrol/organizers/customers.html' template_name = 'pretixcontrol/organizers/customers.html'
permission = 'organizer.customers:write' permission = 'organizer.customers:read'
context_object_name = 'customers' context_object_name = 'customers'
def get_queryset(self): def get_queryset(self):
@@ -3018,7 +3018,7 @@ class CustomerListView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixi
class CustomerDetailView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, PaginationMixin, ListView): class CustomerDetailView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, PaginationMixin, ListView):
template_name = 'pretixcontrol/organizers/customer.html' template_name = 'pretixcontrol/organizers/customer.html'
permission = 'organizer.customers:write' permission = 'organizer.customers:read'
context_object_name = 'orders' context_object_name = 'orders'
def get_queryset(self): def get_queryset(self):

View File

@@ -117,7 +117,7 @@ class SubEventList(EventPermissionRequiredMixin, PaginationMixin, SubEventQueryM
model = SubEvent model = SubEvent
context_object_name = 'subevents' context_object_name = 'subevents'
template_name = 'pretixcontrol/subevents/index.html' template_name = 'pretixcontrol/subevents/index.html'
permission = 'event.settings.general:write' permission = None
def get_queryset(self): def get_queryset(self):
return super().get_queryset(True).prefetch_related( return super().get_queryset(True).prefetch_related(
@@ -156,7 +156,7 @@ class SubEventList(EventPermissionRequiredMixin, PaginationMixin, SubEventQueryM
class SubEventDelete(EventPermissionRequiredMixin, CompatDeleteView): class SubEventDelete(EventPermissionRequiredMixin, CompatDeleteView):
model = SubEvent model = SubEvent
template_name = 'pretixcontrol/subevents/delete.html' template_name = 'pretixcontrol/subevents/delete.html'
permission = 'event.settings.general:write' permission = 'event.subevents:write'
context_object_name = 'subevents' context_object_name = 'subevents'
def get_object(self, queryset=None) -> SubEvent: def get_object(self, queryset=None) -> SubEvent:
@@ -508,7 +508,7 @@ class SubEventEditorMixin(MetaDataEditorMixin):
class SubEventUpdate(EventPermissionRequiredMixin, SubEventEditorMixin, UpdateView): class SubEventUpdate(EventPermissionRequiredMixin, SubEventEditorMixin, UpdateView):
model = SubEvent model = SubEvent
template_name = 'pretixcontrol/subevents/detail.html' template_name = 'pretixcontrol/subevents/detail.html'
permission = 'event.settings.general:write' permission = 'event.subevents:write'
context_object_name = 'subevent' context_object_name = 'subevent'
form_class = SubEventForm form_class = SubEventForm
@@ -575,7 +575,7 @@ class SubEventUpdate(EventPermissionRequiredMixin, SubEventEditorMixin, UpdateVi
class SubEventCreate(SubEventEditorMixin, EventPermissionRequiredMixin, CreateView): class SubEventCreate(SubEventEditorMixin, EventPermissionRequiredMixin, CreateView):
model = SubEvent model = SubEvent
template_name = 'pretixcontrol/subevents/detail.html' template_name = 'pretixcontrol/subevents/detail.html'
permission = 'event.settings.general:write' permission = 'event.subevents:write'
context_object_name = 'subevent' context_object_name = 'subevent'
form_class = SubEventForm form_class = SubEventForm
@@ -669,7 +669,7 @@ class SubEventCreate(SubEventEditorMixin, EventPermissionRequiredMixin, CreateVi
class SubEventBulkAction(SubEventQueryMixin, EventPermissionRequiredMixin, View): class SubEventBulkAction(SubEventQueryMixin, EventPermissionRequiredMixin, View):
permission = 'event.settings.general:write' permission = 'event.subevents:write'
@transaction.atomic @transaction.atomic
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
@@ -740,7 +740,7 @@ class SubEventBulkAction(SubEventQueryMixin, EventPermissionRequiredMixin, View)
class SubEventBulkCreate(SubEventEditorMixin, EventPermissionRequiredMixin, AsyncFormView): class SubEventBulkCreate(SubEventEditorMixin, EventPermissionRequiredMixin, AsyncFormView):
model = SubEvent model = SubEvent
template_name = 'pretixcontrol/subevents/bulk.html' template_name = 'pretixcontrol/subevents/bulk.html'
permission = 'event.settings.general:write' permission = 'event.subevents:write'
context_object_name = 'subevent' context_object_name = 'subevent'
form_class = SubEventBulkForm form_class = SubEventBulkForm
itemformclass = BulkSubEventItemForm itemformclass = BulkSubEventItemForm
@@ -1065,7 +1065,7 @@ class SubEventBulkCreate(SubEventEditorMixin, EventPermissionRequiredMixin, Asyn
class SubEventBulkEdit(SubEventQueryMixin, EventPermissionRequiredMixin, FormView): class SubEventBulkEdit(SubEventQueryMixin, EventPermissionRequiredMixin, FormView):
permission = 'event.settings.general:write' permission = 'event.subevents:write'
form_class = SubEventBulkEditForm form_class = SubEventBulkEditForm
template_name = 'pretixcontrol/subevents/bulk_edit.html' template_name = 'pretixcontrol/subevents/bulk_edit.html'
context_object_name = 'subevent' context_object_name = 'subevent'
@@ -1170,7 +1170,10 @@ class SubEventBulkEdit(SubEventQueryMixin, EventPermissionRequiredMixin, FormVie
kwargs = {} kwargs = {}
if self.sampled_quotas is not None: if self.sampled_quotas is not None:
kwargs['instance'] = self.get_queryset()[0] try:
kwargs['instance'] = self.get_queryset()[0]
except IndexError:
raise Http404("No matching dates")
formsetclass = inlineformset_factory( formsetclass = inlineformset_factory(
SubEvent, Quota, SubEvent, Quota,

View File

@@ -1013,10 +1013,16 @@ def item_meta_values(request, organizer, event):
}) })
@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): def devices_select2(request, **kwargs):
allowed = (
# This check 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
request.user.has_organizer_permission(request.organizer, "organizer.devices:read", request=request) or
request.user.get_events_with_permission("event.orders:read").filter(organizer=request.organizer).exists()
)
if not allowed:
raise PermissionDenied()
query = request.GET.get('query', '') query = request.GET.get('query', '')
try: try:
page = int(request.GET.get('page', '1')) page = int(request.GET.get('page', '1'))
@@ -1051,10 +1057,16 @@ def devices_select2(request, **kwargs):
return JsonResponse(doc) return JsonResponse(doc)
@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): def gate_select2(request, **kwargs):
allowed = (
# This check 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
request.user.has_organizer_permission(request.organizer, "organizer.devices:read", request=request) or
request.user.get_events_with_permission("event.orders:read").filter(organizer=request.organizer).exists()
)
if not allowed:
raise PermissionDenied()
query = request.GET.get('query', '') query = request.GET.get('query', '')
try: try:
page = int(request.GET.get('page', '1')) page = int(request.GET.get('page', '1'))

View File

@@ -28,8 +28,6 @@ OLD_TO_NEW_EVENT_MIGRATION = {
"can_change_event_settings": [ "can_change_event_settings": [
"event.settings.general:write", "event.settings.general:write",
"event.settings.payment:write", "event.settings.payment:write",
"event.settings.plugins:write",
"event.settings.email.sender:write",
"event.settings.tax:write", "event.settings.tax:write",
"event.settings.invoicing:write", "event.settings.invoicing:write",
"event.subevents:write", "event.subevents:write",

View File

@@ -33,6 +33,13 @@ $(function () {
dependents[cleanName($(this).attr("name"))] = $(this) dependents[cleanName($(this).attr("name"))] = $(this)
}) })
const dependentsDisabled = [];
for (var k in dependents) {
if (dependents[k].prop("disabled")) {
dependentsDisabled.push(k);
}
}
if (!Object.values(dependents).some((el) => el.length)) { if (!Object.values(dependents).some((el) => el.length)) {
// No address fields found, do not create request // No address fields found, do not create request
return; return;
@@ -101,7 +108,7 @@ $(function () {
label.append('<i class="label-required">' + gettext('required') + '</i>') label.append('<i class="label-required">' + gettext('required') + '</i>')
} }
} }
for (var k in dependents) dependents[k].prop("disabled", false); for (var k in dependents) dependents[k].prop("disabled", dependentsDisabled.includes(k));
loader.hide(); loader.hide();
} }
@@ -158,7 +165,7 @@ $(function () {
required = false; required = false;
dependent.closest(".form-group").toggle(visible).toggleClass('required', required); dependent.closest(".form-group").toggle(visible).toggleClass('required', required);
dependent.prop("required", required).prop("disabled", false); dependent.prop("required", required).prop("disabled", dependentsDisabled.includes(k));
} }
}).finally(function () { }).finally(function () {
loader.hide(); loader.hide();

View File

@@ -340,11 +340,12 @@ var form_handlers = function (el) {
} }
el.find("input[data-checkbox-dependency]").each(function () { el.find("input[data-checkbox-dependency]").each(function () {
var initially_disabled = $(this).prop("disabled");
var dependent = $(this), var dependent = $(this),
dependency = findDependency($(this).attr("data-checkbox-dependency"), this), dependency = findDependency($(this).attr("data-checkbox-dependency"), this),
update = function () { update = function () {
var enabled = dependency.prop('checked'); var enabled = dependency.prop('checked');
dependent.prop('disabled', !enabled).closest('.form-group, .form-field-boundary').toggleClass('disabled', !enabled); dependent.prop('disabled', !enabled || initially_disabled).closest('.form-group, .form-field-boundary').toggleClass('disabled', !enabled);
if (!enabled && !dependent.is('[data-checkbox-dependency-visual]')) { if (!enabled && !dependent.is('[data-checkbox-dependency-visual]')) {
dependent.prop('checked', false); dependent.prop('checked', false);
dependent.trigger('change') dependent.trigger('change')
@@ -366,11 +367,12 @@ var form_handlers = function (el) {
}); });
el.find("div[data-display-dependency], textarea[data-display-dependency], input[data-display-dependency], select[data-display-dependency], button[data-display-dependency]").each(function () { el.find("div[data-display-dependency], textarea[data-display-dependency], input[data-display-dependency], select[data-display-dependency], button[data-display-dependency]").each(function () {
var initially_disabled = $(this).prop("disabled");
var dependent = $(this), var dependent = $(this),
dependency = findDependency($(this).attr("data-display-dependency"), this), dependency = findDependency($(this).attr("data-display-dependency"), this),
update = function (ev) { update = function (ev) {
var enabled = dependency.toArray().some(function(d) { var enabled = dependency.toArray().some(function(d) {
if (d.disabled) return false; if (d.disabled && !initially_disabled) return false;
if (d.type === 'checkbox' || d.type === 'radio') { if (d.type === 'checkbox' || d.type === 'radio') {
return d.checked; return d.checked;
} else if (d.type === 'select-one') { } else if (d.type === 'select-one') {
@@ -391,7 +393,7 @@ var form_handlers = function (el) {
} }
var $toggling = dependent; var $toggling = dependent;
if (dependent.is("[data-disable-dependent]")) { if (dependent.is("[data-disable-dependent]")) {
$toggling.attr('disabled', !enabled).trigger("change"); $toggling.attr('disabled', !enabled || initially_disabled).trigger("change");
} }
const tagName = dependent.get(0).tagName.toLowerCase() const tagName = dependent.get(0).tagName.toLowerCase()
if (tagName !== "div" && tagName !== "button") { if (tagName !== "div" && tagName !== "button") {

View File

@@ -1387,7 +1387,14 @@ def test_get_event_settings(token_client, organizer, event):
@pytest.mark.django_db @pytest.mark.django_db
def test_patch_event_settings(token_client, organizer, event): def test_patch_event_settings(token_client, organizer, event, team):
team.all_event_permissions = False
team.limit_event_permissions = {
"event.settings.general:write": True,
"event.settings.tax:write": True,
}
team.save()
organizer.settings.imprint_url = 'https://example.org' organizer.settings.imprint_url = 'https://example.org'
resp = token_client.patch( resp = token_client.patch(
'/api/v1/organizers/{}/events/{}/settings/'.format(organizer.slug, event.slug), '/api/v1/organizers/{}/events/{}/settings/'.format(organizer.slug, event.slug),
@@ -1503,6 +1510,29 @@ def test_patch_event_settings(token_client, organizer, event):
event.settings.flush() event.settings.flush()
assert set(event.settings.locales) == set(locales) assert set(event.settings.locales) == set(locales)
resp = token_client.patch(
'/api/v1/organizers/{}/events/{}/settings/'.format(organizer.slug, event.slug),
{
'display_net_prices': True,
},
format='json'
)
assert resp.status_code == 200
event.settings.flush()
assert event.settings.display_net_prices
resp = token_client.patch(
'/api/v1/organizers/{}/events/{}/settings/'.format(organizer.slug, event.slug),
{
'invoice_address_asked': False,
},
format='json'
)
assert resp.status_code == 400
assert resp.data == {
'invoice_address_asked': ['Setting this field requires permission event.settings.invoicing:write']
}
@pytest.mark.django_db @pytest.mark.django_db
def test_patch_event_settings_validation(token_client, organizer, event): def test_patch_event_settings_validation(token_client, organizer, event):

View File

@@ -59,7 +59,7 @@ event_urls = [
] ]
event_permission_sub_urls = [ event_permission_sub_urls = [
('get', 'event.settings.general:write', 'settings/', 200), ('get', None, 'settings/', 200),
('patch', 'event.settings.general:write', 'settings/', 200), ('patch', 'event.settings.general:write', 'settings/', 200),
('get', 'event.orders:read', 'revokedsecrets/', 200), ('get', 'event.orders:read', 'revokedsecrets/', 200),
('get', 'event.orders:read', 'revokedsecrets/1/', 404), ('get', 'event.orders:read', 'revokedsecrets/1/', 404),
@@ -118,12 +118,15 @@ event_permission_sub_urls = [
('delete', 'event.items:write', 'items/1/addons/1/', 404), ('delete', 'event.items:write', 'items/1/addons/1/', 404),
('get', None, 'subevents/', 200), ('get', None, 'subevents/', 200),
('get', None, 'subevents/1/', 404), ('get', None, 'subevents/1/', 404),
('post', 'event.subevents:write', 'subevents/', 400),
('patch', 'event.subevents:write', 'subevents/1/', 404),
('put', 'event.subevents:write', 'subevents/1/', 404),
('get', None, 'taxrules/', 200), ('get', None, 'taxrules/', 200),
('get', None, 'taxrules/1/', 404), ('get', None, 'taxrules/1/', 404),
('post', 'event.settings.general:write', 'taxrules/', 400), ('post', 'event.settings.tax:write', 'taxrules/', 400),
('put', 'event.settings.general:write', 'taxrules/1/', 404), ('put', 'event.settings.tax:write', 'taxrules/1/', 404),
('patch', 'event.settings.general:write', 'taxrules/1/', 404), ('patch', 'event.settings.tax:write', 'taxrules/1/', 404),
('delete', 'event.settings.general:write', 'taxrules/1/', 404), ('delete', 'event.settings.tax:write', 'taxrules/1/', 404),
('get', 'event.settings.general:write', 'sendmail_rules/', 200), ('get', 'event.settings.general:write', 'sendmail_rules/', 200),
('get', 'event.settings.general:write', 'sendmail_rules/1/', 404), ('get', 'event.settings.general:write', 'sendmail_rules/1/', 404),
('post', 'event.settings.general:write', 'sendmail_rules/', 400), ('post', 'event.settings.general:write', 'sendmail_rules/', 400),
@@ -214,16 +217,16 @@ org_permission_sub_urls = [
('put', 'organizer.settings.general:write', 'webhooks/1/', 404), ('put', 'organizer.settings.general:write', 'webhooks/1/', 404),
('patch', 'organizer.settings.general:write', 'webhooks/1/', 404), ('patch', 'organizer.settings.general:write', 'webhooks/1/', 404),
('delete', 'organizer.settings.general:write', 'webhooks/1/', 404), ('delete', 'organizer.settings.general:write', 'webhooks/1/', 404),
('get', 'organizer.customers:write', 'customers/', 200), ('get', 'organizer.customers:read', 'customers/', 200),
('post', 'organizer.customers:write', 'customers/', 201), ('post', 'organizer.customers:write', 'customers/', 201),
('get', 'organizer.customers:write', 'customers/1/', 404), ('get', 'organizer.customers:read', 'customers/1/', 404),
('patch', 'organizer.customers:write', 'customers/1/', 404), ('patch', 'organizer.customers:write', 'customers/1/', 404),
('post', 'organizer.customers:write', 'customers/1/anonymize/', 404), ('post', 'organizer.customers:write', 'customers/1/anonymize/', 404),
('put', 'organizer.customers:write', 'customers/1/', 404), ('put', 'organizer.customers:write', 'customers/1/', 404),
('delete', 'organizer.customers:write', 'customers/1/', 404), ('delete', 'organizer.customers:write', 'customers/1/', 404),
('get', 'organizer.customers:write', 'memberships/', 200), ('get', 'organizer.customers:read', 'memberships/', 200),
('post', 'organizer.customers:write', 'memberships/', 400), ('post', 'organizer.customers:write', 'memberships/', 400),
('get', 'organizer.customers:write', 'memberships/1/', 404), ('get', 'organizer.customers:read', 'memberships/1/', 404),
('patch', 'organizer.customers:write', 'memberships/1/', 404), ('patch', 'organizer.customers:write', 'memberships/1/', 404),
('put', 'organizer.customers:write', 'memberships/1/', 404), ('put', 'organizer.customers:write', 'memberships/1/', 404),
('delete', 'organizer.customers:write', 'memberships/1/', 404), ('delete', 'organizer.customers:write', 'memberships/1/', 404),
@@ -239,18 +242,18 @@ org_permission_sub_urls = [
('patch', 'organizer.settings.general:write', 'membershiptypes/1/', 404), ('patch', 'organizer.settings.general:write', 'membershiptypes/1/', 404),
('put', 'organizer.settings.general:write', 'membershiptypes/1/', 404), ('put', 'organizer.settings.general:write', 'membershiptypes/1/', 404),
('delete', 'organizer.settings.general:write', 'membershiptypes/1/', 404), ('delete', 'organizer.settings.general:write', 'membershiptypes/1/', 404),
('get', 'organizer.giftcards:write', 'giftcards/', 200), ('get', 'organizer.giftcards:read', 'giftcards/', 200),
('post', 'organizer.giftcards:write', 'giftcards/', 400), ('post', 'organizer.giftcards:write', 'giftcards/', 400),
('get', 'organizer.giftcards:write', 'giftcards/1/', 404), ('get', 'organizer.giftcards:read', 'giftcards/1/', 404),
('put', 'organizer.giftcards:write', 'giftcards/1/', 404), ('put', 'organizer.giftcards:write', 'giftcards/1/', 404),
('patch', 'organizer.giftcards:write', 'giftcards/1/', 404), ('patch', 'organizer.giftcards:write', 'giftcards/1/', 404),
('get', 'organizer.giftcards:write', 'giftcards/1/transactions/', 404), ('get', 'organizer.giftcards:read', 'giftcards/1/transactions/', 404),
('get', 'organizer.giftcards:write', 'giftcards/1/transactions/1/', 404), ('get', 'organizer.giftcards:read', 'giftcards/1/transactions/1/', 404),
('get', 'organizer.settings.general:write', 'devices/', 200), ('get', 'organizer.devices:read', 'devices/', 200),
('post', 'organizer.settings.general:write', 'devices/', 400), ('post', 'organizer.devices:write', 'devices/', 400),
('get', 'organizer.settings.general:write', 'devices/1/', 404), ('get', 'organizer.devices:read', 'devices/1/', 404),
('put', 'organizer.settings.general:write', 'devices/1/', 404), ('put', 'organizer.devices:write', 'devices/1/', 404),
('patch', 'organizer.settings.general:write', 'devices/1/', 404), ('patch', 'organizer.devices:write', 'devices/1/', 404),
('get', 'organizer.teams:write', 'teams/', 200), ('get', 'organizer.teams:write', 'teams/', 200),
('post', 'organizer.teams:write', 'teams/', 400), ('post', 'organizer.teams:write', 'teams/', 400),
('get', 'organizer.teams:write', 'teams/{team_id}/', 200), ('get', 'organizer.teams:write', 'teams/{team_id}/', 200),
@@ -266,7 +269,11 @@ org_permission_sub_urls = [
('get', 'organizer.teams:write', 'teams/{team_id}/tokens/0/', 404), ('get', 'organizer.teams:write', 'teams/{team_id}/tokens/0/', 404),
('delete', 'organizer.teams:write', 'teams/{team_id}/tokens/0/', 404), ('delete', 'organizer.teams:write', 'teams/{team_id}/tokens/0/', 404),
('post', 'organizer.teams:write', 'teams/{team_id}/tokens/', 400), ('post', 'organizer.teams:write', 'teams/{team_id}/tokens/', 400),
('get', 'organizer.reusablemedia:read', 'reusablemedia/', 200),
('get', 'organizer.reusablemedia:read', 'reusablemedia/1/', 404), ('get', 'organizer.reusablemedia:read', 'reusablemedia/1/', 404),
('post', 'organizer.reusablemedia:write', 'reusablemedia/', 400),
('patch', 'organizer.reusablemedia:write', 'reusablemedia/1/', 404),
('put', 'organizer.reusablemedia:write', 'reusablemedia/1/', 404),
] ]

View File

@@ -119,7 +119,39 @@ def test_medium_list(token_client, organizer, event, medium):
@pytest.mark.django_db @pytest.mark.django_db
def test_medium_detail(token_client, organizer, event, medium, giftcard, customer): def test_medium_detail_permission_missing(token_client, organizer, event, medium, giftcard, customer, team):
team.all_organizer_permissions = False
team.limit_organizer_permissions = {
"organizer.reusablemedia:read": True,
}
team.save()
resp = token_client.get(
'/api/v1/organizers/{}/reusablemedia/{}/?expand=linked_giftcard'.format(
organizer.slug, medium.pk
)
)
assert resp.status_code == 403
assert "No permission to access gift card details." in str(resp.data)
resp = token_client.get(
'/api/v1/organizers/{}/reusablemedia/{}/?expand=customer'.format(
organizer.slug, medium.pk
)
)
assert resp.status_code == 403
assert "No permission to access customer details." in str(resp.data)
@pytest.mark.django_db
def test_medium_detail(token_client, organizer, event, medium, giftcard, customer, team):
team.all_organizer_permissions = False
team.limit_organizer_permissions = {
"organizer.reusablemedia:read": True,
"organizer.customers:read": True,
"organizer.giftcards:read": True,
}
team.save()
res = dict(TEST_MEDIUM_RES) res = dict(TEST_MEDIUM_RES)
res["id"] = medium.pk res["id"] = medium.pk
res["created"] = medium.created.isoformat().replace('+00:00', 'Z') res["created"] = medium.created.isoformat().replace('+00:00', 'Z')
@@ -340,7 +372,16 @@ def test_medium_lookup_not_found(token_client, organizer, organizer2, medium):
@pytest.mark.django_db @pytest.mark.django_db
def test_medium_lookup_autocreate(token_client, organizer): def test_medium_lookup_autocreate(token_client, organizer, team):
team.all_organizer_permissions = False
team.limit_organizer_permissions = {
"organizer.reusablemedia:read": True,
"organizer.reusablemedia:write": True,
"organizer.customers:read": True,
"organizer.giftcards:read": True,
}
team.save()
# Disabled # Disabled
resp = token_client.post( resp = token_client.post(
'/api/v1/organizers/{}/reusablemedia/lookup/'.format(organizer.slug), '/api/v1/organizers/{}/reusablemedia/lookup/'.format(organizer.slug),
@@ -386,7 +427,15 @@ def test_medium_lookup_autocreate(token_client, organizer):
@pytest.mark.django_db @pytest.mark.django_db
def test_medium_autocreate_giftcard(token_client, organizer): def test_medium_autocreate_giftcard(token_client, organizer, team):
team.all_organizer_permissions = False
team.limit_organizer_permissions = {
"organizer.reusablemedia:write": True,
"organizer.reusablemedia:read": True,
"organizer.customers:read": True,
"organizer.giftcards:read": True,
}
team.save()
organizer.settings.reusable_media_type_nfc_mf0aes_autocreate_giftcard = True organizer.settings.reusable_media_type_nfc_mf0aes_autocreate_giftcard = True
organizer.settings.reusable_media_type_nfc_mf0aes_autocreate_giftcard_currency = 'USD' organizer.settings.reusable_media_type_nfc_mf0aes_autocreate_giftcard_currency = 'USD'
resp = token_client.post( resp = token_client.post(

View File

@@ -175,8 +175,6 @@ def test_team_update_legacy_add_perm(token_client, organizer, event, second_team
assert second_team.limit_event_permissions == { assert second_team.limit_event_permissions == {
"event.settings.general:write": True, "event.settings.general:write": True,
"event.settings.payment:write": True, "event.settings.payment:write": True,
"event.settings.plugins:write": True,
"event.settings.email.sender:write": True,
"event.settings.tax:write": True, "event.settings.tax:write": True,
"event.settings.invoicing:write": True, "event.settings.invoicing:write": True,
"event.subevents:write": True, "event.subevents:write": True,

View File

@@ -57,9 +57,9 @@ class ItemFormTest(SoupTest):
date_from=datetime.datetime(2013, 12, 26, tzinfo=datetime.timezone.utc), date_from=datetime.datetime(2013, 12, 26, tzinfo=datetime.timezone.utc),
) )
self.item1 = Item.objects.create(event=self.event1, name="Standard", default_price=0, position=1) self.item1 = Item.objects.create(event=self.event1, name="Standard", default_price=0, position=1)
t = Team.objects.create(organizer=self.orga1, all_event_permissions=True) self.team = Team.objects.create(organizer=self.orga1, all_event_permissions=True)
t.members.add(self.user) self.team.members.add(self.user)
t.limit_events.add(self.event1) self.team.limit_events.add(self.event1)
self.client.login(email='dummy@dummy.dummy', password='dummy') self.client.login(email='dummy@dummy.dummy', password='dummy')
@@ -270,6 +270,14 @@ class QuestionsTest(ItemFormTest):
tbl = doc.select('.container-fluid table.table-bordered tbody')[0] tbl = doc.select('.container-fluid table.table-bordered tbody')[0]
assert tbl.select('tr')[0].select('td')[0].text.strip() == '42' assert tbl.select('tr')[0].select('td')[0].text.strip() == '42'
# Test permission requirement
self.team.all_event_permissions = False
self.team.limit_event_permissions = {}
self.team.save()
doc = self.get_doc('/control/event/%s/%s/questions/%s/' % (self.orga1.slug, self.event1.slug, c.id))
assert not doc.select('.container-fluid table.table-bordered tbody')
assert doc.select('.empty-collection')
def test_set_dependency(self): def test_set_dependency(self):
with scopes_disabled(): with scopes_disabled():
q1 = Question.objects.create(event=self.event1, question="What country are you from?", type="C", required=True) q1 = Question.objects.create(event=self.event1, question="What country are you from?", type="C", required=True)

View File

@@ -44,7 +44,7 @@ from pretix.base.models import Event, Order, Organizer, Team, User
@pytest.fixture @pytest.fixture
def env(): def env():
o = Organizer.objects.create(name='Dummy', slug='dummy') o = Organizer.objects.create(name='Dummy', slug='dummy', plugins='pretix.plugins.banktransfer')
event = Event.objects.create( event = Event.objects.create(
organizer=o, name='Dummy', slug='dummy', organizer=o, name='Dummy', slug='dummy',
date_from=now(), plugins='pretix.plugins.banktransfer' date_from=now(), plugins='pretix.plugins.banktransfer'
@@ -311,29 +311,29 @@ event_permission_urls = [
("event.settings.general:write", "delete/", 200, HTTP_GET), ("event.settings.general:write", "delete/", 200, HTTP_GET),
("event.settings.general:write", "dangerzone/", 200, HTTP_GET), ("event.settings.general:write", "dangerzone/", 200, HTTP_GET),
("event.settings.general:write", "settings/", 200, HTTP_GET), ("event.settings.general:write", "settings/", 200, HTTP_GET),
("event.settings.general:write", "settings/plugins", 200, HTTP_GET), # ("event.settings.payment:write", "settings/payment", 200, HTTP_GET), GET allowed also with other permissions
("event.settings.general:write", "settings/payment", 200, HTTP_GET), ("event.settings.payment:write", "settings/payment", 200, HTTP_POST),
("event.settings.payment:write", "settings/payment/banktransfer", 200, HTTP_GET),
("event.settings.general:write", "settings/tickets", 200, HTTP_GET), ("event.settings.general:write", "settings/tickets", 200, HTTP_GET),
("event.settings.general:write", "settings/email", 200, HTTP_GET), ("event.settings.general:write", "settings/email", 200, HTTP_GET),
("event.settings.general:write", "settings/email/setup", 200, HTTP_GET), ("event.settings.general:write", "settings/email/setup", 200, HTTP_GET),
("event.settings.general:write", "settings/cancel", 200, HTTP_GET), ("event.settings.general:write", "settings/cancel", 200, HTTP_GET),
("event.settings.general:write", "settings/invoice", 200, HTTP_GET),
("event.settings.general:write", "settings/widget", 200, HTTP_GET), ("event.settings.general:write", "settings/widget", 200, HTTP_GET),
("event.settings.general:write", "settings/invoice/preview", 200, HTTP_GET), ("event.settings.invoicing:write", "settings/invoice", 200, HTTP_GET),
("event.settings.general:write", "settings/tax/", 200, HTTP_GET), ("event.settings.invoicing:write", "settings/invoice/preview", 200, HTTP_GET),
("event.settings.general:write", "settings/tax/1/", 404, HTTP_GET), ("event.settings.tax:write", "settings/tax/", 200, HTTP_GET),
("event.settings.general:write", "settings/tax/add", 200, HTTP_GET), ("event.settings.tax:write", "settings/tax/1/", 404, HTTP_GET),
("event.settings.general:write", "settings/tax/1/delete", 404, HTTP_GET), ("event.settings.tax:write", "settings/tax/add", 200, HTTP_GET),
("event.settings.general:write", "settings/tax/1/default", 404, HTTP_POST), ("event.settings.tax:write", "settings/tax/1/delete", 404, HTTP_GET),
("event.settings.tax:write", "settings/tax/1/default", 404, HTTP_POST),
("event.settings.general:write", "comment/", 405, HTTP_GET), ("event.settings.general:write", "comment/", 405, HTTP_GET),
# Lists are currently not access-controlled (None, "items/", 200, HTTP_GET),
# ("event.items:write", "items/", 200),
("event.items:write", "items/add", 200, HTTP_GET), ("event.items:write", "items/add", 200, HTTP_GET),
("event.items:write", "items/1/up", 404, HTTP_POST), ("event.items:write", "items/1/up", 404, HTTP_POST),
("event.items:write", "items/1/down", 404, HTTP_POST), ("event.items:write", "items/1/down", 404, HTTP_POST),
("event.items:write", "items/reorder/2/", 400, HTTP_POST), ("event.items:write", "items/reorder/2/", 400, HTTP_POST),
("event.items:write", "items/1/delete", 404, HTTP_GET), ("event.items:write", "items/1/delete", 404, HTTP_GET),
# ("event.items:write", "categories/", 200), (None, "categories/", 200, HTTP_GET),
# We don't have to create categories and similar objects # We don't have to create categories and similar objects
# for testing this, it is enough to test that a 404 error # for testing this, it is enough to test that a 404 error
# is returned instead of a 403 one. # is returned instead of a 403 one.
@@ -343,29 +343,30 @@ event_permission_urls = [
("event.items:write", "categories/2/down", 404, HTTP_POST), ("event.items:write", "categories/2/down", 404, HTTP_POST),
("event.items:write", "categories/reorder", 400, HTTP_POST), ("event.items:write", "categories/reorder", 400, HTTP_POST),
("event.items:write", "categories/add", 200, HTTP_GET), ("event.items:write", "categories/add", 200, HTTP_GET),
# ("event.items:write", "questions/", 200, HTTP_GET), (None, "questions/", 200, HTTP_GET),
("event.items:write", "questions/2/", 404, HTTP_GET), (None, "questions/2/", 404, HTTP_GET),
("event.items:write", "questions/2/delete", 404, HTTP_GET), ("event.items:write", "questions/2/delete", 404, HTTP_GET),
("event.items:write", "questions/reorder", 400, HTTP_POST), ("event.items:write", "questions/reorder", 400, HTTP_POST),
("event.items:write", "questions/add", 200, HTTP_GET), ("event.items:write", "questions/add", 200, HTTP_GET),
# ("event.items:write", "quotas/", 200, HTTP_GET), (None, "quotas/", 200, HTTP_GET),
("event.items:write", "quotas/2/change", 404, HTTP_GET), ("event.items:write", "quotas/2/change", 404, HTTP_GET),
("event.items:write", "quotas/2/delete", 404, HTTP_GET), ("event.items:write", "quotas/2/delete", 404, HTTP_GET),
("event.items:write", "quotas/add", 200, HTTP_GET), ("event.items:write", "quotas/add", 200, HTTP_GET),
# ("event.items:write", "discounts/", 200), (None, "discounts/", 200, HTTP_GET),
# We don't have to create categories and similar objects
# for testing this, it is enough to test that a 404 error
# is returned instead of a 403 one.
("event.items:write", "discounts/2/", 404, HTTP_GET), ("event.items:write", "discounts/2/", 404, HTTP_GET),
("event.items:write", "discounts/2/delete", 404, HTTP_GET), ("event.items:write", "discounts/2/delete", 404, HTTP_GET),
("event.items:write", "discounts/2/up", 404, HTTP_POST), ("event.items:write", "discounts/2/up", 404, HTTP_POST),
("event.items:write", "discounts/2/down", 404, HTTP_POST), ("event.items:write", "discounts/2/down", 404, HTTP_POST),
("event.items:write", "discounts/reorder", 400, HTTP_POST), ("event.items:write", "discounts/reorder", 400, HTTP_POST),
("event.items:write", "discounts/add", 200, HTTP_GET), ("event.items:write", "discounts/add", 200, HTTP_GET),
("event.settings.general:write", "subevents/", 200, HTTP_GET), (None, "subevents/", 200, HTTP_GET),
("event.settings.general:write", "subevents/2/", 404, HTTP_GET), ("event.subevents:write", "subevents/2/", 404, HTTP_GET),
("event.settings.general:write", "subevents/2/delete", 404, HTTP_GET), ("event.subevents:write", "subevents/2/", 404, HTTP_POST),
("event.settings.general:write", "subevents/add", 200, HTTP_GET), ("event.subevents:write", "subevents/2/delete", 404, HTTP_GET),
("event.subevents:write", "subevents/add", 200, HTTP_GET),
("event.subevents:write", "subevents/bulk_add", 200, HTTP_GET),
("event.subevents:write", "subevents/bulk_action", 302, HTTP_POST),
("event.subevents:write", "subevents/bulk_edit", 404, HTTP_POST),
("event.orders:read", "orders/overview/", 200, HTTP_GET), ("event.orders:read", "orders/overview/", 200, HTTP_GET),
("event.orders:read", "orders/export/", 200, HTTP_GET), ("event.orders:read", "orders/export/", 200, HTTP_GET),
("event.orders:read", "orders/export/do", 302, HTTP_POST), ("event.orders:read", "orders/export/do", 302, HTTP_POST),
@@ -389,7 +390,7 @@ event_permission_urls = [
("event.orders:write", "orders/import/", 200, HTTP_GET), ("event.orders:write", "orders/import/", 200, HTTP_GET),
("event.orders:write", "orders/import/0ab7b081-92d3-4480-82de-2f8b056fd32f/", 404, HTTP_GET), ("event.orders:write", "orders/import/0ab7b081-92d3-4480-82de-2f8b056fd32f/", 404, HTTP_GET),
("event.orders:read", "orders/FOO/answer/5/", 404, HTTP_GET), ("event.orders:read", "orders/FOO/answer/5/", 404, HTTP_GET),
("event.orders:write", "cancel/", 200, HTTP_GET), ("event:cancel", "cancel/", 200, HTTP_GET),
("event.vouchers:write", "vouchers/add", 200, HTTP_GET), ("event.vouchers:write", "vouchers/add", 200, HTTP_GET),
("event.vouchers:write", "vouchers/bulk_add", 200, HTTP_GET), ("event.vouchers:write", "vouchers/bulk_add", 200, HTTP_GET),
("event.vouchers:read", "vouchers/", 200, HTTP_GET), ("event.vouchers:read", "vouchers/", 200, HTTP_GET),
@@ -425,6 +426,8 @@ def test_wrong_event_permission(perf_patch, client, env, perm, url, code, http_m
t = Team( t = Team(
pk=2, organizer=env[2], all_events=True pk=2, organizer=env[2], all_events=True
) )
if not perm:
pytest.skip()
t.all_event_permissions = False t.all_event_permissions = False
t.limit_event_permissions.pop(perm, None) t.limit_event_permissions.pop(perm, None)
t.save() t.save()
@@ -539,17 +542,17 @@ organizer_permission_urls = [
("organizer.settings.general:write", "organizer/dummy/outgoingmails", 200), ("organizer.settings.general:write", "organizer/dummy/outgoingmails", 200),
("organizer.settings.general:write", "organizer/dummy/outgoingmail/1/", 404), ("organizer.settings.general:write", "organizer/dummy/outgoingmail/1/", 404),
("organizer.settings.general:write", "organizer/dummy/outgoingmail/bulk_action", 405), ("organizer.settings.general:write", "organizer/dummy/outgoingmail/bulk_action", 405),
("organizer.settings.general:write", "organizer/dummy/devices", 200), ("organizer.devices:read", "organizer/dummy/devices", 200),
("organizer.settings.general:write", "organizer/dummy/devices/select2", 200), ("organizer.devices:read", "organizer/dummy/devices/select2", 200),
("organizer.settings.general:write", "organizer/dummy/device/add", 200), ("organizer.devices:write", "organizer/dummy/device/add", 200),
("organizer.settings.general:write", "organizer/dummy/device/1/edit", 404), ("organizer.devices:write", "organizer/dummy/device/1/edit", 404),
("organizer.settings.general:write", "organizer/dummy/device/1/connect", 404), ("organizer.devices:write", "organizer/dummy/device/1/connect", 404),
("organizer.settings.general:write", "organizer/dummy/device/1/revoke", 404), ("organizer.devices:write", "organizer/dummy/device/1/revoke", 404),
("organizer.settings.general:write", "organizer/dummy/gates", 200), ("organizer.devices:read", "organizer/dummy/gates", 200),
("organizer.settings.general:write", "organizer/dummy/gates/select2", 200), ("organizer.devices:read", "organizer/dummy/gates/select2", 200),
("organizer.settings.general:write", "organizer/dummy/gate/add", 200), ("organizer.devices:write", "organizer/dummy/gate/add", 200),
("organizer.settings.general:write", "organizer/dummy/gate/1/edit", 404), ("organizer.devices:write", "organizer/dummy/gate/1/edit", 404),
("organizer.settings.general:write", "organizer/dummy/gate/1/delete", 404), ("organizer.devices:write", "organizer/dummy/gate/1/delete", 404),
("organizer.settings.general:write", "organizer/dummy/properties", 200), ("organizer.settings.general:write", "organizer/dummy/properties", 200),
("organizer.settings.general:write", "organizer/dummy/property/add", 200), ("organizer.settings.general:write", "organizer/dummy/property/add", 200),
("organizer.settings.general:write", "organizer/dummy/property/1/edit", 404), ("organizer.settings.general:write", "organizer/dummy/property/1/edit", 404),
@@ -566,12 +569,12 @@ organizer_permission_urls = [
("organizer.settings.general:write", "organizer/dummy/ssoprovider/add", 200), ("organizer.settings.general:write", "organizer/dummy/ssoprovider/add", 200),
("organizer.settings.general:write", "organizer/dummy/ssoprovider/1/edit", 404), ("organizer.settings.general:write", "organizer/dummy/ssoprovider/1/edit", 404),
("organizer.settings.general:write", "organizer/dummy/ssoprovider/1/delete", 404), ("organizer.settings.general:write", "organizer/dummy/ssoprovider/1/delete", 404),
("organizer.customers:write", "organizer/dummy/customers", 200), ("organizer.customers:read", "organizer/dummy/customers", 200),
("organizer.customers:write", "organizer/dummy/customer/ABC/edit", 404), ("organizer.customers:write", "organizer/dummy/customer/ABC/edit", 404),
("organizer.customers:write", "organizer/dummy/customer/ABC/anonymize", 404), ("organizer.customers:write", "organizer/dummy/customer/ABC/anonymize", 404),
("organizer.customers:write", "organizer/dummy/customer/ABC/membership/add", 404), ("organizer.customers:write", "organizer/dummy/customer/ABC/membership/add", 404),
("organizer.customers:write", "organizer/dummy/customer/ABC/membership/1/edit", 404), ("organizer.customers:write", "organizer/dummy/customer/ABC/membership/1/edit", 404),
("organizer.customers:write", "organizer/dummy/customer/ABC/", 404), ("organizer.customers:read", "organizer/dummy/customer/ABC/", 404),
("organizer.reusablemedia:read", "organizer/dummy/reusable_media", 200), ("organizer.reusablemedia:read", "organizer/dummy/reusable_media", 200),
("organizer.reusablemedia:write", "organizer/dummy/reusable_media/1/edit", 404), ("organizer.reusablemedia:write", "organizer/dummy/reusable_media/1/edit", 404),
("organizer.reusablemedia:read", "organizer/dummy/reusable_media/1/", 404), ("organizer.reusablemedia:read", "organizer/dummy/reusable_media/1/", 404),