@@ -259,31 +261,34 @@
{% endif %}
- {% if not e.voucher %}
-
-
+ {% if 'event.orders:write' in request.eventpermset %}
+ {% if not e.voucher %}
+
+
-
-
-
+
+
+
-
- {% else %}
-
-
-
+
+ {% else %}
+
+
+
+ {% endif %}
{% endif %}
|
diff --git a/src/pretix/control/views/checkin.py b/src/pretix/control/views/checkin.py
index dca7fa6dcf..6b35e18753 100644
--- a/src/pretix/control/views/checkin.py
+++ b/src/pretix/control/views/checkin.py
@@ -295,7 +295,7 @@ class CheckInListBulkActionView(CheckInListQueryMixin, EventPermissionRequiredMi
class CheckinListList(EventPermissionRequiredMixin, PaginationMixin, ListView):
model = CheckinList
context_object_name = 'checkinlists'
- permission = 'event.orders:read'
+ permission = ('event.orders:read', 'event.settings.general:write')
template_name = 'pretixcontrol/checkin/lists.html'
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
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,
- 'organizer.settings.general:write',
+ 'organizer.devices:read',
self.request
)
ctx['filter_form'] = self.filter_form
@@ -578,6 +578,12 @@ class CheckInResetView(CheckInListQueryMixin, EventPermissionRequiredMixin, Asyn
permission = "event.orders:write"
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):
return reverse(
"control:event.orders.checkinlists",
diff --git a/src/pretix/control/views/event.py b/src/pretix/control/views/event.py
index 29bb26232b..4ff84a45bd 100644
--- a/src/pretix/control/views/event.py
+++ b/src/pretix/control/views/event.py
@@ -502,7 +502,7 @@ class EventPlugins(EventSettingsViewMixin, EventPermissionRequiredMixin, Templat
class PaymentProviderSettings(EventSettingsViewMixin, EventPermissionRequiredMixin, TemplateView, SingleObjectMixin):
model = Event
context_object_name = 'event'
- permission = 'event.settings.general:write'
+ permission = 'event.settings.payment:write'
template_name = 'pretixcontrol/event/payment_provider.html'
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))
-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'
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:
return reverse('control:event.settings.payment', kwargs={
@@ -647,10 +665,11 @@ class PaymentSettings(EventSettingsViewMixin, EventSettingsFormView):
return context
-class TaxSettings(EventSettingsViewMixin, EventSettingsFormView):
+class TaxSettings(WritePermissionMixin, EventSettingsViewMixin, EventSettingsFormView):
template_name = 'pretixcontrol/event/tax.html'
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:
return reverse('control:event.settings.tax', kwargs={
@@ -666,11 +685,12 @@ class TaxSettings(EventSettingsViewMixin, EventSettingsFormView):
return context
-class InvoiceSettings(EventSettingsViewMixin, EventSettingsFormView):
+class InvoiceSettings(WritePermissionMixin, EventSettingsViewMixin, EventSettingsFormView):
model = Event
form_class = InvoiceSettingsForm
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):
types = get_transmission_types()
@@ -738,7 +758,7 @@ class CancelSettings(EventSettingsViewMixin, EventSettingsFormView):
class InvoicePreview(EventPermissionRequiredMixin, View):
- permission = 'event.settings.general:write'
+ permission = 'event.settings.invoicing:write'
def get(self, request, *args, **kwargs):
fname, ftype, fcontent = build_preview_invoice_pdf(request.event)
@@ -1297,7 +1317,7 @@ class TaxCreate(EventSettingsViewMixin, EventPermissionRequiredMixin, CreateView
model = TaxRule
form_class = TaxRuleForm
template_name = 'pretixcontrol/event/tax_edit.html'
- permission = 'event.settings.general:write'
+ permission = 'event.settings.tax:write'
context_object_name = 'taxrule'
def get_success_url(self) -> str:
@@ -1358,7 +1378,7 @@ class TaxUpdate(EventSettingsViewMixin, EventPermissionRequiredMixin, UpdateView
model = TaxRule
form_class = TaxRuleForm
template_name = 'pretixcontrol/event/tax_edit.html'
- permission = 'event.settings.general:write'
+ permission = 'event.settings.tax:write'
context_object_name = 'rule'
def get_object(self, queryset=None) -> TaxRule:
@@ -1422,7 +1442,7 @@ class TaxUpdate(EventSettingsViewMixin, EventPermissionRequiredMixin, UpdateView
class TaxDefault(EventSettingsViewMixin, EventPermissionRequiredMixin, DetailView):
model = TaxRule
- permission = 'event.settings.general:write'
+ permission = 'event.settings.tax:write'
def get_object(self, queryset=None) -> TaxRule:
try:
@@ -1467,7 +1487,7 @@ class TaxDefault(EventSettingsViewMixin, EventPermissionRequiredMixin, DetailVie
class TaxDelete(EventSettingsViewMixin, EventPermissionRequiredMixin, CompatDeleteView):
model = TaxRule
template_name = 'pretixcontrol/event/tax_delete.html'
- permission = 'event.settings.general:write'
+ permission = 'event.settings.tax:write'
context_object_name = 'taxrule'
def get_object(self, queryset=None) -> TaxRule:
diff --git a/src/pretix/control/views/item.py b/src/pretix/control/views/item.py
index a74dc7da78..179b8cd095 100644
--- a/src/pretix/control/views/item.py
+++ b/src/pretix/control/views/item.py
@@ -664,7 +664,7 @@ class QuestionMixin:
class QuestionView(EventPermissionRequiredMixin, ChartContainingView, DetailView):
model = Question
template_name = 'pretixcontrol/items/question.html'
- permission = 'event.items:write'
+ permission = None
template_name_field = 'question'
@cached_property
diff --git a/src/pretix/control/views/orders.py b/src/pretix/control/views/orders.py
index 0ae858fa39..e915867fb5 100644
--- a/src/pretix/control/views/orders.py
+++ b/src/pretix/control/views/orders.py
@@ -2961,7 +2961,7 @@ class RefundList(EventPermissionRequiredMixin, PaginationMixin, ListView):
class EventCancel(EventPermissionRequiredMixin, AsyncAction, FormView):
template_name = 'pretixcontrol/orders/cancel.html'
- permission = 'event.orders:write'
+ permission = 'event:cancel'
form_class = EventCancelForm
task = cancel_event
known_errortypes = ['OrderError']
diff --git a/src/pretix/control/views/organizer.py b/src/pretix/control/views/organizer.py
index e4a74f0041..a54dcfc310 100644
--- a/src/pretix/control/views/organizer.py
+++ b/src/pretix/control/views/organizer.py
@@ -1231,7 +1231,7 @@ class DeviceQueryMixin:
class DeviceListView(DeviceQueryMixin, OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, ListView):
model = Device
template_name = 'pretixcontrol/organizers/devices.html'
- permission = 'organizer.settings.general:write'
+ permission = 'organizer.devices:read'
context_object_name = 'devices'
paginate_by = 100
@@ -1244,7 +1244,7 @@ class DeviceListView(DeviceQueryMixin, OrganizerDetailViewMixin, OrganizerPermis
class DeviceCreateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, CreateView):
model = Device
template_name = 'pretixcontrol/organizers/device_edit.html'
- permission = 'organizer.settings.general:write'
+ permission = 'organizer.devices:write'
form_class = DeviceForm
def get_form_kwargs(self):
@@ -1275,7 +1275,7 @@ class DeviceCreateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixi
class DeviceLogView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, ListView):
template_name = 'pretixcontrol/organizers/device_logs.html'
- permission = 'organizer.settings.general:write'
+ permission = 'organizer.devices:read'
model = LogEntry
context_object_name = 'logs'
paginate_by = 20
@@ -1303,7 +1303,7 @@ class DeviceLogView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin,
class DeviceUpdateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, UpdateView):
model = Device
template_name = 'pretixcontrol/organizers/device_edit.html'
- permission = 'organizer.settings.general:write'
+ permission = 'organizer.devices:write'
context_object_name = 'device'
form_class = DeviceForm
@@ -1346,7 +1346,7 @@ class DeviceUpdateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixi
class DeviceBulkUpdateView(DeviceQueryMixin, OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, FormView):
template_name = 'pretixcontrol/organizers/device_bulk_edit.html'
- permission = 'organizer.settings.general:write'
+ permission = 'organizer.devices:write'
context_object_name = 'device'
form_class = DeviceBulkEditForm
@@ -1460,7 +1460,7 @@ class DeviceBulkUpdateView(DeviceQueryMixin, OrganizerDetailViewMixin, Organizer
class DeviceConnectView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, DetailView):
model = Device
template_name = 'pretixcontrol/organizers/device_connect.html'
- permission = 'organizer.settings.general:write'
+ permission = 'organizer.devices:write'
context_object_name = 'device'
def get_object(self, queryset=None):
@@ -1492,7 +1492,7 @@ class DeviceConnectView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMix
class DeviceRevokeView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, DetailView):
model = Device
template_name = 'pretixcontrol/organizers/device_revoke.html'
- permission = 'organizer.settings.general:write'
+ permission = 'organizer.devices:write'
context_object_name = 'device'
def get_object(self, queryset=None):
@@ -2307,7 +2307,7 @@ class RunScheduledExportView(OrganizerPermissionRequiredMixin, ExportMixin, View
class GateListView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, ListView):
model = Gate
template_name = 'pretixcontrol/organizers/gates.html'
- permission = 'organizer.settings.general:write'
+ permission = 'organizer.devices:read'
context_object_name = 'gates'
def get_queryset(self):
@@ -2317,7 +2317,7 @@ class GateListView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, L
class GateCreateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, CreateView):
model = Gate
template_name = 'pretixcontrol/organizers/gate_edit.html'
- permission = 'organizer.settings.general:write'
+ permission = 'organizer.devices:write'
form_class = GateForm
def get_form_kwargs(self):
@@ -2351,7 +2351,7 @@ class GateCreateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin,
class GateUpdateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, UpdateView):
model = Gate
template_name = 'pretixcontrol/organizers/gate_edit.html'
- permission = 'organizer.settings.general:write'
+ permission = 'organizer.devices:write'
context_object_name = 'gate'
form_class = GateForm
@@ -2386,7 +2386,7 @@ class GateUpdateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin,
class GateDeleteView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, CompatDeleteView):
model = Gate
template_name = 'pretixcontrol/organizers/gate_delete.html'
- permission = 'organizer.settings.general:write'
+ permission = 'organizer.devices:write'
context_object_name = 'gate'
def get_object(self, queryset=None):
@@ -2996,7 +2996,7 @@ class SSOClientDeleteView(OrganizerDetailViewMixin, OrganizerPermissionRequiredM
class CustomerListView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, PaginationMixin, ListView):
model = Customer
template_name = 'pretixcontrol/organizers/customers.html'
- permission = 'organizer.customers:write'
+ permission = 'organizer.customers:read'
context_object_name = 'customers'
def get_queryset(self):
@@ -3017,7 +3017,7 @@ class CustomerListView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixi
class CustomerDetailView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, PaginationMixin, ListView):
template_name = 'pretixcontrol/organizers/customer.html'
- permission = 'organizer.customers:write'
+ permission = 'organizer.customers:read'
context_object_name = 'orders'
def get_queryset(self):
diff --git a/src/pretix/control/views/subevents.py b/src/pretix/control/views/subevents.py
index f1a08cd12e..4ecf6a8237 100644
--- a/src/pretix/control/views/subevents.py
+++ b/src/pretix/control/views/subevents.py
@@ -117,7 +117,7 @@ class SubEventList(EventPermissionRequiredMixin, PaginationMixin, SubEventQueryM
model = SubEvent
context_object_name = 'subevents'
template_name = 'pretixcontrol/subevents/index.html'
- permission = 'event.settings.general:write'
+ permission = None
def get_queryset(self):
return super().get_queryset(True).prefetch_related(
@@ -156,7 +156,7 @@ class SubEventList(EventPermissionRequiredMixin, PaginationMixin, SubEventQueryM
class SubEventDelete(EventPermissionRequiredMixin, CompatDeleteView):
model = SubEvent
template_name = 'pretixcontrol/subevents/delete.html'
- permission = 'event.settings.general:write'
+ permission = 'event.subevents:write'
context_object_name = 'subevents'
def get_object(self, queryset=None) -> SubEvent:
@@ -508,7 +508,7 @@ class SubEventEditorMixin(MetaDataEditorMixin):
class SubEventUpdate(EventPermissionRequiredMixin, SubEventEditorMixin, UpdateView):
model = SubEvent
template_name = 'pretixcontrol/subevents/detail.html'
- permission = 'event.settings.general:write'
+ permission = 'event.subevents:write'
context_object_name = 'subevent'
form_class = SubEventForm
@@ -575,7 +575,7 @@ class SubEventUpdate(EventPermissionRequiredMixin, SubEventEditorMixin, UpdateVi
class SubEventCreate(SubEventEditorMixin, EventPermissionRequiredMixin, CreateView):
model = SubEvent
template_name = 'pretixcontrol/subevents/detail.html'
- permission = 'event.settings.general:write'
+ permission = 'event.subevents:write'
context_object_name = 'subevent'
form_class = SubEventForm
@@ -669,7 +669,7 @@ class SubEventCreate(SubEventEditorMixin, EventPermissionRequiredMixin, CreateVi
class SubEventBulkAction(SubEventQueryMixin, EventPermissionRequiredMixin, View):
- permission = 'event.settings.general:write'
+ permission = 'event.subevents:write'
@transaction.atomic
def post(self, request, *args, **kwargs):
@@ -740,7 +740,7 @@ class SubEventBulkAction(SubEventQueryMixin, EventPermissionRequiredMixin, View)
class SubEventBulkCreate(SubEventEditorMixin, EventPermissionRequiredMixin, AsyncFormView):
model = SubEvent
template_name = 'pretixcontrol/subevents/bulk.html'
- permission = 'event.settings.general:write'
+ permission = 'event.subevents:write'
context_object_name = 'subevent'
form_class = SubEventBulkForm
itemformclass = BulkSubEventItemForm
@@ -1065,7 +1065,7 @@ class SubEventBulkCreate(SubEventEditorMixin, EventPermissionRequiredMixin, Asyn
class SubEventBulkEdit(SubEventQueryMixin, EventPermissionRequiredMixin, FormView):
- permission = 'event.settings.general:write'
+ permission = 'event.subevents:write'
form_class = SubEventBulkEditForm
template_name = 'pretixcontrol/subevents/bulk_edit.html'
context_object_name = 'subevent'
@@ -1170,7 +1170,10 @@ class SubEventBulkEdit(SubEventQueryMixin, EventPermissionRequiredMixin, FormVie
kwargs = {}
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(
SubEvent, Quota,
diff --git a/src/pretix/control/views/typeahead.py b/src/pretix/control/views/typeahead.py
index 55676efa94..73213472bf 100644
--- a/src/pretix/control/views/typeahead.py
+++ b/src/pretix/control/views/typeahead.py
@@ -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):
+ 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', '')
try:
page = int(request.GET.get('page', '1'))
@@ -1051,10 +1057,16 @@ def devices_select2(request, **kwargs):
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):
+ 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', '')
try:
page = int(request.GET.get('page', '1'))
diff --git a/src/pretix/helpers/permission_migration.py b/src/pretix/helpers/permission_migration.py
index f421df0052..f6d88710db 100644
--- a/src/pretix/helpers/permission_migration.py
+++ b/src/pretix/helpers/permission_migration.py
@@ -28,8 +28,6 @@ OLD_TO_NEW_EVENT_MIGRATION = {
"can_change_event_settings": [
"event.settings.general:write",
"event.settings.payment:write",
- "event.settings.plugins:write",
- "event.settings.email.sender:write",
"event.settings.tax:write",
"event.settings.invoicing:write",
"event.subevents:write",
diff --git a/src/pretix/static/pretixbase/js/addressform.js b/src/pretix/static/pretixbase/js/addressform.js
index c4298b97f3..21604f5f0a 100644
--- a/src/pretix/static/pretixbase/js/addressform.js
+++ b/src/pretix/static/pretixbase/js/addressform.js
@@ -33,6 +33,13 @@ $(function () {
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)) {
// No address fields found, do not create request
return;
@@ -101,7 +108,7 @@ $(function () {
label.append('
' + gettext('required') + '')
}
}
- for (var k in dependents) dependents[k].prop("disabled", false);
+ for (var k in dependents) dependents[k].prop("disabled", dependentsDisabled.includes(k));
loader.hide();
}
@@ -158,7 +165,7 @@ $(function () {
required = false;
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 () {
loader.hide();
diff --git a/src/pretix/static/pretixcontrol/js/ui/main.js b/src/pretix/static/pretixcontrol/js/ui/main.js
index ed24303c2a..77f0cb2215 100644
--- a/src/pretix/static/pretixcontrol/js/ui/main.js
+++ b/src/pretix/static/pretixcontrol/js/ui/main.js
@@ -340,11 +340,12 @@ var form_handlers = function (el) {
}
el.find("input[data-checkbox-dependency]").each(function () {
+ var initially_disabled = $(this).prop("disabled");
var dependent = $(this),
dependency = findDependency($(this).attr("data-checkbox-dependency"), this),
update = function () {
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]')) {
dependent.prop('checked', false);
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 () {
+ var initially_disabled = $(this).prop("disabled");
var dependent = $(this),
dependency = findDependency($(this).attr("data-display-dependency"), this),
update = function (ev) {
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') {
return d.checked;
} else if (d.type === 'select-one') {
@@ -391,7 +393,7 @@ var form_handlers = function (el) {
}
var $toggling = 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()
if (tagName !== "div" && tagName !== "button") {
diff --git a/src/tests/api/test_events.py b/src/tests/api/test_events.py
index 18f63caf35..08d999d2eb 100644
--- a/src/tests/api/test_events.py
+++ b/src/tests/api/test_events.py
@@ -1387,7 +1387,14 @@ def test_get_event_settings(token_client, organizer, event):
@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'
resp = token_client.patch(
'/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()
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
def test_patch_event_settings_validation(token_client, organizer, event):
diff --git a/src/tests/api/test_permissions.py b/src/tests/api/test_permissions.py
index 9c73ce0881..b11e97251b 100644
--- a/src/tests/api/test_permissions.py
+++ b/src/tests/api/test_permissions.py
@@ -59,7 +59,7 @@ event_urls = [
]
event_permission_sub_urls = [
- ('get', 'event.settings.general:write', 'settings/', 200),
+ ('get', None, 'settings/', 200),
('patch', 'event.settings.general:write', 'settings/', 200),
('get', 'event.orders:read', 'revokedsecrets/', 200),
('get', 'event.orders:read', 'revokedsecrets/1/', 404),
@@ -118,12 +118,15 @@ event_permission_sub_urls = [
('delete', 'event.items:write', 'items/1/addons/1/', 404),
('get', None, 'subevents/', 200),
('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/1/', 404),
- ('post', 'event.settings.general:write', 'taxrules/', 400),
- ('put', 'event.settings.general:write', 'taxrules/1/', 404),
- ('patch', 'event.settings.general:write', 'taxrules/1/', 404),
- ('delete', 'event.settings.general:write', 'taxrules/1/', 404),
+ ('post', 'event.settings.tax:write', 'taxrules/', 400),
+ ('put', 'event.settings.tax:write', 'taxrules/1/', 404),
+ ('patch', 'event.settings.tax: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/1/', 404),
('post', 'event.settings.general:write', 'sendmail_rules/', 400),
@@ -214,16 +217,16 @@ org_permission_sub_urls = [
('put', 'organizer.settings.general:write', 'webhooks/1/', 404),
('patch', '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),
- ('get', 'organizer.customers:write', 'customers/1/', 404),
+ ('get', 'organizer.customers:read', 'customers/1/', 404),
('patch', 'organizer.customers:write', 'customers/1/', 404),
('post', 'organizer.customers:write', 'customers/1/anonymize/', 404),
('put', '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),
- ('get', 'organizer.customers:write', 'memberships/1/', 404),
+ ('get', 'organizer.customers:read', 'memberships/1/', 404),
('patch', 'organizer.customers:write', 'memberships/1/', 404),
('put', '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),
('put', '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),
- ('get', 'organizer.giftcards:write', 'giftcards/1/', 404),
+ ('get', 'organizer.giftcards:read', 'giftcards/1/', 404),
('put', 'organizer.giftcards:write', 'giftcards/1/', 404),
('patch', 'organizer.giftcards:write', 'giftcards/1/', 404),
- ('get', 'organizer.giftcards:write', 'giftcards/1/transactions/', 404),
- ('get', 'organizer.giftcards:write', 'giftcards/1/transactions/1/', 404),
- ('get', 'organizer.settings.general:write', 'devices/', 200),
- ('post', 'organizer.settings.general:write', 'devices/', 400),
- ('get', 'organizer.settings.general:write', 'devices/1/', 404),
- ('put', 'organizer.settings.general:write', 'devices/1/', 404),
- ('patch', 'organizer.settings.general:write', 'devices/1/', 404),
+ ('get', 'organizer.giftcards:read', 'giftcards/1/transactions/', 404),
+ ('get', 'organizer.giftcards:read', 'giftcards/1/transactions/1/', 404),
+ ('get', 'organizer.devices:read', 'devices/', 200),
+ ('post', 'organizer.devices:write', 'devices/', 400),
+ ('get', 'organizer.devices:read', 'devices/1/', 404),
+ ('put', 'organizer.devices:write', 'devices/1/', 404),
+ ('patch', 'organizer.devices:write', 'devices/1/', 404),
('get', 'organizer.teams:write', 'teams/', 200),
('post', 'organizer.teams:write', 'teams/', 400),
('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),
('delete', 'organizer.teams:write', 'teams/{team_id}/tokens/0/', 404),
('post', 'organizer.teams:write', 'teams/{team_id}/tokens/', 400),
+ ('get', 'organizer.reusablemedia:read', 'reusablemedia/', 200),
('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),
]
diff --git a/src/tests/api/test_reusable_media.py b/src/tests/api/test_reusable_media.py
index 2938e524a6..30614e67be 100644
--- a/src/tests/api/test_reusable_media.py
+++ b/src/tests/api/test_reusable_media.py
@@ -119,7 +119,39 @@ def test_medium_list(token_client, organizer, event, medium):
@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["id"] = medium.pk
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
-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
resp = token_client.post(
'/api/v1/organizers/{}/reusablemedia/lookup/'.format(organizer.slug),
@@ -386,7 +427,15 @@ def test_medium_lookup_autocreate(token_client, organizer):
@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_currency = 'USD'
resp = token_client.post(
diff --git a/src/tests/api/test_teams.py b/src/tests/api/test_teams.py
index 7792e6b1dd..96ab60c2ee 100644
--- a/src/tests/api/test_teams.py
+++ b/src/tests/api/test_teams.py
@@ -175,8 +175,6 @@ def test_team_update_legacy_add_perm(token_client, organizer, event, second_team
assert second_team.limit_event_permissions == {
"event.settings.general: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.invoicing:write": True,
"event.subevents:write": True,
diff --git a/src/tests/control/test_items.py b/src/tests/control/test_items.py
index 1cf62756ad..d7d37eaaac 100644
--- a/src/tests/control/test_items.py
+++ b/src/tests/control/test_items.py
@@ -57,9 +57,9 @@ class ItemFormTest(SoupTest):
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)
- t = Team.objects.create(organizer=self.orga1, all_event_permissions=True)
- t.members.add(self.user)
- t.limit_events.add(self.event1)
+ self.team = Team.objects.create(organizer=self.orga1, all_event_permissions=True)
+ self.team.members.add(self.user)
+ self.team.limit_events.add(self.event1)
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]
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):
with scopes_disabled():
q1 = Question.objects.create(event=self.event1, question="What country are you from?", type="C", required=True)
diff --git a/src/tests/control/test_permissions.py b/src/tests/control/test_permissions.py
index 84ca45a0cf..223707d524 100644
--- a/src/tests/control/test_permissions.py
+++ b/src/tests/control/test_permissions.py
@@ -44,7 +44,7 @@ from pretix.base.models import Event, Order, Organizer, Team, User
@pytest.fixture
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(
organizer=o, name='Dummy', slug='dummy',
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", "dangerzone/", 200, HTTP_GET),
("event.settings.general:write", "settings/", 200, HTTP_GET),
- ("event.settings.general:write", "settings/plugins", 200, HTTP_GET),
- ("event.settings.general:write", "settings/payment", 200, HTTP_GET),
+ # ("event.settings.payment:write", "settings/payment", 200, HTTP_GET), GET allowed also with other permissions
+ ("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/email", 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/invoice", 200, HTTP_GET),
("event.settings.general:write", "settings/widget", 200, HTTP_GET),
- ("event.settings.general:write", "settings/invoice/preview", 200, HTTP_GET),
- ("event.settings.general:write", "settings/tax/", 200, HTTP_GET),
- ("event.settings.general:write", "settings/tax/1/", 404, HTTP_GET),
- ("event.settings.general:write", "settings/tax/add", 200, HTTP_GET),
- ("event.settings.general:write", "settings/tax/1/delete", 404, HTTP_GET),
- ("event.settings.general:write", "settings/tax/1/default", 404, HTTP_POST),
+ ("event.settings.invoicing:write", "settings/invoice", 200, HTTP_GET),
+ ("event.settings.invoicing:write", "settings/invoice/preview", 200, HTTP_GET),
+ ("event.settings.tax:write", "settings/tax/", 200, HTTP_GET),
+ ("event.settings.tax:write", "settings/tax/1/", 404, HTTP_GET),
+ ("event.settings.tax:write", "settings/tax/add", 200, HTTP_GET),
+ ("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),
- # Lists are currently not access-controlled
- # ("event.items:write", "items/", 200),
+ (None, "items/", 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/down", 404, HTTP_POST),
("event.items:write", "items/reorder/2/", 400, HTTP_POST),
("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
# for testing this, it is enough to test that a 404 error
# 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/reorder", 400, HTTP_POST),
("event.items:write", "categories/add", 200, HTTP_GET),
- # ("event.items:write", "questions/", 200, HTTP_GET),
- ("event.items:write", "questions/2/", 404, HTTP_GET),
+ (None, "questions/", 200, HTTP_GET),
+ (None, "questions/2/", 404, HTTP_GET),
("event.items:write", "questions/2/delete", 404, HTTP_GET),
("event.items:write", "questions/reorder", 400, HTTP_POST),
("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/delete", 404, HTTP_GET),
("event.items:write", "quotas/add", 200, HTTP_GET),
- # ("event.items:write", "discounts/", 200),
- # 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.
+ (None, "discounts/", 200, HTTP_GET),
("event.items:write", "discounts/2/", 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/down", 404, HTTP_POST),
("event.items:write", "discounts/reorder", 400, HTTP_POST),
("event.items:write", "discounts/add", 200, HTTP_GET),
- ("event.settings.general:write", "subevents/", 200, HTTP_GET),
- ("event.settings.general:write", "subevents/2/", 404, HTTP_GET),
- ("event.settings.general:write", "subevents/2/delete", 404, HTTP_GET),
- ("event.settings.general:write", "subevents/add", 200, HTTP_GET),
+ (None, "subevents/", 200, HTTP_GET),
+ ("event.subevents:write", "subevents/2/", 404, HTTP_GET),
+ ("event.subevents:write", "subevents/2/", 404, HTTP_POST),
+ ("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/export/", 200, HTTP_GET),
("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/0ab7b081-92d3-4480-82de-2f8b056fd32f/", 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/bulk_add", 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(
pk=2, organizer=env[2], all_events=True
)
+ if not perm:
+ pytest.skip()
t.all_event_permissions = False
t.limit_event_permissions.pop(perm, None)
t.save()
@@ -539,17 +542,17 @@ organizer_permission_urls = [
("organizer.settings.general:write", "organizer/dummy/outgoingmails", 200),
("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/devices", 200),
- ("organizer.settings.general:write", "organizer/dummy/devices/select2", 200),
- ("organizer.settings.general:write", "organizer/dummy/device/add", 200),
- ("organizer.settings.general:write", "organizer/dummy/device/1/edit", 404),
- ("organizer.settings.general:write", "organizer/dummy/device/1/connect", 404),
- ("organizer.settings.general:write", "organizer/dummy/device/1/revoke", 404),
- ("organizer.settings.general:write", "organizer/dummy/gates", 200),
- ("organizer.settings.general:write", "organizer/dummy/gates/select2", 200),
- ("organizer.settings.general:write", "organizer/dummy/gate/add", 200),
- ("organizer.settings.general:write", "organizer/dummy/gate/1/edit", 404),
- ("organizer.settings.general:write", "organizer/dummy/gate/1/delete", 404),
+ ("organizer.devices:read", "organizer/dummy/devices", 200),
+ ("organizer.devices:read", "organizer/dummy/devices/select2", 200),
+ ("organizer.devices:write", "organizer/dummy/device/add", 200),
+ ("organizer.devices:write", "organizer/dummy/device/1/edit", 404),
+ ("organizer.devices:write", "organizer/dummy/device/1/connect", 404),
+ ("organizer.devices:write", "organizer/dummy/device/1/revoke", 404),
+ ("organizer.devices:read", "organizer/dummy/gates", 200),
+ ("organizer.devices:read", "organizer/dummy/gates/select2", 200),
+ ("organizer.devices:write", "organizer/dummy/gate/add", 200),
+ ("organizer.devices:write", "organizer/dummy/gate/1/edit", 404),
+ ("organizer.devices:write", "organizer/dummy/gate/1/delete", 404),
("organizer.settings.general:write", "organizer/dummy/properties", 200),
("organizer.settings.general:write", "organizer/dummy/property/add", 200),
("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/1/edit", 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/anonymize", 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/", 404),
+ ("organizer.customers:read", "organizer/dummy/customer/ABC/", 404),
("organizer.reusablemedia:read", "organizer/dummy/reusable_media", 200),
("organizer.reusablemedia:write", "organizer/dummy/reusable_media/1/edit", 404),
("organizer.reusablemedia:read", "organizer/dummy/reusable_media/1/", 404),