Organizer-level plugins (#5305)

* Add version notes to the docs

* Adapt signal handling

* Add UI

* Add API

* API and tests

* Fix registry

* Update doc/development/api/plugins.rst

Co-authored-by: Felix Rindt <felix@rindt.me>

* Fix failing tests

* Apply suggestions from code review

Co-authored-by: Richard Schreiber <schreiber@rami.io>

* Update src/pretix/control/templates/pretixcontrol/organizers/plugin_events.html

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

* Update src/pretix/control/templates/pretixcontrol/organizers/plugins.html

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

* Update src/pretix/control/templates/pretixcontrol/organizers/plugins.html

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

* Update src/pretix/control/navigation.py

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

* Update src/pretix/control/urls.py

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

* Apply suggestion from @wiffbi

* REbase migration

* Fix review note

* Fix test cases

* Remove plugin from all events if disabled on org level

* Update doc/development/api/plugins.rst

* Unify registries

* Rebase migration

---------

Co-authored-by: Felix Rindt <felix@rindt.me>
Co-authored-by: Richard Schreiber <schreiber@rami.io>
Co-authored-by: luelista <weller@rami.io>
This commit is contained in:
Raphael Michel
2025-08-19 11:33:34 +02:00
committed by GitHub
parent 56964b6764
commit a51a6123f5
50 changed files with 1623 additions and 192 deletions

View File

@@ -855,6 +855,39 @@ def test_event_update_plugins_validation(token_client, organizer, event, item, m
)
assert resp.status_code == 200
resp = token_client.patch(
'/api/v1/organizers/{}/events/{}/'.format(organizer.slug, event.slug),
{
"plugins": ["tests.testdummyorga"]
},
format='json'
)
assert resp.status_code == 400
assert resp.data == {"plugins": ["Plugin cannot be enabled on this level: 'tests.testdummyorga'."]}
resp = token_client.patch(
'/api/v1/organizers/{}/events/{}/'.format(organizer.slug, event.slug),
{
"plugins": ["tests.testdummyhybrid"]
},
format='json'
)
assert resp.status_code == 400
assert resp.data == {"plugins": ["Plugin should be enabled on organizer level first: 'tests.testdummyhybrid'."]}
with scopes_disabled():
organizer.enable_plugin("tests.testdummyhybrid")
organizer.save()
resp = token_client.patch(
'/api/v1/organizers/{}/events/{}/'.format(organizer.slug, event.slug),
{
"plugins": ["tests.testdummyhybrid"]
},
format='json'
)
assert resp.status_code == 200
resp = token_client.patch(
'/api/v1/organizers/{}/events/{}/'.format(organizer.slug, event.slug),
{

View File

@@ -423,7 +423,7 @@ def test_use_token_for_access_one_organizer(client, admin_user, organizer, appli
assert resp.status_code == 200
data = json.loads(resp.content.decode())
assert data == {'count': 1, 'next': None, 'previous': None, 'results': [
{'name': 'Dummy', 'slug': 'dummy', 'public_url': 'http://example.com/dummy/'}
{'name': 'Dummy', 'slug': 'dummy', 'public_url': 'http://example.com/dummy/', 'plugins': []}
]}
resp = client.get('/api/v1/organizers/dummy/events/', HTTP_AUTHORIZATION='Bearer %s' % access_token)
assert resp.status_code == 200
@@ -470,8 +470,8 @@ def test_use_token_for_access_two_organizers(client, admin_user, organizer, appl
assert resp.status_code == 200
data = json.loads(resp.content.decode())
assert data == {'count': 2, 'next': None, 'previous': None, 'results': [
{'name': 'A', 'slug': 'a', 'public_url': 'http://example.com/a/'},
{'name': 'Dummy', 'slug': 'dummy', 'public_url': 'http://example.com/dummy/'},
{'name': 'A', 'slug': 'a', 'public_url': 'http://example.com/a/', 'plugins': []},
{'name': 'Dummy', 'slug': 'dummy', 'public_url': 'http://example.com/dummy/', 'plugins': []},
]}
resp = client.get('/api/v1/organizers/dummy/events/', HTTP_AUTHORIZATION='Bearer %s' % access_token)
assert resp.status_code == 200

View File

@@ -19,14 +19,19 @@
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
# <https://www.gnu.org/licenses/>.
#
import zoneinfo
from datetime import datetime
import pytest
from django.core.files.base import ContentFile
from django_scopes import scopes_disabled
from tests.const import SAMPLE_PNG
TEST_ORGANIZER_RES = {
"name": "Dummy",
"slug": "dummy",
"public_url": "http://example.com/dummy/"
"public_url": "http://example.com/dummy/",
"plugins": [],
}
@@ -45,24 +50,57 @@ def test_organizer_detail(token_client, organizer):
@pytest.mark.django_db
def test_get_settings(token_client, organizer):
organizer.settings.event_list_type = "week"
resp = token_client.get(
'/api/v1/organizers/{}/settings/'.format(organizer.slug,),
def test_organizer_patch(token_client, organizer):
with scopes_disabled():
# An event needs to exist for the backwards-compatibility mechanism in get_all_plugins to trigger
event = organizer.events.create(
name="Event", slug="e2", live=True,
date_from=datetime(2020, 1, 10, 16, 0, tzinfo=zoneinfo.ZoneInfo("UTC")),
date_to=datetime(2020, 1, 10, 17, 0, tzinfo=zoneinfo.ZoneInfo("UTC")),
)
resp = token_client.patch(
'/api/v1/organizers/{}/'.format(organizer.slug),
{
'slug': 'willbeignored',
'name': 'Willbeignored',
'plugins': ['tests.testdummyorga', 'tests.testdummyhybrid']
},
format='json',
)
assert resp.status_code == 200
assert resp.data['event_list_type'] == "week"
assert resp.data['slug'] == 'dummy'
assert resp.data['name'] == 'Dummy'
assert set(resp.data['plugins']) == {'tests.testdummyorga', 'tests.testdummyhybrid'}
resp = token_client.get(
'/api/v1/organizers/{}/settings/?explain=true'.format(organizer.slug),
resp = token_client.patch(
'/api/v1/organizers/{}/'.format(organizer.slug),
{
'slug': 'willbeignored',
'name': 'Willbeignored',
'plugins': ['pretix.plugins.statistics']
},
format='json',
)
assert resp.status_code == 400
assert resp.data == {
"plugins": ["Plugin cannot be enabled on this level: 'pretix.plugins.statistics'."]
}
event.plugins = "tests.testdummyhybrid,tests.testdummy"
event.save()
resp = token_client.patch(
'/api/v1/organizers/{}/'.format(organizer.slug),
{
'slug': 'willbeignored',
'name': 'Willbeignored',
'plugins': ['tests.testdummyorga']
},
format='json',
)
assert resp.status_code == 200
assert resp.data['event_list_type'] == {
"value": "week",
"label": "Default overview style",
"help_text": "If your event series has more than 50 dates in the future, only the month or week calendar can be used.",
"readonly": False,
}
event.refresh_from_db()
assert event.plugins == "tests.testdummy"
@pytest.mark.django_db

View File

@@ -203,6 +203,7 @@ event_permission_sub_urls = [
]
org_permission_sub_urls = [
('patch', 'can_change_organizer_settings', '', 200),
('get', 'can_change_organizer_settings', 'settings/', 200),
('patch', 'can_change_organizer_settings', 'settings/', 200),
('get', 'can_change_organizer_settings', 'webhooks/', 200),