Skip to content

Commit 016ee71

Browse files
feat: persist timeslot types made visible in the schedule editor (ietf-tools#3778)
This adds a POST action to the edit_meeting_schedule view that sets a list of currently visible timeslot types in the user's session. On display of the schedule editor, this is consulted before defaulting to show only 'regular' timeslots. An ajax request is made to update the session when changing the settings in the editor.
1 parent cb3177e commit 016ee71

4 files changed

Lines changed: 123 additions & 32 deletions

File tree

ietf/meeting/tests_views.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3898,6 +3898,69 @@ def test_slot_to_the_right(self):
38983898
self.assertTrue(mars_slot.slot_to_the_right)
38993899
self.assertTrue(mars_scheduled.slot_to_the_right)
39003900

3901+
def test_updateview(self):
3902+
"""The updateview action should set visible timeslot types in the session"""
3903+
meeting = MeetingFactory(type_id='ietf')
3904+
url = urlreverse('ietf.meeting.views.edit_meeting_schedule', kwargs={'num': meeting.number})
3905+
types_to_enable = ['regular', 'reg', 'other']
3906+
r = self.client.post(
3907+
url,
3908+
{
3909+
'action': 'updateview',
3910+
'enabled_timeslot_types[]': types_to_enable,
3911+
},
3912+
)
3913+
self.assertEqual(r.status_code, 200)
3914+
session_data = self.client.session
3915+
self.assertIn('edit_meeting_schedule', session_data)
3916+
self.assertCountEqual(
3917+
session_data['edit_meeting_schedule']['enabled_timeslot_types'],
3918+
types_to_enable,
3919+
'Should set types requested',
3920+
)
3921+
3922+
r = self.client.post(
3923+
url,
3924+
{
3925+
'action': 'updateview',
3926+
'enabled_timeslot_types[]': types_to_enable + ['faketype'],
3927+
},
3928+
)
3929+
self.assertEqual(r.status_code, 200)
3930+
session_data = self.client.session
3931+
self.assertIn('edit_meeting_schedule', session_data)
3932+
self.assertCountEqual(
3933+
session_data['edit_meeting_schedule']['enabled_timeslot_types'],
3934+
types_to_enable,
3935+
'Should ignore unknown types',
3936+
)
3937+
3938+
def test_persistent_enabled_timeslot_types(self):
3939+
meeting = MeetingFactory(type_id='ietf')
3940+
TimeSlotFactory(meeting=meeting, type_id='other')
3941+
TimeSlotFactory(meeting=meeting, type_id='reg')
3942+
3943+
# test default behavior (only 'regular' enabled)
3944+
r = self.client.get(urlreverse('ietf.meeting.views.edit_meeting_schedule', kwargs={'num': meeting.number}))
3945+
self.assertEqual(r.status_code, 200)
3946+
q = PyQuery(r.content)
3947+
self.assertEqual(len(q('#timeslot-type-toggles-modal input[value="regular"][checked]')), 1)
3948+
self.assertEqual(len(q('#timeslot-type-toggles-modal input[value="other"]:not([checked])')), 1)
3949+
self.assertEqual(len(q('#timeslot-type-toggles-modal input[value="reg"]:not([checked])')), 1)
3950+
3951+
# test with 'regular' and 'other' enabled via session store
3952+
client_session = self.client.session # must store as var, new session is created on access
3953+
client_session['edit_meeting_schedule'] = {
3954+
'enabled_timeslot_types': ['regular', 'other']
3955+
}
3956+
client_session.save()
3957+
r = self.client.get(urlreverse('ietf.meeting.views.edit_meeting_schedule', kwargs={'num': meeting.number}))
3958+
self.assertEqual(r.status_code, 200)
3959+
q = PyQuery(r.content)
3960+
self.assertEqual(len(q('#timeslot-type-toggles-modal input[value="regular"][checked]')), 1)
3961+
self.assertEqual(len(q('#timeslot-type-toggles-modal input[value="other"][checked]')), 1)
3962+
self.assertEqual(len(q('#timeslot-type-toggles-modal input[value="reg"]:not([checked])')), 1)
3963+
39013964

39023965
class SessionDetailsTests(TestCase):
39033966

ietf/meeting/views.py

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -728,10 +728,18 @@ def _json_response(success, status=None, **extra_data):
728728
return JsonResponse(data, status=status)
729729

730730
if request.method == 'POST':
731-
if not can_edit:
732-
permission_denied(request, "Can't edit this schedule.")
733-
734731
action = request.POST.get('action')
732+
if action == 'updateview':
733+
# allow updateview action even if can_edit is false, it affects only the user's session
734+
sess_data = request.session.setdefault('edit_meeting_schedule', {})
735+
enabled_types = [ts_type.slug for ts_type in TimeSlotTypeName.objects.filter(
736+
used=True,
737+
slug__in=request.POST.getlist('enabled_timeslot_types[]', [])
738+
)]
739+
sess_data['enabled_timeslot_types'] = enabled_types
740+
return _json_response(True)
741+
elif not can_edit:
742+
permission_denied(request, "Can't edit this schedule.")
735743

736744
# Handle ajax requests. Most of these return JSON responses with at least a 'success' key.
737745
# For the swapdays and swaptimeslots actions, the response is either a redirect to the
@@ -949,6 +957,16 @@ def cubehelix(i, total, hue=1.2, start_angle=0.5):
949957
key=lambda tstype: tstype.name,
950958
)
951959

960+
# extract view configuration from session store
961+
session_data = request.session.get('edit_meeting_schedule', None)
962+
if session_data is None:
963+
enabled_timeslot_types = ['regular']
964+
else:
965+
enabled_timeslot_types = [
966+
ts_type.slug for ts_type in timeslot_types
967+
if ts_type.slug in session_data.get('enabled_timeslot_types', [])
968+
]
969+
952970
return render(request, "meeting/edit_meeting_schedule.html", {
953971
'meeting': meeting,
954972
'schedule': schedule,
@@ -963,6 +981,7 @@ def cubehelix(i, total, hue=1.2, start_angle=0.5):
963981
'timeslot_types': timeslot_types,
964982
'hide_menu': True,
965983
'lock_time': lock_time,
984+
'enabled_timeslot_types': enabled_timeslot_types,
966985
})
967986

968987

ietf/static/js/edit-meeting-schedule.js

Lines changed: 37 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,17 @@ $(function () {
1919
alert("Error: " + errorText);
2020
}
2121

22+
function ajaxCall(action, data) {
23+
const ajaxData = { action: action };
24+
Object.assign(ajaxData, data);
25+
return jQuery.ajax({
26+
url: window.location.href,
27+
method: "post",
28+
timeout: 5 * 1000,
29+
data: ajaxData,
30+
});
31+
}
32+
2233
/**
2334
* Time to treat as current time for computing whether to lock timeslots
2435
* @returns {*} Moment object equal to lockSeconds in the future
@@ -437,26 +448,20 @@ $(function () {
437448
}
438449

439450
if (dropParent.hasClass("unassigned-sessions")) {
440-
jQuery.ajax({
441-
url: window.location.href,
442-
method: "post",
443-
timeout: 5 * 1000,
444-
data: {
445-
action: "unassign",
446-
session: sessionElement.id.slice("session".length)
447-
}
448-
}).fail(failHandler).done(done);
451+
ajaxCall(
452+
"unassign",
453+
{ session: sessionElement.id.slice('session'.length) }
454+
).fail(failHandler)
455+
.done(done);
449456
} else {
450-
jQuery.ajax({
451-
url: window.location.href,
452-
method: "post",
453-
data: {
454-
action: "assign",
457+
ajaxCall(
458+
"assign",
459+
{
455460
session: sessionElement.id.slice("session".length),
456461
timeslot: dropParent.attr("id").slice("timeslot".length)
457-
},
458-
timeout: 5 * 1000
459-
}).fail(failHandler).done(done);
462+
}
463+
).fail(failHandler)
464+
.done(done);
460465
}
461466
});
462467

@@ -759,16 +764,20 @@ $(function () {
759764

760765
// Toggling timeslot types
761766
function updateTimeSlotTypeToggling() {
762-
let checked = [];
763-
timeSlotTypeInputs.filter(":checked").each(function () {
764-
checked.push("[data-type=" + this.value + "]");
765-
});
767+
const checkedTypes = jQuery.map(timeSlotTypeInputs.filter(":checked"), elt => elt.value);
768+
const checkedSelectors = checkedTypes.map(t => '[data-type="' + t + '"]').join(",");
766769

767-
sessions.filter(checked.join(",")).removeClass('hidden-timeslot-type');
768-
sessions.not(checked.join(",")).addClass('hidden-timeslot-type');
769-
timeslots.filter(checked.join(",")).removeClass('hidden-timeslot-type');
770-
timeslots.not(checked.join(",")).addClass('hidden-timeslot-type');
770+
sessions.filter(checkedSelectors).removeClass('hidden-timeslot-type');
771+
sessions.not(checkedSelectors).addClass('hidden-timeslot-type');
772+
timeslots.filter(checkedSelectors).removeClass('hidden-timeslot-type');
773+
timeslots.not(checkedSelectors).addClass('hidden-timeslot-type');
771774
updateGridVisibility();
775+
return checkedTypes;
776+
}
777+
778+
function updateTimeSlotTypeTogglingAndSave() {
779+
const checkedTypes = updateTimeSlotTypeToggling();
780+
ajaxCall('updateview', {enabled_timeslot_types: checkedTypes});
772781
}
773782

774783
// Toggling session purposes
@@ -783,23 +792,23 @@ $(function () {
783792
}
784793

785794
if (timeSlotTypeInputs.length > 0) {
786-
timeSlotTypeInputs.on("change", updateTimeSlotTypeToggling);
795+
timeSlotTypeInputs.on("change", updateTimeSlotTypeTogglingAndSave);
787796
updateTimeSlotTypeToggling();
788797
schedEditor.find('#timeslot-type-toggles-modal .timeslot-type-toggles .select-all')
789798
.get(0)
790799
.addEventListener(
791800
'click',
792801
function() {
793802
timeSlotTypeInputs.prop('checked', true);
794-
updateTimeSlotTypeToggling();
803+
updateTimeSlotTypeTogglingAndSave();
795804
});
796805
schedEditor.find('#timeslot-type-toggles-modal .timeslot-type-toggles .clear-all')
797806
.get(0)
798807
.addEventListener(
799808
'click',
800809
function() {
801810
timeSlotTypeInputs.prop('checked', false);
802-
updateTimeSlotTypeToggling();
811+
updateTimeSlotTypeTogglingAndSave();
803812
});
804813
}
805814

ietf/templates/meeting/edit_meeting_schedule.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,7 @@ <h5 class="modal-title" id="timeslot-type-toggles-modal-title">Displayed types</
312312
<label class="timeslot-type-{{ type.slug }}">
313313
<input type="checkbox"
314314
class="form-check-input"
315-
{% if type.slug == 'regular' %}checked{% endif %}
315+
{% if type.slug in enabled_timeslot_types %}checked{% endif %}
316316
value="{{ type.slug }}">
317317
{{ type }}
318318
</label>

0 commit comments

Comments
 (0)