Projet

Général

Profil

0003-matomo-views-form-manual-and-automatic-configuration.patch

Voir les différences:

Subject: [PATCH 3/4] matomo: views form manual and automatic configuration
 (#31778)

 hobo/matomo/__init__.py                       |   0
 hobo/matomo/forms.py                          |  36 ++++
 .../matomo/templates/hobo/matomo_disable.html |  20 ++
 .../templates/hobo/matomo_enable_auto.html    |  20 ++
 .../templates/hobo/matomo_enable_manual.html  |  20 ++
 hobo/matomo/templates/hobo/matomo_home.html   | 101 +++++++++
 hobo/matomo/urls.py                           |  26 +++
 hobo/matomo/views.py                          | 115 ++++++++++
 hobo/settings.py                              |   1 +
 hobo/static/css/style.css                     |   4 +
 hobo/templates/hobo/home.html                 |   1 +
 hobo/urls.py                                  |   3 +
 tests/test_matomo_views.py                    | 200 ++++++++++++++++++
 13 files changed, 547 insertions(+)
 create mode 100644 hobo/matomo/__init__.py
 create mode 100644 hobo/matomo/forms.py
 create mode 100644 hobo/matomo/templates/hobo/matomo_disable.html
 create mode 100644 hobo/matomo/templates/hobo/matomo_enable_auto.html
 create mode 100644 hobo/matomo/templates/hobo/matomo_enable_manual.html
 create mode 100644 hobo/matomo/templates/hobo/matomo_home.html
 create mode 100644 hobo/matomo/urls.py
 create mode 100644 hobo/matomo/views.py
 create mode 100644 tests/test_matomo_views.py
hobo/matomo/forms.py
1
# hobo - portal to configure and deploy applications
2
# Copyright (C) 2019  Entr'ouvert
3
#
4
# This program is free software: you can redistribute it and/or modify it
5
# under the terms of the GNU Affero General Public License as published
6
# by the Free Software Foundation, either version 3 of the License, or
7
# (at your option) any later version.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU Affero General Public License for more details.
13
#
14
# You should have received a copy of the GNU Affero General Public License
15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16

  
17
from django import forms
18
from django.utils.translation import ugettext_lazy as _
19

  
20

  
21
class SettingsForm(forms.Form):
22
    """
23
    According to publik-base-theme/templates/includes/tracking.html,
24
    the 2 tracking_js variables are merged into the unique below field.
25
    If JS code added is compliant to CNIL policy, we store it into
26
    'cnil_compliant_visits_tracking_js' else into 'visits_tracking_js'.
27
    The goal is to display a banner advertising users about intrusive JS.
28
    """
29
    tracking_js = forms.CharField(
30
        label=_('Tracking Javascript'),
31
        required=False,
32
        widget=forms.Textarea())
33

  
34

  
35
class EnableForm(forms.Form):
36
    pass
hobo/matomo/templates/hobo/matomo_disable.html
1
{% extends "hobo/matomo_home.html" %}
2
{% load i18n %}
3

  
4
{% block appbar %}
5
  <h2>{% trans "User tracking" %}</h2>
6
{% endblock %}
7

  
8
{% block content %}
9
<form method="post">
10
{% csrf_token %}
11
<p>
12
{% trans "Are you sure you want to disable user tracking support?" %}
13
{{ form.as_p }}
14
<div class="buttons">
15
  <button class="submit-button">{% trans "Disable" %}</button>
16
  <a class="cancel" href="{% url 'matomo-home' %}">{% trans "Cancel" %}</a>
17
</div>
18
</form>
19

  
20
{% endblock %}
hobo/matomo/templates/hobo/matomo_enable_auto.html
1
{% extends "hobo/matomo_home.html" %}
2
{% load i18n %}
3

  
4
{% block appbar %}
5
  <h2>{% trans "User tracking" %}</h2>
6
{% endblock %}
7

  
8
{% block content %}
9
<form method="post">
10
{% csrf_token %}
11
<p>
12
{% trans "Are you sure you want to enable automatic user tracking support?" %}
13
{{ form.as_p }}
14
<div class="buttons">
15
<button class="submit-button">{% trans "Enable" %}</button>
16
<a class="cancel" href="{% url 'matomo-home' %}">{% trans "Cancel" %}</a>
17
</div>
18
</form>
19

  
20
{% endblock %}
hobo/matomo/templates/hobo/matomo_enable_manual.html
1
{% extends "hobo/matomo_home.html" %}
2
{% load i18n %}
3

  
4
{% block appbar %}
5
  <h2>{% trans "User tracking" %}</h2>
6
{% endblock %}
7

  
8
{% block content %}
9
<form method="post">
10
  {% csrf_token %}
11
  {{ form.as_p }}
12
<p>
13

  
14
<div class="buttons">
15
<button class="submit-button">{% trans "Enable" %}</button>
16
<a class="cancel" href="{% url 'matomo-home' %}">{% trans "Cancel" %}</a>
17
</div>
18
</form>
19

  
20
{% endblock %}
hobo/matomo/templates/hobo/matomo_home.html
1
{% extends "hobo/base.html" %}
2
{% load i18n %}
3

  
4
{% block breadcrumb %}
5
{{ block.super }}
6
<a href="{% url 'matomo-home' %}">Matomo</a>
7
{% endblock %}
8

  
9
{% block appbar %}
10
<h2>{% trans "User tracking" %}</h2>
11
{% if enabled %}
12
<span class="actions">
13
  <a rel="popup" href="{% url 'matomo-disable' %}">{% trans 'Disable' %}</a>
14
  {% if mode == 'auto' %}
15
  <a href={{ logme_url }}>{% trans "Open visit tracking dashboard" %}</a>
16
  {% endif %}
17
</span>
18
{% endif %}
19
{% endblock %}
20

  
21
{% block content %}
22

  
23
<div class="infonotice">
24
  {% if not enabled %}
25
  <p>
26
    {% blocktrans %}
27
    The audience measurement tools are deployed to obtain information about
28
    visitor navigation. They help to understand where users come from on a site
29
    and reconstruct their browsing activity. These tools use technologies that
30
    permit to trace users on your site and associate a "referrer" or campaign
31
    with a unique identifier.
32
    {% endblocktrans %}
33
  </p>
34
    {% if ws_available %}
35
    <p>
36
      {% blocktrans %}
37
      Publik can automatically use a tool called "Matomo", which is the tracker
38
      solution recommended by the National Commission for Data Protection and Liberties (<a
39
      href="https://www.cnil.fr/fr/solutions-pour-les-cookies-de-mesure-daudience">CNIL-France</a>).
40
      {% endblocktrans %}
41
    </p>
42
    {% else %}
43
    <p>
44
      {% blocktrans %}
45
      Matomo is the tracker solution recommended by the National Commission for Data
46
      Protection and Liberties (<a
47
      href="https://www.cnil.fr/fr/solutions-pour-les-cookies-de-mesure-daudience">CNIL-France</a>).
48
      It requires <a href="https://www.cnil.fr/sites/default/files/typo/document/Configuration_piwik.pdf"
49
      >little configuration</a> to be exempt from legal consent.
50
      {% endblocktrans %}
51
    </p>
52
  {% endif %}
53
  {% else %}
54
  {% if mode == 'manual' %}
55
  {% trans "Manual configuration." %}
56
  {% elif mode == 'auto' %}
57
  {% trans "Automatic configuration." %}
58
  {% endif %}
59
  {% endif %}
60
</div>
61

  
62
{% if not enabled %}
63
<p>
64
  {% trans "Support is currently disabled." %}
65
</p>
66
<p>
67
  <a class="button" rel="popup" href="{% url 'matomo-enable-manual' %}">{% trans 'Manual Configuration' %}</a>
68
  {% if ws_available %}
69
  <a class="button" rel="popup" href="{% url 'matomo-enable-auto' %}">{% trans 'Automatic Configuration' %}</a>
70
  {% endif %}
71
</p>
72
{% else %}
73
{% if tracking_js != '' %}
74
{% if cnil_ack_level == 'success' %}
75
<div class="successnotice">
76
  {% trans "Excellent respect of user rights." %}
77
</div>
78
{% elif cnil_ack_level == 'warning' %}
79
<div class="warningnotice">
80
  {% trans "Good respect of user rights." %}
81
</div>
82
{% elif cnil_ack_level == 'error' %}
83
<div class="errornotice">
84
  {% trans "No respect of user rights." %}
85
</div>
86
{% endif %}
87
{% endif %}
88

  
89
{% if mode == 'manual' %}
90
<form method="post">
91
  {% csrf_token %}
92
  {{ form.as_p }}
93

  
94
  <div class="buttons">
95
    <button class="submit-button">{% trans "Save" %}</button>
96
  </div>
97
</form>
98
{% endif %}
99

  
100
{% endif %}
101
{% endblock %}
hobo/matomo/urls.py
1
# hobo - portal to configure and deploy applications
2
# Copyright (C) 2019  Entr'ouvert
3
#
4
# This program is free software: you can redistribute it and/or modify it
5
# under the terms of the GNU Affero General Public License as published
6
# by the Free Software Foundation, either version 3 of the License, or
7
# (at your option) any later version.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU Affero General Public License for more details.
13
#
14
# You should have received a copy of the GNU Affero General Public License
15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16

  
17
from django.conf.urls import url
18

  
19
from . import views
20

  
21
urlpatterns = [
22
    url(r'^$', views.home, name='matomo-home'),
23
    url(r'^enable-manual$', views.enable_manual, name='matomo-enable-manual'),
24
    url(r'^enable-auto$', views.enable_auto, name='matomo-enable-auto'),
25
    url(r'^disable$', views.disable, name='matomo-disable'),
26
]
hobo/matomo/views.py
1
# hobo - portal to configure and deploy applications
2
# Copyright (C) 2019  Entr'ouvert
3
#
4
# This program is free software: you can redistribute it and/or modify it
5
# under the terms of the GNU Affero General Public License as published
6
# by the Free Software Foundation, either version 3 of the License, or
7
# (at your option) any later version.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU Affero General Public License for more details.
13
#
14
# You should have received a copy of the GNU Affero General Public License
15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16

  
17
from django.core.urlresolvers import reverse_lazy
18
from django.conf import settings
19
from django.contrib import messages
20
from django.views.generic import RedirectView, FormView
21

  
22
from .forms import SettingsForm, EnableForm
23
from .utils import get_variable, get_variable_value, \
24
    get_tracking_js, put_tracking_js, \
25
    MatomoException, MatomoError, MatomoWS, \
26
    compute_cnil_acknowledgment_level, auto_configure_matomo
27

  
28

  
29
class HomeView(FormView):
30
    template_name = 'hobo/matomo_home.html'
31
    form_class = SettingsForm
32
    success_url = reverse_lazy('matomo-home')
33

  
34
    def get_initial(self):
35
        initial = super(HomeView, self).get_initial()
36
        initial['tracking_js'] = get_tracking_js()
37
        return initial
38

  
39
    def form_valid(self, form):
40
        tracking_js = form.cleaned_data['tracking_js']
41
        put_tracking_js(tracking_js)
42
        return super(HomeView, self).form_valid(form)
43

  
44
    def get_context_data(self, **kwargs):
45
        context = super(HomeView, self).get_context_data(**kwargs)
46
        tracking_js = get_tracking_js()
47
        logme_url = get_variable_value('matomo_logme_url')
48
        context['logme_url'] = logme_url
49
        context['tracking_js'] = tracking_js
50

  
51
        # compute contextual values
52
        context['cnil_ack_level'] = compute_cnil_acknowledgment_level(tracking_js)
53
        try:
54
            MatomoWS()
55
        except MatomoError:
56
            context['ws_available'] = False
57
        else:
58
            context['ws_available'] = True
59
        context['enabled'] = tracking_js != ''
60
        if logme_url != '':
61
            context['mode'] = 'auto'
62
        else:
63
            context['mode'] = 'manual'
64
        return context
65

  
66
home = HomeView.as_view()
67

  
68

  
69
class EnableManualView(FormView):
70
    form_class = SettingsForm
71
    template_name = 'hobo/matomo_enable_manual.html'
72
    success_url = reverse_lazy('matomo-home')
73

  
74
    def get_initial(self):
75
        initial = super(EnableManualView, self).get_initial()
76
        initial['tracking_js'] = get_tracking_js()
77
        return initial
78

  
79
    def form_valid(self, form):
80
        tracking_js = form.cleaned_data['tracking_js']
81
        put_tracking_js(tracking_js)
82
        logme_url = get_variable('matomo_logme_url')
83
        logme_url.delete()
84
        return super(EnableManualView, self).form_valid(form)
85

  
86
enable_manual = EnableManualView.as_view()
87

  
88

  
89
class EnableAutoView(FormView):
90
    form_class = EnableForm
91
    template_name = 'hobo/matomo_enable_auto.html'
92
    success_url = reverse_lazy('matomo-home')
93

  
94
    def form_valid(self, form):
95
        try:
96
            auto_configure_matomo()
97
        except MatomoException as exc:
98
            messages.error(self.request, str(exc))
99
        return super(EnableAutoView, self).form_valid(form)
100

  
101
enable_auto = EnableAutoView.as_view()
102

  
103

  
104
class DisableView(FormView):
105
    form_class = EnableForm
106
    template_name = 'hobo/matomo_disable.html'
107
    success_url = reverse_lazy('matomo-home')
108

  
109
    def form_valid(self, form):
110
        put_tracking_js('')
111
        variable = get_variable('matomo_logme_url')
112
        variable.delete()
113
        return super(DisableView, self).form_valid(form)
114

  
115
disable = DisableView.as_view()
hobo/settings.py
43 43
    'gadjo',
44 44
    'hobo.environment',
45 45
    'hobo.franceconnect',
46
    'hobo.matomo',
46 47
    'hobo.profile',
47 48
    'hobo.theme',
48 49
    'hobo.emails',
hobo/static/css/style.css
214 214
div#services span {
215 215
	margin-right: 1rem;
216 216
}
217

  
218
textarea#id_tracking_js {
219
       width: 100%;
220
}
hobo/templates/hobo/home.html
10 10
    <li><a href="{% url 'theme-home' %}">{% trans 'Theme' %}</a></li>
11 11
    <li><a href="{% url 'emails-home' %}">{% trans 'Emails' %}</a></li>
12 12
    <li><a href="{% url 'franceconnect-home' %}">FranceConnect</a></li>
13
    <li><a href="{% url 'matomo-home' %}">{% trans 'User tracking' %}</a></li>
13 14
    <li><a href="{% url 'environment-home' %}">{% trans 'Services' %}</a></li>
14 15
    <li><a href="{% url 'environment-variables' %}">{% trans 'Variables' %}</a></li>
15 16
  </ul>
hobo/urls.py
8 8
from .urls_utils import decorated_includes
9 9
from .environment.urls import urlpatterns as environment_urls
10 10
from .franceconnect.urls import urlpatterns as franceconnect_urls
11
from .matomo.urls import urlpatterns as matomo_urls
11 12
from .profile.urls import urlpatterns as profile_urls
12 13
from .theme.urls import urlpatterns as theme_urls
13 14
from .emails.urls import urlpatterns as emails_urls
......
20 21
                                             include(profile_urls))),
21 22
    url(r'^franceconnect/',
22 23
        decorated_includes(admin_required, include(franceconnect_urls))),
24
    url(r'^matomo/',
25
        decorated_includes(admin_required, include(matomo_urls))),
23 26
    url(r'^theme/', decorated_includes(admin_required,
24 27
                                             include(theme_urls))),
25 28
    url(r'^emails/', decorated_includes(admin_required, include(emails_urls))),
tests/test_matomo_views.py
1
# -*- coding: utf-8 -*-
2

  
3
import mock
4
import pytest
5
import re
6
from requests import Response
7
from webtest import TestApp
8

  
9
from django.conf import settings
10
from django.contrib.auth.models import User
11
from django.test import override_settings
12

  
13
from hobo.environment.models import Variable, Wcs, Combo, Fargo
14
from hobo.wsgi import application
15

  
16
pytestmark = pytest.mark.django_db
17

  
18
CONFIG = {'URL': 'https://matomo.test',
19
          'TOKEN_AUTH': '1234',
20
          'EMAIL_TEMPLATE': 'noreply+%s@entrouvert.test'}
21

  
22
GET_NO_SITE_FROM_URL = """<?xml version="1.0" encoding="utf-8" ?>
23
<result />
24
"""
25

  
26
ADD_SITE_SUCCESS = """<?xml version="1.0" encoding="utf-8" ?>
27
<result>42</result>
28
"""
29

  
30
DEL_UNKNOWN_USER = """<?xml version="1.0" encoding="utf-8" ?>
31
<result>
32
        <error message="User 'hobo.dev.publik.love' doesn't exist." />
33
</result>
34
"""
35

  
36
MATOMO_SUCCESS = """<?xml version="1.0" encoding="utf-8" ?>
37
<result>
38
    <success message="ok" />
39
</result>
40
"""
41

  
42
JAVASCRIPT_TAG_BAD_RESPONSE = """<?xml version="1.0" encoding="utf-8" ?>
43
<no_result_tag/>
44
"""
45

  
46
JAVASCRIPT_TAG = """<?xml version="1.0" encoding="utf-8" ?>
47
<result>&lt;!-- Matomo --&gt;
48
&lt;script type=&quot;text/javascript&quot;&gt;
49
  var _paq = window._paq || [];
50
  /* tracker methods like &quot;setCustomDimension&quot; should be called before &quot;trackPageView&quot; */
51
  _paq.push(['trackPageView']);
52
  _paq.push(['enableLinkTracking']);
53
  (function() {
54
    var u=&quot;//matomo-test.entrouvert.org/&quot;;
55
    _paq.push(['setTrackerUrl', u+'piwik.php']);
56
    _paq.push(['setSiteId', '7']);
57
    var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
58
    g.type='text/javascript'; g.async=true; g.defer=true; g.src=u+'piwik.js'; s.parentNode.insertBefore(g,s);
59
  })();
60
&lt;/script&gt;
61
&lt;!-- End Matomo Code --&gt;
62
</result>
63
"""
64

  
65
@pytest.fixture
66
def admin_user():
67
    try:
68
        user = User.objects.get(username='admin')
69
    except User.DoesNotExist:
70
        user = User.objects.create_superuser('admin', email=None, password='admin')
71
    return user
72

  
73
def login(app, username='admin', password='admin'):
74
    login_page = app.get('/login/')
75
    login_form = login_page.forms[0]
76
    login_form['username'] = username
77
    login_form['password'] = password
78
    resp = login_form.submit()
79
    assert resp.status_int == 302
80
    return app
81

  
82
def test_unlogged_access():
83
    # connect while not being logged in
84
    app = TestApp(application)
85
    resp = app.get('/matomo/', status=302)
86
    assert resp.location.endswith('/login/?next=/matomo/')
87

  
88
def test_access(admin_user):
89
    app = login(TestApp(application))
90
    assert app.get('/matomo/', status=200)
91

  
92
def test_disable(admin_user):
93
    app = login(TestApp(application))
94
    resp1 = app.get('/matomo/disable', status=200)
95
    resp2 = resp1.form.submit()
96
    assert resp2.location.endswith('/matomo/')
97

  
98
def test_enable_manual(admin_user):
99
    """scenario where user manually paste a javascript code"""
100
    app = login(TestApp(application))
101

  
102
    # get matomo's validation page
103
    resp1 = app.get('/matomo/enable-manual', status=200)
104
    assert re.search('<textarea.* name="tracking_js"', resp1.body)
105
    assert resp1.body.find('enable manual user tracking support?') != -1
106

  
107
    # validate and get matomo's home page
108
    resp1.form['tracking_js'] = '...js_code_1...'
109
    resp2 = resp1.form.submit().follow()
110
    assert resp2.body.find('Manual configuration.')
111
    assert re.search('<textarea.* name="tracking_js"', resp2.body)
112
    assert resp2.body.find('...js_code_1...</textarea>') != -1
113
    assert resp2.body.find('<button class="submit-button">Save</button>') != -1
114

  
115
    # update JS code on matomo's home page
116
    resp2.form['tracking_js'] = '...js_code_2...'
117
    resp3 = resp2.form.submit().follow()
118
    assert resp3.body.find('Manual configuration.') != -1
119
    assert re.search('<textarea.* name="tracking_js"', resp3.body)
120
    assert resp3.body.find('...js_code_2...</textarea>') != -1
121
    assert resp3.body.find('<button class="submit-button">Save</button>') != -1
122
    assert resp3.body.find('Good respect of user rights') != -1
123

  
124
def auto_conf_mocked_post(url, **kwargs):
125
    contents = [GET_NO_SITE_FROM_URL, ADD_SITE_SUCCESS,
126
                DEL_UNKNOWN_USER, MATOMO_SUCCESS,
127
                JAVASCRIPT_TAG]
128
    response = Response()
129
    response._content = contents[auto_conf_mocked_post.cpt]
130
    response.status_code = 200
131

  
132
    auto_conf_mocked_post.cpt += 1
133
    return response
134

  
135
def auto_conf_failure_mocked_post(url, **kwargs):
136
    contents = [GET_NO_SITE_FROM_URL, ADD_SITE_SUCCESS,
137
                DEL_UNKNOWN_USER, MATOMO_SUCCESS,
138
                JAVASCRIPT_TAG_BAD_RESPONSE]
139
    response = Response()
140
    response._content = contents[auto_conf_mocked_post.cpt]
141
    response.status_code = 200
142

  
143
    auto_conf_mocked_post.cpt += 1
144
    return response
145

  
146
def test_available_options(admin_user):
147
    """check available buttons (manual/automatic configurations)"""
148
    with override_settings(MATOMO_SERVER=CONFIG):
149
        app = login(TestApp(application))
150
        resp = app.get('/matomo/', status=200)
151
        assert str(resp).find('href="/matomo/enable-manual"') != -1
152
        assert str(resp).find('href="/matomo/enable-auto"') != -1
153

  
154
    # without configuration: no automatic configuration available
155
    app = login(TestApp(application))
156
    resp = app.get('/matomo/', status=200)
157
    assert str(resp).find('href="/matomo/enable-manual"') != -1
158
    assert str(resp).find('href="/matomo/enable-auto"') == -1
159

  
160
@mock.patch('requests.post', side_effect=auto_conf_mocked_post)
161
def test_enable_auto(mocked_post, admin_user):
162
    """succesfull automatic scenario"""
163
    Combo.objects.create(base_url='https://combo.dev.publik.love',
164
                         template_name='portal-user')
165
    Wcs.objects.create(base_url='https://wcs.dev.publik.love')
166
    Fargo.objects.create(base_url='https://fargo.dev.publik.love')
167

  
168
    auto_conf_mocked_post.cpt = 0
169
    with override_settings(MATOMO_SERVER=CONFIG):
170
        app = login(TestApp(application))
171
        resp1 = app.get('/matomo/enable-auto', status=200)
172
        resp2 = resp1.form.submit()
173

  
174
        # call utils.py::auto_configure_matomo()
175
        resp3 = resp2.follow()
176
        print resp3.body
177

  
178
        # expect the CNIL compliance message is displayed
179
        assert resp3.body.find('Excellent respect of user rights') != -1
180

  
181

  
182
@mock.patch('requests.post', side_effect=auto_conf_failure_mocked_post)
183
def test_enable_auto_failure(mocked_post, admin_user):
184
    """error on automatic scenario"""
185
    Combo.objects.create(base_url='https://combo.dev.publik.love',
186
                         template_name='portal-user')
187
    Wcs.objects.create(base_url='https://wcs.dev.publik.love')
188
    Fargo.objects.create(base_url='https://fargo.dev.publik.love')
189

  
190
    auto_conf_mocked_post.cpt = 0
191
    with override_settings(MATOMO_SERVER=CONFIG):
192
        app = login(TestApp(application))
193
        resp1 = app.get('/matomo/enable-auto', status=200)
194
        resp2 = resp1.form.submit()
195

  
196
        # call utils.py::auto_configure_matomo()
197
        resp3 = resp2.follow()
198

  
199
        # expect a Django error message is displayed
200
        assert resp3.body.find('class="error">get_javascript_tag fails') != -1
0
-