diff --git a/lingo/agendas/views.py b/lingo/agendas/views.py index 268f3fe..1257042 100644 --- a/lingo/agendas/views.py +++ b/lingo/agendas/views.py @@ -22,6 +22,7 @@ from .models import Agenda class AgendaMixin: agenda = None + tab_anchor = None def set_agenda(self, **kwargs): self.agenda = get_object_or_404(Agenda, id=kwargs.get('pk')) @@ -43,4 +44,7 @@ class AgendaMixin: return kwargs def get_success_url(self): - return reverse('lingo-manager-agenda-detail', kwargs={'pk': self.agenda.pk}) + url = reverse('lingo-manager-agenda-detail', kwargs={'pk': self.agenda.pk}) + if self.tab_anchor: + url += '#open:%s' % self.tab_anchor + return url diff --git a/lingo/manager/static/css/style.scss b/lingo/manager/static/css/style.scss index e1aeb0c..3d4c2ab 100644 --- a/lingo/manager/static/css/style.scss +++ b/lingo/manager/static/css/style.scss @@ -94,3 +94,12 @@ div.test-tool-result .infonotice h3 { margin-top: 0; font-weight: normal; } + +.pk-tabs--container { + & > div { + padding: 1ex; + .panel--buttons a.button { + line-height: inherit; + } + } +} diff --git a/lingo/manager/static/js/lingo.manager.js b/lingo/manager/static/js/lingo.manager.js index bc33b30..251c036 100644 --- a/lingo/manager/static/js/lingo.manager.js +++ b/lingo/manager/static/js/lingo.manager.js @@ -31,4 +31,12 @@ $(function() { }); } }); + + /* focus tab from #open: 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/lingo/pricing/templates/lingo/pricing/manager_agenda_detail.html b/lingo/pricing/templates/lingo/pricing/manager_agenda_detail.html index 7964e6c..662fb4c 100644 --- a/lingo/pricing/templates/lingo/pricing/manager_agenda_detail.html +++ b/lingo/pricing/templates/lingo/pricing/manager_agenda_detail.html @@ -18,60 +18,67 @@ {% block content %}
-

{% trans "Pricing" context 'pricing' %}

-
- {% if agenda_pricings %} - - {% else %} -
- {% blocktrans %} - This agenda doesn't have any pricing defined yet. - {% endblocktrans %} +
+
+ +
- {% endif %} -
-
+
-
-

{% trans "Booking check options" %} - {% trans 'Configure' %} -

-
-
    - {% if agenda.check_type_group %} -
  • {% trans "Check type group:" %} {{ agenda.check_type_group }} -
      -
    • {% trans "Absences:" %} -
        - {% for check_type in agenda.check_type_group.check_types.absences %} -
      • {{ check_type }}
      • - {% empty %} -
      • ({% trans "No absence check type defined" %})
      • - {% endfor %} -
      -
    • -
    • {% trans "Presences:" %} -
        - {% for check_type in agenda.check_type_group.check_types.presences %} -
      • {{ check_type }}
      • - {% empty %} -
      • ({% trans "No presence check type defined" %})
      • - {% endfor %} -
      -
    • -
    -
  • - {% else %} -
  • {% trans "No check types configured for this agenda." %}
  • - {% endif %} -
+
+ {% if agenda_pricings %} + + {% else %} +
+ {% blocktrans %} + This agenda doesn't have any pricing defined yet. + {% endblocktrans %} +
+ {% endif %} +
+ + + +
{% endblock %} diff --git a/lingo/pricing/templates/lingo/pricing/manager_agenda_pricing_detail.html b/lingo/pricing/templates/lingo/pricing/manager_agenda_pricing_detail.html index a24a340..357c932 100644 --- a/lingo/pricing/templates/lingo/pricing/manager_agenda_pricing_detail.html +++ b/lingo/pricing/templates/lingo/pricing/manager_agenda_pricing_detail.html @@ -22,71 +22,83 @@ {% endblock %} {% block content %} +{% with iter_matrix=object.iter_pricing_matrix|list %}
-

{% trans "Options" %}

-
-
    -
  • {% trans "Pricing model:" %} {{ object.pricing }}
  • -
  • {% blocktrans with start=object.date_start|date:'d/m/Y' end=object.date_end|date:'d/m/Y' %}From {{ start }} to {{ end }}{% endblocktrans %}
  • -
-
-
- -
-

{% trans "Test tool" %}

-
-
- {{ test_tool_form.as_p }} -
- -
-
- {% if request.GET and test_tool_form.is_valid %} - {% with test_tool_form.compute as pricing_data %} -
-
-

{% trans "Computed pricing data" %}

- {% if pricing_data.pricing is not None %}

{% trans "Pricing:" %} {{ pricing_data.pricing|stringformat:".2f" }}

{% endif %} -
{{ pricing_data|pprint }}
-
+
+
+ + + {% for matrix in iter_matrix %} + + {% empty %} + + {% endfor %}
+
+ +
+
    +
  • {% trans "Pricing model:" %} {{ object.pricing }}
  • +
  • {% blocktrans with start=object.date_start|date:'d/m/Y' end=object.date_end|date:'d/m/Y' %}From {{ start }} to {{ end }}{% endblocktrans %}
  • +
+
+ + -
+
-{% for matrix in object.iter_pricing_matrix %} -
- {% if matrix.criteria %}

{{ matrix.criteria.label }}

{% endif %} -
- - {% if matrix.rows.0.cells.0.criteria %} - - - - {% for cell in matrix.rows.0.cells %}{% endfor %} - - - {% endif %} - - {% for row in matrix.rows %} - - - {% for cell in row.cells %}{% endfor %} - - {% endfor %} - -
{{ cell.criteria.label }}
{{ row.criteria.label }}{{ cell.value|floatformat:"2"|default_if_none:"" }}
-

- {% trans "Edit pricing" %} -

+ {% for matrix in iter_matrix %} + + {% empty %} + + {% endfor %} + +
-{% empty %} -
- {% blocktrans %} - This pricing model is misconfigured. - {% endblocktrans %} -
-{% endfor %} +{% endwith %} {% endblock %} diff --git a/lingo/pricing/templates/lingo/pricing/manager_pricing_detail.html b/lingo/pricing/templates/lingo/pricing/manager_pricing_detail.html index 7910834..4e95519 100644 --- a/lingo/pricing/templates/lingo/pricing/manager_pricing_detail.html +++ b/lingo/pricing/templates/lingo/pricing/manager_pricing_detail.html @@ -24,74 +24,79 @@ {% block content %}
-

{% trans "Variables" %}

- -
- {% if object.extra_variables %} -

- - {% for key in object.get_extra_variables_keys %}{{ key }}{% if not forloop.last %}, {% endif %}{% endfor %} -

- {% endif %} - {% trans 'Define variables' %} +
+
+ + +
-
+
-
-

{% trans "Criterias" %}

- {% with criterias=object.criterias.all categories=object.categories.all %} - {% if categories %} -
- {% blocktrans %}Use drag and drop with the ⣿ handles to reorder categories.{% endblocktrans %} -
- {% endif %} -
- {% for category in categories %} -
-

{% trans "Category:" %} {{ category }}

-

{% trans "Selected criterias:" %}

-
    - {% for criteria in criterias %} - {% if criteria.category == category %} -
  • {{ criteria }}
  • - {% endif %} - {% endfor %} -
-

- {% trans "Change criterias selection" %} - {% trans "Remove this category" %} -

+
+ {% if object.extra_variables %} + + {% for key in object.get_extra_variables_keys %}{{ key }}{% if not forloop.last %}, {% endif %}{% endfor %} + {% endif %} +
- {% endfor %} - {% if object.categories.count < 3 %} -

- {% trans "Add a category" %} ({% trans "max 3 categories" %}) -

- {% endif %} - {% endwith %} + + + + +
- -
-

{% trans "Used in agendas" %}

-
- {% if agendas %} - - {% else %} -
- {% blocktrans %} - This Pricing model is not used yet. - {% endblocktrans %} -
- {% endif %} -
{% endblock %} diff --git a/lingo/pricing/urls.py b/lingo/pricing/urls.py index ded8c7c..365f481 100644 --- a/lingo/pricing/urls.py +++ b/lingo/pricing/urls.py @@ -183,6 +183,11 @@ urlpatterns = [ views.agenda_pricing_export, name='lingo-manager-agenda-pricing-export', ), + url( + r'^agenda-pricing/(?P\d+)/test-tool/$', + views.agenda_pricing_test_tool, + name='lingo-manager-agenda-pricing-test-tool', + ), url( r'^agenda-pricing/(?P\d+)/matrix/edit/$', views.agenda_pricing_matrix_edit, diff --git a/lingo/pricing/views.py b/lingo/pricing/views.py index fb62e35..93df2ff 100644 --- a/lingo/pricing/views.py +++ b/lingo/pricing/views.py @@ -426,7 +426,7 @@ class PricingCriteriaCategoryAddView(FormView): return super().form_valid(form) def get_success_url(self): - return reverse('lingo-manager-pricing-detail', args=[self.object.pk]) + return '%s#open:criterias' % reverse('lingo-manager-pricing-detail', args=[self.object.pk]) pricing_criteria_category_add = PricingCriteriaCategoryAddView.as_view() @@ -462,7 +462,7 @@ class PricingCriteriaCategoryEditView(FormView): return super().form_valid(form) def get_success_url(self): - return reverse('lingo-manager-pricing-detail', args=[self.object.pk]) + return '%s#open:criterias' % reverse('lingo-manager-pricing-detail', args=[self.object.pk]) pricing_criteria_category_edit = PricingCriteriaCategoryEditView.as_view() @@ -487,7 +487,7 @@ class PricingCriteriaCategoryDeleteView(DeleteView): return HttpResponseRedirect(self.get_success_url()) def get_success_url(self): - return reverse('lingo-manager-pricing-detail', args=[self.pricing.pk]) + return '%s#open:criterias' % reverse('lingo-manager-pricing-detail', args=[self.pricing.pk]) pricing_criteria_category_delete = PricingCriteriaCategoryDeleteView.as_view() @@ -721,6 +721,7 @@ class AgendaBookingCheckSettingsView(AgendaMixin, UpdateView): template_name = 'lingo/pricing/manager_agenda_form.html' model = Agenda fields = ['check_type_group'] + tab_anchor = 'check' def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) @@ -775,6 +776,17 @@ class AgendaPricingDetailView(DetailView): agenda_pricing_detail = AgendaPricingDetailView.as_view() +class AgendaPricingTestToolView(RedirectView): + def get_redirect_url(self, *args, **kwargs): + return '%s?%s#open:debug' % ( + reverse('lingo-manager-agenda-pricing-detail', args=[kwargs['pk']]), + self.request.GET.urlencode(), + ) + + +agenda_pricing_test_tool = AgendaPricingTestToolView.as_view() + + class AgendaPricingEditView(UpdateView): template_name = 'lingo/pricing/manager_agenda_pricing_form.html' model = AgendaPricing @@ -888,7 +900,10 @@ class AgendaPricingMatrixEdit(FormView): return self.form_invalid(form) def get_success_url(self): - return reverse('lingo-manager-agenda-pricing-detail', args=[self.object.pk]) + return '%s#open:matrix-%s' % ( + reverse('lingo-manager-agenda-pricing-detail', args=[self.object.pk]), + self.kwargs.get('slug') or '', + ) agenda_pricing_matrix_edit = AgendaPricingMatrixEdit.as_view() diff --git a/tests/pricing/manager/test_agenda_pricing.py b/tests/pricing/manager/test_agenda_pricing.py index 15855ac..7962cbe 100644 --- a/tests/pricing/manager/test_agenda_pricing.py +++ b/tests/pricing/manager/test_agenda_pricing.py @@ -161,7 +161,7 @@ def test_detail_agenda_pricing_3_categories(app, admin_user): app = login(app) resp = app.get('/manage/pricing/agenda-pricing/%s/' % agenda_pricing.pk) - assert '

Crit 1-1

' in resp + assert '>Pricings - Crit 1-1' in resp ths = resp.pyquery.find('table.pricing-matrix-crit-1-1 thead th') assert len(ths) == 4 assert ths[0].text is None @@ -202,7 +202,7 @@ def test_detail_agenda_pricing_3_categories(app, admin_user): ].text == '132.00' ) - assert '

Crit 1-2

' in resp + assert '>Pricings - Crit 1-2' in resp ths = resp.pyquery.find('table.pricing-matrix-crit-1-2 thead th') assert len(ths) == 4 assert ths[0].text is None @@ -341,7 +341,7 @@ def test_detail_agenda_pricing_test_tool(mock_pricing_data, mock_subscriptions, resp.form['user_external_id'] = 'user:1' resp.form['adult_external_id'] = 'adult:1' resp.form['booking_status'] = 'presence' - resp = resp.form.submit() + resp = resp.form.submit().follow() assert 'Computed pricing data' not in resp assert resp.context['test_tool_form'].errors['event_slug'] == [ 'This event takes place outside the period covered by this pricing' @@ -349,7 +349,7 @@ def test_detail_agenda_pricing_test_tool(mock_pricing_data, mock_subscriptions, assert mock_event.call_args_list == [mock.call('foo-bar@foo')] assert mock_pricing_data.call_args_list == [] mock_event.return_value = {'start_datetime': '2021-10-01T12:00:00+02:00'} - resp = resp.form.submit() + resp = resp.form.submit().follow() assert 'Computed pricing data' not in resp assert resp.context['test_tool_form'].errors['event_slug'] == [ 'This event takes place outside the period covered by this pricing' @@ -359,14 +359,14 @@ def test_detail_agenda_pricing_test_tool(mock_pricing_data, mock_subscriptions, # check event_slug & agenda resp.form['event_slug'] = 'foo@foo' - resp = resp.form.submit() + resp = resp.form.submit().follow() assert resp.context['test_tool_form'].errors['event_slug'] == ['The agenda identifier is wrong (foo)'] # check subscriptions dates mock_subscriptions.return_value = [] mock_event.reset_mock() resp.form['event_slug'] = 'foo-bar@foo' - resp = resp.form.submit() + resp = resp.form.submit().follow() assert mock_event.call_args_list == [mock.call('foo-bar@foo')] assert mock_pricing_data.call_args_list == [] assert 'Computed pricing data' not in resp @@ -384,7 +384,7 @@ def test_detail_agenda_pricing_test_tool(mock_pricing_data, mock_subscriptions, 'date_end': '2021-09-03', }, ] - resp = resp.form.submit() + resp = resp.form.submit().follow() assert 'Computed pricing data' not in resp assert resp.context['test_tool_form'].errors['user_external_id'] == [ 'No subscription found for this event' @@ -405,7 +405,7 @@ def test_detail_agenda_pricing_test_tool(mock_pricing_data, mock_subscriptions, }, ] mock_pricing_data.return_value = {'foo': 'bar', 'pricing': Decimal('42')} - resp = resp.form.submit() + resp = resp.form.submit().follow() assert 'Computed pricing data' in resp assert mock_pricing_data.call_args_list == [ mock.call( @@ -424,7 +424,7 @@ def test_detail_agenda_pricing_test_tool(mock_pricing_data, mock_subscriptions, assert '
{'foo': 'bar', 'pricing': Decimal('42')}
' in resp mock_pricing_data.side_effect = PricingError(details={'foo': 'error'}) - resp = resp.form.submit() + resp = resp.form.submit().follow() assert 'Computed pricing data' in resp assert '
{'error': {'foo': 'error'}}
' in resp @@ -451,12 +451,12 @@ def test_detail_agenda_pricing_test_tool_event_error(mock_subscriptions, mock_ev resp.form['booking_status'] = 'presence' # agenda slug is removed from error message - resp = resp.form.submit() + resp = resp.form.submit().follow() assert resp.context['test_tool_form'].errors['event_slug'] == ['foo bar foo-event'] # except it was included in event_slug resp.form['event_slug'] = 'foo-bar@foo-event' - resp = resp.form.submit() + resp = resp.form.submit().follow() assert resp.context['test_tool_form'].errors['event_slug'] == ['foo bar foo-bar@foo-event'] @@ -481,7 +481,7 @@ def test_detail_agenda_pricing_test_tool_subscription_error(mock_subscriptions, resp.form['user_external_id'] = 'user:1' resp.form['adult_external_id'] = 'adult:1' resp.form['booking_status'] = 'presence' - resp = resp.form.submit() + resp = resp.form.submit().follow() assert resp.context['test_tool_form'].errors['user_external_id'] == ['foo bar'] @@ -532,7 +532,7 @@ def test_detail_agenda_pricing_test_tool_booking_status( resp.form['user_external_id'] = 'user:1' resp.form['adult_external_id'] = 'adult:1' resp.form['booking_status'] = 'presence' - resp = resp.form.submit() + resp = resp.form.submit().follow() assert mock_pricing_data.call_args_list[0][1]['check_status'] == { 'check_type': None, 'status': 'presence', @@ -540,7 +540,7 @@ def test_detail_agenda_pricing_test_tool_booking_status( mock_pricing_data.reset_mock() resp.form['booking_status'] = 'presence::foo-presence-reason' - resp = resp.form.submit() + resp = resp.form.submit().follow() assert mock_pricing_data.call_args_list[0][1]['check_status'] == { 'check_type': 'foo-presence-reason', 'status': 'presence', @@ -548,12 +548,12 @@ def test_detail_agenda_pricing_test_tool_booking_status( mock_pricing_data.reset_mock() resp.form['booking_status'] = 'absence' - resp = resp.form.submit() + resp = resp.form.submit().follow() assert mock_pricing_data.call_args_list[0][1]['check_status'] == {'check_type': None, 'status': 'absence'} mock_pricing_data.reset_mock() resp.form['booking_status'] = 'absence::foo-absence-reason' - resp = resp.form.submit() + resp = resp.form.submit().follow() assert mock_pricing_data.call_args_list[0][1]['check_status'] == { 'check_type': 'foo-absence-reason', 'status': 'absence', @@ -631,7 +631,9 @@ def test_edit_agenda_pricing_matrix_3_categories(app, admin_user): resp.form['form-3-crit_1'] = '124' resp.form['form-3-crit_2'] = '134' resp = resp.form.submit() - assert resp.location.endswith('/manage/pricing/agenda-pricing/%s/' % agenda_pricing.pk) + assert resp.location.endswith( + '/manage/pricing/agenda-pricing/%s/#open:matrix-crit-1-1' % agenda_pricing.pk + ) agenda_pricing.refresh_from_db() assert agenda_pricing.pricing_data == { 'cat-1:crit-1-1': { @@ -739,7 +741,7 @@ def test_edit_agenda_pricing_matrix_2_categories(app, admin_user): resp.form['form-3-crit_1'] = '124' resp.form['form-3-crit_2'] = '134' resp = resp.form.submit() - assert resp.location.endswith('/manage/pricing/agenda-pricing/%s/' % agenda_pricing.pk) + assert resp.location.endswith('/manage/pricing/agenda-pricing/%s/#open:matrix-' % agenda_pricing.pk) agenda_pricing.refresh_from_db() assert agenda_pricing.pricing_data == { 'cat-2:crit-2-1': { @@ -805,7 +807,7 @@ def test_edit_agenda_pricing_matrix_1_category(app, admin_user): resp.form['form-2-crit_0'] = '113' resp.form['form-3-crit_0'] = '914' resp = resp.form.submit() - assert resp.location.endswith('/manage/pricing/agenda-pricing/%s/' % agenda_pricing.pk) + assert resp.location.endswith('/manage/pricing/agenda-pricing/%s/#open:matrix-' % agenda_pricing.pk) agenda_pricing.refresh_from_db() assert agenda_pricing.pricing_data == { 'cat-3:crit-3-1': 111, diff --git a/tests/pricing/manager/test_pricing.py b/tests/pricing/manager/test_pricing.py index d8dc187..76abfa1 100644 --- a/tests/pricing/manager/test_pricing.py +++ b/tests/pricing/manager/test_pricing.py @@ -173,7 +173,7 @@ def test_pricing_add_category(app, admin_user): ] resp.form['category'] = category1.pk resp = resp.form.submit() - assert resp.location.endswith('/manage/pricing/model/%s/' % pricing.pk) + assert resp.location.endswith('/manage/pricing/model/%s/#open:criterias' % pricing.pk) resp = resp.follow() assert '/manage/pricing/model/%s/category/add/' % pricing.pk in resp assert list( @@ -236,7 +236,7 @@ def test_pricing_edit_category(app, admin_user): assert list(resp.context['form'].initial['criterias']) == [] resp.form['criterias'] = [criteria1.pk, criteria3.pk] resp = resp.form.submit() - assert resp.location.endswith('/manage/pricing/model/%s/' % pricing.pk) + assert resp.location.endswith('/manage/pricing/model/%s/#open:criterias' % pricing.pk) resp = resp.follow() assert list(pricing.criterias.order_by('pk')) == [criteria1, criteria3] @@ -268,7 +268,7 @@ def test_pricing_delete_category(app, admin_user): resp = app.get('/manage/pricing/model/%s/' % pricing.pk) resp = resp.click(href='/manage/pricing/model/%s/category/%s/delete/' % (pricing.pk, category1.pk)) resp = resp.form.submit() - assert resp.location.endswith('/manage/pricing/model/%s/' % pricing.pk) + assert resp.location.endswith('/manage/pricing/model/%s/#open:criterias' % pricing.pk) resp = resp.follow() assert list(pricing.categories.all()) == [category2] assert list(pricing.criterias.all()) == [criteria2]