Pluggable permissions (#5728)

* Data model draft

* Refactor query and assignment usages of old permissions

* Backend UI

* API serializer

* Big string replace

* Docs, tests and fixes for teams api

* Update docs for device auth

* Eliminate old names

* Make tests pass

* Use new permissions, remove inconsistencies

* Add test for translations

* Show plugin permissions

* Add permission for seating plans

* Fix plugin activation

* Fix failing test

* Refactor to permission groups

* Update doc/api/resources/devices.rst

Co-authored-by: luelista <weller@rami.io>

* Update doc/api/resources/events.rst

Co-authored-by: luelista <weller@rami.io>

* Update src/pretix/api/serializers/organizer.py

Co-authored-by: luelista <weller@rami.io>

* Fix typo

* Fix python version compat

* Replacement after rebase

* Add proper permission handling for exports

* Docs for exporters

* Runtime linting of permission names

* Fix typos

* Show export page even without orders permission

* More legacy compat

* Do not strongly validate before plugins are loaded

* Rebase migration

* Add permission for outgoing mails

* Review notes

* Update doc/api/resources/teams.rst

Co-authored-by: Richard Schreiber <schreiber@pretix.eu>

* Clean up logic around exporters

* Review and failures

* Fix migration leading to forbidden combination

* Handle permissions on event copying

* Remove print-statements

* Make test clearer

* Review feedback

* Add AnyPermissionOf

* migration safety

---------

Co-authored-by: luelista <weller@rami.io>
Co-authored-by: Richard Schreiber <schreiber@pretix.eu>
This commit is contained in:
Raphael Michel
2026-03-17 14:43:56 +01:00
committed by GitHub
parent eddde2b6c0
commit df0b580dd6
203 changed files with 5374 additions and 2331 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -21,12 +21,13 @@
#
from django.views.generic import TemplateView
from pretix.base.permissions import AnyPermissionOf
from pretix.control.permissions import EventPermissionRequiredMixin
from pretix.helpers.countries import CachedCountries
class IndexView(EventPermissionRequiredMixin, TemplateView):
permission = ('can_change_orders', 'can_checkin_orders')
permission = AnyPermissionOf('event.orders:write', 'event.orders:checkin')
template_name = 'pretixplugins/webcheckin/index.html'
def get_context_data(self, **kwargs):