From 4f0d5f0a64830f0620defa25c624eb424c4aa913 Mon Sep 17 00:00:00 2001 From: Phin Wolkwitz Date: Mon, 7 Sep 2026 16:07:10 +0200 Subject: [PATCH] Add API-endpoint for event-meta-properties --- doc/api/resources/event_meta_properties.rst | 244 ++++++++++++++++++++ src/pretix/api/serializers/organizer.py | 16 +- src/pretix/api/urls.py | 1 + src/pretix/api/views/organizer.py | 56 ++++- src/pretix/control/logdisplay.py | 4 + 5 files changed, 314 insertions(+), 7 deletions(-) create mode 100644 doc/api/resources/event_meta_properties.rst diff --git a/doc/api/resources/event_meta_properties.rst b/doc/api/resources/event_meta_properties.rst new file mode 100644 index 0000000000..4cdb5f72e8 --- /dev/null +++ b/doc/api/resources/event_meta_properties.rst @@ -0,0 +1,244 @@ +Event Meta Properties +===================== + +Resource description +-------------------- + +An Event Meta Property is used to to define meta information fields for its events. +This information can be re-used, for example, in ticket layouts. + +The Event Meta Properties resource contains the following public fields: + +.. rst-class:: rest-resource-table + +===================================== ========================== ======================================================= +Field Type Description +===================================== ========================== ======================================================= +id integer Unique ID for this property +name string Name of the property +default string Value of the default option +required boolean If ``true``, an event can only be taken live if the + property is set. In event series, it's always optional + to set a value for individual dates +protected boolean If ``true``, this property can only be changed by + organizer-level administrators +filter_public boolean If ``true``, this property will be shown to filter + events in the public event list and calendar +public_label string Public name of the property +filter_allowed boolean If ``true``, this property will be shown to filter + events or reports in the backend, and it can also be + used for hidden filter parameters in the frontend +choices list of objects List of JSON objects representing all permitted values + for this property, or ``null`` for no limitation. + Each choice object has a required internal name named + ``key`` and optional public name named ``label`` + consisting of a dictionary of i18n string translations, + as well as other implementation based key-value-pairs +===================================== ========================== ======================================================= + +Endpoints +--------- + +.. http:get:: /api/v1/organizers/(organizer)/event_meta_properties/ + + Returns a list of all Meta Properties for the organizer. + + **Example request**: + + .. sourcecode:: http + + GET /api/v1/organizers/bigevents/meta_properties/ HTTP/1.1 + Host: pretix.eu + Accept: application/json, text/javascript + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Vary: Accept + Content-Type: application/json + + { + "count": 1, + "next": null, + "previous": null, + "results": [ + { + "id": 1, + "name": "Color", + "default": "blue", + "required": false, + "protected": false, + "filter_public": false, + "public_label": {}, + "filter_allowed": true, + "choices": [ + { + "key": "blue", + "ORDER": 1, + "label": { + "en": "Blue" + }, + "DELETE": false + } + ] + } + ] + } + + :param organizer: The ``slug`` field of the organizer + :statuscode 200: no error + :statuscode 401: Authentication failure + :statuscode 403: The requested organizer does not exist **or** you have no permission to view this resource. + +.. http:get:: /api/v1/organizers/(organizer)/event_meta_properties/(id)/ + + Returns information on one property, identified by its id. + + **Example request**: + + .. sourcecode:: http + + GET /api/v1/organizers/bigevents/event_meta_properties/1/ HTTP/1.1 + Host: pretix.eu + Accept: application/json, text/javascript + + **Example response**: + + .. sourcecode:: http + + { + "id": 1, + "name": "Color", + "default": "blue", + "required": false, + "protected": false, + "filter_public": false, + "public_label": {}, + "filter_allowed": true, + "choices": null + } + + :param organizer: The ``slug`` field of the organizer + :param id: The ``id`` field of the meta property to retrieve + :statuscode 200: no error + :statuscode 401: Authentication failure + :statuscode 403: The requested organizer does not exist **or** you have no permission to view this resource. + +.. http:post:: /api/v1/organizers/(organizer)/event_meta_properties/ + + Creates a new meta property + + **Example request**: + + .. sourcecode:: http + + POST /api/v1/organizers/bigevents/event_meta_properties/ HTTP/1.1 + Host: pretix.eu + Accept: application/json, text/javascript + Content-Type: application/json + + { + "name": "ref-code", + "default": "abcde", + "required": true, + "choices": null + } + + + **Example response**: + + .. sourcecode:: http + + { + "id": 2, + "name": "reference", + "default": "abcde", + "required": true, + "protected": false, + "filter_public": false, + "public_label": null, + "filter_allowed": true, + "choices": null + } + + :param organizer: The ``slug`` field of the organizer + :statuscode 201: no error + :statuscode 400: The meta property could not be created due to invalid submitted data. + :statuscode 401: Authentication failure + :statuscode 403: The requested organizer does not exist **or** you have no permission to create this resource. + +.. http:patch:: /api/v1/organizers/(organizer)/event_meta_properties/(id)/ + + Update a meta property. You can also use ``PUT`` instead of ``PATCH``. With ``PUT``, you have to 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. + + You can change all fields of the resource except the ``id`` field. + + **Example request**: + + .. sourcecode:: http + + PATCH /api/v1/organizers/bigevents/event_meta_properties/2/ HTTP/1.1 + Host: pretix.eu + Accept: application/json, text/javascript + Content-Type: application/json + Content-Length: 94 + + { + "required": false + } + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Vary: Accept + Content-Type: application/json + + { + "id": 3, + "name": "reference", + "default": "abcde", + "required": false, + "protected": false, + "filter_public": false, + "public_label": null, + "filter_allowed": true, + "choices": null + } + + :param organizer: The ``slug`` field of the organizer + :param id: The ``id`` field of the meta property to modify + :statuscode 200: no error + :statuscode 400: The property could not be modified due to invalid submitted data + :statuscode 401: Authentication failure + :statuscode 403: The requested organizer does not exist **or** you have no permission to change this resource. + +.. http:delete:: /api/v1/organizers/(organizer)/event_meta_properties/(id)/ + + Delete a meta property. + + **Example request**: + + .. sourcecode:: http + + DELETE /api/v1/organizers/bigevents/event_meta_properties/1/ HTTP/1.1 + Host: pretix.eu + Accept: application/json, text/javascript + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 204 No Content + Vary: Accept + + :param organizer: The ``slug`` field of the organizer + :param id: The ``id`` field of the meta property to delete + :statuscode 204: no error + :statuscode 401: Authentication failure + :statuscode 403: The requested organizer does not exist **or** you have no permission to delete this resource. diff --git a/src/pretix/api/serializers/organizer.py b/src/pretix/api/serializers/organizer.py index 671c25d8e0..b72419f613 100644 --- a/src/pretix/api/serializers/organizer.py +++ b/src/pretix/api/serializers/organizer.py @@ -40,9 +40,10 @@ from pretix.api.serializers.settings import SettingsSerializer from pretix.base.auth import get_auth_backends from pretix.base.i18n import get_language_without_region from pretix.base.models import ( - Customer, Device, GiftCard, GiftCardAcceptance, GiftCardTransaction, - Membership, MembershipType, OrderPosition, Organizer, ReusableMedium, - SalesChannel, SeatingPlan, Team, TeamAPIToken, TeamInvite, User, + Customer, Device, EventMetaProperty, GiftCard, GiftCardAcceptance, + GiftCardTransaction, Membership, MembershipType, OrderPosition, Organizer, + ReusableMedium, SalesChannel, SeatingPlan, Team, TeamAPIToken, TeamInvite, + User, ) from pretix.base.models.seating import SeatingPlanLayoutValidator from pretix.base.permissions import ( @@ -640,3 +641,12 @@ class OrganizerSettingsSerializer(SettingsSerializer): ) # TODO: make sure pub is always correct return 'pub/' + fname + + +class EventMetaPropertiesSerializer(I18nAwareModelSerializer): + class Meta: + model = EventMetaProperty + fields = ( + 'id', 'name', 'default', 'required', 'protected', 'filter_public', 'public_label', 'filter_allowed', + 'choices' + ) diff --git a/src/pretix/api/urls.py b/src/pretix/api/urls.py index 911ce8c9e0..a42c98a0f6 100644 --- a/src/pretix/api/urls.py +++ b/src/pretix/api/urls.py @@ -68,6 +68,7 @@ orga_router.register(r'scheduled_exports', exporters.ScheduledOrganizerExportVie orga_router.register(r'exporters', exporters.OrganizerExportersViewSet, basename='exporters') orga_router.register(r'transactions', order.OrganizerTransactionViewSet) orga_router.register(r'orderpositions', order.OrganizerOrderPositionViewSet, basename='orderpositions') +orga_router.register(r'event_meta_properties', organizer.EventMetaPropertiesViewSet) team_router = routers.DefaultRouter() team_router.register(r'members', organizer.TeamMemberViewSet) diff --git a/src/pretix/api/views/organizer.py b/src/pretix/api/views/organizer.py index db28044b30..aff82a4f6e 100644 --- a/src/pretix/api/views/organizer.py +++ b/src/pretix/api/views/organizer.py @@ -44,15 +44,16 @@ from pretix.api.models import OAuthAccessToken from pretix.api.pagination import TotalOrderingFilter from pretix.api.serializers.organizer import ( CustomerCreateSerializer, CustomerSerializer, DeviceSerializer, - GiftCardSerializer, GiftCardTransactionSerializer, MembershipSerializer, + EventMetaPropertiesSerializer, GiftCardSerializer, + GiftCardTransactionSerializer, MembershipSerializer, MembershipTypeSerializer, OrganizerSerializer, OrganizerSettingsSerializer, SalesChannelSerializer, SeatingPlanSerializer, TeamAPITokenSerializer, TeamInviteSerializer, TeamMemberSerializer, TeamSerializer, ) from pretix.base.models import ( - Customer, Device, Event, GiftCard, GiftCardTransaction, LogEntry, - Membership, MembershipType, Organizer, SalesChannel, SeatingPlan, Team, - TeamAPIToken, TeamInvite, User, + Customer, Device, Event, EventMetaProperty, GiftCard, GiftCardTransaction, + LogEntry, Membership, MembershipType, Organizer, SalesChannel, SeatingPlan, + Team, TeamAPIToken, TeamInvite, User, ) from pretix.base.plugins import ( PLUGIN_LEVEL_EVENT, PLUGIN_LEVEL_EVENT_ORGANIZER_HYBRID, @@ -846,3 +847,50 @@ class SalesChannelViewSet(viewsets.ModelViewSet): data={'id': instance.pk} ) instance.delete() + + +class EventMetaPropertiesViewSet(viewsets.ModelViewSet): + serializer_class = EventMetaPropertiesSerializer + queryset = EventMetaProperty.objects.none() + write_permission = 'organizer.settings.general:write' + + def get_queryset(self): + qs = EventMetaProperty.objects.all() + return qs + + def get_serializer_context(self): + ctx = super().get_serializer_context() + ctx['organizer'] = self.request.organizer + return ctx + + @transaction.atomic() + def perform_destroy(self, instance): + instance.log_action( + 'pretix.organizer.event_meta_property.deleted', + user=self.request.user, + auth=self.request.auth, + data={'id': instance.pk} + ) + instance.delete() + + @transaction.atomic() + def perform_create(self, serializer): + inst = serializer.save(organizer_id=self.request.organizer.pk) + serializer.instance.log_action( + 'pretix.organizer.event_meta_property.added', + user=self.request.user, + auth=self.request.auth, + data=self.request.data, + ) + return inst + + @transaction.atomic() + def perform_update(self, serializer): + inst = serializer.save(organizer_id=self.request.organizer.pk) + serializer.instance.log_action( + 'pretix.organizer.event_meta_property.changed', + user=self.request.user, + auth=self.request.auth, + data=self.request.data, + ) + return inst diff --git a/src/pretix/control/logdisplay.py b/src/pretix/control/logdisplay.py index 60d44f50a2..54b82207d3 100644 --- a/src/pretix/control/logdisplay.py +++ b/src/pretix/control/logdisplay.py @@ -717,6 +717,10 @@ class CoreUserImpersonatedLogEntryType(UserImpersonatedLogEntryType): 'pretix.organizer.export.schedule.failed': _('A scheduled export has failed: {reason}.'), 'pretix.organizer.outgoingmails.retried': _('Failed emails have been scheduled to be retried.'), 'pretix.organizer.outgoingmails.aborted': _('Queued emails have been aborted.'), + 'pretix.property.created': _('An organizer meta property has been created.'), + 'pretix.property.deleted': _('An organizer meta property has been deleted.'), + 'pretix.property.changed': _('An organizer meta property has been changed.'), + 'pretix.property.reordered': _('An organizer meta property has been reordered.'), 'pretix.giftcards.acceptance.added': _('Gift card acceptance for another organizer has been added.'), 'pretix.giftcards.acceptance.removed': _('Gift card acceptance for another organizer has been removed.'), 'pretix.giftcards.acceptance.acceptor.invited': _('A new gift card acceptor has been invited.'),