diff --git a/doc/api/resources/subevents.rst b/doc/api/resources/subevents.rst index b7616197c7..e308e267ca 100644 --- a/doc/api/resources/subevents.rst +++ b/doc/api/resources/subevents.rst @@ -51,6 +51,7 @@ seating_plan integer If reserved sea plan. Otherwise ``null``. seat_category_mapping object An object mapping categories of the seating plan (strings) to items in the event (integers or ``null``). +last_modified datetime Last modification of this object ===================================== ========================== ======================================================= .. versionchanged:: 1.7 @@ -80,6 +81,10 @@ seat_category_mapping object An object mappi The ``disabled`` attribute has been added to ``item_price_overrides`` and ``variation_price_overrides``. +.. versionchanged:: 3.12 + + The ``last_modified`` attribute has been added. + Endpoints --------- @@ -148,6 +153,8 @@ Endpoints :query ends_after: If set to a date and time, only events that happen during of after the given time are returned. :param organizer: The ``slug`` field of a valid organizer :param event: The ``slug`` field of the main event + :query datetime modified_since: Only return objects that have changed since the given date. Be careful: This does not + allow you to know if a subevent was deleted. :query array attr[meta_data_key]: By providing the key and value of a meta data attribute, the list of sub-events will only contain the sub-events matching the set criteria. Providing ``?attr[Format]=Seminar`` would return only those sub-events having set their ``Format`` meta data to ``Seminar``, ``?attr[Format]=`` only those, that diff --git a/src/pretix/api/serializers/event.py b/src/pretix/api/serializers/event.py index cb10601ab3..2e9cb1a4d1 100644 --- a/src/pretix/api/serializers/event.py +++ b/src/pretix/api/serializers/event.py @@ -369,7 +369,7 @@ class SubEventSerializer(I18nAwareModelSerializer): fields = ('id', 'name', 'date_from', 'date_to', 'active', 'date_admission', 'presale_start', 'presale_end', 'location', 'geo_lat', 'geo_lon', 'event', 'is_public', 'seating_plan', 'item_price_overrides', 'variation_price_overrides', 'meta_data', - 'seat_category_mapping') + 'seat_category_mapping', 'last_modified') def validate(self, data): data = super().validate(data) diff --git a/src/pretix/api/views/event.py b/src/pretix/api/views/event.py index e40b8535dd..b48a1058d1 100644 --- a/src/pretix/api/views/event.py +++ b/src/pretix/api/views/event.py @@ -4,7 +4,7 @@ from django.db.models import ProtectedError, Q from django.utils.timezone import now from django_filters.rest_framework import DjangoFilterBackend, FilterSet from django_scopes import scopes_disabled -from rest_framework import filters, views, viewsets +from rest_framework import filters, serializers, views, viewsets from rest_framework.exceptions import PermissionDenied from rest_framework.response import Response @@ -194,6 +194,7 @@ with scopes_disabled(): is_past = django_filters.rest_framework.BooleanFilter(method='is_past_qs') is_future = django_filters.rest_framework.BooleanFilter(method='is_future_qs') ends_after = django_filters.rest_framework.IsoDateTimeFilter(method='ends_after_qs') + modified_since = django_filters.IsoDateTimeFilter(field_name='last_modified', lookup_expr='gte') class Meta: model = SubEvent @@ -233,6 +234,8 @@ class SubEventViewSet(ConditionalListView, viewsets.ModelViewSet): write_permission = 'can_change_event_settings' filter_backends = (DjangoFilterBackend, filters.OrderingFilter) filterset_class = SubEventFilter + ordering = ('date_from',) + ordering_fields = ('id', 'date_from', 'last_modified') def get_queryset(self): if getattr(self.request, 'event', None): @@ -254,6 +257,20 @@ class SubEventViewSet(ConditionalListView, viewsets.ModelViewSet): 'subeventitem_set', 'subeventitemvariation_set', 'seat_category_mappings' ) + def list(self, request, **kwargs): + date = serializers.DateTimeField().to_representation(now()) + queryset = self.filter_queryset(self.get_queryset()) + + page = self.paginate_queryset(queryset) + if page is not None: + serializer = self.get_serializer(page, many=True) + resp = self.get_paginated_response(serializer.data) + resp['X-Page-Generated'] = date + return resp + + serializer = self.get_serializer(queryset, many=True) + return Response(serializer.data, headers={'X-Page-Generated': date}) + def perform_update(self, serializer): original_data = self.get_serializer(instance=serializer.instance).data super().perform_update(serializer) diff --git a/src/pretix/base/migrations/0164_subevent_last_modified.py b/src/pretix/base/migrations/0164_subevent_last_modified.py new file mode 100644 index 0000000000..f4a1b5519e --- /dev/null +++ b/src/pretix/base/migrations/0164_subevent_last_modified.py @@ -0,0 +1,18 @@ +# Generated by Django 3.0.9 on 2020-10-15 16:12 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('pretixbase', '0163_device_security_profile'), + ] + + operations = [ + migrations.AddField( + model_name='subevent', + name='last_modified', + field=models.DateTimeField(auto_now=True, db_index=True), + ), + ] diff --git a/src/pretix/base/models/event.py b/src/pretix/base/models/event.py index 1ae9010109..ec40ce71b0 100644 --- a/src/pretix/base/models/event.py +++ b/src/pretix/base/models/event.py @@ -1036,6 +1036,9 @@ class SubEvent(EventMixin, LoggedModel): ) seating_plan = models.ForeignKey('SeatingPlan', on_delete=models.PROTECT, null=True, blank=True, related_name='subevents') + last_modified = models.DateTimeField( + auto_now=True, db_index=True + ) items = models.ManyToManyField('Item', through='SubEventItem') variations = models.ManyToManyField('ItemVariation', through='SubEventItemVariation') diff --git a/src/pretix/base/orderimport.py b/src/pretix/base/orderimport.py index 1b49ab382f..8ba6a47090 100644 --- a/src/pretix/base/orderimport.py +++ b/src/pretix/base/orderimport.py @@ -18,8 +18,9 @@ from i18nfield.strings import LazyI18nString from pretix.base.channels import get_all_sales_channels from pretix.base.forms.questions import guess_country from pretix.base.models import ( - ItemVariation, OrderPosition, QuestionAnswer, QuestionOption, Seat, - Question) + ItemVariation, OrderPosition, Question, QuestionAnswer, QuestionOption, + Seat, +) from pretix.base.services.pricing import get_price from pretix.base.settings import ( COUNTRIES_WITH_STATE_IN_ADDRESS, PERSON_NAME_SCHEMES, diff --git a/src/tests/api/test_subevents.py b/src/tests/api/test_subevents.py index 5c9c166025..080de1b9be 100644 --- a/src/tests/api/test_subevents.py +++ b/src/tests/api/test_subevents.py @@ -101,6 +101,7 @@ def item2(event2): def test_subevent_list(token_client, organizer, event, subevent): res = dict(TEST_SUBEVENT_RES) res["id"] = subevent.pk + res["last_modified"] = subevent.last_modified.isoformat().replace('+00:00', 'Z') resp = token_client.get('/api/v1/organizers/{}/events/{}/subevents/'.format(organizer.slug, event.slug)) assert resp.status_code == 200 assert [res] == resp.data['results'] @@ -141,16 +142,6 @@ def test_subevent_list_filter(token_client, organizer, event, subevent): assert resp.data['count'] == 0 -@pytest.mark.django_db -def test_subevent_get(token_client, organizer, event, subevent): - res = dict(TEST_SUBEVENT_RES) - res["id"] = subevent.pk - resp = token_client.get('/api/v1/organizers/{}/events/{}/subevents/{}/'.format(organizer.slug, event.slug, - subevent.pk)) - assert resp.status_code == 200 - assert res == resp.data - - @pytest.mark.django_db def test_subevent_create(token_client, organizer, event, subevent, meta_prop, item): resp = token_client.post( @@ -597,6 +588,7 @@ def test_subevent_update_keep_subeventitems(token_client, organizer, event, sube def test_subevent_detail(token_client, organizer, event, subevent): res = dict(TEST_SUBEVENT_RES) res["id"] = subevent.pk + res["last_modified"] = subevent.last_modified.isoformat().replace('+00:00', 'Z') resp = token_client.get('/api/v1/organizers/{}/events/{}/subevents/{}/'.format(organizer.slug, event.slug, subevent.pk)) assert resp.status_code == 200