Projet

Général

Profil

0002-seo-add-robots-views-20263.patch

Nicolas Roche, 07 avril 2020 19:17

Télécharger (16,1 ko)

Voir les différences:

Subject: [PATCH 2/2] seo: add robots views (#20263)

 hobo/seo/__init__.py                          |  0
 hobo/seo/forms.py                             | 29 ++++++
 hobo/seo/templates/hobo/robots_customize.html | 20 ++++
 hobo/seo/templates/hobo/seo_home.html         | 66 +++++++++++++
 hobo/seo/urls.py                              | 26 +++++
 hobo/seo/views.py                             | 99 +++++++++++++++++++
 hobo/settings.py                              |  1 +
 hobo/templates/hobo/home.html                 |  1 +
 hobo/urls.py                                  |  2 +
 tests/test_seo.py                             | 71 +++++++++++++
 10 files changed, 315 insertions(+)
 create mode 100644 hobo/seo/__init__.py
 create mode 100644 hobo/seo/forms.py
 create mode 100644 hobo/seo/templates/hobo/robots_customize.html
 create mode 100644 hobo/seo/templates/hobo/seo_home.html
 create mode 100644 hobo/seo/urls.py
 create mode 100644 hobo/seo/views.py
hobo/seo/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
    content = forms.CharField(
23
        label=_('content of /robots.txt file'),
24
        required=False,
25
        widget=forms.Textarea)
26

  
27

  
28
class EnableForm(forms.Form):
29
    pass
hobo/seo/templates/hobo/robots_customize.html
1
{% extends "hobo/seo_home.html" %}
2
{% load i18n %}
3

  
4
{% block appbar %}
5
  <h2>{% trans 'Robots Exclusion Protocol' %}</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 'seo-home' %}">{% trans "Cancel" %}</a>
17
</div>
18
</form>
19

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

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

  
9
{% block appbar %}
10
<h2>{% trans 'Search Engine Optimization' %}</h2>
11
<span class="actions">
12
  <a
13
    {% if mode == 'customize' %}class="disabled"{% endif %}
14
    rel="popup" href="{% url 'robots-customize' %}">
15
    {% trans 'Customize' %}
16
  </a>
17
  <a
18
    {% if mode == 'disallow' %}class="disabled"{% endif %}
19
    href="{% url 'robots-disallow' %}">
20
    {% trans 'Do not allow indexation' %}
21
  </a>
22
  <a
23
    {% if mode == 'allow' %}class="disabled"{% endif %}
24
    href="{% url 'robots-allow' %}">
25
    {% trans 'Allow indexation' %}
26
  </a>
27
</span>
28
{% endblock %}
29

  
30
{% block content %}
31
{% if mode == 'allow' %}
32
<div class="infonotice">
33
  <p>
34
    {% blocktrans %}
35
    Web Robots (also called "Wanderers" or "Spiders") are Web client programs that automatically traverse the Web's hypertext structure by retrieving a document, and recursively retrieving all documents that are referenced.
36
    Web site owners use the /robots.txt file to give instructions about their site to web robots; this is called The Robots Exclusion Protocol.
37
    {% endblocktrans %}
38
  </p>
39
</div>
40
{% endif %}
41

  
42
{% if mode == 'allow' %}
43
<div class="warningnotice">
44
  {% trans "Web Robots are currently allowed." %}
45
</div>
46
{% endif %}
47

  
48
{% if mode == 'disallow' %}
49
<div class="successnotice">
50
  {% trans "Web Robots are currently not allowed." %}
51
</div>
52
{% endif %}
53

  
54
{% if mode == 'customize' or mode == 'disallow' %}
55
<form method="post">
56
  {% csrf_token %}
57
  {{ form.as_p }}
58

  
59
  {% if mode == 'customize' %}
60
  <div class="buttons">
61
    <button class="submit-button">{% trans "Save" %}</button>
62
  </div>
63
  {% endif %}
64
</form>
65
{% endif %}
66
{% endblock %}
hobo/seo/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='seo-home'),
23
    url(r'^allow$', views.allow, name='robots-allow'),
24
    url(r'^disallow$', views.disallow, name='robots-disallow'),
25
    url(r'^customize$', views.customize, name='robots-customize'),
26
]
hobo/seo/views.py
1
# hobo - portal to configure and deploy applications
2
# Copyright (C) 2020  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.views.generic import FormView, RedirectView
19

  
20
from hobo.environment.utils import get_variable
21
from .forms import SettingsForm
22

  
23

  
24
ALLOW = ""
25
DISALLOW = """User-agent: *
26
Disallow: /"""
27

  
28
def put_robots_txt(content):
29
    variable = get_variable('robots_txt')
30
    variable.value = content
31
    variable.save()
32

  
33
def get_mode(content):
34
    content = content.strip().replace('\r', '')
35
    if content == ALLOW:
36
        return 'allow'
37
    elif content == DISALLOW:
38
        return 'disallow'
39
    else:
40
        return 'customize'
41

  
42
class HomeView(FormView):
43
    template_name = 'hobo/seo_home.html'
44
    form_class = SettingsForm
45
    success_url = reverse_lazy('seo-home')
46

  
47
    def get_initial(self):
48
        initial = super(HomeView, self).get_initial()
49
        initial['content'] = get_variable('robots_txt').value
50
        initial['mode'] = get_mode(initial['content'])
51
        return initial
52

  
53
    def form_valid(self, form):
54
        content = form.cleaned_data['content']
55
        put_robots_txt(content)
56
        return super(HomeView, self).form_valid(form)
57

  
58
    def get_context_data(self, **kwargs):
59
        context = super(HomeView, self).get_context_data(**kwargs)
60
        mode = get_mode(get_variable('robots_txt').value)
61
        context['form'].fields['content'].disabled = mode != 'customize'
62
        context['mode'] = mode
63
        return context
64

  
65
home = HomeView.as_view()
66

  
67

  
68
class AllowView(RedirectView):
69
    def get_redirect_url(self):
70
        put_robots_txt(ALLOW)
71
        return reverse_lazy('seo-home')
72

  
73
allow = AllowView.as_view()
74

  
75

  
76
class DisallowView(RedirectView):
77
    def get_redirect_url(self):
78
        put_robots_txt(DISALLOW)
79
        return reverse_lazy('seo-home')
80

  
81
disallow = DisallowView.as_view()
82

  
83

  
84
class CustomizeView(FormView):
85
    template_name = 'hobo/robots_customize.html'
86
    form_class = SettingsForm
87
    success_url = reverse_lazy('seo-home')
88

  
89
    def get_initial(self):
90
        initial = super(CustomizeView, self).get_initial()
91
        initial['content'] = get_variable('robots_txt').value
92
        return initial
93

  
94
    def form_valid(self, form):
95
        content = form.cleaned_data['content']
96
        put_robots_txt(content)
97
        return super(CustomizeView, self).form_valid(form)
98

  
99
customize = CustomizeView.as_view()
hobo/settings.py
41 41
    'rest_framework',
42 42
    'mellon',
43 43
    'gadjo',
44 44
    'hobo.debug',
45 45
    'hobo.environment',
46 46
    'hobo.franceconnect',
47 47
    'hobo.matomo',
48 48
    'hobo.profile',
49
    'hobo.seo',
49 50
    'hobo.theme',
50 51
    'hobo.emails',
51 52
    'hobo.deploy',
52 53
)
53 54

  
54 55
MIDDLEWARE_CLASSES = (
55 56
    'hobo.middleware.seo.RobotMiddleware',
56 57
    'hobo.middleware.xforwardedfor.XForwardedForMiddleware',
hobo/templates/hobo/home.html
10 10
    <li><a href="{% url 'profile-home' %}">{% trans 'User Profile' %}</a></li>
11 11
    {% endif %}
12 12
    <li><a href="{% url 'theme-home' %}">{% trans 'Theme' %}</a></li>
13 13
    <li><a href="{% url 'emails-home' %}">{% trans 'Emails' %}</a></li>
14 14
    {% if has_authentic %}
15 15
    <li><a href="{% url 'franceconnect-home' %}">FranceConnect</a></li>
16 16
    {% endif %}
17 17
    <li><a href="{% url 'matomo-home' %}">{% trans 'User tracking' %}</a></li>
18
    <li><a href="{% url 'seo-home' %}">{% trans 'Search Engine Optimization' %}</a></li>
18 19
    <li><a href="{% url 'environment-home' %}">{% trans 'Services' %}</a></li>
19 20
    <li><a href="{% url 'environment-variables' %}">{% trans 'Variables' %}</a></li>
20 21
    <li><a href="{% url 'debug-home' %}">{% trans 'Debugging' %}</a></li>
21 22
  </ul>
22 23
  </span>
23 24
{% endblock %}
24 25

  
25 26
{% block content %}
hobo/urls.py
23 23
from .urls_utils import decorated_includes
24 24
from .environment.urls import urlpatterns as environment_urls
25 25
from .franceconnect.urls import urlpatterns as franceconnect_urls
26 26
from .matomo.urls import urlpatterns as matomo_urls
27 27
from .profile.urls import urlpatterns as profile_urls
28 28
from .theme.urls import urlpatterns as theme_urls
29 29
from .emails.urls import urlpatterns as emails_urls
30 30
from .debug.urls import urlpatterns as debug_urls
31
from .seo.urls import urlpatterns as seo_urls
31 32

  
32 33
admin.autodiscover()
33 34

  
34 35
urlpatterns = [
35 36
    url(r'^$', home, name='home'),
36 37
    url(r'^sites/', decorated_includes(admin_required, include(environment_urls))),
37 38
    url(r'^profile/', decorated_includes(admin_required, include(profile_urls))),
38 39
    url(r'^franceconnect/', decorated_includes(admin_required, include(franceconnect_urls))),
39 40
    url(r'^visits-tracking/', decorated_includes(admin_required, include(matomo_urls))),
40 41
    url(r'^theme/', decorated_includes(admin_required, include(theme_urls))),
41 42
    url(r'^emails/', decorated_includes(admin_required, include(emails_urls))),
43
    url(r'^seo/', decorated_includes(admin_required, include(seo_urls))),
42 44
    url(r'^debug/', decorated_includes(admin_required, include(debug_urls))),
43 45
    url(r'^api/health/$', health_json, name='health-json'),
44 46
    url(r'^menu.json$', menu_json, name='menu_json'),
45 47
    url(r'^hobos.json$', hobo),
46 48
    url(r'^admin/', admin.site.urls),
47 49
]
48 50

  
49 51
# add authentication patterns
tests/test_seo.py
13 13
#
14 14
# You should have received a copy of the GNU Affero General Public License
15 15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.import pytest
16 16
import pytest
17 17

  
18 18
from hobo.environment.utils import get_variable
19 19
from hobo.seo.views import ALLOW, DISALLOW
20 20

  
21
from test_manager import login
22

  
21 23

  
22 24
pytestmark = pytest.mark.django_db
23 25

  
24 26
def test_middleware(app):
25 27
    variable = get_variable('robots_txt')
26 28
    variable.value = DISALLOW
27 29
    variable.save()
28 30
    resp = app.get('/robots.txt', status=200)
......
33 35
    variable.save()
34 36
    resp = app.get('/robots.txt', status=200)
35 37
    assert resp.text == ALLOW
36 38

  
37 39
    variable = get_variable('robots_txt')
38 40
    variable.delete()
39 41
    resp = app.get('/robots.txt', status=200)
40 42
    assert resp.text == ALLOW
43

  
44
def test_unlogged_access(app):
45
    # connect while not being logged in
46
    resp = app.get('/seo/', status=302)
47
    assert resp.location.endswith('/login/?next=/seo/')
48

  
49
def test_home(app, admin_user):
50
    login(app)
51
    resp = app.get('/seo/', status=200)
52
    assert [
53
        x['href'] for x in resp.html.find('span', {'class': 'actions'}).find_all('a')
54
    ] == ['/seo/customize', '/seo/disallow', '/seo/allow']
55

  
56
    variable = get_variable('robots_txt')
57
    variable.value = ALLOW
58
    variable.save()
59
    resp = app.get('/seo/', status=200)
60
    assert 'disabled' in resp.html.find('a', {'href': '/seo/allow'})['class']
61
    assert not resp.html.textarea
62

  
63
    variable = get_variable('robots_txt')
64
    variable.value = DISALLOW
65
    variable.save()
66
    resp = app.get('/seo/', status=200)
67
    assert 'disabled' in resp.html.find('a', {'href': '/seo/disallow'})['class']
68
    assert resp.html.textarea.text.strip() == DISALLOW
69
    assert resp.html.textarea.has_attr('disabled')
70
    assert not resp.html.button
71

  
72
    variable = get_variable('robots_txt')
73
    variable.value = "some content"
74
    variable.save()
75
    resp = app.get('/seo/', status=200)
76
    assert 'disabled' in resp.html.find('a', {'href': '/seo/customize'})['class']
77
    assert resp.html.textarea.text.strip() == 'some content'
78
    assert not resp.html.textarea.has_attr('disabled')
79
    resp.form['content'] = 'some new content'
80
    resp = resp.form.submit()
81
    assert get_variable('robots_txt').value == 'some new content'
82

  
83
def test_allow(app, admin_user):
84
    login(app)
85
    variable = get_variable('robots_txt')
86
    variable.value = "some content"
87
    variable.save()
88
    resp = app.get('/seo/allow', status=302)
89
    assert resp.location.endswith('/seo/')
90
    assert get_variable('robots_txt').value == ALLOW
91

  
92
def test_disallow(app, admin_user):
93
    login(app)
94
    variable = get_variable('robots_txt')
95
    variable.value = "some content"
96
    variable.save()
97
    resp = app.get('/seo/disallow', status=302)
98
    assert resp.location.endswith('/seo/')
99
    assert get_variable('robots_txt').value == DISALLOW
100

  
101
def test_custom(app, admin_user):
102
    login(app)
103
    variable = get_variable('robots_txt')
104
    variable.value = "some content"
105
    variable.save()
106
    resp = app.get('/seo/customize', status=200)
107
    assert resp.html.textarea['name'] == 'content'
108
    assert resp.html.textarea.text.strip() == 'some content'
109
    resp.form['content'] = 'some new content'
110
    resp = resp.form.submit()
111
    assert get_variable('robots_txt').value == 'some new content'
41
-