0001-environment-import-and-export-parameters-33672.patch
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 |
- |