0001-start-the-maintenance-application-64868.patch
hobo/maintenance/forms.py | ||
---|---|---|
1 |
# hobo - portal to configure and deploy applications |
|
2 |
# Copyright (C) 2015-2022 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 MaintenanceForm(forms.Form): |
|
22 |
maintenance_page = forms.BooleanField(required=False, label=_('Enable maintenance page')) |
|
23 |
maintenance_page_message = forms.CharField( |
|
24 |
required=False, widget=forms.Textarea, label=_('Maintenance page message') |
|
25 |
) |
|
26 |
maintenance_pass_trough_header = forms.CharField( |
|
27 |
required=False, label=_('Maintenance HTTP header pass through') |
|
28 |
) |
|
29 |
disable_cron = forms.BooleanField(required=False, label=_('Disable cron jobs')) |
hobo/maintenance/management/commands/disable_maintenance_page.py | ||
---|---|---|
1 |
from django.core.management.base import BaseCommand, CommandError |
|
2 | ||
3 |
from hobo.environment.models import Variable |
|
4 |
from hobo.environment.utils import get_setting_variable |
|
5 | ||
6 | ||
7 |
class Command(BaseCommand): |
|
8 |
help = 'Toggle maintenance page' |
|
9 | ||
10 |
def handle(self, *args, **options): |
|
11 |
maintenance_page_variable = get_setting_variable('MAINTENANCE_PAGE') |
|
12 |
if not bool(maintenance_page_variable.json): |
|
13 |
self.stdout.write(self.style.SUCCESS('The maintenance page is already disabled.')) |
|
14 |
return |
|
15 |
maintenance_page_variable.json = False |
|
16 |
maintenance_page_variable.save() |
|
17 |
self.stdout.write(self.style.SUCCESS('Maintenance page disabled.')) |
hobo/maintenance/templates/hobo/maintenance/home.html | ||
---|---|---|
1 |
{% extends "hobo/base.html" %} |
|
2 |
{% load i18n %} |
|
3 | ||
4 |
{% block breadcrumb %} |
|
5 |
{{ block.super }} |
|
6 |
<a href="{% url 'maintenance-home' %}">{% trans "Maintenance" %}</a> |
|
7 |
{% endblock %} |
|
8 | ||
9 |
{% block appbar %} |
|
10 |
<h2>{% trans 'Maintenance' %}</h2> |
|
11 |
{% endblock %} |
|
12 | ||
13 |
{% block content %} |
|
14 |
<form action="." method="post"> |
|
15 |
{% csrf_token %} |
|
16 |
{{ form.as_p }} |
|
17 |
<div class="buttons"> |
|
18 |
<button>{% trans "Submit" %}</button> |
|
19 |
</div> |
|
20 |
</form> |
|
21 |
{% endblock %} |
hobo/maintenance/templates/hobo/maintenance/maintenance_page.html | ||
---|---|---|
1 |
{% load i18n %} |
|
2 | ||
3 |
<html> |
|
4 |
<body> |
|
5 |
<h1>{% trans "This site is currently unavailable." %}</h1> |
|
6 |
<p>{{ maintenance_message|default:"" }}</p> |
|
7 |
</body> |
|
8 |
</html> |
hobo/maintenance/urls.py | ||
---|---|---|
1 |
# hobo - portal to configure and deploy applications |
|
2 |
# Copyright (C) 2015-2022 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='maintenance-home'), |
|
23 |
] |
hobo/maintenance/views.py | ||
---|---|---|
1 |
# hobo - portal to configure and deploy applications |
|
2 |
# Copyright (C) 2015-2022 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 import settings |
|
18 |
from django.urls import reverse_lazy |
|
19 |
from django.utils.functional import cached_property |
|
20 |
from django.views.generic import FormView |
|
21 | ||
22 |
from hobo.environment.utils import get_setting_variable |
|
23 | ||
24 |
from .forms import MaintenanceForm |
|
25 | ||
26 | ||
27 |
class HomeView(FormView): |
|
28 |
template_name = 'hobo/maintenance/home.html' |
|
29 |
form_class = MaintenanceForm |
|
30 |
success_url = reverse_lazy('maintenance-home') |
|
31 | ||
32 |
@cached_property |
|
33 |
def maintenance_page_variable(self): |
|
34 |
return get_setting_variable('MAINTENANCE_PAGE') |
|
35 | ||
36 |
@cached_property |
|
37 |
def maintenance_page_message_variable(self): |
|
38 |
return get_setting_variable('MAINTENANCE_PAGE_MESSAGE') |
|
39 | ||
40 |
@cached_property |
|
41 |
def maintenance_pass_trough_header_variable(self): |
|
42 |
return get_setting_variable('MAINTENANCE_PASS_THROUGH_HEADER') |
|
43 | ||
44 |
@cached_property |
|
45 |
def tenant_disable_cron_jobs_variable(self): |
|
46 |
return get_setting_variable('TENANT_DISABLE_CRON_JOBS') |
|
47 | ||
48 |
def get_initial(self): |
|
49 |
initial = super().get_initial() |
|
50 |
initial['maintenance_page'] = bool(self.maintenance_page_variable.json) |
|
51 |
initial['maintenance_page_message'] = self.maintenance_page_message_variable.value |
|
52 |
initial['maintenance_pass_trough_header'] = self.maintenance_pass_trough_header_variable.value |
|
53 |
initial['disable_cron'] = bool(self.tenant_disable_cron_jobs_variable.json) |
|
54 |
return initial |
|
55 | ||
56 |
def form_valid(self, form): |
|
57 |
self.maintenance_page_variable.json = form.cleaned_data['maintenance_page'] |
|
58 |
self.maintenance_page_variable.save() |
|
59 |
self.maintenance_page_message_variable.value = form.cleaned_data['maintenance_page_message'] |
|
60 |
self.maintenance_page_message_variable.save() |
|
61 | ||
62 |
self.maintenance_pass_trough_header_variable.value = form.cleaned_data[ |
|
63 |
'maintenance_pass_trough_header' |
|
64 |
] |
|
65 |
self.maintenance_pass_trough_header_variable.save() |
|
66 |
self.tenant_disable_cron_jobs_variable.json = form.cleaned_data['disable_cron'] |
|
67 |
self.tenant_disable_cron_jobs_variable.save() |
|
68 |
return super().form_valid(form) |
|
69 | ||
70 |
def get_context_data(self, **kwargs): |
|
71 |
ctx = super().get_context_data(**kwargs) |
|
72 |
pass_through_ips_setting = getattr(settings, 'MAINTENANCE_PASS_THROUGH_IPS', []) |
|
73 |
ctx['pass_through_ips'] = ', '.join(pass_through_ips_setting) |
|
74 |
return ctx |
|
75 | ||
76 | ||
77 |
home = HomeView.as_view() |
hobo/middleware/maintenance.py | ||
---|---|---|
15 | 15 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
16 | 16 | |
17 | 17 |
from django.conf import settings |
18 |
from django.http import HttpResponse
|
|
18 |
from django.template.response import TemplateResponse
|
|
19 | 19 |
from django.utils.translation import ugettext as _ |
20 | 20 | |
21 | 21 | |
... | ... | |
38 | 38 |
def __call__(self, request): |
39 | 39 |
maintenance_mode = getattr(settings, 'MAINTENANCE_PAGE', None) |
40 | 40 |
if maintenance_mode and not pass_through(request): |
41 |
maintenance_msg = _('The site is under maintenance') |
|
42 |
return HttpResponse('<h1>%s</h1>' % maintenance_msg, status=503) |
|
41 |
maintenance_message = getattr(settings, 'MAINTENANCE_PAGE_MESSAGE', '') |
|
42 |
context = {'maintenance_message': maintenance_message} |
|
43 |
return TemplateResponse( |
|
44 |
request, 'hobo/maintenance/maintenance_page.html', context=context, status=503 |
|
45 |
).render() |
|
43 | 46 |
return self.get_response(request) |
hobo/settings.py | ||
---|---|---|
46 | 46 |
'hobo.debug', |
47 | 47 |
'hobo.environment', |
48 | 48 |
'hobo.franceconnect', |
49 |
'hobo.maintenance', |
|
49 | 50 |
'hobo.matomo', |
50 | 51 |
'hobo.profile', |
51 | 52 |
'hobo.seo', |
hobo/templates/hobo/home.html | ||
---|---|---|
117 | 117 |
<a class="button button-paragraph" href="{% url 'environment-home' %}">{% trans 'Services' %}</a> |
118 | 118 |
<a class="button button-paragraph" href="{% url 'environment-variables' %}">{% trans 'Variables' %}</a> |
119 | 119 |
<a class="button button-paragraph" href="{% url 'debug-home' %}">{% trans 'Debugging' %}</a> |
120 | ||
120 |
{% if show_maintenance_menu %}<a class="button button-paragraph" href="{% url 'maintenance-home' %}">{% trans 'Maintenance' %}</a>{% endif %} |
|
121 | 121 |
</aside> |
122 | 122 |
{% endblock %} |
hobo/urls.py | ||
---|---|---|
23 | 23 |
from .emails.urls import urlpatterns as emails_urls |
24 | 24 |
from .environment.urls import urlpatterns as environment_urls |
25 | 25 |
from .franceconnect.urls import urlpatterns as franceconnect_urls |
26 |
from .maintenance.urls import urlpatterns as maintenance_urls |
|
26 | 27 |
from .matomo.urls import urlpatterns as matomo_urls |
27 | 28 |
from .profile.urls import urlpatterns as profile_urls |
28 | 29 |
from .seo.urls import urlpatterns as seo_urls |
... | ... | |
45 | 46 |
url(r'^sms/', decorated_includes(admin_required, include(sms_urls))), |
46 | 47 |
url(r'^debug/', decorated_includes(admin_required, include(debug_urls))), |
47 | 48 |
url(r'^applications/', decorated_includes(admin_required, include(applications_urls))), |
49 |
url(r'^maintenance/', decorated_includes(admin_required, include(maintenance_urls))), |
|
48 | 50 |
url(r'^api/health/$', health_json, name='health-json'), |
49 | 51 |
url(r'^menu.json$', menu_json, name='menu_json'), |
50 | 52 |
url(r'^hobos.json$', hobo), |
hobo/views.py | ||
---|---|---|
39 | 39 |
context['has_authentic'] = bool(Authentic.objects.filter(secondary=False)) |
40 | 40 |
context['has_global_title'] = Variable.objects.filter(name='global_title').exists() |
41 | 41 |
context['has_default_from_email'] = Variable.objects.filter(name='default_from_email').exists() |
42 |
context['show_maintenance_menu'] = bool(getattr(settings, 'MAINTENANCE_PASS_THROUGH_IPS', [])) |
|
42 | 43 |
return context |
43 | 44 | |
44 | 45 |
tests/test_maintenance.py | ||
---|---|---|
1 | 1 |
import mock |
2 | 2 |
from test_manager import login |
3 | 3 | |
4 |
from hobo.environment.models import Variable |
|
5 |
from hobo.environment.utils import get_setting_variable |
|
6 |
from hobo.maintenance.management.commands.disable_maintenance_page import Command |
|
7 | ||
4 | 8 | |
5 | 9 |
def test_maintenance_middleware(app, admin_user, db, monkeypatch, settings): |
6 | 10 |
app = login(app) |
... | ... | |
9 | 13 | |
10 | 14 |
settings.MAINTENANCE_PAGE = True |
11 | 15 |
resp = app.get('/', status=503) |
12 |
assert 'The site is under maintenance' in resp.text |
|
16 |
assert 'This site is currently unavailable.' in resp.text |
|
17 | ||
18 |
# check custom maintenance message |
|
19 |
settings.MAINTENANCE_PAGE_MESSAGE = 'foobar' |
|
20 |
resp = app.get('/', status=503) |
|
21 |
assert 'foobar' in resp.text |
|
13 | 22 | |
14 | 23 |
settings.MAINTENANCE_PASS_THROUGH_IPS = ['127.0.0.1'] |
15 | 24 |
resp = app.get('/') |
... | ... | |
21 | 30 |
settings.MAINTENANCE_PASS_THROUGH_HEADER = 'X-Entrouvert' |
22 | 31 |
resp = app.get('/', headers={'X-Entrouvert': 'yes'}) |
23 | 32 |
assert resp.status_code == 200 |
33 | ||
34 | ||
35 |
def test_manage(app, admin_user, settings): |
|
36 |
assert Variable.objects.filter(name='SETTING_MAINTENANCE_PAGE').count() == 0 |
|
37 |
assert Variable.objects.filter(name='SETTING_MAINTENANCE_MESSAGE').count() == 0 |
|
38 |
assert Variable.objects.filter(name='SETTING_MAINTENANCE_PASS_THROUGH_HEADER').count() == 0 |
|
39 |
assert Variable.objects.filter(name='TENANT_DISABLE_CRON_JOBS').count() == 0 |
|
40 |
assert not getattr(settings, 'MAINTENANCE_PASS_THROUGH_IPS', []) |
|
41 | ||
42 |
login(app) |
|
43 |
resp = app.get('/') |
|
44 |
assert 'Maintenance' not in resp.text |
|
45 |
settings.MAINTENANCE_PASS_THROUGH_IPS = ['127.0.0.1'] |
|
46 |
resp = app.get('/') |
|
47 |
assert 'Maintenance' in resp.text |
|
48 | ||
49 |
resp = app.get('/maintenance/') |
|
50 |
resp.form.set('maintenance_page', True) |
|
51 |
resp.form.set('maintenance_page_message', 'Foo') |
|
52 |
resp.form.set('maintenance_pass_trough_header', 'X-Entrouvert') |
|
53 |
resp.form.set('disable_cron', True) |
|
54 |
resp = resp.form.submit().follow() |
|
55 |
assert Variable.objects.filter(name='SETTING_MAINTENANCE_PAGE').get().value == 'true' |
|
56 |
assert Variable.objects.filter(name='SETTING_MAINTENANCE_PAGE_MESSAGE').get().value == 'Foo' |
|
57 |
assert ( |
|
58 |
Variable.objects.filter(name='SETTING_MAINTENANCE_PASS_THROUGH_HEADER').get().value == 'X-Entrouvert' |
|
59 |
) |
|
60 |
assert Variable.objects.filter(name='SETTING_TENANT_DISABLE_CRON_JOBS').get().value == 'true' |
|
61 | ||
62 |
resp.form.set('maintenance_page', False) |
|
63 |
resp.form.set('maintenance_page_message', '') |
|
64 |
resp.form.set('maintenance_pass_trough_header', '') |
|
65 |
resp.form.set('disable_cron', False) |
|
66 |
resp = resp.form.submit().follow() |
|
67 |
assert Variable.objects.filter(name='SETTING_MAINTENANCE_PAGE').get().value == 'false' |
|
68 |
assert Variable.objects.filter(name='SETTING_MAINTENANCE_PAGE_MESSAGE').get().value == '' |
|
69 |
assert Variable.objects.filter(name='SETTING_MAINTENANCE_PASS_THROUGH_HEADER').get().value == '' |
|
70 |
assert Variable.objects.filter(name='SETTING_TENANT_DISABLE_CRON_JOBS').get().value == 'false' |
|
71 | ||
72 | ||
73 |
def test_disable_maintenance_page_command(db): |
|
74 |
maintenance_page_variable = get_setting_variable('MAINTENANCE_PAGE') |
|
75 |
assert not bool(maintenance_page_variable.json) |
|
76 |
command = Command() |
|
77 |
command.handle() |
|
78 |
maintenance_page_variable = get_setting_variable('MAINTENANCE_PAGE') |
|
79 |
assert not bool(maintenance_page_variable.json) |
|
80 |
maintenance_page_variable.json = True |
|
81 |
maintenance_page_variable.save() |
|
82 |
assert bool(maintenance_page_variable.json) |
|
83 |
command.handle() |
|
84 |
maintenance_page_variable = get_setting_variable('MAINTENANCE_PAGE') |
|
85 |
assert not bool(maintenance_page_variable.json) |
|
24 |
- |