diff --git a/events/api.py b/events/api.py index 268d30c9b..77703b371 100644 --- a/events/api.py +++ b/events/api.py @@ -41,6 +41,7 @@ GeoModelSerializer, GeoModelAPIView, build_bbox_filter, srid_to_srs ) from munigeo.models import AdministrativeDivision +from rest_framework_bulk import BulkListSerializer, BulkModelViewSet import pytz import bleach import django_filters @@ -1102,6 +1103,8 @@ def to_representation(self, obj): class Meta: model = Event exclude = ['is_recurring_super', 'deleted'] + list_serializer_class = BulkListSerializer + def _format_images_v0_1(data): if 'images' not in data: @@ -1318,7 +1321,7 @@ class Meta: fields = ('division',) -class EventViewSet(viewsets.ModelViewSet, JSONAPIViewSet): +class EventViewSet(BulkModelViewSet, JSONAPIViewSet): """ # Filtering retrieved events @@ -1440,6 +1443,9 @@ def filter_queryset(self, queryset): srs=self.srs) return queryset.filter() + def allow_bulk_destroy(self, qs, filtered): + return False + register_view(EventViewSet, 'event') diff --git a/events/tests/test_event_delete.py b/events/tests/test_event_delete.py new file mode 100644 index 000000000..fc2588512 --- /dev/null +++ b/events/tests/test_event_delete.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- +import pytest +from .utils import versioned_reverse as reverse + + +@pytest.mark.django_db +def test_list_endpoint_delete(api_client, user, event): + api_client.force_authenticate(user) + + response = api_client.delete(reverse('event-list'), format='json') + assert response.status_code == 405 diff --git a/events/tests/test_event_post.py b/events/tests/test_event_post.py index 6da5de255..4a0fe27d7 100644 --- a/events/tests/test_event_post.py +++ b/events/tests/test_event_post.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +from copy import deepcopy from datetime import timedelta import pytest @@ -334,3 +335,30 @@ def test_name_required_in_some_language(api_client, minimal_event_dict, user, na if not is_valid: assert force_text(response.data['name'][0]) == 'The name must be specified.' + + +@pytest.mark.django_db +def test_multiple_event_creation(api_client, minimal_event_dict, user): + api_client.force_authenticate(user) + minimal_event_dict_2 = deepcopy(minimal_event_dict) + minimal_event_dict_2['name']['fi'] = 'testing_2' + + response = api_client.post(reverse('event-list'), [minimal_event_dict, minimal_event_dict_2], format='json') + assert response.status_code == 201 + + event_names = set(Event.objects.values_list('name_fi', flat=True)) + assert event_names == {'testing', 'testing_2'} + + +@pytest.mark.django_db +def test_multiple_event_creation_second_fails(api_client, minimal_event_dict, user): + api_client.force_authenticate(user) + minimal_event_dict_2 = deepcopy(minimal_event_dict) + minimal_event_dict_2.pop('name') # name is required, so the event update event should fail + + response = api_client.post(reverse('event-list'), [minimal_event_dict, minimal_event_dict_2], format='json') + assert response.status_code == 400 + assert 'name' in response.data[1] + + # the first event should not be created either + assert Event.objects.count() == 0 diff --git a/events/tests/test_event_put.py b/events/tests/test_event_put.py index e3a775ab0..8e4cb0d78 100644 --- a/events/tests/test_event_put.py +++ b/events/tests/test_event_put.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- from datetime import timedelta +from copy import deepcopy from django.utils import timezone import pytest @@ -387,4 +388,52 @@ def test__empty_api_key_cannot_update_an_event(api_client, event, complex_event_ detail_url = reverse('event-detail', kwargs={'pk': event.pk}) response = update_with_put(api_client, detail_url, complex_event_dict, credentials={'apikey': ''}) - assert response.status_code == 401 \ No newline at end of file + assert response.status_code == 401 + + +@pytest.mark.django_db +def test_multiple_event_update(api_client, minimal_event_dict, user): + api_client.force_authenticate(user) + minimal_event_dict_2 = deepcopy(minimal_event_dict) + minimal_event_dict_2['name']['fi'] = 'testing_2' + + # create events first + resp = create_with_post(api_client, minimal_event_dict) + minimal_event_dict['id'] = resp.data['id'] + resp = create_with_post(api_client, minimal_event_dict_2) + minimal_event_dict_2['id'] = resp.data['id'] + + minimal_event_dict['name']['fi'] = 'updated_name' + minimal_event_dict_2['name']['fi'] = 'updated_name_2' + + response = api_client.put(reverse('event-list'), [minimal_event_dict, minimal_event_dict_2], format='json') + assert response.status_code == 200 + + event_names = set(Event.objects.values_list('name_fi', flat=True)) + + assert event_names == {'updated_name', 'updated_name_2'} + + +@pytest.mark.django_db +def test_multiple_event_update_second_fails(api_client, minimal_event_dict, user): + api_client.force_authenticate(user) + minimal_event_dict_2 = deepcopy(minimal_event_dict) + minimal_event_dict_2['name']['fi'] = 'testing_2' + + # create events first + resp = create_with_post(api_client, minimal_event_dict) + minimal_event_dict['id'] = resp.data['id'] + resp = create_with_post(api_client, minimal_event_dict_2) + minimal_event_dict_2['id'] = resp.data['id'] + + minimal_event_dict['name']['fi'] = 'updated_name' + minimal_event_dict_2.pop('name') # name is required, so the event update event should fail + + response = api_client.put(reverse('event-list'), [minimal_event_dict, minimal_event_dict_2], format='json') + assert response.status_code == 400 + assert 'name' in response.data[1] + + event_names = set(Event.objects.values_list('name_fi', flat=True)) + + # verify that first event isn't updated either + assert event_names == {'testing', 'testing_2'} diff --git a/linkedevents/api.py b/linkedevents/api.py index 96fee2cd1..fa4b27f54 100644 --- a/linkedevents/api.py +++ b/linkedevents/api.py @@ -1,9 +1,16 @@ -from rest_framework.routers import DefaultRouter +import copy +from rest_framework.routers import DefaultRouter, SimpleRouter from events.api import all_views as events_views from helevents.api import all_views as users_views class LinkedEventsAPIRouter(DefaultRouter): + # these are from Django REST Framework bulk BulkRouter with 'delete' excluded + routes = copy.deepcopy(SimpleRouter.routes) + routes[0].mapping.update({ + 'put': 'bulk_update', + 'patch': 'partial_bulk_update', + }) def __init__(self): super(LinkedEventsAPIRouter, self).__init__() diff --git a/requirements.in b/requirements.in index 84b2203b3..c34f12782 100644 --- a/requirements.in +++ b/requirements.in @@ -38,3 +38,4 @@ Pillow django-cleanup django-allauth django-leaflet +djangorestframework-bulk diff --git a/requirements.txt b/requirements.txt index bc7cd51bd..fe73a35cb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -25,6 +25,7 @@ django-modeltranslation==0.12 django-mptt==0.8.6 django-reversion==2.0.7 django==1.9.11 +djangorestframework-bulk==0.2.1 djangorestframework-gis==0.10.1 djangorestframework-jwt==1.8.0 djangorestframework==3.3.3 @@ -66,4 +67,4 @@ wrapt==1.10.8 # via astroid # The following packages are commented out because they are # considered to be unsafe in a requirements file: -# setuptools # via icalendar +# setuptools # via djangorestframework-bulk, icalendar