Projet

Général

Profil

0001-datasource-import-export-13722.patch

Lauréline Guérin, 12 juin 2020 14:42

Télécharger (8,62 ko)

Voir les différences:

Subject: [PATCH] datasource: import/export (#13722)

 tests/test_admin_pages.py                     | 57 ++++++++++++
 wcs/admin/data_sources.py                     | 90 ++++++++++++++++++-
 wcs/data_sources.py                           |  1 +
 .../wcs/backoffice/data-sources.html          |  1 +
 4 files changed, 145 insertions(+), 4 deletions(-)
tests/test_admin_pages.py
5403 5403
    assert 'delete-button' in resp.text
5404 5404

  
5405 5405

  
5406
def test_data_sources_export(pub):
5407
    create_superuser(pub)
5408
    create_role()
5409

  
5410
    NamedDataSource.wipe()
5411
    data_source = NamedDataSource(name='foobar')
5412
    data_source.data_source = {'type': 'formula', 'value': '[]'}
5413
    data_source.store()
5414

  
5415
    app = login(get_app(pub))
5416
    resp = app.get('/backoffice/settings/data-sources/1/')
5417

  
5418
    resp = resp.click(href='export')
5419
    xml_export = resp.text
5420

  
5421
    ds = StringIO(xml_export)
5422
    data_source2 = NamedDataSource.import_from_xml(ds)
5423
    assert data_source2.name == 'foobar'
5424

  
5425

  
5426
def test_data_sources_import(pub):
5427
    create_superuser(pub)
5428
    create_role()
5429

  
5430
    NamedDataSource.wipe()
5431
    data_source = NamedDataSource(name='foobar')
5432
    data_source.data_source = {'type': 'formula', 'value': '[]'}
5433
    data_source.store()
5434
    data_source_xml = ET.tostring(data_source.export_to_xml(include_id=True))
5435

  
5436
    NamedDataSource.wipe()
5437
    assert NamedDataSource.count() == 0
5438

  
5439
    app = login(get_app(pub))
5440
    resp = app.get('/backoffice/settings/data-sources/')
5441
    resp = resp.click(href='import')
5442
    resp.forms[0]['file'] = Upload('datasource.wcs', data_source_xml)
5443
    resp = resp.forms[0].submit()
5444
    assert NamedDataSource.count() == 1
5445

  
5446
    # import the same datasource a second time, make sure slug is not reused
5447
    resp = app.get('/backoffice/settings/data-sources/')
5448
    resp = resp.click(href='import')
5449
    resp.forms[0]['file'] = Upload('datasource.wcs', data_source_xml)
5450
    resp = resp.forms[0].submit()
5451
    assert NamedDataSource.count() == 2
5452
    assert NamedDataSource.get(1).slug == 'foobar'
5453
    assert NamedDataSource.get(2).slug == 'foobar-1'
5454

  
5455
    # import an invalid file
5456
    resp = app.get('/backoffice/settings/data-sources/')
5457
    resp = resp.click(href='import')
5458
    resp.form['file'] = Upload('datasource.wcs', b'garbage')
5459
    resp = resp.form.submit()
5460
    assert 'Invalid File' in resp.text
5461

  
5462

  
5406 5463
def test_data_sources_edit_slug(pub):
5407 5464
    create_superuser(pub)
5408 5465
    NamedDataSource.wipe()
wcs/admin/data_sources.py
14 14
# You should have received a copy of the GNU General Public License
15 15
# along with this program; if not, see <http://www.gnu.org/licenses/>.
16 16

  
17
import xml.etree.ElementTree as ET
18

  
17 19
from quixote import redirect
18 20
from quixote.directory import Directory
19 21
from quixote.html import TemplateIO, htmltext
20 22

  
21
from wcs.qommon import _
23
from wcs.qommon import _, force_str
22 24
from wcs.qommon import errors, template
23 25
from wcs.qommon.form import *
24
from wcs.qommon.misc import json_response
26
from wcs.qommon.form import FileWidget
27
from wcs.qommon.form import Form
28
from wcs.qommon.form import UrlWidget
29
from wcs.qommon.form import get_response
30
from wcs.qommon.form import get_session
31
from wcs.qommon import misc
25 32
from wcs.qommon.backoffice.menu import html_top
26 33
from wcs.data_sources import (NamedDataSource, DataSourceSelectionWidget,
27 34
        get_structured_items)
......
123 130

  
124 131

  
125 132
class NamedDataSourcePage(Directory):
126
    _q_exports = ['', 'edit', 'delete']
133
    _q_exports = ['', 'edit', 'delete', 'export']
127 134
    do_not_call_in_templates = True
128 135

  
129 136
    def __init__(self, component):
......
222 229
            self.datasource.remove_self()
223 230
            return redirect('..')
224 231

  
232
    def export(self):
233
        x = self.datasource.export_to_xml(include_id=True)
234
        misc.indent_xml(x)
235
        response = get_response()
236
        response.set_content_type('application/x-wcs-datasource')
237
        response.set_header(
238
            'content-disposition',
239
            'attachment; filename=datasource-%s.wcs' % self.datasource.slug)
240
        return '<?xml version="1.0"?>\n' + force_str(ET.tostring(x))
241

  
225 242

  
226 243
class NamedDataSourcesDirectory(Directory):
227
    _q_exports = ['', 'new']
244
    _q_exports = ['', 'new', ('import', 'p_import')]
228 245

  
229 246
    def _q_traverse(self, path):
230 247
        get_response().breadcrumb.append( ('data-sources/', _('Data Sources')) )
......
239 256
        r += htmltext('<div id="appbar">')
240 257
        r += htmltext('<h2>%s</h2>') % _('Data Sources')
241 258
        r += htmltext('<span class="actions">')
259
        r += htmltext('<a href="import" rel="popup">%s</a>') % _('Import')
242 260
        r += htmltext('<a class="new-item" href="new">%s</a>') % _('New Data Source')
243 261
        r += htmltext('</span>')
244 262
        r += htmltext('</div>')
......
275 293

  
276 294
    def _q_lookup(self, component):
277 295
        return NamedDataSourcePage(component)
296

  
297
    def p_import(self):
298
        form = Form(enctype='multipart/form-data')
299
        import_title = _('Import Data Source')
300

  
301
        form.add(FileWidget, 'file', title=_('File'), required=False)
302
        form.add(UrlWidget, 'url', title=_('Address'), required=False, size=50)
303
        form.add_submit('submit',  import_title)
304
        form.add_submit('cancel', _('Cancel'))
305

  
306
        if form.get_submit() == 'cancel':
307
            return redirect('.')
308

  
309
        if form.is_submitted() and not form.has_errors():
310
            try:
311
                return self.import_submit(form)
312
            except ValueError:
313
                pass
314

  
315
        get_response().breadcrumb.append(('import', _('Import')))
316
        html_top('datasources', title=import_title)
317
        r = TemplateIO(html=True)
318
        r += htmltext('<h2>%s</h2>') % import_title
319
        r += htmltext('<p>%s</p>') % _(
320
            'You can install a new data source by uploading a file '
321
            'or by pointing to the data source URL.')
322
        r += form.render()
323
        return r.getvalue()
324

  
325
    def import_submit(self, form):
326
        self.imported_datasource = None
327
        if form.get_widget('file').parse():
328
            fp = form.get_widget('file').parse().fp
329
        elif form.get_widget('url').parse():
330
            url = form.get_widget('url').parse()
331
            try:
332
                fp = misc.urlopen(url)
333
            except misc.ConnectionError as e:
334
                form.set_error('url', _('Error loading datasource (%s).') % str(e))
335
                raise ValueError()
336
        else:
337
            form.set_error('file', _('You have to enter a file or a URL.'))
338
            raise ValueError()
339

  
340
        error = False
341
        try:
342
            datasource = NamedDataSource.import_from_xml(fp)
343
            get_session().message = (
344
                'info', _('This datasource has been successfully imported.'))
345
        except ValueError:
346
            error = True
347

  
348
        if error:
349
            msg = _('Invalid File')
350
            if form.get_widget('url').parse():
351
                form.set_error('url', msg)
352
            else:
353
                form.set_error('file', msg)
354
            raise ValueError()
355

  
356
        self.imported_datasource = datasource
357
        datasource.slug = None  # a new one will be set in .store()
358
        datasource.store()
359
        return redirect('%s/' % datasource.id)
wcs/data_sources.py
264 264

  
265 265
class NamedDataSource(XmlStorableObject):
266 266
    _names = 'datasources'
267
    _indexes = ['slug']
267 268
    _xml_tagname = 'datasources'
268 269

  
269 270
    name = None
wcs/templates/wcs/backoffice/data-sources.html
4 4
<div id="appbar">
5 5
<h2>{% trans "Data Source" %} - {{ datasource.name }}</h2>
6 6
<span class="actions">
7
  <a href="export">{% trans "Export" %}</a>
7 8
  <a href="delete" rel="popup">{% trans "Delete" %}</a>
8 9
  <a href="edit">{% trans "Edit" %}</a>
9 10
</span>
10
-