diff --git a/core/tests.py b/core/tests.py
index 60b2f28..75c4e28 100644
--- a/core/tests.py
+++ b/core/tests.py
@@ -1,4 +1,5 @@
from datetime import datetime
+import json
from django.contrib.auth.models import User
from django.test import Client, TestCase
@@ -64,6 +65,18 @@ class EventDashboardMutationTests(TestCase):
is_published=True,
)
+ def test_staff_can_export_event_backup(self):
+ self.client.login(username='staffer', password='pass12345')
+ response = self.client.get(reverse('event_export'))
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response['Content-Type'], 'application/json')
+ self.assertIn('attachment; filename="roadshow-calendar-events-', response['Content-Disposition'])
+ payload = json.loads(response.content)
+ self.assertEqual(payload['version'], 1)
+ self.assertEqual(payload['event_count'], 1)
+ self.assertEqual(payload['events'][0]['slug'], self.event.slug)
+ self.assertEqual(payload['events'][0]['name'], self.event.name)
+
def test_staff_can_open_edit_page(self):
self.client.login(username='staffer', password='pass12345')
response = self.client.get(reverse('event_edit', args=[self.event.slug]))
diff --git a/core/urls.py b/core/urls.py
index 4f0ba4c..fd44bcf 100644
--- a/core/urls.py
+++ b/core/urls.py
@@ -8,6 +8,7 @@ from .views import (
event_dashboard,
event_dashboard_detail,
event_delete,
+ event_export,
event_detail,
event_edit,
event_list,
@@ -21,6 +22,7 @@ urlpatterns = [
path('events/', event_list, name='event_list'),
path('events//', event_detail, name='event_detail'),
path('dashboard/events/', event_dashboard, name='event_dashboard'),
+ path('dashboard/events/export/', event_export, name='event_export'),
path('dashboard/events/new/', event_create, name='event_create'),
path('dashboard/events//edit/', event_edit, name='event_edit'),
path('dashboard/events//delete/', event_delete, name='event_delete'),
diff --git a/core/views.py b/core/views.py
index 47f241f..615a255 100644
--- a/core/views.py
+++ b/core/views.py
@@ -1,9 +1,11 @@
import calendar
from collections import defaultdict
from datetime import datetime, time, timedelta
+import json
from django.contrib import messages
from django.contrib.auth.decorators import login_required
+from django.http import HttpResponse
from django.core.exceptions import PermissionDenied
from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse
@@ -115,6 +117,30 @@ def _show_embed_header(request):
return request.GET.get('header', '1') != '0'
+def _serialize_event(event):
+ return {
+ 'name': event.name,
+ 'slug': event.slug,
+ 'location': event.location,
+ 'start': event.start.isoformat(),
+ 'end': event.end.isoformat(),
+ 'event_url': event.event_url,
+ 'summary': event.summary,
+ 'is_published': event.is_published,
+ }
+
+
+def _build_event_backup_payload():
+ events = list(Event.objects.all().order_by('start', 'name'))
+ return {
+ 'version': 1,
+ 'project': PROJECT_NAME,
+ 'exported_at': timezone.now().isoformat(),
+ 'event_count': len(events),
+ 'events': [_serialize_event(event) for event in events],
+ }
+
+
@login_required(login_url='login')
def event_dashboard(request):
if not request.user.is_staff:
@@ -133,6 +159,21 @@ def event_dashboard(request):
)
+@login_required(login_url='login')
+def event_export(request):
+ if not request.user.is_staff:
+ raise PermissionDenied
+
+ payload = _build_event_backup_payload()
+ timestamp = timezone.localtime().strftime('%Y%m%d-%H%M%S')
+ response = HttpResponse(
+ json.dumps(payload, indent=2),
+ content_type='application/json',
+ )
+ response['Content-Disposition'] = f'attachment; filename="roadshow-calendar-events-{timestamp}.json"'
+ return response
+
+
@login_required(login_url='login')
def event_create(request):
if not request.user.is_staff: