From ce23a475f381b335be0c419a589ec50378286953 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    | 10 +++
 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, 71 insertions(+), 17 deletions(-)

diff --git a/chrono/manager/static/js/chrono.manager.js b/chrono/manager/static/js/chrono.manager.js
index 78ddf600..ab783f0e 100644
--- a/chrono/manager/static/js/chrono.manager.js
+++ b/chrono/manager/static/js/chrono.manager.js
@@ -81,4 +81,14 @@ $(function() {
       });
     }
   });
+
+  window.addEventListener('DOMContentLoaded', function() {
+    /* 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 064bdfff..caed4179 100644
--- a/chrono/manager/views.py
+++ b/chrono/manager/views.py
@@ -1201,6 +1201,8 @@ class ViewableAgendaMixin:
 
 
 class ManagedAgendaMixin(ViewableAgendaMixin):
+    tab_anchor = None
+
     def check_permissions(self, user):
         return self.agenda.can_be_managed(user)
 
@@ -1212,7 +1214,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):
@@ -1233,6 +1238,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()
@@ -1241,6 +1247,7 @@ agenda_booking_delays = AgendaBookingDelaysView.as_view()
 class AgendaRolesView(AgendaEditView):
     form_class = AgendaRolesForm
     title = _('Configure roles')
+    tab_anchor = 'permissions'
 
 
 agenda_roles = AgendaRolesView.as_view()
@@ -1249,6 +1256,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'))
@@ -1263,6 +1271,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')
@@ -1795,6 +1804,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
@@ -1808,11 +1818,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:
@@ -1837,11 +1851,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()
@@ -1859,13 +1877,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()
@@ -1892,12 +1913,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 AgendaSettings(ManagedAgendaMixin, DetailView):
@@ -2136,6 +2160,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:
@@ -2158,6 +2183,7 @@ class AgendaReminderSettingsView(AgendaEditView):
     title = _('Reminder Settings')
     model = AgendaReminderSettings
     form_class = AgendaReminderForm
+    tab_anchor = 'reminders'
 
     def get_object(self):
         try:
@@ -2592,6 +2618,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')
@@ -2613,6 +2640,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')
@@ -2674,7 +2702,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})
         )
 
 
@@ -2775,6 +2804,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()
@@ -2794,6 +2824,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()
@@ -2810,6 +2841,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()
@@ -2818,6 +2850,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()
@@ -2847,6 +2880,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')
@@ -2859,6 +2893,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')
@@ -2870,6 +2905,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
@@ -2939,6 +2975,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)
@@ -2969,6 +3006,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)
@@ -3016,6 +3054,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:
@@ -3062,6 +3101,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)
@@ -3123,6 +3163,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()
@@ -3150,6 +3191,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()
@@ -3180,6 +3222,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()
@@ -3448,7 +3491,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 8ac2ec6f..9229d951 100644
--- a/tests/manager/test_all.py
+++ b/tests/manager/test_all.py
@@ -2274,7 +2274,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

