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

@@ -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('<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();
}
@@ -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();

View File

@@ -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") {

View File

@@ -417,6 +417,30 @@ div.scrolling-multiple-choice, div.scrolling-choice {
}
}
}
.team-permission-groups {
border: 1px solid $input-border;
border-radius: $input-border-radius;
padding: 10px 15px;
.radio {
display: inline-block;
margin-right: 10px;
&:not(:has(.fa-fw)) {
// Visual adjustment between options with and without help text
&:after {
display: inline-block;
content: " ";
padding-right: 1.28571em;
}
}
}
.control-label {
text-align: left;
}
.form-group:last-child, .help-block {
margin-bottom: 0;
}
}
table td > .checkbox {
margin: 0;
position: static;