Projet

Général

Profil

0001-environment-import-and-export-parameters-33672.patch

Nicolas Roche, 15 mai 2020 13:45

Télécharger (12,2 ko)

Voir les différences:

Subject: [PATCH] environment: import and export parameters (#33672)

 hobo/environment/forms.py                     |  4 ++
 .../templates/environment/import.html         | 22 +++++++
 hobo/environment/urls.py                      |  3 +
 hobo/environment/utils.py                     | 37 ++++++++++++
 hobo/environment/views.py                     | 35 ++++++++++-
 hobo/templates/hobo/home.html                 |  2 +
 tests/test_environment.py                     | 58 +++++++++++++++++++
 7 files changed, 160 insertions(+), 1 deletion(-)
 create mode 100644 hobo/environment/templates/environment/import.html
hobo/environment/forms.py
205 205
            if variable.value != form.cleaned_data[variable_name]:
206 206
                variable.value = form.cleaned_data[variable_name]
207 207
                variable.save()
208 208
                changed = True
209 209
        if changed and self.success_message:
210 210
            messages.info(self.request, self.success_message)
211 211

  
212 212
        return HttpResponseRedirect('.')
213

  
214

  
215
class ImportForm(forms.Form):
216
    parameters_json = forms.FileField(label=_('Parameters Export File'))
hobo/environment/templates/environment/import.html
1
{% extends "hobo/base.html" %}
2
{% load i18n %}
3

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

  
8
{% block breadcrumb %}
9
{{ block.super }}
10
<a href="{% url 'environment-import' %}">{% trans 'Parameters Import' %}</a>
11
{% endblock %}
12

  
13
{% block content %}
14
<form method="post" enctype="multipart/form-data">
15
  {% csrf_token %}
16
  {{ form.as_p }}
17
  <div class="buttons">
18
    <button class="submit-button">{% trans "Import" %}</button>
19
    <a class="cancel" href="{% url 'home' %}">{% trans 'Cancel' %}</a>
20
  </div>
21
</form>
22
{% endblock %}
hobo/environment/urls.py
29 29
    url(r'^check_operational/(?P<service>\w+)/(?P<slug>[\w-]+)$',
30 30
        views.operational_check_view, name='operational-check'),
31 31
    url(r'^new-(?P<service>\w+)$', views.ServiceCreateView.as_view(), name='create-service'),
32 32
    url(r'^save-(?P<service>\w+)/(?P<slug>[\w-]+)$', views.ServiceUpdateView.as_view(), name='save-service'),
33 33
    url(r'^delete-(?P<service>\w+)/(?P<slug>[\w-]+)$', views.ServiceDeleteView.as_view(), name='delete-service'),
34 34

  
35 35
    url(r'^new-variable-(?P<service>\w+)/(?P<slug>[\w-]+)$',
36 36
        views.VariableCreateView.as_view(), name='new-variable-service',),
37

  
38
    url(r'^import/$', views.ImportView.as_view(), name='environment-import'),
39
    url(r'^export$', views.ExportView.as_view(), name='environment-export'),
37 40
    url(r'^debug.json$', views.debug_json, name='debug-json'),
38 41
]
hobo/environment/utils.py
16 16

  
17 17
from django.conf import settings
18 18
from django.core.urlresolvers import reverse
19 19
from django.db import connection
20 20
from django.utils.six.moves.urllib.parse import urlparse
21 21
from django.utils.encoding import force_text
22 22

  
23 23
from hobo.middleware.utils import StoreRequestMiddleware
24
from hobo.profile.utils import get_profile_dict
24 25

  
25 26

  
26 27
def get_installed_services():
27 28
    from .models import AVAILABLE_SERVICES
28 29
    installed_services = []
29 30
    for available_service in AVAILABLE_SERVICES:
30 31
        installed_services.extend(available_service.objects.all())
31 32
    return installed_services
......
121 122
    except Variable.DoesNotExist:
122 123
        variable = Variable(
123 124
            name='SETTING_' + setting_name,
124 125
            label=label or '',
125 126
            service=service,
126 127
            auto=True)
127 128

  
128 129
    return variable
130

  
131

  
132
def export_parameters():
133
    from .models import Variable
134

  
135
    # TODO : export too : label, auto?
136
    variables = []
137
    for var in Variable.objects.filter(service_pk__isnull=True):
138
        variables.append({
139
            'name': var.name,
140
            'label': var.label,
141
            'value': var.value,
142
            'auto': var.auto})
143
    parameters = {'variables': variables}
144
    parameters.update(get_profile_dict())
145
    return parameters
146

  
147

  
148
def import_parameters(parameters):
149
    from .models import Variable
150
    from hobo.profile.models import AttributeDefinition
151

  
152
    objects = []
153
    try:
154
        for var in parameters['variables']:
155
            objects.append(Variable(**var))
156
        for field in parameters['profile']['fields']:
157
            objects.append(AttributeDefinition(**field))
158
    except Exception as exc:
159
        return {'err': True, 'message': '%s, %s' % (exc.__class__.__name__, exc)}
160

  
161
    Variable.objects.filter(service_pk__isnull=True).delete()
162
    AttributeDefinition.objects.all().delete()
163
    for obj in objects:
164
        obj.save()
165
    return {'err': False}
hobo/environment/views.py
17 17
import json
18 18
import string
19 19

  
20 20
from django.conf import settings
21 21
from django.contrib.contenttypes.models import ContentType
22 22
from django.core.urlresolvers import reverse_lazy
23 23
from django.http import HttpResponse, HttpResponseRedirect, Http404
24 24
from django.shortcuts import get_object_or_404
25
from django.utils.encoding import force_text
26
from django.utils.translation import ugettext_lazy as _
27
from django.views.generic import View
25 28
from django.views.generic.base import TemplateView
26
from django.views.generic.edit import CreateView, UpdateView, DeleteView
29
from django.views.generic.edit import CreateView, UpdateView, DeleteView, FormView
27 30

  
28 31
from .models import Variable, AVAILABLE_SERVICES
29 32
from . import forms, utils
30 33

  
31 34

  
32 35
class AvailableService(object):
33 36
    def __init__(self, klass):
34 37
        self.id = klass.Extra.service_id
......
194 197
        service_id = self.kwargs.pop('service')
195 198
        service_slug = self.kwargs.pop('slug')
196 199
        for service in AVAILABLE_SERVICES:
197 200
            if service.Extra.service_id == service_id:
198 201
                return service.objects.get(slug=service_slug)
199 202
        return None
200 203

  
201 204

  
205
class ImportView(FormView):
206
    form_class = forms.ImportForm
207
    template_name = 'environment/import.html'
208
    success_url = reverse_lazy('home')
209

  
210
    def form_valid(self, form):
211
        try:
212
            parameters_json = json.loads(
213
                force_text(self.request.FILES['parameters_json'].read()))
214
        except ValueError:
215
            form.add_error('parameters_json', _('File is not in the expected JSON format.'))
216
            return self.form_invalid(form)
217

  
218
        result = utils.import_parameters(parameters_json)
219
        if result.get('err'):
220
            form.add_error('parameters_json',
221
                           _('Parameters import fails: %s') % result.get('message'))
222
            return self.form_invalid(form)
223

  
224
        return super(ImportView, self).form_valid(form)
225

  
226

  
227
class ExportView(View):
228

  
229
    def get(self, request, *args, **kwargs):
230
        response = HttpResponse(content_type='application/json')
231
        json.dump(utils.export_parameters(), response, indent=2)
232
        return response
233

  
234

  
202 235
def operational_check_view(request, service, slug, **kwargs):
203 236

  
204 237
    for klass in AVAILABLE_SERVICES:
205 238
        if klass.Extra.service_id == service:
206 239
            break
207 240
    else:
208 241
        raise Http404()
209 242

  
hobo/templates/hobo/home.html
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 18
    <li><a href="{% url 'seo-home' %}">{% trans 'Indexing' %}</a></li>
19 19
    <li><a href="{% url 'environment-home' %}">{% trans 'Services' %}</a></li>
20 20
    <li><a href="{% url 'environment-variables' %}">{% trans 'Variables' %}</a></li>
21
    <li><a rel="popup" href="{% url 'environment-import' %}">{% trans 'Import' %}</a></li>
22
    <li><a download href="{% url 'environment-export' %}">{% trans 'Export' %}</a></li>
21 23
    <li><a href="{% url 'debug-home' %}">{% trans 'Debugging' %}</a></li>
22 24
  </ul>
23 25
  </span>
24 26
{% endblock %}
25 27

  
26 28
{% block content %}
27 29

  
28 30
{% if services %}
tests/test_environment.py
1 1
# -*- coding: utf-8 -*-
2
import json
2 3
import pytest
4
from webtest import Upload
3 5

  
4 6
from django.core.exceptions import ValidationError
5 7
from django.core.management import call_command
6 8
from django.utils import timezone
7 9

  
8 10
from hobo.environment.models import AVAILABLE_SERVICES, Combo, Passerelle, ServiceBase, Variable
9 11

  
10 12
from test_manager import login
......
311 313
    combo.last_operational_success_timestamp = '2022-2-22'
312 314
    combo.save()
313 315
    call_command('check_operational', '-v2')
314 316
    captured = capsys.readouterr()
315 317
    assert captured.out.split('\n')[:-1] == [
316 318
        'foo is NOT operational',
317 319
        '  last operational success: 2022-02-22 00:00:00+00:00'
318 320
    ]
321

  
322

  
323
def test_export_import_view(app, admin_user):
324
    Variable.objects.create(name='foo', value='bar')
325
    app = login(app, 'admin', 'password')
326
    resp = app.get('/sites/export', status=200)
327
    assert [x for x in sorted(resp.json.keys())] == ['profile', 'variables']
328
    assert resp.json['variables'] == [
329
        {'name': 'foo', 'label': '', 'value': 'bar', 'auto': False}]
330
    assert resp.json['profile']['fields'][0]['name'] == 'title'
331
    assert resp.json['profile']['fields'][0]['required'] is False
332
    assert resp.json['profile']['fields'][1]['name'] == 'first_name'
333
    assert resp.json['profile']['fields'][1]['required'] is True
334

  
335
    # modify exported file
336
    export = resp.json
337
    export['variables'][0]['label'] = 'foo'
338
    fields = export['profile']['fields']
339
    fields[0], fields[1] = fields[1], fields[0]
340
    export_json = json.dumps(export)
341

  
342
    # import invalid json
343
    resp = app.get('/', status=200)
344
    resp = resp.click('Import')
345
    resp.form['parameters_json'] = Upload(
346
        'export.json', b'garbage', 'application/json')
347
    resp = resp.form.submit()
348
    assert resp.html.find('ul', {'class': 'errorlist'}).li.text == \
349
        'File is not in the expected JSON format.'
350

  
351
    # import empty json
352
    resp = app.get('/', status=200)
353
    resp = resp.click('Import')
354
    resp.form['parameters_json'] = Upload(
355
        'export.json', b'{}', 'application/json')
356
    resp = resp.form.submit()
357
    assert resp.html.find('ul', {'class': 'errorlist'}).li.text == \
358
        "Parameters import fails: KeyError, 'variables'"
359

  
360
    # import valid content
361
    resp = app.get('/', status=200)
362
    resp = resp.click('Import')
363
    resp.form['parameters_json'] = Upload(
364
        'export.json', export_json.encode('utf-8'), 'application/json')
365
    resp = resp.form.submit().follow()
366

  
367
    # check imported parameters
368
    app = login(app, 'admin', 'password')
369
    resp = app.get('/sites/export', status=200)
370
    assert [x for x in sorted(resp.json.keys())] == ['profile', 'variables']
371
    assert resp.json['variables'] == [
372
        {'name': 'foo', 'label': 'foo', 'value': 'bar', 'auto': False}]
373
    assert resp.json['profile']['fields'][1]['name'] == 'title'
374
    assert resp.json['profile']['fields'][1]['required'] is False
375
    assert resp.json['profile']['fields'][0]['name'] == 'first_name'
376
    assert resp.json['profile']['fields'][0]['required'] is True
319
-