瀏覽代碼

Add error handling to event ajax views (#504)

Vince Salvino 2 年之前
父節點
當前提交
3f37dffc46
共有 3 個文件被更改,包括 167 次插入71 次删除
  1. 61 12
      coderedcms/tests/test_urls.py
  2. 2 1
      coderedcms/urls.py
  3. 104 58
      coderedcms/views.py

+ 61 - 12
coderedcms/tests/test_urls.py

@@ -2,6 +2,7 @@ import pytest
 import unittest
 
 from ast import literal_eval
+from datetime import timedelta
 
 from django.urls import reverse
 from django.test import Client
@@ -76,21 +77,19 @@ class TestEventURLs(unittest.TestCase):
         self.root_page.add_child(instance=event_page)
         occurrence = EventOccurrence(
             event=event_page,
-            start='2019-01-01T10:00:00+0000',
-            end='2019-01-01T11:00:00+0000'
+            start=timezone.now(),
+            end=timezone.now() + timedelta(hours=1),
         )
         occurrence.save()
 
+        ajax_url = reverse("event_generate_single_ical")
+
         response = self.client.post(
-            "/ical/generate/single/",
+            ajax_url,
             {
                 'event_pk': event_page.pk,
-                'datetime_start': EventOccurrence.objects.get(
-                    event=event_page
-                ).start.strftime("%Y-%m-%d %H:%M:%S%z").replace(' ', 'T'),
-                'datetime_end': EventOccurrence.objects.get(
-                    event=event_page
-                ).end.strftime("%Y-%m-%d %H:%M:%S%z").replace(' ', 'T'),
+                'datetime_start': occurrence.start.strftime("%Y-%m-%dT%H:%M:%S%z"),
+                'datetime_end': occurrence.end.strftime("%Y-%m-%dT%H:%M:%S%z"),
             },
             follow=True
         )
@@ -119,6 +118,30 @@ class TestEventURLs(unittest.TestCase):
             )
         )
 
+        # Test that garbage requests are handled appropriately.
+        response = self.client.post(ajax_url)
+        self.assertEqual(response.status_code, 400)
+        response = self.client.post(ajax_url, {"event_pk": "junk"})
+        self.assertEqual(response.status_code, 400)
+        response = self.client.post(
+            ajax_url,
+            {
+                "event_pk": "junk",
+                "datetime_start": "junk",
+                "datetime_end": "junk",
+            }
+        )
+        self.assertEqual(response.status_code, 400)
+        response = self.client.post(
+            ajax_url,
+            {
+                "event_pk": "junk",
+                "datetime_start": "2022-07-14T10:00:00+0000",
+                "datetime_end": "2022-07-14T10:00:00+0000",
+            }
+        )
+        self.assertEqual(response.status_code, 404)
+
     def test_generate_recurring_event(self):
         event_page = EventPage(
             path='/recurring-event/',
@@ -134,8 +157,10 @@ class TestEventURLs(unittest.TestCase):
         )
         occurrence.save()
 
+        ajax_url = reverse("event_generate_recurring_ical")
+
         response = self.client.post(
-            "/ical/generate/recurring/",
+            ajax_url,
             {'event_pk': event_page.pk},
             follow=True
         )
@@ -164,6 +189,12 @@ class TestEventURLs(unittest.TestCase):
             )
         )
 
+        # Test that garbage requests are handled appropriately.
+        response = self.client.post(ajax_url)
+        self.assertEqual(response.status_code, 400)
+        response = self.client.post(ajax_url, {"event_pk": "junk"})
+        self.assertEqual(response.status_code, 404)
+
     def test_generate_calendar(self):
         calendar_page = EventIndexPage(
             path='/event-index-page/',
@@ -187,8 +218,10 @@ class TestEventURLs(unittest.TestCase):
         )
         occurrence.save()
 
+        ajax_url = reverse("event_generate_ical_for_calendar")
+
         response = self.client.post(
-            "/ical/generate/calendar/",
+            ajax_url,
             {'page_id': calendar_page.pk},
             follow=True
         )
@@ -214,6 +247,12 @@ class TestEventURLs(unittest.TestCase):
             )
         )
 
+        # Test that garbage requests are handled appropriately.
+        response = self.client.post(ajax_url)
+        self.assertEqual(response.status_code, 400)
+        response = self.client.post(ajax_url, {"page_id": "junk"})
+        self.assertEqual(response.status_code, 404)
+
     def test_ajax_calendar(self):
         calendar_page = EventIndexPage(
             path='/event-index-page/',
@@ -237,8 +276,10 @@ class TestEventURLs(unittest.TestCase):
         )
         occurrence_one.save()
 
+        ajax_url = reverse("event_get_calendar_events")
+
         response = self.client.post(
-            "/ajax/calendar/events/?pid=" + str(calendar_page.pk),
+            f"{ajax_url}?pid={calendar_page.pk}",
             follow=True,
             **{'HTTP_X_REQUESTED_WITH': 'XMLHttpRequest'}
         )
@@ -262,6 +303,14 @@ class TestEventURLs(unittest.TestCase):
             event_local_end.strftime("%Y-%m-%dT%H:%M:%S%z")
         )
 
+        # Test that garbage requests are handled appropriately.
+        response = self.client.post(ajax_url)
+        self.assertEqual(response.status_code, 400)
+        response = self.client.post(f"{ajax_url}?pid=junk&start=junk&end=junk")
+        self.assertEqual(response.status_code, 400)
+        response = self.client.post(f"{ajax_url}?pid=junk")
+        self.assertEqual(response.status_code, 404)
+
 
 @pytest.mark.django_db
 class TestFavicon(unittest.TestCase):

+ 2 - 1
coderedcms/urls.py

@@ -31,7 +31,8 @@ urlpatterns = [
          name='event_generate_recurring_ical'),
     path('ical/generate/calendar/', event_generate_ical_for_calendar,
          name='event_generate_ical_for_calendar'),
-    path('ajax/calendar/events/', event_get_calendar_events, name='event_get_calendar_events'),
+    path('ajax/calendar/events/', event_get_calendar_events,
+         name='event_get_calendar_events'),
 
     # Wagtail
     path('', include(wagtailcore_urls)),

+ 104 - 58
coderedcms/views.py

@@ -8,6 +8,7 @@ from django.core.paginator import Paginator, InvalidPage, EmptyPage, PageNotAnIn
 from django.shortcuts import redirect, render
 from django.utils import timezone
 from django.utils.translation import ngettext, gettext_lazy as _
+from django.views.decorators.http import require_POST
 from icalendar import Calendar
 from wagtail.admin import messages
 from wagtail.core.models import Page, get_page_models
@@ -114,88 +115,133 @@ def robots(request):
     )
 
 
+@require_POST
 def event_generate_single_ical_for_event(request):
-    if request.method == "POST":
+    # Parse input.
+    try:
         event_pk = request.POST['event_pk']
-        event_page_models = CoderedEventPage.__subclasses__()
+    except KeyError:
+        return HttpResponse("event_pk required", status=400)
+
+    try:
         dt_start_str = utils.fix_ical_datetime_format(request.POST['datetime_start'])
         dt_end_str = utils.fix_ical_datetime_format(request.POST['datetime_end'])
+        dt_start = None
+        dt_end = None
+        if dt_start_str:
+            dt_start = datetime.strptime(dt_start_str, "%Y-%m-%dT%H:%M:%S%z")
+        if dt_end_str:
+            dt_end = datetime.strptime(dt_end_str, "%Y-%m-%dT%H:%M:%S%z")
+    except KeyError:
+        return HttpResponse(
+            "datetime_start and datetime_end required.",
+            status=400,
+        )
+    except ValueError:
+        return HttpResponse(
+            "datetime_start and datetime_end must be valid datetimes.",
+            status=400,
+        )
 
-        dt_start = datetime.strptime(dt_start_str, "%Y-%m-%dT%H:%M:%S%z") if dt_start_str else None
-        dt_end = datetime.strptime(dt_end_str, "%Y-%m-%dT%H:%M:%S%z") if dt_end_str else None
-        for event_page_model in event_page_models:
-            try:
-                event = event_page_model.objects.get(pk=event_pk)
-                break
-            except event_page_model.DoesNotExist:
-                pass
-        ical = Calendar()
-        ical.add_component(event.create_single_ical(dt_start=dt_start, dt_end=dt_end))
-        response = HttpResponse(ical.to_ical(), content_type="text/calendar")
-        response['Filename'] = "{0}.ics".format(event.slug)
-        response['Content-Disposition'] = 'attachment; filename={0}.ics'.format(event.slug)
-        return response
-    raise Http404()
+    # Get the page.
+    try:
+        event = CoderedPage.objects.get(pk=event_pk).specific
+    except (CoderedPage.DoesNotExist, ValueError):
+        raise Http404("Event does not exist")
+
+    # Generate the ical file.
+    ical = Calendar()
+    ical.add_component(event.create_single_ical(dt_start=dt_start, dt_end=dt_end))
+    response = HttpResponse(ical.to_ical(), content_type="text/calendar")
+    response['Filename'] = "{0}.ics".format(event.slug)
+    response['Content-Disposition'] = 'attachment; filename={0}.ics'.format(event.slug)
+    return response
 
 
+@require_POST
 def event_generate_recurring_ical_for_event(request):
-    if request.method == "POST":
+    # Parse input.
+    try:
         event_pk = request.POST['event_pk']
-        event_page_models = CoderedEventPage.__subclasses__()
-        for event_page_model in event_page_models:
-            try:
-                event = event_page_model.objects.get(pk=event_pk)
-                break
-            except event_page_model.DoesNotExist:
-                pass
-        ical = Calendar()
-        for e in event.create_recurring_ical():
-            ical.add_component(e)
-        response = HttpResponse(ical.to_ical(), content_type="text/calendar")
-        response['Filename'] = "{0}.ics".format(event.slug)
-        response['Content-Disposition'] = 'attachment; filename={0}.ics'.format(event.slug)
-        return response
-    raise Http404()
+    except KeyError:
+        return HttpResponse("event_pk required", status=400)
+
+    # Get the page.
+    try:
+        event = CoderedPage.objects.get(pk=event_pk).specific
+    except (CoderedPage.DoesNotExist, ValueError):
+        raise Http404("Event does not exist")
 
+    # Generate the ical file.
+    ical = Calendar()
+    for e in event.create_recurring_ical():
+        ical.add_component(e)
+    response = HttpResponse(ical.to_ical(), content_type="text/calendar")
+    response['Filename'] = "{0}.ics".format(event.slug)
+    response['Content-Disposition'] = 'attachment; filename={0}.ics'.format(event.slug)
+    return response
 
+
+@require_POST
 def event_generate_ical_for_calendar(request):
-    if request.method == "POST":
-        try:
-            page = CoderedPage.objects.get(id=request.POST.get('page_id')).specific
-        except ValueError:
-            raise Http404
-
-        ical = Calendar()
-        for event_page in page.get_index_children():
-            for e in event_page.specific.create_recurring_ical():
-                ical.add_component(e)
-        response = HttpResponse(ical.to_ical(), content_type="text/calendar")
-        response['Filename'] = "calendar.ics"
-        response['Content-Disposition'] = 'attachment; filename=calendar.ics'
-        return response
-    raise Http404()
+    # Parse input.
+    try:
+        page_id = request.POST["page_id"]
+    except KeyError:
+        return HttpResponse("page_id required", status=400)
+
+    # Get the page.
+    try:
+        page = CoderedPage.objects.get(pk=page_id).specific
+    except (CoderedPage.DoesNotExist, ValueError):
+        raise Http404("Page does not exist")
+
+    # Generate the ical file.
+    ical = Calendar()
+    for event_page in page.get_index_children():
+        for e in event_page.specific.create_recurring_ical():
+            ical.add_component(e)
+    response = HttpResponse(ical.to_ical(), content_type="text/calendar")
+    response['Filename'] = "calendar.ics"
+    response['Content-Disposition'] = 'attachment; filename=calendar.ics'
+    return response
 
 
 def event_get_calendar_events(request):
     """
     JSON list of events compatible with fullcalendar.js
     """
+    # Parse input.
     try:
-        page = CoderedPage.objects.get(id=request.GET.get('pid')).specific
-    except ValueError:
-        raise Http404
+        page_id = request.GET["pid"]
+    except KeyError:
+        return HttpResponse("pid required", status=400)
+
     start = None
     end = None
     start_str = request.GET.get('start', None)
     end_str = request.GET.get('end', None)
-    if start_str:
-        start = timezone.make_aware(
-            datetime.strptime(start_str[:10], "%Y-%m-%d"),
-        )
-    if end_str:
-        end = timezone.make_aware(
-            datetime.strptime(end_str[:10], "%Y-%m-%d"),
+    try:
+        if start_str:
+            start = timezone.make_aware(
+                datetime.strptime(start_str[:10], "%Y-%m-%d"),
+            )
+        if end_str:
+            end = timezone.make_aware(
+                datetime.strptime(end_str[:10], "%Y-%m-%d"),
+            )
+    except ValueError:
+        return HttpResponse(
+            "start and end must be valid datetimes.",
+            status=400
         )
+
+    # Get the page.
+    try:
+        page = CoderedPage.objects.get(pk=page_id).specific
+    except (CoderedPage.DoesNotExist, ValueError):
+        raise Http404("Page does not exist")
+
     return JsonResponse(
         page.get_calendar_events(start=start, end=end),
         safe=False