feat: persist timeslot types made visible in the schedule editor (#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.
This commit is contained in:
parent
cb3177efb9
commit
016ee71855
|
@ -3898,6 +3898,69 @@ class EditTests(TestCase):
|
|||
self.assertTrue(mars_slot.slot_to_the_right)
|
||||
self.assertTrue(mars_scheduled.slot_to_the_right)
|
||||
|
||||
def test_updateview(self):
|
||||
"""The updateview action should set visible timeslot types in the session"""
|
||||
meeting = MeetingFactory(type_id='ietf')
|
||||
url = urlreverse('ietf.meeting.views.edit_meeting_schedule', kwargs={'num': meeting.number})
|
||||
types_to_enable = ['regular', 'reg', 'other']
|
||||
r = self.client.post(
|
||||
url,
|
||||
{
|
||||
'action': 'updateview',
|
||||
'enabled_timeslot_types[]': types_to_enable,
|
||||
},
|
||||
)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
session_data = self.client.session
|
||||
self.assertIn('edit_meeting_schedule', session_data)
|
||||
self.assertCountEqual(
|
||||
session_data['edit_meeting_schedule']['enabled_timeslot_types'],
|
||||
types_to_enable,
|
||||
'Should set types requested',
|
||||
)
|
||||
|
||||
r = self.client.post(
|
||||
url,
|
||||
{
|
||||
'action': 'updateview',
|
||||
'enabled_timeslot_types[]': types_to_enable + ['faketype'],
|
||||
},
|
||||
)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
session_data = self.client.session
|
||||
self.assertIn('edit_meeting_schedule', session_data)
|
||||
self.assertCountEqual(
|
||||
session_data['edit_meeting_schedule']['enabled_timeslot_types'],
|
||||
types_to_enable,
|
||||
'Should ignore unknown types',
|
||||
)
|
||||
|
||||
def test_persistent_enabled_timeslot_types(self):
|
||||
meeting = MeetingFactory(type_id='ietf')
|
||||
TimeSlotFactory(meeting=meeting, type_id='other')
|
||||
TimeSlotFactory(meeting=meeting, type_id='reg')
|
||||
|
||||
# test default behavior (only 'regular' enabled)
|
||||
r = self.client.get(urlreverse('ietf.meeting.views.edit_meeting_schedule', kwargs={'num': meeting.number}))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertEqual(len(q('#timeslot-type-toggles-modal input[value="regular"][checked]')), 1)
|
||||
self.assertEqual(len(q('#timeslot-type-toggles-modal input[value="other"]:not([checked])')), 1)
|
||||
self.assertEqual(len(q('#timeslot-type-toggles-modal input[value="reg"]:not([checked])')), 1)
|
||||
|
||||
# test with 'regular' and 'other' enabled via session store
|
||||
client_session = self.client.session # must store as var, new session is created on access
|
||||
client_session['edit_meeting_schedule'] = {
|
||||
'enabled_timeslot_types': ['regular', 'other']
|
||||
}
|
||||
client_session.save()
|
||||
r = self.client.get(urlreverse('ietf.meeting.views.edit_meeting_schedule', kwargs={'num': meeting.number}))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertEqual(len(q('#timeslot-type-toggles-modal input[value="regular"][checked]')), 1)
|
||||
self.assertEqual(len(q('#timeslot-type-toggles-modal input[value="other"][checked]')), 1)
|
||||
self.assertEqual(len(q('#timeslot-type-toggles-modal input[value="reg"]:not([checked])')), 1)
|
||||
|
||||
|
||||
class SessionDetailsTests(TestCase):
|
||||
|
||||
|
|
|
@ -728,10 +728,18 @@ def edit_meeting_schedule(request, num=None, owner=None, name=None):
|
|||
return JsonResponse(data, status=status)
|
||||
|
||||
if request.method == 'POST':
|
||||
if not can_edit:
|
||||
permission_denied(request, "Can't edit this schedule.")
|
||||
|
||||
action = request.POST.get('action')
|
||||
if action == 'updateview':
|
||||
# allow updateview action even if can_edit is false, it affects only the user's session
|
||||
sess_data = request.session.setdefault('edit_meeting_schedule', {})
|
||||
enabled_types = [ts_type.slug for ts_type in TimeSlotTypeName.objects.filter(
|
||||
used=True,
|
||||
slug__in=request.POST.getlist('enabled_timeslot_types[]', [])
|
||||
)]
|
||||
sess_data['enabled_timeslot_types'] = enabled_types
|
||||
return _json_response(True)
|
||||
elif not can_edit:
|
||||
permission_denied(request, "Can't edit this schedule.")
|
||||
|
||||
# Handle ajax requests. Most of these return JSON responses with at least a 'success' key.
|
||||
# For the swapdays and swaptimeslots actions, the response is either a redirect to the
|
||||
|
@ -949,6 +957,16 @@ def edit_meeting_schedule(request, num=None, owner=None, name=None):
|
|||
key=lambda tstype: tstype.name,
|
||||
)
|
||||
|
||||
# extract view configuration from session store
|
||||
session_data = request.session.get('edit_meeting_schedule', None)
|
||||
if session_data is None:
|
||||
enabled_timeslot_types = ['regular']
|
||||
else:
|
||||
enabled_timeslot_types = [
|
||||
ts_type.slug for ts_type in timeslot_types
|
||||
if ts_type.slug in session_data.get('enabled_timeslot_types', [])
|
||||
]
|
||||
|
||||
return render(request, "meeting/edit_meeting_schedule.html", {
|
||||
'meeting': meeting,
|
||||
'schedule': schedule,
|
||||
|
@ -963,6 +981,7 @@ def edit_meeting_schedule(request, num=None, owner=None, name=None):
|
|||
'timeslot_types': timeslot_types,
|
||||
'hide_menu': True,
|
||||
'lock_time': lock_time,
|
||||
'enabled_timeslot_types': enabled_timeslot_types,
|
||||
})
|
||||
|
||||
|
||||
|
|
|
@ -19,6 +19,17 @@ $(function () {
|
|||
alert("Error: " + errorText);
|
||||
}
|
||||
|
||||
function ajaxCall(action, data) {
|
||||
const ajaxData = { action: action };
|
||||
Object.assign(ajaxData, data);
|
||||
return jQuery.ajax({
|
||||
url: window.location.href,
|
||||
method: "post",
|
||||
timeout: 5 * 1000,
|
||||
data: ajaxData,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Time to treat as current time for computing whether to lock timeslots
|
||||
* @returns {*} Moment object equal to lockSeconds in the future
|
||||
|
@ -437,26 +448,20 @@ $(function () {
|
|||
}
|
||||
|
||||
if (dropParent.hasClass("unassigned-sessions")) {
|
||||
jQuery.ajax({
|
||||
url: window.location.href,
|
||||
method: "post",
|
||||
timeout: 5 * 1000,
|
||||
data: {
|
||||
action: "unassign",
|
||||
session: sessionElement.id.slice("session".length)
|
||||
}
|
||||
}).fail(failHandler).done(done);
|
||||
ajaxCall(
|
||||
"unassign",
|
||||
{ session: sessionElement.id.slice('session'.length) }
|
||||
).fail(failHandler)
|
||||
.done(done);
|
||||
} else {
|
||||
jQuery.ajax({
|
||||
url: window.location.href,
|
||||
method: "post",
|
||||
data: {
|
||||
action: "assign",
|
||||
ajaxCall(
|
||||
"assign",
|
||||
{
|
||||
session: sessionElement.id.slice("session".length),
|
||||
timeslot: dropParent.attr("id").slice("timeslot".length)
|
||||
},
|
||||
timeout: 5 * 1000
|
||||
}).fail(failHandler).done(done);
|
||||
}
|
||||
).fail(failHandler)
|
||||
.done(done);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -759,16 +764,20 @@ $(function () {
|
|||
|
||||
// Toggling timeslot types
|
||||
function updateTimeSlotTypeToggling() {
|
||||
let checked = [];
|
||||
timeSlotTypeInputs.filter(":checked").each(function () {
|
||||
checked.push("[data-type=" + this.value + "]");
|
||||
});
|
||||
const checkedTypes = jQuery.map(timeSlotTypeInputs.filter(":checked"), elt => elt.value);
|
||||
const checkedSelectors = checkedTypes.map(t => '[data-type="' + t + '"]').join(",");
|
||||
|
||||
sessions.filter(checked.join(",")).removeClass('hidden-timeslot-type');
|
||||
sessions.not(checked.join(",")).addClass('hidden-timeslot-type');
|
||||
timeslots.filter(checked.join(",")).removeClass('hidden-timeslot-type');
|
||||
timeslots.not(checked.join(",")).addClass('hidden-timeslot-type');
|
||||
sessions.filter(checkedSelectors).removeClass('hidden-timeslot-type');
|
||||
sessions.not(checkedSelectors).addClass('hidden-timeslot-type');
|
||||
timeslots.filter(checkedSelectors).removeClass('hidden-timeslot-type');
|
||||
timeslots.not(checkedSelectors).addClass('hidden-timeslot-type');
|
||||
updateGridVisibility();
|
||||
return checkedTypes;
|
||||
}
|
||||
|
||||
function updateTimeSlotTypeTogglingAndSave() {
|
||||
const checkedTypes = updateTimeSlotTypeToggling();
|
||||
ajaxCall('updateview', {enabled_timeslot_types: checkedTypes});
|
||||
}
|
||||
|
||||
// Toggling session purposes
|
||||
|
@ -783,7 +792,7 @@ $(function () {
|
|||
}
|
||||
|
||||
if (timeSlotTypeInputs.length > 0) {
|
||||
timeSlotTypeInputs.on("change", updateTimeSlotTypeToggling);
|
||||
timeSlotTypeInputs.on("change", updateTimeSlotTypeTogglingAndSave);
|
||||
updateTimeSlotTypeToggling();
|
||||
schedEditor.find('#timeslot-type-toggles-modal .timeslot-type-toggles .select-all')
|
||||
.get(0)
|
||||
|
@ -791,7 +800,7 @@ $(function () {
|
|||
'click',
|
||||
function() {
|
||||
timeSlotTypeInputs.prop('checked', true);
|
||||
updateTimeSlotTypeToggling();
|
||||
updateTimeSlotTypeTogglingAndSave();
|
||||
});
|
||||
schedEditor.find('#timeslot-type-toggles-modal .timeslot-type-toggles .clear-all')
|
||||
.get(0)
|
||||
|
@ -799,7 +808,7 @@ $(function () {
|
|||
'click',
|
||||
function() {
|
||||
timeSlotTypeInputs.prop('checked', false);
|
||||
updateTimeSlotTypeToggling();
|
||||
updateTimeSlotTypeTogglingAndSave();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -312,7 +312,7 @@
|
|||
<label class="timeslot-type-{{ type.slug }}">
|
||||
<input type="checkbox"
|
||||
class="form-check-input"
|
||||
{% if type.slug == 'regular' %}checked{% endif %}
|
||||
{% if type.slug in enabled_timeslot_types %}checked{% endif %}
|
||||
value="{{ type.slug }}">
|
||||
{{ type }}
|
||||
</label>
|
||||
|
|
Loading…
Reference in a new issue