Repository: CTPUG/wafer

Issue:
Flag to hide the schedule until it's ready
Probably a django setting to keep the schedule from publishing until the content team is ready.

It would be nice to have a button on the site to do this, but no obvious place to store the state.


Discussion:

Resolving patch:
diff --git a/wafer/schedule/admin.py b/wafer/schedule/admin.py
--- a/wafer/schedule/admin.py
+++ b/wafer/schedule/admin.py
@@ -356,8 +356,7 @@ def changelist_view(self, request, extra_context=None):
             if failed_items:
                 errors[err_type].extend(failed_items)
         extra_context['errors'] = errors
-        return super().changelist_view(request,
-                                                              extra_context)
+        return super().changelist_view(request, extra_context)
 
     def get_urls(self):
         from wafer.schedule.views import ScheduleEditView
diff --git a/wafer/schedule/views.py b/wafer/schedule/views.py
--- a/wafer/schedule/views.py
+++ b/wafer/schedule/views.py
@@ -169,7 +169,13 @@ def get_context_data(self, **kwargs):
         context['prev_block'] = None
         context['next_block'] = None
         if not check_schedule():
+            if self.request.user.is_staff:
+                context['validation_errors'] = validate_schedule()
             return context
+        if settings.WAFER_HIDE_SCHEDULE:
+            if not self.request.user.is_staff:
+                return context
+            context['draft_warning'] = True
         context['active'] = True
         try:
             block_id = int(self.request.GET.get('block', -1))
@@ -220,24 +226,24 @@ def get_context_data(self, **kwargs):
 class CurrentView(TemplateView):
     template_name = 'wafer.schedule/current.html'
 
-    def _parse_timestamp(self, given_timestamp):
+    def _parse_timestamp(self, timestamp):
         """
         Parse a user provided timestamp query string parameter.
         Return a TZ aware datetime, or None.
         """
-        if not given_timestamp:
+        if not timestamp:
             return None
         try:
-            timestamp = parse_datetime(given_timestamp)
+            timestamp = parse_datetime(timestamp)
         except ValueError as e:
             messages.error(self.request,
-                           f'Failed to parse timestamp ({given_timestamp}): {e}')
+                           'Failed to parse timestamp: %s' % e)
             # Short circuit out here
             return None
         if timestamp is None:
             # If parse_datetime completely fails to extract anything
             # we end up here
-            messages.error(self.request, f'Failed to parse timestamp {given_timestamp}')
+            messages.error(self.request, 'Failed to parse timestamp')
             return None
         if not timezone.is_aware(timestamp):
             timestamp = timezone.make_aware(timestamp)
@@ -299,8 +305,19 @@ def get_context_data(self, **kwargs):
         # If the schedule is invalid, return a context with active=False
         context['active'] = False
         if not check_schedule():
+            if self.request.user.is_staff:
+                context['validation_errors'] = validate_schedule()
             return context
-        # The schedule is valid, so add active=True and empty slots
+        # The schedule is valid
+        if settings.WAFER_HIDE_SCHEDULE:
+            if not self.request.user.is_staff:
+                # The schedule is hidden, and the user doesn't get to see
+                # the draft version
+                return context
+            # Display the 'This is a draft warning' because we're hiding the
+            # schedule
+            context['draft_warning'] = True
+        # We're displaying the schedule so add active=True and empty slots
         context['active'] = True
         context['slots'] = []
         # Allow refresh time to be overridden
diff --git a/wafer/settings.py b/wafer/settings.py
--- a/wafer/settings.py
+++ b/wafer/settings.py
@@ -359,3 +359,6 @@
     'gitlab': _('gitlab profile'),
     'bitbucket': _('bitbucket profile'),
 }
+
+# Hide the schedule from users without permission to edit it
+WAFER_HIDE_SCHEDULE = False


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
@@ -991,6 +991,113 @@ def validate_schedule(response):
             items[2].get_details(),
             '</a></td>']), html=True)
 
+    def test_view_invalid(self):
+        """Test that invalid schedules return a inactive schedule."""
+        day1 = ScheduleBlock.objects.create(
+            start_time=D.datetime(2013, 9, 22, 7, 0, 0,
+                                  tzinfo=D.timezone.utc),
+            end_time=D.datetime(2013, 9, 22, 19, 0, 0,
+                                tzinfo=D.timezone.utc),
+            )
+        venue1 = Venue.objects.create(order=1, name='Venue 1')
+        venue1.blocks.add(day1)
+        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)
+        end = D.datetime(2013, 9, 22, 12, 0, 0, tzinfo=D.timezone.utc)
+        cur1 = D.datetime(2013, 9, 22, 10, 30, 0, tzinfo=D.timezone.utc)
+
+        slot1 = Slot.objects.create(start_time=start1, end_time=start2)
+        slot2 = Slot.objects.create(start_time=start1, end_time=end)
+
+        talk = create_talk('Test talk', status=ACCEPTED, username='john')
+
+        item1 = ScheduleItem.objects.create(venue=venue1,
+                                            talk_id=talk.pk)
+        item1.slots.add(slot1)
+        item2 = ScheduleItem.objects.create(venue=venue1,
+                                            talk_id=talk.pk)
+        item2.slots.add(slot2)
+
+        c = Client()
+        response = c.get('/schedule/')
+        assert response.context['active'] is False
+        assert 'validation_errors' not in response.context
+
+        # Check the logged in users get the same result
+        ordinary = create_client('jill', False)
+        response = ordinary.get('/schedule/')
+        assert response.context['active'] is False
+        assert 'validation_errors' not in response.context
+
+        # Check that a user with elevated privileges sees validation errors
+        admin = create_client('admin', True)
+
+        response = admin.get('/schedule/')
+        assert response.context['active'] is False
+        assert 'validation_errors' in response.context
+
+    def test_view_hidden(self):
+        """Test that the schedule is hidden by the appropriate setting."""
+        with self.settings(WAFER_HIDE_SCHEDULE=True):
+
+            day1 = ScheduleBlock.objects.create(start_time=D.datetime(2013, 9, 22, 1, 0, 0,
+                                                                      tzinfo=D.timezone.utc),
+                                                end_time=D.datetime(2013, 9, 22, 23, 0, 0,
+                                                                    tzinfo=D.timezone.utc),
+                                                )
+            day2 = ScheduleBlock.objects.create(start_time=D.datetime(2013, 9, 23, 1, 0, 0,
+                                                                      tzinfo=D.timezone.utc),
+                                                end_time=D.datetime(2013, 9, 23, 23, 0, 0,
+                                                                    tzinfo=D.timezone.utc),
+                                                )
+            venue1 = Venue.objects.create(order=1, name='Venue 1')
+            venue1.blocks.add(day1)
+            venue1.blocks.add(day2)
+            venue2 = Venue.objects.create(order=2, name='Venue 2')
+            venue2.blocks.add(day1)
+            venue2.blocks.add(day2)
+
+            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)
+            end1 = D.datetime(2013, 9, 22, 12, 0, 0, tzinfo=D.timezone.utc)
+
+            start3 = D.datetime(2013, 9, 23, 12, 0, 0, tzinfo=D.timezone.utc)
+            end2 = D.datetime(2013, 9, 23, 13, 0, 0, tzinfo=D.timezone.utc)
+
+            pages = make_pages(6)
+            venues = [venue1, venue1, venue1, venue2, venue2, venue2]
+            items = make_items(venues, pages)
+
+            slot1 = Slot.objects.create(start_time=start1, end_time=start2)
+            slot2 = Slot.objects.create(start_time=start2, end_time=end1)
+            slot3 = Slot.objects.create(start_time=start3, end_time=end2)
+
+            items[0].slots.add(slot1)
+            items[3].slots.add(slot1)
+            items[1].slots.add(slot2)
+            items[4].slots.add(slot2)
+            items[2].slots.add(slot3)
+            items[5].slots.add(slot3)
+
+
+            c = Client()
+            response = c.get('/schedule/')
+            assert response.context['active'] is False
+            assert 'draft_warning' not in response.context
+
+            # Check the logged in users get the same result
+            ordinary = create_client('jill', False)
+            response = ordinary.get('/schedule/')
+            assert response.context['active'] is False
+            assert 'draft_warning' not in response.context
+
+            # Check that a user with elevated privileges sees validation errors
+            admin = create_client('admin', True)
+
+            response = admin.get('/schedule/')
+            assert response.context['active'] is True
+            assert 'draft_warning' in response.context
+
 
 class CurrentViewTests(TestCase):
 
@@ -1461,6 +1568,83 @@ def test_current_view_invalid(self):
         response = c.get('/schedule/current/',
                          {'timestamp': cur1.isoformat()})
         assert response.context['active'] is False
+        assert 'validation_errors' not in response.context
+
+        # Check the logged in users get the same result
+        ordinary = create_client('jill', False)
+        response = ordinary.get('/schedule/current/',
+                                {'timestamp': cur1.isoformat()})
+        assert response.context['active'] is False
+        assert 'validation_errors' not in response.context
+
+        # Check that a user with elevated privileges sees validation errors
+        admin = create_client('admin', True)
+
+        response = admin.get('/schedule/current/',
+                             {'timestamp': cur1.isoformat()})
+        assert response.context['active'] is False
+        assert 'validation_errors' in response.context
+
+    def test_view_hidden(self):
+        """Test that the schedule is hidden by the appropriate setting."""
+        with self.settings(WAFER_HIDE_SCHEDULE=True):
+
+            day1 = ScheduleBlock.objects.create(start_time=D.datetime(2013, 9, 22, 1, 0, 0,
+                                                                      tzinfo=D.timezone.utc),
+                                                end_time=D.datetime(2013, 9, 22, 23, 0, 0,
+                                                                    tzinfo=D.timezone.utc),
+                                                )
+            day2 = ScheduleBlock.objects.create(start_time=D.datetime(2013, 9, 23, 1, 0, 0,
+                                                                      tzinfo=D.timezone.utc),
+                                                end_time=D.datetime(2013, 9, 23, 23, 0, 0,
+                                                                    tzinfo=D.timezone.utc),
+                                                )
+            venue1 = Venue.objects.create(order=1, name='Venue 1')
+            venue1.blocks.add(day1)
+            venue1.blocks.add(day2)
+            venue2 = Venue.objects.create(order=2, name='Venue 2')
+            venue2.blocks.add(day1)
+            venue2.blocks.add(day2)
+
+            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)
+            end1 = D.datetime(2013, 9, 22, 12, 0, 0, tzinfo=D.timezone.utc)
+
+            start3 = D.datetime(2013, 9, 23, 12, 0, 0, tzinfo=D.timezone.utc)
+            end2 = D.datetime(2013, 9, 23, 13, 0, 0, tzinfo=D.timezone.utc)
+
+            pages = make_pages(6)
+            venues = [venue1, venue1, venue1, venue2, venue2, venue2]
+            items = make_items(venues, pages)
+
+            slot1 = Slot.objects.create(start_time=start1, end_time=start2)
+            slot2 = Slot.objects.create(start_time=start2, end_time=end1)
+            slot3 = Slot.objects.create(start_time=start3, end_time=end2)
+
+            items[0].slots.add(slot1)
+            items[3].slots.add(slot1)
+            items[1].slots.add(slot2)
+            items[4].slots.add(slot2)
+            items[2].slots.add(slot3)
+            items[5].slots.add(slot3)
+
+            c = Client()
+            response = c.get('/schedule/current/')
+            assert response.context['active'] is False
+            assert 'draft_warning' not in response.context
+
+            # Check the logged in users get the same result
+            ordinary = create_client('jill', False)
+            response = ordinary.get('/schedule/current/')
+            assert response.context['active'] is False
+            assert 'draft_warning' not in response.context
+
+            # Check that a user with elevated privileges sees validation errors
+            admin = create_client('admin', True)
+
+            response = admin.get('/schedule/current/')
+            assert response.context['active'] is True
+            assert 'draft_warning' in response.context
 
 
 class NonHTMLViewTests(TestCase):