Projet

Général

Profil

0001-start-the-maintenance-application-64868.patch

Emmanuel Cazenave, 19 mai 2022 19:29

Télécharger (16,9 ko)

Voir les différences:

Subject: [PATCH] start the maintenance application (#64868)

 hobo/maintenance/forms.py                     | 29 +++++++
 .../commands/disable_maintenance_page.py      | 17 ++++
 .../templates/hobo/maintenance/home.html      | 21 +++++
 .../hobo/maintenance/maintenance_page.html    |  8 ++
 hobo/maintenance/urls.py                      | 23 ++++++
 hobo/maintenance/views.py                     | 77 +++++++++++++++++++
 hobo/middleware/maintenance.py                |  9 ++-
 hobo/settings.py                              |  1 +
 hobo/templates/hobo/home.html                 |  2 +-
 hobo/urls.py                                  |  2 +
 hobo/views.py                                 |  1 +
 tests/test_maintenance.py                     | 64 ++++++++++++++-
 12 files changed, 249 insertions(+), 5 deletions(-)
 create mode 100644 hobo/maintenance/forms.py
 create mode 100644 hobo/maintenance/management/commands/disable_maintenance_page.py
 create mode 100644 hobo/maintenance/templates/hobo/maintenance/home.html
 create mode 100644 hobo/maintenance/templates/hobo/maintenance/maintenance_page.html
 create mode 100644 hobo/maintenance/urls.py
 create mode 100644 hobo/maintenance/views.py
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
-