datasource: import/export (#13722)
This commit is contained in:
parent
106787acef
commit
7fe3a4b473
|
@ -5450,6 +5450,63 @@ def test_data_sources_in_use_delete(pub):
|
|||
assert 'delete-button' in resp.text
|
||||
|
||||
|
||||
def test_data_sources_export(pub):
|
||||
create_superuser(pub)
|
||||
create_role()
|
||||
|
||||
NamedDataSource.wipe()
|
||||
data_source = NamedDataSource(name='foobar')
|
||||
data_source.data_source = {'type': 'formula', 'value': '[]'}
|
||||
data_source.store()
|
||||
|
||||
app = login(get_app(pub))
|
||||
resp = app.get('/backoffice/settings/data-sources/1/')
|
||||
|
||||
resp = resp.click(href='export')
|
||||
xml_export = resp.text
|
||||
|
||||
ds = StringIO(xml_export)
|
||||
data_source2 = NamedDataSource.import_from_xml(ds)
|
||||
assert data_source2.name == 'foobar'
|
||||
|
||||
|
||||
def test_data_sources_import(pub):
|
||||
create_superuser(pub)
|
||||
create_role()
|
||||
|
||||
NamedDataSource.wipe()
|
||||
data_source = NamedDataSource(name='foobar')
|
||||
data_source.data_source = {'type': 'formula', 'value': '[]'}
|
||||
data_source.store()
|
||||
data_source_xml = ET.tostring(data_source.export_to_xml(include_id=True))
|
||||
|
||||
NamedDataSource.wipe()
|
||||
assert NamedDataSource.count() == 0
|
||||
|
||||
app = login(get_app(pub))
|
||||
resp = app.get('/backoffice/settings/data-sources/')
|
||||
resp = resp.click(href='import')
|
||||
resp.forms[0]['file'] = Upload('datasource.wcs', data_source_xml)
|
||||
resp = resp.forms[0].submit()
|
||||
assert NamedDataSource.count() == 1
|
||||
|
||||
# import the same datasource a second time, make sure slug is not reused
|
||||
resp = app.get('/backoffice/settings/data-sources/')
|
||||
resp = resp.click(href='import')
|
||||
resp.forms[0]['file'] = Upload('datasource.wcs', data_source_xml)
|
||||
resp = resp.forms[0].submit()
|
||||
assert NamedDataSource.count() == 2
|
||||
assert NamedDataSource.get(1).slug == 'foobar'
|
||||
assert NamedDataSource.get(2).slug == 'foobar-1'
|
||||
|
||||
# import an invalid file
|
||||
resp = app.get('/backoffice/settings/data-sources/')
|
||||
resp = resp.click(href='import')
|
||||
resp.form['file'] = Upload('datasource.wcs', b'garbage')
|
||||
resp = resp.form.submit()
|
||||
assert 'Invalid File' in resp.text
|
||||
|
||||
|
||||
def test_data_sources_edit_slug(pub):
|
||||
create_superuser(pub)
|
||||
NamedDataSource.wipe()
|
||||
|
|
|
@ -564,6 +564,7 @@ def test_named_datasource_id_parameter(requests_pub):
|
|||
|
||||
def test_named_datasource_in_formdef():
|
||||
from wcs.formdef import FormDef
|
||||
NamedDataSource.wipe()
|
||||
datasource = NamedDataSource(name='foobar')
|
||||
datasource.data_source = {'type': 'json', 'value': 'http://whatever/'}
|
||||
datasource.store()
|
||||
|
|
|
@ -14,14 +14,21 @@
|
|||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
from quixote import redirect
|
||||
from quixote.directory import Directory
|
||||
from quixote.html import TemplateIO, htmltext
|
||||
|
||||
from wcs.qommon import _
|
||||
from wcs.qommon import _, force_str
|
||||
from wcs.qommon import errors, template
|
||||
from wcs.qommon.form import *
|
||||
from wcs.qommon.misc import json_response
|
||||
from wcs.qommon.form import FileWidget
|
||||
from wcs.qommon.form import Form
|
||||
from wcs.qommon.form import UrlWidget
|
||||
from wcs.qommon.form import get_response
|
||||
from wcs.qommon.form import get_session
|
||||
from wcs.qommon import misc
|
||||
from wcs.qommon.backoffice.menu import html_top
|
||||
from wcs.data_sources import (NamedDataSource, DataSourceSelectionWidget,
|
||||
get_structured_items)
|
||||
|
@ -123,7 +130,7 @@ class NamedDataSourceUI(object):
|
|||
|
||||
|
||||
class NamedDataSourcePage(Directory):
|
||||
_q_exports = ['', 'edit', 'delete']
|
||||
_q_exports = ['', 'edit', 'delete', 'export']
|
||||
do_not_call_in_templates = True
|
||||
|
||||
def __init__(self, component):
|
||||
|
@ -222,9 +229,19 @@ class NamedDataSourcePage(Directory):
|
|||
self.datasource.remove_self()
|
||||
return redirect('..')
|
||||
|
||||
def export(self):
|
||||
x = self.datasource.export_to_xml(include_id=True)
|
||||
misc.indent_xml(x)
|
||||
response = get_response()
|
||||
response.set_content_type('application/x-wcs-datasource')
|
||||
response.set_header(
|
||||
'content-disposition',
|
||||
'attachment; filename=datasource-%s.wcs' % self.datasource.slug)
|
||||
return '<?xml version="1.0"?>\n' + force_str(ET.tostring(x))
|
||||
|
||||
|
||||
class NamedDataSourcesDirectory(Directory):
|
||||
_q_exports = ['', 'new']
|
||||
_q_exports = ['', 'new', ('import', 'p_import')]
|
||||
|
||||
def _q_traverse(self, path):
|
||||
get_response().breadcrumb.append( ('data-sources/', _('Data Sources')) )
|
||||
|
@ -239,6 +256,7 @@ class NamedDataSourcesDirectory(Directory):
|
|||
r += htmltext('<div id="appbar">')
|
||||
r += htmltext('<h2>%s</h2>') % _('Data Sources')
|
||||
r += htmltext('<span class="actions">')
|
||||
r += htmltext('<a href="import" rel="popup">%s</a>') % _('Import')
|
||||
r += htmltext('<a class="new-item" href="new">%s</a>') % _('New Data Source')
|
||||
r += htmltext('</span>')
|
||||
r += htmltext('</div>')
|
||||
|
@ -275,3 +293,51 @@ class NamedDataSourcesDirectory(Directory):
|
|||
|
||||
def _q_lookup(self, component):
|
||||
return NamedDataSourcePage(component)
|
||||
|
||||
def p_import(self):
|
||||
form = Form(enctype='multipart/form-data')
|
||||
import_title = _('Import Data Source')
|
||||
|
||||
form.add(FileWidget, 'file', title=_('File'), required=True)
|
||||
form.add_submit('submit', import_title)
|
||||
form.add_submit('cancel', _('Cancel'))
|
||||
|
||||
if form.get_submit() == 'cancel':
|
||||
return redirect('.')
|
||||
|
||||
if form.is_submitted() and not form.has_errors():
|
||||
try:
|
||||
return self.import_submit(form)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
get_response().breadcrumb.append(('import', _('Import')))
|
||||
html_top('datasources', title=import_title)
|
||||
r = TemplateIO(html=True)
|
||||
r += htmltext('<h2>%s</h2>') % import_title
|
||||
r += htmltext('<p>%s</p>') % _(
|
||||
'You can install a new data source by uploading a file '
|
||||
'or by pointing to the data source URL.')
|
||||
r += form.render()
|
||||
return r.getvalue()
|
||||
|
||||
def import_submit(self, form):
|
||||
self.imported_datasource = None
|
||||
fp = form.get_widget('file').parse().fp
|
||||
|
||||
error = False
|
||||
try:
|
||||
datasource = NamedDataSource.import_from_xml(fp)
|
||||
get_session().message = (
|
||||
'info', _('This datasource has been successfully imported.'))
|
||||
except ValueError:
|
||||
error = True
|
||||
|
||||
if error:
|
||||
form.set_error('file', _('Invalid File'))
|
||||
raise ValueError()
|
||||
|
||||
self.imported_datasource = datasource
|
||||
datasource.slug = None # a new one will be set in .store()
|
||||
datasource.store()
|
||||
return redirect('%s/' % datasource.id)
|
||||
|
|
|
@ -264,6 +264,7 @@ def get_object(data_source):
|
|||
|
||||
class NamedDataSource(XmlStorableObject):
|
||||
_names = 'datasources'
|
||||
_indexes = ['slug']
|
||||
_xml_tagname = 'datasources'
|
||||
|
||||
name = None
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
<div id="appbar">
|
||||
<h2>{% trans "Data Source" %} - {{ datasource.name }}</h2>
|
||||
<span class="actions">
|
||||
<a href="export">{% trans "Export" %}</a>
|
||||
<a href="delete" rel="popup">{% trans "Delete" %}</a>
|
||||
<a href="edit">{% trans "Edit" %}</a>
|
||||
</span>
|
||||
|
|
Loading…
Reference in New Issue