Projet

Général

Profil

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

Nicolas Roche, 29 juillet 2020 17:53

Télécharger (15,3 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                     |  36 +++++-
 hobo/templates/hobo/home.html                 |   2 +
 tests/test_environment.py                     | 113 ++++++++++++++++++
 7 files changed, 216 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
19 19
from django.conf import settings
20 20
from django.urls import reverse
21 21
from django.db import connection
22 22
from django.utils.six.moves.urllib.parse import urlparse
23 23
from django.utils.encoding import force_text
24 24

  
25 25
from hobo.middleware.utils import StoreRequestMiddleware
26 26
from hobo.multitenant.settings_loaders import KnownServices
27
from hobo.profile.utils import get_profile_dict
27 28

  
28 29

  
29 30
def get_installed_services():
30 31
    from .models import AVAILABLE_SERVICES
31 32
    installed_services = []
32 33
    for available_service in AVAILABLE_SERVICES:
33 34
        installed_services.extend(available_service.objects.all())
34 35
    return installed_services
......
139 140
    except Variable.DoesNotExist:
140 141
        variable = Variable(
141 142
            name='SETTING_' + setting_name,
142 143
            label=label or '',
143 144
            service=service,
144 145
            auto=True)
145 146

  
146 147
    return variable
148

  
149

  
150
def export_parameters():
151
    from .models import Variable
152

  
153
    variables = []
154
    for var in Variable.objects.filter(service_pk__isnull=True):
155
        variables.append({
156
            'name': var.name,
157
            'label': var.label,
158
            'value': var.value,
159
            'auto': var.auto})
160
    parameters = {'variables': variables}
161
    parameters.update(get_profile_dict())
162
    return parameters
163

  
164

  
165
def import_parameters(parameters):
166
    from .models import Variable
167
    from hobo.profile.models import AttributeDefinition
168

  
169
    objects = []
170
    try:
171
        for variable in parameters.get('variables'):
172
            obj, created = Variable.objects.get_or_create(name=variable['name'])
173
            objects.append(Variable(id=obj.id, **variable))
174
        for field in parameters.get('profile', []).get('fields'):
175
            obj, created = AttributeDefinition.objects.get_or_create(
176
                label=field['label'], name=field['name'])
177
            objects.append(AttributeDefinition(id=obj.id, order=obj.order, **field))
178
    except Exception as exc:
179
        return {'err': True, 'message': '%s, %s' % (exc.__class__.__name__, exc)}
180

  
181
    for obj in objects:
182
        obj.save()
183
    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.urls 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') % force_text(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
        response['Content-Disposition'] = 'attachment; filename="hobo-export.json"'
232
        json.dump(utils.export_parameters(), response, indent=2)
233
        return response
234

  
235

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

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

  
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 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 not has_global_title %}
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
11
from hobo.profile.models import AttributeDefinition
9 12

  
10 13
from test_manager import login
11 14

  
12 15
pytestmark = pytest.mark.django_db
13 16

  
14 17

  
15 18
def test_service_id():
16 19
    for service in AVAILABLE_SERVICES:
......
311 314
    combo.last_operational_success_timestamp = '2022-2-22'
312 315
    combo.save()
313 316
    call_command('check_operational', '-v2')
314 317
    captured = capsys.readouterr()
315 318
    assert captured.out.split('\n')[:-1] == [
316 319
        'foo is NOT operational',
317 320
        '  last operational success: 2022-02-22 00:00:00+00:00'
318 321
    ]
322

  
323

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

  
337
    # modify exported file
338
    export = resp.json
339
    export['variables'][0]['label'] = 'bar'
340
    fields = export['profile']['fields']
341
    assert fields[0]['name'] == 'title'
342
    assert fields[1]['name'] == 'first_name'
343
    fields[0]['description'] = 'genre'
344
    fields[0], fields[1] = fields[1], fields[0]
345
    export_json = json.dumps(export)
346

  
347
    # add new content
348
    Variable.objects.create(name='foo2', value='bar').save()
349
    AttributeDefinition.objects.create(name='prefered_color').save()
350
    assert Variable.objects.count() == 2
351
    assert AttributeDefinition.objects.count() == 12
352
    assert Variable.objects.get(name='foo').label == ''
353
    assert AttributeDefinition.objects.get(name='title').description == ''
354
    assert AttributeDefinition.objects.get(name='title').order == 1
355
    assert AttributeDefinition.objects.get(name='first_name').order == 2
356
    assert AttributeDefinition.objects.get(name='prefered_color').order == 12
357

  
358
    # import valid content
359
    resp = app.get('/', status=200)
360
    resp = resp.click('Import')
361
    resp.form['parameters_json'] = Upload(
362
        'export.json', export_json.encode('utf-8'), 'application/json')
363
    resp = resp.form.submit()
364
    assert Variable.objects.count() == 2
365
    assert AttributeDefinition.objects.count() == 12
366
    assert Variable.objects.get(name='foo').label == 'bar'
367
    assert AttributeDefinition.objects.get(name='title').description == 'genre'
368
    assert AttributeDefinition.objects.get(name='title').order == 1
369
    assert AttributeDefinition.objects.get(name='first_name').order == 2
370
    assert AttributeDefinition.objects.get(name='prefered_color').order == 12
371

  
372
    # import empty json
373
    resp = app.get('/', status=200)
374
    resp = resp.click('Import')
375
    resp.form['parameters_json'] = Upload(
376
        'export.json', b'{}', 'application/json')
377
    resp = resp.form.submit()
378
    assert Variable.objects.count() == 2
379
    assert AttributeDefinition.objects.count() == 12
380
    assert Variable.objects.get(name='foo').label == 'bar'
381
    assert AttributeDefinition.objects.get(name='title').description == 'genre'
382
    assert AttributeDefinition.objects.get(name='title').order == 1
383
    assert AttributeDefinition.objects.get(name='first_name').order == 2
384
    assert AttributeDefinition.objects.get(name='prefered_color').order == 12
385

  
386
    # import from scratch
387
    Variable.objects.all().delete()
388
    AttributeDefinition.objects.all().delete()
389
    Variable.objects.create(name='foo2', value='bar').save()
390
    AttributeDefinition.objects.create(name='prefered_color').save()
391
    assert Variable.objects.count() == 1
392
    assert AttributeDefinition.objects.count() == 1
393
    resp = app.get('/', status=200)
394
    resp = resp.click('Import')
395
    resp.form['parameters_json'] = Upload(
396
        'export.json', export_json.encode('utf-8'), 'application/json')
397
    resp = resp.form.submit()
398
    assert Variable.objects.count() == 2
399
    assert AttributeDefinition.objects.count() == 12
400
    assert Variable.objects.get(name='foo').label == 'bar'
401
    assert AttributeDefinition.objects.get(name='title').order == 3
402
    assert AttributeDefinition.objects.get(name='first_name').order == 2
403
    assert AttributeDefinition.objects.get(name='prefered_color').order == 1
404

  
405
    # import invalid json
406
    resp = app.get('/', status=200)
407
    resp = resp.click('Import')
408
    resp.form['parameters_json'] = Upload(
409
        'export.json', b'garbage', 'application/json')
410
    resp = resp.form.submit()
411
    assert resp.html.find('ul', {'class': 'errorlist'}).li.text == \
412
        'File is not in the expected JSON format.'
413
    assert Variable.objects.count() == 2
414
    assert AttributeDefinition.objects.count() == 12
415

  
416
    # import corrupted json
417
    export['variables'][0]['label'] = 'foofoo'
418
    fields = export['profile']['fields']
419
    assert fields[1]['name'] == 'title'
420
    fields[1]['label'] = 'Genre'
421
    export_json = json.dumps(export)
422
    resp = app.get('/', status=200)
423
    resp = resp.click('Import')
424
    resp.form['parameters_json'] = Upload(
425
        'export.json', export_json.encode('utf-8'), 'application/json')
426
    resp = resp.form.submit()
427
    assert 'Parameters import fails: IntegrityError' in resp.html.find(
428
        'ul', {'class': 'errorlist'}).li.text
429
    assert Variable.objects.count() == 2
430
    assert AttributeDefinition.objects.count() == 12
431
    assert Variable.objects.get(name='foo').label == 'bar'
319
-