From f3e6e961360c850f2357de93d15324c446a4a1f1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Laur=C3=A9line=20Gu=C3=A9rin?= <zebuline@entrouvert.com>
Date: Tue, 24 May 2022 22:14:41 +0200
Subject: [PATCH 2/2] manager: open the correct tab after redirect (#65653)

---
 chrono/manager/static/js/chrono.manager.js    |  8 +++
 chrono/manager/views.py                       | 62 ++++++++++++++++---
 tests/manager/test_all.py                     |  2 +-
 tests/manager/test_meetings_agenda_options.py |  2 +-
 tests/manager/test_resource.py                |  4 +-
 tests/manager/test_timeperiod.py              |  2 +-
 tests/manager/test_unavailability_calendar.py |  6 +-
 7 files changed, 69 insertions(+), 17 deletions(-)

diff --git a/chrono/manager/static/js/chrono.manager.js b/chrono/manager/static/js/chrono.manager.js
index 97d87a18..717e757c 100644
--- a/chrono/manager/static/js/chrono.manager.js
+++ b/chrono/manager/static/js/chrono.manager.js
@@ -48,4 +48,12 @@ $(function() {
       total_form.val(form_num + 1);
     })
   }
+
+  /* focus tab from #open:<tab slug> anchor, to point to open panel */
+  if (document.location.hash && document.location.hash.indexOf('#open:') == 0) {
+    const $tab_button = $('#tab-' + document.location.hash.substring(6) + '[role=tab]');
+    if ($tab_button.length) {
+      $('.pk-tabs')[0].tabs.selectTab($tab_button[0]);
+    }
+  }
 });
diff --git a/chrono/manager/views.py b/chrono/manager/views.py
index adaea892..282abd49 100644
--- a/chrono/manager/views.py
+++ b/chrono/manager/views.py
@@ -1154,6 +1154,8 @@ class ViewableAgendaMixin:
 
 
 class ManagedAgendaMixin(ViewableAgendaMixin):
+    tab_anchor = None
+
     def check_permissions(self, user):
         return self.agenda.can_be_managed(user)
 
@@ -1165,7 +1167,10 @@ class ManagedAgendaMixin(ViewableAgendaMixin):
         return kwargs
 
     def get_success_url(self):
-        return reverse('chrono-manager-agenda-settings', kwargs={'pk': self.agenda.id})
+        url = reverse('chrono-manager-agenda-settings', kwargs={'pk': self.agenda.id})
+        if self.tab_anchor:
+            url += '#open:%s' % self.tab_anchor
+        return url
 
 
 class AgendaEditView(ManagedAgendaMixin, UpdateView):
@@ -1186,6 +1191,7 @@ agenda_edit = AgendaEditView.as_view()
 class AgendaBookingDelaysView(AgendaEditView):
     form_class = AgendaBookingDelaysForm
     title = _('Configure booking delays')
+    tab_anchor = 'delays'
 
 
 agenda_booking_delays = AgendaBookingDelaysView.as_view()
@@ -1194,6 +1200,7 @@ agenda_booking_delays = AgendaBookingDelaysView.as_view()
 class AgendaRolesView(AgendaEditView):
     form_class = AgendaRolesForm
     title = _('Configure roles')
+    tab_anchor = 'permissions'
 
 
 agenda_roles = AgendaRolesView.as_view()
@@ -1202,6 +1209,7 @@ agenda_roles = AgendaRolesView.as_view()
 class AgendaDisplaySettingsView(AgendaEditView):
     form_class = AgendaDisplaySettingsForm
     title = _("Configure display options")
+    tab_anchor = 'display-options'
 
     def set_agenda(self, **kwargs):
         self.agenda = get_object_or_404(Agenda.objects.exclude(kind='virtual'), pk=kwargs.get('pk'))
@@ -1216,6 +1224,7 @@ agenda_display_settings = AgendaDisplaySettingsView.as_view()
 class AgendaBookingCheckSettingsView(AgendaEditView):
     form_class = AgendaBookingCheckSettingsForm
     title = _("Configure booking check options")
+    tab_anchor = 'booking-check-options'
 
     def set_agenda(self, **kwargs):
         self.agenda = get_object_or_404(Agenda, pk=kwargs.get('pk'), kind='events')
@@ -1748,6 +1757,7 @@ agenda_open_events_view = AgendaOpenEventsView.as_view()
 
 class ManagedAgendaSubobjectMixin:
     agenda = None
+    tab_anchor = None
 
     def dispatch(self, request, *args, **kwargs):
         self.agenda = self.get_object().agenda
@@ -1761,11 +1771,15 @@ class ManagedAgendaSubobjectMixin:
         return context
 
     def get_success_url(self):
-        return reverse('chrono-manager-agenda-settings', kwargs={'pk': self.agenda.id})
+        url = reverse('chrono-manager-agenda-settings', kwargs={'pk': self.agenda.id})
+        if self.tab_anchor:
+            url += '#open:%s' % self.tab_anchor
+        return url
 
 
 class ManagedDeskMixin:
     desk = None
+    tab_anchor = None
 
     def dispatch(self, request, *args, **kwargs):
         try:
@@ -1790,11 +1804,15 @@ class ManagedDeskMixin:
         return context
 
     def get_success_url(self):
-        return reverse('chrono-manager-agenda-settings', kwargs={'pk': self.desk.agenda.id})
+        url = reverse('chrono-manager-agenda-settings', kwargs={'pk': self.desk.agenda.id})
+        if self.tab_anchor:
+            url += '#open:%s' % self.tab_anchor
+        return url
 
 
 class ManagedTimePeriodMixin:
     agenda = None
+    tab_anchor = None
 
     def dispatch(self, request, *args, **kwargs):
         self.time_period = self.get_object()
@@ -1812,13 +1830,16 @@ class ManagedTimePeriodMixin:
         return context
 
     def get_success_url(self):
-        return reverse('chrono-manager-agenda-settings', kwargs={'pk': self.agenda.id})
+        url = reverse('chrono-manager-agenda-settings', kwargs={'pk': self.agenda.id})
+        if self.tab_anchor:
+            url += '#open:%s' % self.tab_anchor
+        return url
 
 
 class ManagedTimePeriodExceptionMixin:
-
     desk = None
     unavailability_calendar = None
+    tab_anchor = None
 
     def dispatch(self, request, *args, **kwargs):
         object_ = self.get_object()
@@ -1845,12 +1866,15 @@ class ManagedTimePeriodExceptionMixin:
 
     def get_success_url(self):
         if self.desk:
-            return reverse('chrono-manager-agenda-settings', kwargs={'pk': self.desk.agenda.id})
+            url = reverse('chrono-manager-agenda-settings', kwargs={'pk': self.desk.agenda.id})
         elif self.unavailability_calendar:
-            return reverse(
+            url = reverse(
                 'chrono-manager-unavailability-calendar-settings',
                 kwargs={'pk': self.unavailability_calendar.pk},
             )
+        if self.tab_anchor:
+            url += '#open:%s' % self.tab_anchor
+        return url
 
 
 class AgendaSettingsRedirectView(RedirectView):
@@ -2092,6 +2116,7 @@ class AgendaNotificationsSettingsView(ManagedAgendaMixin, UpdateView):
     template_name = 'chrono/manager_agenda_notifications_form.html'
     model = AgendaNotificationsSettings
     form_class = AgendaNotificationsForm
+    tab_anchor = 'notifications'
 
     def get_object(self):
         try:
@@ -2114,6 +2139,7 @@ class AgendaReminderSettingsView(AgendaEditView):
     title = _('Reminder Settings')
     model = AgendaReminderSettings
     form_class = AgendaReminderForm
+    tab_anchor = 'reminders'
 
     def get_object(self):
         try:
@@ -2548,6 +2574,7 @@ event_checked = EventCheckedView.as_view()
 class AgendaAddResourceView(ManagedAgendaMixin, FormView):
     template_name = 'chrono/manager_agenda_resource_form.html'
     form_class = AgendaResourceForm
+    tab_anchor = 'resources'
 
     def set_agenda(self, **kwargs):
         self.agenda = get_object_or_404(Agenda, id=kwargs.get('pk'), kind='meetings')
@@ -2569,6 +2596,7 @@ class AgendaResourceDeleteView(ManagedAgendaMixin, DeleteView):
     template_name = 'chrono/manager_confirm_delete.html'
     model = Resource
     pk_url_kwarg = 'resource_pk'
+    tab_anchor = 'resources'
 
     def set_agenda(self, **kwargs):
         self.agenda = get_object_or_404(Agenda, id=kwargs.get('pk'), kind='meetings')
@@ -2630,7 +2658,8 @@ class UnavailabilityCalendarToggleView(ManagedDeskMixin, DetailView):
                         break
 
         return HttpResponseRedirect(
-            reverse('chrono-manager-agenda-settings', kwargs={'pk': self.desk.agenda_id})
+            '%s#open:time-periods'
+            % reverse('chrono-manager-agenda-settings', kwargs={'pk': self.desk.agenda_id})
         )
 
 
@@ -2731,6 +2760,7 @@ def process_time_period_add_form(form, desk=None, agenda=None):
 class AgendaAddTimePeriodView(ManagedDeskMixin, FormView):
     template_name = 'chrono/manager_time_period_form.html'
     form_class = TimePeriodAddForm
+    tab_anchor = 'time-periods'
 
     def get_form_kwargs(self):
         return super(FormView, self).get_form_kwargs()
@@ -2750,6 +2780,7 @@ agenda_add_time_period = AgendaAddTimePeriodView.as_view()
 class VirtualAgendaAddTimePeriodView(ManagedAgendaMixin, FormView):
     template_name = 'chrono/manager_time_period_form.html'
     form_class = TimePeriodAddForm
+    tab_anchor = 'time-periods'
 
     def get_form_kwargs(self):
         return super(FormView, self).get_form_kwargs()
@@ -2766,6 +2797,7 @@ class TimePeriodEditView(ManagedTimePeriodMixin, UpdateView):
     template_name = 'chrono/manager_time_period_form.html'
     model = TimePeriod
     form_class = TimePeriodForm
+    tab_anchor = 'time-periods'
 
 
 time_period_edit = TimePeriodEditView.as_view()
@@ -2774,6 +2806,7 @@ time_period_edit = TimePeriodEditView.as_view()
 class TimePeriodDeleteView(ManagedTimePeriodMixin, DeleteView):
     template_name = 'chrono/manager_confirm_delete.html'
     model = TimePeriod
+    tab_anchor = 'time-periods'
 
     def delete(self, request, *args, **kwargs):
         time_period = self.get_object()
@@ -2803,6 +2836,7 @@ class AgendaAddDesk(ManagedAgendaMixin, CreateView):
     model = Desk
     form_class = NewDeskForm
     agenda = None
+    tab_anchor = 'desks'
 
     def set_agenda(self, **kwargs):
         self.agenda = get_object_or_404(Agenda, pk=kwargs.get('pk'), kind='meetings')
@@ -2815,6 +2849,7 @@ class DeskEditView(ManagedAgendaSubobjectMixin, UpdateView):
     template_name = 'chrono/manager_desk_form.html'
     model = Desk
     form_class = DeskForm
+    tab_anchor = 'desks'
 
     def get_queryset(self):
         return super().get_queryset().filter(agenda__kind='meetings')
@@ -2826,6 +2861,7 @@ desk_edit = DeskEditView.as_view()
 class DeskDeleteView(ManagedAgendaSubobjectMixin, DeleteView):
     template_name = 'chrono/manager_confirm_delete.html'
     model = Desk
+    tab_anchor = 'desks'
 
     def dispatch(self, request, *args, **kwargs):
         agenda = self.get_object().agenda
@@ -2895,6 +2931,7 @@ class AgendaAddTimePeriodExceptionView(ManagedDeskMixin, CreateView):
     template_name = 'chrono/manager_time_period_exception_form.html'
     model = TimePeriodException
     form_class = NewTimePeriodExceptionForm
+    tab_anchor = 'time-periods'
 
     def get_context_data(self, **kwargs):
         context = super().get_context_data(**kwargs)
@@ -2925,6 +2962,7 @@ class TimePeriodExceptionEditView(ManagedTimePeriodExceptionMixin, UpdateView):
     template_name = 'chrono/manager_time_period_exception_form.html'
     model = TimePeriodException
     form_class = TimePeriodExceptionForm
+    tab_anchor = 'time-periods'
 
     def form_valid(self, form):
         result = super().form_valid(form)
@@ -2972,6 +3010,7 @@ time_period_exception_extract_list = TimePeriodExceptionExtractListView.as_view(
 class TimePeriodExceptionDeleteView(ManagedTimePeriodExceptionMixin, DeleteView):
     template_name = 'chrono/manager_confirm_exception_delete.html'
     model = TimePeriodException
+    tab_anchor = 'time-periods'
 
     def get_success_url(self):
         if self.desk:
@@ -3018,6 +3057,7 @@ class DeskImportTimePeriodExceptionsView(ManagedAgendaSubobjectMixin, UpdateView
     model = Desk
     form_class = DeskExceptionsImportForm
     template_name = 'chrono/manager_import_exceptions.html'
+    tab_anchor = 'time-periods'
 
     def get_context_data(self, **kwargs):
         context = super().get_context_data(**kwargs)
@@ -3079,6 +3119,7 @@ desk_import_time_period_exceptions = DeskImportTimePeriodExceptionsView.as_view(
 class TimePeriodExceptionSourceDeleteView(ManagedTimePeriodExceptionMixin, DeleteView):
     template_name = 'chrono/manager_confirm_source_delete.html'
     model = TimePeriodExceptionSource
+    tab_anchor = 'time-periods'
 
     def delete(self, request, *args, **kwargs):
         source = self.get_object()
@@ -3106,6 +3147,7 @@ class TimePeriodExceptionSourceReplaceView(ManagedTimePeriodExceptionMixin, Upda
     model = TimePeriodExceptionSource
     form_class = TimePeriodExceptionSourceReplaceForm
     template_name = 'chrono/manager_replace_exceptions.html'
+    tab_anchor = 'time-periods'
 
     def get_queryset(self):
         queryset = super().get_queryset()
@@ -3136,6 +3178,7 @@ time_period_exception_source_replace = TimePeriodExceptionSourceReplaceView.as_v
 
 class TimePeriodExceptionSourceRefreshView(ManagedTimePeriodExceptionMixin, DetailView):
     model = TimePeriodExceptionSource
+    tab_anchor = 'time-periods'
 
     def get_queryset(self):
         queryset = super().get_queryset()
@@ -3404,7 +3447,8 @@ class TimePeriodExceptionSourceToggleView(ManagedTimePeriodExceptionMixin, Detai
 
         messages.info(self.request, message % {'source': source, 'desk': source.desk})
         return HttpResponseRedirect(
-            reverse('chrono-manager-agenda-settings', kwargs={'pk': source.desk.agenda_id})
+            '%s#open:time-periods'
+            % reverse('chrono-manager-agenda-settings', kwargs={'pk': source.desk.agenda_id})
         )
 
 
diff --git a/tests/manager/test_all.py b/tests/manager/test_all.py
index 5d1ee271..4cc5e8f5 100644
--- a/tests/manager/test_all.py
+++ b/tests/manager/test_all.py
@@ -2248,7 +2248,7 @@ def test_virtual_agenda_settings_delete_excluded_period(app, admin_user):
     url = '/manage/timeperiods/%s/delete' % tp.pk
     resp = resp.click(href=url)
     resp = resp.form.submit()
-    assert resp.location.endswith('/manage/agendas/%s/settings' % agenda.id)
+    assert resp.location.endswith('/manage/agendas/%s/settings#open:time-periods' % agenda.id)
     assert TimePeriod.objects.count() == 0
 
 
diff --git a/tests/manager/test_meetings_agenda_options.py b/tests/manager/test_meetings_agenda_options.py
index 4d654385..1d1245ed 100644
--- a/tests/manager/test_meetings_agenda_options.py
+++ b/tests/manager/test_meetings_agenda_options.py
@@ -378,7 +378,7 @@ def test_meetings_agenda_delete_desk(app, admin_user):
     resp = resp.click('Desk A')
     resp = resp.click('Delete')
     resp = resp.form.submit()
-    assert resp.location.endswith('/manage/agendas/%s/settings' % agenda.pk)
+    assert resp.location.endswith('/manage/agendas/%s/settings#open:desks' % agenda.pk)
     assert Desk.objects.count() == 1
 
     # only one desk
diff --git a/tests/manager/test_resource.py b/tests/manager/test_resource.py
index 26a78165..feb02f5c 100644
--- a/tests/manager/test_resource.py
+++ b/tests/manager/test_resource.py
@@ -572,7 +572,7 @@ def test_meetings_agenda_resources(app, admin_user):
     assert list(resp.context['form'].fields['resource'].queryset) == [resource]
     resp.form['resource'] = resource.pk
     resp = resp.form.submit()
-    assert resp.location.endswith('/manage/agendas/%s/settings' % agenda.pk)
+    assert resp.location.endswith('/manage/agendas/%s/settings#open:resources' % agenda.pk)
     assert list(agenda.resources.all()) == [resource]
     resp = resp.follow()
     assert '/manage/resource/%s/' % resource.pk in resp.text
@@ -582,7 +582,7 @@ def test_meetings_agenda_resources(app, admin_user):
 
     resp = app.get('/manage/agendas/%s/resource/%s/delete/' % (agenda.pk, resource.pk))
     resp = resp.form.submit()
-    assert resp.location.endswith('/manage/agendas/%s/settings' % agenda.pk)
+    assert resp.location.endswith('/manage/agendas/%s/settings#open:resources' % agenda.pk)
     assert list(agenda.resources.all()) == []
     resp = resp.follow()
     assert '/manage/resource/%s/' % resource.pk not in resp.text
diff --git a/tests/manager/test_timeperiod.py b/tests/manager/test_timeperiod.py
index 6d1c303e..71853f03 100644
--- a/tests/manager/test_timeperiod.py
+++ b/tests/manager/test_timeperiod.py
@@ -211,7 +211,7 @@ def test_meetings_agenda_delete_time_period(app, admin_user):
     resp = resp.click('Wednesday', index=0)
     resp = resp.click('Delete')
     resp = resp.form.submit()
-    assert resp.location.endswith('/manage/agendas/%s/settings' % agenda.id)
+    assert resp.location.endswith('/manage/agendas/%s/settings#open:time-periods' % agenda.id)
     assert TimePeriod.objects.count() == 1
 
 
diff --git a/tests/manager/test_unavailability_calendar.py b/tests/manager/test_unavailability_calendar.py
index 314e0977..567f9fcc 100644
--- a/tests/manager/test_unavailability_calendar.py
+++ b/tests/manager/test_unavailability_calendar.py
@@ -216,7 +216,7 @@ def test_unavailability_calendar_in_desk(app, admin_user):
     resp = resp.click(
         href='/manage/desk/%s/unavailability-calendar/%s/toggle/' % (desk.pk, unavailability_calendar.pk)
     )
-    settings_url = '/manage/agendas/%s/settings' % agenda.pk
+    settings_url = '/manage/agendas/%s/settings#open:time-periods' % agenda.pk
     assert resp.location.endswith(settings_url)
     # exception from calendar displayed on the settings page
     resp = app.get(settings_url)
@@ -233,7 +233,7 @@ def test_unavailability_calendar_in_desk(app, admin_user):
     resp = resp.click(
         href='/manage/desk/%s/unavailability-calendar/%s/toggle/' % (desk.pk, unavailability_calendar.pk)
     )
-    assert resp.location.endswith('/manage/agendas/%s/settings' % agenda.pk)
+    assert resp.location.endswith('/manage/agendas/%s/settings#open:time-periods' % agenda.pk)
     resp = app.get(exceptions_url)
     assert 'calendar' in resp.text
     assert 'enable' in resp.text
@@ -255,7 +255,7 @@ def test_unavailability_calendar_in_desk(app, admin_user):
     resp = resp.click(
         href='/manage/desk/%s/unavailability-calendar/%s/toggle/' % (desk.pk, unavailability_calendar.pk)
     )
-    assert resp.location.endswith('/manage/agendas/%s/settings' % agenda.pk)
+    assert resp.location.endswith('/manage/agendas/%s/settings#open:time-periods' % agenda.pk)
     resp = resp.follow()
     assert 'One or several bookings overlap with exceptions.' in resp.text
 
-- 
2.35.1

