0002-seo-add-robots-views-20263.patch
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 |
- |