0002-misc-add-visualization-import-export-30854.patch
bijoe/management/commands/export_site.py | ||
---|---|---|
1 |
# bijoe - BI dashboard |
|
2 |
# Copyright (C) 2015 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 |
import json |
|
18 |
import sys |
|
19 | ||
20 |
from django.core.management.base import BaseCommand, CommandError |
|
21 | ||
22 |
from bijoe.utils import export_site |
|
23 | ||
24 | ||
25 |
class Command(BaseCommand): |
|
26 |
help = 'Export the site' |
|
27 | ||
28 |
def add_arguments(self, parser): |
|
29 |
parser.add_argument( |
|
30 |
'--output', metavar='FILE', default=None, |
|
31 |
help='name of a file to write output to') |
|
32 | ||
33 |
def handle(self, *args, **options): |
|
34 |
if options['output']: |
|
35 |
output = open(options['output'], 'w') |
|
36 |
else: |
|
37 |
output = sys.stdout |
|
38 |
json.dump(export_site(), output, indent=4) |
bijoe/management/commands/import_site.py | ||
---|---|---|
1 |
# bijoe - BI dashboard |
|
2 |
# Copyright (C) 2015 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 |
import json |
|
18 |
import sys |
|
19 | ||
20 |
from django.core.management.base import BaseCommand |
|
21 | ||
22 |
from bijoe.utils import import_site |
|
23 | ||
24 | ||
25 |
class Command(BaseCommand): |
|
26 |
help = 'Import an exported site' |
|
27 | ||
28 |
def add_arguments(self, parser): |
|
29 |
parser.add_argument( |
|
30 |
'filename', metavar='FILENAME', type=str, |
|
31 |
help='name of file to import') |
|
32 |
parser.add_argument( |
|
33 |
'--clean', action='store_true', default=False, |
|
34 |
help='Clean site before importing') |
|
35 |
parser.add_argument( |
|
36 |
'--if-empty', action='store_true', default=False, |
|
37 |
help='Import only if site is empty') |
|
38 | ||
39 |
def handle(self, filename, **options): |
|
40 |
if filename == '-': |
|
41 |
fd = sys.stdin |
|
42 |
else: |
|
43 |
fd = open(filename) |
|
44 |
import_site(json.load(fd), if_empty=options['if_empty'], clean=options['clean']) |
bijoe/templates/bijoe/homepage.html | ||
---|---|---|
6 | 6 |
<a>{% trans "Homepage" %}</a> |
7 | 7 |
{% endblock %} |
8 | 8 | |
9 |
{% block appbar %} |
|
10 |
<h2>{% trans "Visualizations" %}</h2> |
|
11 |
<span class="actions"> |
|
12 |
<a class="extra-actions-menu-opener"></a> |
|
13 |
</span> |
|
14 |
<ul class="extra-actions-menu"> |
|
15 |
<li><a rel="popup" href="{% url 'visualizations-import' %}">{% trans 'Import' %}</a></li> |
|
16 |
<li><a download href="{% url 'visualizations-export' %}">{% trans 'Export' %}</a></li> |
|
17 |
</ul> |
|
18 |
{% endblock %} |
|
19 | ||
9 | 20 |
{% block content %} |
10 | 21 |
{% if visualizations %} |
11 |
<h2>{% trans "Visualizations" %}</h2> |
|
12 | 22 |
{% include "bijoe/visualizations_list.html" %} |
23 |
{% else %} |
|
24 |
{% trans "No visualizations to display yet." %} |
|
13 | 25 |
{% endif %} |
14 | 26 |
{% if warehouses %} |
15 | 27 |
<h2>{% trans "Data sources" %}</h2> |
bijoe/templates/bijoe/visualization.html | ||
---|---|---|
16 | 16 |
<a rel="popup" class="button" href="{% url "rename-visualization" pk=object.pk %}">{% trans "Rename" %}</a> |
17 | 17 |
<a rel="popup" class="button" href="{% url "delete-visualization" pk=object.pk %}">{% trans "Delete" %}</a> |
18 | 18 |
<a class="button" href="{% url "visualization-ods" pk=object.pk %}">{% trans "Export as ODS" %}</a> |
19 |
<a download class="button" href="{% url "export-visualization" pk=object.pk %}">{% trans "Export as JSON" %}</a> |
|
19 | 20 |
<a href="{{ iframe_url }}" class="button">{% trans "URL for IFRAME" %}</a> |
20 | 21 |
{% endblock %} |
21 | 22 |
bijoe/templates/bijoe/visualizations.html | ||
---|---|---|
7 | 7 |
{% endblock %} |
8 | 8 | |
9 | 9 |
{% block appbar %} |
10 |
<h2>{% trans "Visualizations" %}</h2> |
|
10 |
<h2>{% trans "Visualizations" %}</h2> |
|
11 |
<span class="actions"> |
|
12 |
<a class="extra-actions-menu-opener"></a> |
|
13 |
</span> |
|
14 |
<ul class="extra-actions-menu"> |
|
15 |
<li><a rel="popup" href="{% url 'visualizations-import' %}">{% trans 'Import' %}</a></li> |
|
16 |
<li><a download href="{% url 'visualizations-export' %}">{% trans 'Export' %}</a></li> |
|
17 |
</ul> |
|
11 | 18 |
{% endblock %} |
12 | 19 | |
13 | 20 |
{% block content %} |
bijoe/templates/bijoe/visualizations_import.html | ||
---|---|---|
1 |
{% extends "bijoe/base.html" %} |
|
2 |
{% load i18n %} |
|
3 | ||
4 |
{% block appbar %} |
|
5 |
<h2>{% trans "Visualizations Import" %}</h2> |
|
6 |
{% endblock %} |
|
7 | ||
8 |
{% block content %} |
|
9 |
<form method="post" enctype="multipart/form-data"> |
|
10 |
{% csrf_token %} |
|
11 |
{{ form.as_p }} |
|
12 |
<div class="buttons"> |
|
13 |
<button class="submit-button">{% trans "Import" %}</button> |
|
14 |
<a class="cancel" href="{% url 'homepage' %}">{% trans 'Cancel' %}</a> |
|
15 |
</div> |
|
16 |
</form> |
|
17 |
{% endblock %} |
|
18 | ||
19 | ||
20 |
bijoe/templates/bijoe/warehouse.html | ||
---|---|---|
28 | 28 |
{% endfor %} |
29 | 29 |
</tbody> |
30 | 30 |
</table> |
31 |
{% if not visualizations_exist %} |
|
32 |
<a rel="popup" href="{% url 'visualizations-import' %}">{% trans 'Import' %}</a> |
|
33 |
{% endif %} |
|
31 | 34 |
{% endblock %} |
bijoe/utils.py | ||
---|---|---|
19 | 19 |
import json |
20 | 20 | |
21 | 21 |
from django.conf import settings |
22 |
from django.db import connection |
|
22 |
from django.db import connection, transaction
|
|
23 | 23 |
from django.utils.translation import ugettext as _ |
24 | 24 |
try: |
25 | 25 |
from functools import lru_cache |
26 | 26 |
except ImportError: |
27 | 27 |
from django.utils.lru_cache import lru_cache |
28 | 28 | |
29 | ||
30 | 29 |
from .schemas import Warehouse |
31 | 30 | |
32 | 31 | |
... | ... | |
63 | 62 |
if len(l) > 2: |
64 | 63 |
l = u', '.join(l[:-1]), l[-1] |
65 | 64 |
return _(u'{0} and {1}').format(l[0], l[1]) |
65 | ||
66 | ||
67 |
def export_site(): |
|
68 |
from bijoe.visualization.models import Visualization |
|
69 | ||
70 |
return {'visualizations': [v.export_json() for v in Visualization.objects.all()]} |
|
71 | ||
72 | ||
73 |
def import_site(data, if_empty=False, clean=False): |
|
74 |
from bijoe.visualization.models import Visualization |
|
75 | ||
76 |
if if_empty and Visualization.objects.exists(): |
|
77 |
return |
|
78 | ||
79 |
if clean: |
|
80 |
Visualization.objects.all().delete() |
|
81 | ||
82 |
results = {'created': 0, 'updated': 0} |
|
83 |
with transaction.atomic(): |
|
84 |
for data in data.get('visualizations', []): |
|
85 |
created = Visualization.import_json(data) |
|
86 |
if created: |
|
87 |
results['created'] += 1 |
|
88 |
else: |
|
89 |
results['updated'] += 1 |
|
90 |
return results |
bijoe/visualization/forms.py | ||
---|---|---|
235 | 235 |
raise ValidationError({'loop': _('You cannot use the same dimension for looping and' |
236 | 236 |
' grouping')}) |
237 | 237 |
return cleaned_data |
238 | ||
239 | ||
240 |
class VisualizationsImportForm(forms.Form): |
|
241 |
visualizations_json = forms.FileField(label=_('Visualizations Export File')) |
bijoe/visualization/models.py | ||
---|---|---|
54 | 54 |
def natural_key(self): |
55 | 55 |
return (self.slug,) |
56 | 56 | |
57 |
def export_json(self): |
|
58 |
visualization = { |
|
59 |
'slug': self.slug, |
|
60 |
'name': self.name, |
|
61 |
'parameters': self.parameters |
|
62 |
} |
|
63 |
return visualization |
|
64 | ||
65 |
@classmethod |
|
66 |
def import_json(cls, data): |
|
67 |
defaults = { |
|
68 |
'name': data['name'], |
|
69 |
'parameters': data['parameters'] |
|
70 |
} |
|
71 |
_, created = cls.objects.update_or_create(slug=data['slug'], defaults=defaults) |
|
72 |
return created |
|
73 | ||
57 | 74 |
def save(self, *args, **kwargs): |
58 | 75 |
if not self.slug: |
59 | 76 |
slug = base_slug = slugify(self.name)[:40].strip('-') |
bijoe/visualization/urls.py | ||
---|---|---|
23 | 23 |
views.visualizations, name='visualizations'), |
24 | 24 |
url(r'^json/$', |
25 | 25 |
views.visualizations_json, name='visualizations-json'), |
26 |
url(r'^import/$', |
|
27 |
views.visualizations_import, name='visualizations-import'), |
|
28 |
url(r'^export$', |
|
29 |
views.visualizations_export, name='visualizations-export'), |
|
26 | 30 |
url(r'^warehouse/(?P<warehouse>[^/]*)/$', views.warehouse, name='warehouse'), |
27 | 31 |
url(r'^warehouse/(?P<warehouse>[^/]*)/(?P<cube>[^/]*)/$', views.cube, name='cube'), |
28 | 32 |
url(r'^warehouse/(?P<warehouse>[^/]*)/(?P<cube>[^/]*)/iframe/$', views.cube_iframe, |
... | ... | |
36 | 40 |
url(r'(?P<pk>\d+)/ods/$', views.visualization_ods, name='visualization-ods'), |
37 | 41 |
url(r'(?P<pk>\d+)/rename/$', views.rename_visualization, name='rename-visualization'), |
38 | 42 |
url(r'(?P<pk>\d+)/delete/$', views.delete_visualization, name='delete-visualization'), |
43 |
url(r'(?P<pk>\d+)/export$', views.export_visualization, name='export-visualization'), |
|
39 | 44 |
] |
bijoe/visualization/views.py | ||
---|---|---|
19 | 19 |
import json |
20 | 20 | |
21 | 21 |
from django.conf import settings |
22 |
from django.contrib import messages |
|
22 | 23 |
from django.utils.encoding import force_text |
23 | 24 |
from django.utils.text import slugify |
24 |
from django.views.generic.edit import CreateView, DeleteView, UpdateView |
|
25 |
from django.utils.translation import ungettext, ugettext as _ |
|
26 |
from django.views.generic.edit import CreateView, DeleteView, UpdateView, FormView |
|
25 | 27 |
from django.views.generic.list import MultipleObjectMixin |
26 | 28 |
from django.views.generic import DetailView, ListView, View, TemplateView |
27 | 29 |
from django.shortcuts import redirect |
... | ... | |
33 | 35 |
from rest_framework import generics |
34 | 36 |
from rest_framework.response import Response |
35 | 37 | |
36 |
from ..utils import get_warehouses
|
|
38 |
from bijoe.utils import get_warehouses, import_site, export_site
|
|
37 | 39 |
from ..engine import Engine |
38 | 40 |
from . import models, forms, signature |
39 | 41 |
from .utils import Visualization |
... | ... | |
52 | 54 |
raise Http404 |
53 | 55 |
ctx['warehouse'] = Engine(warehouse) |
54 | 56 |
ctx['cubes'] = sorted(ctx['warehouse'].cubes, key=lambda cube: cube.label.strip().lower()) |
57 |
ctx['visualizations_exist'] = models.Visualization.objects.exists() |
|
55 | 58 |
return ctx |
56 | 59 | |
57 | 60 | |
... | ... | |
341 | 344 |
}) |
342 | 345 | |
343 | 346 | |
347 |
class ExportVisualizationView(views.AuthorizationMixin, DetailView): |
|
348 |
model = models.Visualization |
|
349 | ||
350 |
def get(self, request, *args, **kwargs): |
|
351 |
response = HttpResponse(content_type='application/json') |
|
352 |
json.dump({'visualizations': [self.get_object().export_json()]}, response, indent=2) |
|
353 |
return response |
|
354 | ||
355 | ||
356 |
class VisualizationsImportView(views.AuthorizationMixin, FormView): |
|
357 |
form_class = forms.VisualizationsImportForm |
|
358 |
template_name = 'bijoe/visualizations_import.html' |
|
359 |
success_url = reverse_lazy('homepage') |
|
360 | ||
361 |
def form_valid(self, form): |
|
362 |
try: |
|
363 |
visualizations_json = json.loads( |
|
364 |
force_text(self.request.FILES['visualizations_json'].read())) |
|
365 |
except ValueError: |
|
366 |
form.add_error('visualizations_json', _('File is not in the expected JSON format.')) |
|
367 |
return self.form_invalid(form) |
|
368 | ||
369 |
results = import_site(visualizations_json) |
|
370 | ||
371 |
if results.get('created') == 0 and results.get('updated') == 0: |
|
372 |
messages.info(self.request, _('No visualizations were found.')) |
|
373 |
else: |
|
374 |
if results.get('created') == 0: |
|
375 |
message1 = _('No visualization created.') |
|
376 |
else: |
|
377 |
message1 = ungettext('A visualization has been created.', |
|
378 |
'%(count)d visualizations have been created.', results['created']) % { |
|
379 |
'count': results['created']} |
|
380 | ||
381 |
if results.get('updated') == 0: |
|
382 |
message2 = _('No visualization updated.') |
|
383 |
else: |
|
384 |
message2 = ungettext('A visualization has been updated.', |
|
385 |
'%(count)d visualizations have been updated.', results['updated']) % { |
|
386 |
'count': results['updated']} |
|
387 |
messages.info(self.request, u'%s %s' % (message1, message2)) |
|
388 | ||
389 |
return super(VisualizationsImportView, self).form_valid(form) |
|
390 | ||
391 | ||
392 |
class VisualizationsExportView(views.AuthorizationMixin, View): |
|
393 | ||
394 |
def get(self, request, *args, **kwargs): |
|
395 |
response = HttpResponse(content_type='application/json') |
|
396 |
json.dump(export_site(), response, indent=2) |
|
397 |
return response |
|
398 | ||
399 | ||
344 | 400 |
warehouse = WarehouseView.as_view() |
345 | 401 |
cube = CubeView.as_view() |
346 | 402 |
cube_iframe = xframe_options_exempt(CubeIframeView.as_view()) |
... | ... | |
349 | 405 |
create_visualization = CreateVisualizationView.as_view() |
350 | 406 |
delete_visualization = DeleteVisualizationView.as_view() |
351 | 407 |
rename_visualization = RenameVisualization.as_view() |
408 |
export_visualization = ExportVisualizationView.as_view() |
|
352 | 409 |
visualization = VisualizationView.as_view() |
353 | 410 |
visualization_iframe = xframe_options_exempt(VisualizationIframeView.as_view()) |
354 | 411 |
visualization_geojson = VisualizationGeoJSONView.as_view() |
355 | 412 |
visualization_ods = VisualizationODSView.as_view() |
356 | 413 |
visualization_json = VisualizationJSONView.as_view() |
414 |
visualizations_import = VisualizationsImportView.as_view() |
|
415 |
visualizations_export = VisualizationsExportView.as_view() |
|
357 | 416 | |
358 | 417 |
cube_iframe.mellon_no_passive = True |
359 | 418 |
visualization_iframe.mellon_no_passive = True |
tests/test_import_export.py | ||
---|---|---|
1 |
# -*- coding: utf-8 -*- |
|
2 | ||
3 |
from __future__ import unicode_literals |
|
4 | ||
5 |
import json |
|
6 |
import os |
|
7 |
import shutil |
|
8 |
import sys |
|
9 |
import tempfile |
|
10 | ||
11 |
import pytest |
|
12 |
from django.core.management import call_command |
|
13 |
from django.utils.encoding import force_bytes |
|
14 |
from django.utils.six import StringIO |
|
15 | ||
16 |
from bijoe.visualization.models import Visualization |
|
17 |
from bijoe.utils import import_site |
|
18 | ||
19 |
pytestmark = pytest.mark.django_db |
|
20 | ||
21 | ||
22 |
def get_output_of_command(command, *args, **kwargs): |
|
23 |
old_stdout = sys.stdout |
|
24 |
output = sys.stdout = StringIO() |
|
25 |
call_command(command, *args, **kwargs) |
|
26 |
sys.stdout = old_stdout |
|
27 |
return output.getvalue() |
|
28 | ||
29 | ||
30 |
def test_import_export(schema1, app): |
|
31 |
parameters = { |
|
32 |
'cube': 'facts1', |
|
33 |
'warehouse': 'schema1', |
|
34 |
'measure': 'duration', |
|
35 |
'representation': 'table', |
|
36 |
'loop': '', |
|
37 |
'filters': {}, |
|
38 |
'drilldown_x': 'date__yearmonth' |
|
39 |
} |
|
40 | ||
41 |
def create_visu(i=0): |
|
42 |
Visualization.objects.create(name='test' + str(i), parameters=parameters) |
|
43 | ||
44 |
for i in range(3): |
|
45 |
create_visu(i) |
|
46 |
output = get_output_of_command('export_site') |
|
47 |
assert len(json.loads(output)['visualizations']) == 3 |
|
48 | ||
49 |
import_site(data={}, clean=True) |
|
50 |
empty_output = get_output_of_command('export_site') |
|
51 |
assert len(json.loads(empty_output)['visualizations']) == 0 |
|
52 | ||
53 |
create_visu() |
|
54 |
old_stdin = sys.stdin |
|
55 |
sys.stdin = StringIO(json.dumps({})) |
|
56 |
assert Visualization.objects.count() == 1 |
|
57 |
try: |
|
58 |
call_command('import_site', '-', clean=True) |
|
59 |
finally: |
|
60 |
sys.stdin = old_stdin |
|
61 |
assert Visualization.objects.count() == 0 |
|
62 | ||
63 |
with tempfile.NamedTemporaryFile() as f: |
|
64 |
f.write(force_bytes(output)) |
|
65 |
f.flush() |
|
66 |
call_command('import_site', f.name) |
|
67 |
assert Visualization.objects.count() == 3 |
|
68 |
for i in range(3): |
|
69 |
visu = Visualization.objects.get(name='test' + str(i)) |
|
70 |
assert visu.parameters == parameters |
|
71 | ||
72 |
visu = Visualization.objects.get(name='test0') |
|
73 |
slug = visu.slug |
|
74 |
visu_json = visu.export_json() |
|
75 |
visu_json['name'] = 'new_name' |
|
76 |
visu_json['parameters']['measure'] = 'test' |
|
77 |
result = import_site(data={'visualizations': [visu_json]}) |
|
78 |
assert result['created'] == 0 and result['updated'] == 1 |
|
79 |
visu = Visualization.objects.get(slug=slug) |
|
80 |
assert visu.name == 'new_name' |
|
81 |
new_params = visu.parameters |
|
82 |
assert new_params.pop('measure') == 'test' |
|
83 |
assert new_params == {k: v for k, v in parameters.items() if k != 'measure'} |
|
84 | ||
85 |
import_site(data={}, if_empty=True) |
|
86 |
assert Visualization.objects.count() == 3 |
|
87 | ||
88 |
import_site(data={}, clean=True) |
|
89 |
tempdir = tempfile.mkdtemp('bijoe-test') |
|
90 |
empty_output = get_output_of_command('export_site', output=os.path.join(tempdir, 't.json')) |
|
91 |
assert os.path.exists(os.path.join(tempdir, 't.json')) |
|
92 |
shutil.rmtree(tempdir) |
tests/test_views.py | ||
---|---|---|
14 | 14 |
# You should have received a copy of the GNU Affero General Public License |
15 | 15 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
16 | 16 | |
17 |
import copy |
|
18 |
import json |
|
19 | ||
17 | 20 |
import pytest |
21 |
from webtest import Upload |
|
18 | 22 | |
19 | 23 |
from django.core.urlresolvers import reverse |
20 | 24 | |
... | ... | |
97 | 101 | |
98 | 102 |
response = app.get('/') |
99 | 103 |
assert response.pyquery('ul li a.disabled').text() == visualization.name |
104 | ||
105 | ||
106 |
def test_import_visualization(schema1, app, admin, visualization): |
|
107 |
login(app, admin) |
|
108 |
resp = app.get('/visualization/%s/' % visualization.id) |
|
109 |
resp = resp.click('Export as JSON') |
|
110 |
assert resp.headers['content-type'] == 'application/json' |
|
111 |
visualization_export = resp.text |
|
112 | ||
113 |
# invalid json |
|
114 |
resp = app.get('/', status=200) |
|
115 |
resp = resp.click('Import') |
|
116 |
resp.form['visualizations_json'] = Upload('export.json', b'garbage', 'application/json') |
|
117 |
resp = resp.form.submit() |
|
118 |
assert 'File is not in the expected JSON format.' in resp.text |
|
119 | ||
120 |
# empty json |
|
121 |
resp = app.get('/', status=200) |
|
122 |
resp = resp.click('Import') |
|
123 |
resp.form['visualizations_json'] = Upload('export.json', b'{}', 'application/json') |
|
124 |
resp = resp.form.submit().follow() |
|
125 |
assert 'No visualizations were found.' in resp.text |
|
126 | ||
127 |
# existing visualization |
|
128 |
resp = app.get('/', status=200) |
|
129 |
resp = resp.click('Import') |
|
130 |
resp.form['visualizations_json'] = Upload('export.json', visualization_export.encode('utf-8'), 'application/json') |
|
131 |
resp = resp.form.submit().follow() |
|
132 |
assert 'No visualization created. A visualization has been updated.' in resp.text |
|
133 |
assert Visualization.objects.count() == 1 |
|
134 | ||
135 |
# new visualization |
|
136 |
Visualization.objects.all().delete() |
|
137 |
resp = app.get('/').follow() |
|
138 |
resp = resp.click('Import') |
|
139 |
resp.form['visualizations_json'] = Upload('export.json', visualization_export.encode('utf-8'), 'application/json') |
|
140 |
resp = resp.form.submit().follow() |
|
141 |
assert 'A visualization has been created. No visualization updated.' in resp.text |
|
142 |
assert Visualization.objects.count() == 1 |
|
143 | ||
144 |
# multiple visualizations |
|
145 |
visualizations = json.loads(visualization_export) |
|
146 |
visualizations['visualizations'].append(copy.copy(visualizations['visualizations'][0])) |
|
147 |
visualizations['visualizations'].append(copy.copy(visualizations['visualizations'][0])) |
|
148 |
visualizations['visualizations'][1]['name'] = 'test 2' |
|
149 |
visualizations['visualizations'][1]['slug'] = 'test-2' |
|
150 |
visualizations['visualizations'][2]['name'] = 'test 3' |
|
151 |
visualizations['visualizations'][2]['slug'] = 'test-3' |
|
152 | ||
153 |
resp = app.get('/', status=200) |
|
154 |
resp = resp.click('Import') |
|
155 |
resp.form['visualizations_json'] = Upload('export.json', json.dumps(visualizations).encode('utf-8'), 'application/json') |
|
156 |
resp = resp.form.submit().follow() |
|
157 |
assert '2 visualizations have been created. A visualization has been updated.' in resp.text |
|
158 |
assert Visualization.objects.count() == 3 |
|
159 | ||
160 |
# global export/import |
|
161 |
resp = app.get('/').click('Export') |
|
162 |
visualizations_export = resp.text |
|
163 |
Visualization.objects.all().delete() |
|
164 | ||
165 |
resp = app.get('/').follow() |
|
166 |
resp = resp.click('Import') |
|
167 |
resp.form['visualizations_json'] = Upload('export.json', visualizations_export.encode('utf-8'), 'application/json') |
|
168 |
resp = resp.form.submit().follow() |
|
169 |
assert '3 visualizations have been created. No visualization updated.' in resp.text |
|
170 |
assert Visualization.objects.count() == 3 |
|
100 |
- |