Repository: CTPUG/wafer

Issue:
Separate video export from pentabarf and all that
Hacking all the video information into the pentabarf export is not the best idea, despite it's convience.

We should add a specific video info export (json probably makes the most sense) to handle this use case.


Discussion:

Resolving patch:
diff --git a/wafer/schedule/urls.py b/wafer/schedule/urls.py
--- 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)
+    VenueView, ICalView, JsonDataView)
 
 router = routers.DefaultRouter()
 router.register(r'scheduleitems', ScheduleItemViewSet)
@@ -16,5 +16,6 @@
     url(r'^pentabarf\.xml$', ScheduleXmlView.as_view(),
         name='wafer_pentabarf_xml'),
     url(r'^schedule\.ics$', ICalView.as_view(), name="schedule.ics"),
+    url(r'^schedule\.json$', JsonDataView.as_view(), name="schedule.json"),
     url(r'^api/', include(router.urls)),
 ]
diff --git a/wafer/schedule/views.py b/wafer/schedule/views.py
--- a/wafer/schedule/views.py
+++ b/wafer/schedule/views.py
@@ -5,11 +5,13 @@
 
 from icalendar import Calendar, Event
 
+import json
+
 from django.db.models import Q
 from django.views.generic import TemplateView, View
 from django.contrib.sites.shortcuts import get_current_site
-from django.core.exceptions import ObjectDoesNotExist
-from django.http import HttpResponse
+from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
+from django.http import HttpResponse, JsonResponse
 from django.utils import timezone
 from django.conf import settings
 
@@ -17,9 +19,8 @@
 from rest_framework import viewsets
 from rest_framework.permissions import IsAdminUser
 from wafer.pages.models import Page
-from wafer.schedule.models import Venue, Slot, ScheduleBlock
+from wafer.schedule.models import Venue, Slot, ScheduleBlock, ScheduleItem
 from wafer.schedule.admin import check_schedule, validate_schedule
-from wafer.schedule.models import ScheduleItem
 from wafer.schedule.serializers import ScheduleItemSerializer
 from wafer.talks.models import ACCEPTED, CANCELLED
 from wafer.talks.models import Talk
@@ -424,3 +425,83 @@ def build(self):
         path = os.path.join(settings.BUILD_DIR, self.build_path)
         self.prep_directory(self.build_path)
         self.build_file(path, self.get_content())
+
+
+class JsonDataView(View, BuildableMixin):
+    build_path = "schedule/schedule.json"
+
+    # Version of the json export, so tools can hopefully track changes
+    # sanely
+    FORMAT_VERSION = "0.1"
+
+    def get(self, request):
+        """Create a json data blob from the schedule"""
+        # This is mainly to be consumed by video processes, so we
+        # mainly keep the naming conventions from pentabarf for historical compatibility
+        #
+        # We restrict this to only people with permission to view profile details, since
+        # there are already other public views of the schedule for unauthenticated consumers
+        if not request.user.is_staff:
+            raise PermissionDenied
+        site = get_current_site(request)
+
+        data = {
+           'conference name': site.name,
+           'domain': site.domain,
+           'version': self.FORMAT_VERSION,
+        }
+
+        data['venues'] = []
+
+        data['events'] = []
+
+        for venue in Venue.objects.all():
+            venue_data = {}
+            venue_data['id'] = venue.pk
+            venue_data['name'] = venue.name
+            venue_data['has_video'] = venue.video
+            venue_data['details'] = venue.notes
+            data['venues'].append(venue_data)
+
+        for item in ScheduleItem.objects.all():
+            sched_event = {}
+            sched_event['id'] = item.pk
+            sched_event['start_time'] = item.get_start_datetime().isoformat()
+            sched_event['duration'] = item.get_duration_minutes()
+            sched_event['room_id'] = item.venue.pk
+            sched_event['title'] = item.get_title()
+            sched_event['url'] = item.get_url()
+            authors = []
+            if item.talk is not None:
+                if item.talk.track:
+                    sched_event['track'] = item.talk.track
+                else:
+                    sched_event['track'] = 'No Track'
+                # We're not rendering anything, so this is presumably 'safe'
+                sched_event['description'] = item.talk.abstract.raw
+                sched_event['video_allowed'] = item.talk.video
+                for person in item.talk.authors.all():
+                    authors.append(person)
+            else:
+                # Presumably a page, which may not have an author or description
+                sched_event['track'] = 'No Track'
+                # More complex logic is probably needed here
+                sched_event['video_allowed'] = True
+                sched_event['description'] = item.page.content.raw
+                for person in item.page.people.all():
+                    authors.append(person)
+            sched_event['authors'] = []
+            for person in authors:
+                person_data = {
+                    'name': person.userprofile.display_name(),
+                    'email': person.email
+                }
+                if person.userprofile.twitter_handle:
+                    person_data['twitter'] = person.userprofile.twitter_handle
+                sched_event['authors'].append(person_data)
+            sched_event['license'] = settings.WAFER_VIDEO_LICENSE
+            sched_event['license_url'] = settings.WAFER_VIDEO_LICENSE_URL
+            data['events'].append(sched_event)
+
+        response = JsonResponse(data, json_dumps_params={'sort_keys': True})
+        return response


Test patch:
diff --git a/wafer/schedule/tests/test_views.py b/wafer/schedule/tests/test_views.py
--- a/wafer/schedule/tests/test_views.py
+++ b/wafer/schedule/tests/test_views.py
@@ -1525,6 +1525,109 @@ def test_ics_view(self):
         self.assertTrue('/test0/' in event['url'])
 
 
+class JsonViewTests(TestCase):
+
+    def setUp(self):
+        # Create the schedule used for these tests
+        timezone.activate('UTC')
+        day1 = ScheduleBlock.objects.create(
+            start_time=D.datetime(2013, 9, 22, 7, 0, 0,
+                                  tzinfo=timezone.utc),
+            end_time=D.datetime(2013, 9, 22, 19, 0, 0,
+                                tzinfo=timezone.utc),
+            )
+        day2 = ScheduleBlock.objects.create(
+            start_time=D.datetime(2013, 9, 23, 7, 0, 0,
+                                  tzinfo=timezone.utc),
+            end_time=D.datetime(2013, 9, 23, 19, 0, 0,
+                                tzinfo=timezone.utc),
+            )
+        venue1 = Venue.objects.create(order=1, name='Venue 1')
+        venue2 = Venue.objects.create(order=2, name='Venue 2')
+        venue1.blocks.add(day1)
+        venue2.blocks.add(day1)
+
+        start1 = D.datetime(2013, 9, 22, 10, 0, 0, tzinfo=timezone.utc)
+        start2 = D.datetime(2013, 9, 22, 11, 0, 0, tzinfo=timezone.utc)
+        start3 = D.datetime(2013, 9, 22, 12, 0, 0, tzinfo=timezone.utc)
+        start4 = D.datetime(2013, 9, 22, 13, 0, 0, tzinfo=timezone.utc)
+        start5 = D.datetime(2013, 9, 22, 14, 0, 0, tzinfo=timezone.utc)
+
+        slots = []
+
+        slots.append(Slot.objects.create(start_time=start1, end_time=start2))
+        slots.append(Slot.objects.create(start_time=start2, end_time=start3))
+        slots.append(Slot.objects.create(start_time=start3, end_time=start4))
+        slots.append(Slot.objects.create(start_time=start4, end_time=start5))
+
+        pages = make_pages(6)
+        venues = [venue1, venue2] * 3
+        items = make_items(venues, pages)
+
+        for index, item in enumerate(items):
+            item.slots.add(slots[(index + 1) // 2])
+
+        user = get_user_model().objects.create_user('jimbob', 'best@wafer.test',
+                                                    'johnpassword')
+
+        talk1 = Talk.objects.create(title="Test talk", status=ACCEPTED,
+                                    corresponding_author_id=user.id)
+        talk1.authors.add(user)
+
+        talk2 = Talk.objects.create(title="Test 2 talk", status=ACCEPTED,
+                                    corresponding_author_id=user.id)
+        talk2.authors.add(user)
+
+        item1 = ScheduleItem.objects.create(venue=venue1,
+                                            talk_id=talk1.pk)
+
+        item2 = ScheduleItem.objects.create(venue=venue2,
+                                            talk_id=talk2.pk)
+        item1.slots.add(slots[0])
+        item2.slots.add(slots[3])
+
+    def test_json_view_unauth(self):
+        """Test that unauthenticated access to the json view fails"""
+        c = Client()
+        response = c.get('/schedule/schedule.json')
+        self.assertEqual(response.status_code, 403)
+
+    def test_json_view_user(self):
+        """Test that ordinary users access the json view fails"""
+        c = create_client('john', False)
+        response = c.get('/schedule/schedule.json')
+        self.assertEqual(response.status_code, 403)
+
+    def test_json_view_admin(self):
+        """Test that admin users can access the json view"""
+        c = create_client('super', True)
+        response = c.get('/schedule/schedule.json')
+        self.assertEqual(response.status_code, 200)
+        data = json.loads(response.content.decode('utf8'))
+        self.assertTrue('version' in data)
+        self.assertEqual(len(data['events']), 8)
+        self.assertEqual(len(data['venues']), 2)
+        # Check that venues have the expected fields
+        self.assertTrue('name' in data['venues'][0])
+        self.assertTrue('id' in data['venues'][0])
+        # Check that events have the expected fields
+        self.assertTrue('license' in data['events'][0])
+        self.assertTrue('room_id' in data['events'][0])
+        self.assertTrue('id' in data['events'][0])
+        self.assertTrue('track' in data['events'][0])
+        self.assertTrue('duration' in data['events'][0])
+
+        # Events are ordered by creation order currently, because of how
+        # we construct the json file, so events[0] is a page and events[6]
+        # is a talk with the same start time
+        self.assertEqual(data['events'][0]['start_time'], data['events'][6]['start_time'])
+        self.assertEqual(len(data['events'][0]['authors']), 0)
+        self.assertEqual(len(data['events'][6]['authors']), 1)
+        # events[7] is talk2
+        self.assertNotEqual(data['events'][0]['start_time'], data['events'][7]['start_time'])
+        self.assertEqual(len(data['events'][7]['authors']), 1)
+
+
 class ScheduleItemViewSetTests(TestCase):
 
     def setUp(self):