Skip to content

Commit

Permalink
Merge pull request #687 from CTPUG/feature/add_setting_to_hide_schedule
Browse files Browse the repository at this point in the history
Feature/add setting to hide schedule
  • Loading branch information
drnlm authored Oct 19, 2023
2 parents cadbb11 + 029e5a6 commit 22aeef1
Show file tree
Hide file tree
Showing 8 changed files with 271 additions and 10 deletions.
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ Installation
7. Wafer uses the Django caching infrastructure in several places, so
the cache table needs to be created using ``manage.py createcachetable``.

8. Create the default 'Page Editors' and 'Talk Mentors' groups using
8. Create the default 'Page Editors', 'Talk Mentors' and other useful groups using
``manage.py wafer_add_default_groups``.

9. Log in and configure the Site:
Expand Down
26 changes: 26 additions & 0 deletions docs/schedule.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@
Schedule
========

Permissions
===========

Setting up the schedule blocks and using the schedule editor requires access
to the admin site.

Specifying Block and Venues
===========================

Expand Down Expand Up @@ -104,3 +110,23 @@ to add the validators to the list.
To display the errors in the admin form, you will also need to extend the
``displayerrors`` block in ``scheduleitem_list.html`` and ``slot_list.html``
templates.

Schedule fails to render
========================

To avoid displaying misleading or incorrect information to attendees, the
schedule will not be rendered if the schedule fails to validate, and it
will display a "The final schedule has not been published" message instead.

Users with permissions to use the schedule editor will see a list of validation
errors as well, to help diagnose the problem preventing the schedule from
rendering correctly. These errors will also be displayed in the schedule editor
and in the admin site.


Hiding the schedule while editing
=================================

The setting ``WAFER_HIDE_SCHEDULE`` will prevent the schedule from rendering for
users without admin access. Users with admin access will see a note that the
schedule is a draft and not public.
3 changes: 1 addition & 2 deletions wafer/schedule/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
18 changes: 17 additions & 1 deletion wafer/schedule/templates/wafer.schedule/current.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,24 @@
<section class="wafer wafer-schedule">
<h1>{% trans "Happening now" %}</h1>
<div class="wafer_schedule">
{% if draft_warning %}
<div class="messages alert alert-warning">
<p><strong>{% trans "This is a draft that is not visible to the public" %}</strong></p>
</div>
{% endif %}
{% if not active %}
{# Schedule is incomplete / invalid, so show nothing #}
{# Show errors to authorised users (checked in get_context_data) #}
{% if validation_errors %}
<div class="messages alert alert-danger">
<p><strong>{% trans "Validation errors:" %}</strong></p>
<ul>
{% for validation_error in validation_errors %}
<li>{{ validation_error }}</li>
{% endfor %}
</ul>
</div>
{% endif %}
{# Schedule is incomplete / invalid, so show general message #}
{% blocktrans trimmed %}
<p>The final schedule has not been published yet.</p>
{% endblocktrans %}
Expand Down
16 changes: 16 additions & 0 deletions wafer/schedule/templates/wafer.schedule/full_schedule.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,23 @@ <h1>
{% trans "Schedule" %}
</h1>
<div class="wafer_schedule">
{% if draft_warning %}
<div class="messages alert alert-warning">
<p><strong>{% trans "This is a draft that is not visible to the public" %}</strong></p>
</div>
{% endif %}
{% if not schedule_pages %}
{# Show errors to authorised users (checked in get_context_data) #}
{% if validation_errors %}
<div class="messages alert alert-danger">
<p><strong>{% trans "Validation errors:" %}</strong></p>
<ul>
{% for validation_error in validation_errors %}
<li>{{ validation_error }}</li>
{% endfor %}
</ul>
</div>
{% endif %}
{# Schedule is incomplete / invalid, so show nothing #}
{% blocktrans trimmed %}
<p>The final schedule has not been published yet.</p>
Expand Down
184 changes: 184 additions & 0 deletions wafer/schedule/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):

Expand Down Expand Up @@ -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):
Expand Down
29 changes: 23 additions & 6 deletions wafer/schedule/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions wafer/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -359,3 +359,6 @@
'gitlab': _('gitlab profile'),
'bitbucket': _('bitbucket profile'),
}

# Hide the schedule from users without permission to edit it
WAFER_HIDE_SCHEDULE = False

0 comments on commit 22aeef1

Please sign in to comment.