From 1960e53697829b7eea798df45505f6f42fef1b91 Mon Sep 17 00:00:00 2001 From: Neil Muller Date: Sun, 26 Nov 2023 11:56:09 +0200 Subject: [PATCH 1/5] Add api endpoint to access validation results --- wafer/schedule/urls.py | 3 ++- wafer/schedule/views.py | 12 ++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/wafer/schedule/urls.py b/wafer/schedule/urls.py index 1917f660..66d4679e 100644 --- a/wafer/schedule/urls.py +++ b/wafer/schedule/urls.py @@ -4,7 +4,7 @@ from wafer.schedule.views import ( CurrentView, ScheduleView, ScheduleItemViewSet, ScheduleXmlView, - VenueView, ICalView, JsonDataView) + VenueView, ICalView, JsonDataView, get_validation_info) router = routers.DefaultRouter() router.register(r'scheduleitems', ScheduleItemViewSet) @@ -17,5 +17,6 @@ name='wafer_pentabarf_xml'), re_path(r'^schedule\.ics$', ICalView.as_view(), name="schedule.ics"), re_path(r'^schedule\.json$', JsonDataView.as_view(), name="schedule.json"), + re_path(r'^api/validate', get_validation_info), re_path(r'^api/', include(router.urls)), ] diff --git a/wafer/schedule/views.py b/wafer/schedule/views.py index 281d4a64..07202801 100644 --- a/wafer/schedule/views.py +++ b/wafer/schedule/views.py @@ -20,6 +20,9 @@ from bakery.views import BuildableDetailView, BuildableTemplateView, BuildableMixin from rest_framework import viewsets from rest_framework.permissions import IsAdminUser +from rest_framework.decorators import api_view, permission_classes +from rest_framework.response import Response + from wafer import __version__ from wafer.pages.models import Page from wafer.schedule.models import ( @@ -345,6 +348,15 @@ def get_context_data(self, **kwargs): return context +@api_view(['GET']) +@permission_classes([IsAdminUser]) +def get_validation_info(request): + """API wrapper around validate schedule for use in the schedule + editor""" + errors = validate_schedule() + return Response({'Validation Status': errors}) + + class ScheduleItemViewSet(viewsets.ModelViewSet): """ API endpoint that allows groups to be viewed or edited. From bcd78326834337a80c40afca94285d8a9921bcd1 Mon Sep 17 00:00:00 2001 From: Neil Muller Date: Mon, 27 Nov 2023 14:08:39 +0200 Subject: [PATCH 2/5] Add tests for the new endpoint --- wafer/schedule/tests/test_validation.py | 98 ++++++++++++++++++++++++- 1 file changed, 95 insertions(+), 3 deletions(-) diff --git a/wafer/schedule/tests/test_validation.py b/wafer/schedule/tests/test_validation.py index a1f34dac..ec53c55f 100644 --- a/wafer/schedule/tests/test_validation.py +++ b/wafer/schedule/tests/test_validation.py @@ -11,7 +11,7 @@ from wafer.schedule.admin import (find_clashes, find_invalid_venues, validate_items, find_duplicate_schedule_items, find_speaker_clashes, prefetch_schedule_items) -from wafer.schedule.tests.test_views import make_pages, make_items +from wafer.schedule.tests.test_views import make_pages, make_items, create_client def get_new_result(result, old_result): @@ -24,7 +24,6 @@ def get_new_result(result, old_result): class ScheduleValidationTests(TestCase): """Test that various validators pick up the correct errors""" - def setUp(self): """Create some blocks, slots and talks to work with""" timezone.activate('UTC') @@ -265,7 +264,7 @@ def test_find_speaker_clashes(self): self.pages[7].people.add(self.author3) self.pages[7].people.add(self.author2) self.pages[7].save() - + all_items = prefetch_schedule_items() result = list(find_speaker_clashes(all_items)) @@ -291,3 +290,96 @@ def test_find_speaker_clashes(self): self.assertIn(self.slots[3], new_result[0][0]) self.assertIn(self.items[6], new_result[0][1]) self.assertIn(self.items[7], new_result[0][1]) + + +class ScheduleValidationApiTests(TestCase): + """Test that validation status can be checked via the api""" + + def setUp(self): + """Create some blocks, slots and talks to work with""" + timezone.activate('UTC') + self.block1 = ScheduleBlock.objects.create( + start_time=D.datetime(2013, 9, 22, 9, 0, 0, + tzinfo=D.timezone.utc), + end_time=D.datetime(2013, 9, 22, 19, 0, 0, + tzinfo=D.timezone.utc)) + + self.venue1 = Venue.objects.create(order=1, name='Venue 1') + self.venue2 = Venue.objects.create(order=2, name='Venue 2') + self.venue1.blocks.add(self.block1) + self.venue2.blocks.add(self.block1) + + start1 = D.datetime(2013, 9, 22, 10, 0, 0, + tzinfo=D.timezone.utc) + start2 = D.datetime(2013, 9, 22, 11, 0, 0, + tzinfo=D.timezone.utc) + start3 = D.datetime(2013, 9, 22, 12, 0, 0, + tzinfo=D.timezone.utc) + self.slots = [] + self.slots.append(Slot.objects.create(start_time=start1, + end_time=start2)) + self.slots.append(Slot.objects.create(start_time=start2, + end_time=start3)) + self.pages = make_pages(4) + venues = [self.venue1, self.venue2] * 2 + self.items = make_items(venues, self.pages) + + for index, item in enumerate(self.items): + item.slots.add(self.slots[index // 2]) + + self.author1 = create_user('Author 1') + self.author2 = create_user('Author 2') + + self.talk1 = create_talk('Talk 1', ACCEPTED, user=self.author1) + self.talk2 = create_talk('Talk 2', ACCEPTED, user=self.author2) + self.talk3 = create_talk('Talk 3', ACCEPTED, user=self.author1) + + def test_normal_permissions(self): + """Check that we don't allow non-superuser access""" + c = create_client('ordinary', superuser=False) + response = c.get('/schedule/api/validate/') + self.assertEqual(response.status_code, 403) + self.assertEqual(response.data, { + "detail": "You do not have permission to perform this action.", + }) + + def test_no_errors(self): + """Check that we get no errors on a valid schedule""" + c = create_client('super', superuser=True) + response = c.get('/schedule/api/validate/') + self.assertEqual(response.status_code, 200) + self.assertEqual(response.data, {'Validation Status': []}) + + def test_find_duplicates(self): + """Test that duplicates are reported""" + # Create a duplicate item + duplicate_item = ScheduleItem.objects.create(venue=self.venue2, + details="Duplicate item", + page_id=self.pages[0].pk) + duplicate_item.slots.add(self.slots[1]) + c = create_client('super', superuser=True) + response = c.get('/schedule/api/validate/') + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.data['Validation Status']), 1) + self.assertIn('Clashes found in schedule', response.data['Validation Status'][0]) + + def test_find_speaker_clashes(self): + """Test that speaker clashes are reported""" + # Check two talks witht the same author at the same time fails + self.items[0].page_id = None + self.items[0].talk_id = self.talk1.pk + self.items[0].save() + + self.items[2].page_id = None + self.items[2].talk_id = self.talk2.pk + self.items[2].save() + + self.items[1].page_id = None + self.items[1].talk_id = self.talk3.pk + self.items[1].save() + c = create_client('super', superuser=True) + response = c.get('/schedule/api/validate/') + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.data['Validation Status']), 1) + self.assertIn('Common speaker in simultaneous schedule items', + response.data['Validation Status'][0]) From b703feef7d4be48ed344e2f84990bd807de5206d Mon Sep 17 00:00:00 2001 From: Neil Muller Date: Mon, 27 Nov 2023 14:37:17 +0200 Subject: [PATCH 3/5] Tweak how the validation section is created to make it easier on the js side --- .../wafer.schedule/edit_schedule.html | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/wafer/schedule/templates/wafer.schedule/edit_schedule.html b/wafer/schedule/templates/wafer.schedule/edit_schedule.html index 44159438..f3908b4f 100644 --- a/wafer/schedule/templates/wafer.schedule/edit_schedule.html +++ b/wafer/schedule/templates/wafer.schedule/edit_schedule.html @@ -1,20 +1,28 @@ {% extends "wafer/base.html" %} {% load i18n %} {% load static %} -{% block title %}Edit Schedule - {{ WAFER_CONFERENCE_NAME }}{% endblock %} +{% block title %}{% trans "Edit Schedule" %} - {{ WAFER_CONFERENCE_NAME }}{% endblock %} {% block content %}
- {% if validation_errors %} -
+ - {% endif %}