Projet

Général

Profil

0001-admin-protect-datasources-in-use-from-deletion-or-sl.patch

Nicolas Roche, 18 octobre 2019 10:41

Télécharger (7,94 ko)

Voir les différences:

Subject: [PATCH] admin: protect datasources in use from deletion or slug
 change (#15163)

 tests/test_admin_pages.py | 56 +++++++++++++++++++++++++++++++++++++++
 tests/test_datasource.py  | 19 +++++++++++++
 wcs/admin/data_sources.py | 30 +++++++++++----------
 wcs/data_sources.py       | 10 +++++++
 4 files changed, 101 insertions(+), 14 deletions(-)
tests/test_admin_pages.py
4817 4817
    data_source.data_source = {'type': 'formula', 'value': '[]'}
4818 4818
    data_source.store()
4819 4819

  
4820
    FormDef.wipe()
4820 4821
    app = login(get_app(pub))
4821 4822
    resp = app.get('/backoffice/settings/data-sources/1/')
4822 4823

  
......
4872 4873
    resp = resp.follow()
4873 4874
    assert NamedDataSource.count() == 0
4874 4875

  
4876
def test_data_sources_in_use_delete(pub):
4877
    create_superuser(pub)
4878
    NamedDataSource.wipe()
4879
    category = NamedDataSource(name='foobar')
4880
    category.store()
4881

  
4882
    FormDef.wipe()
4883
    formdef = FormDef()
4884
    formdef.name = 'form title'
4885
    formdef.fields = [
4886
        fields.ItemField(id='0', label='string', type='item',
4887
                data_source={'type': 'foobar'}),
4888
    ]
4889
    formdef.store()
4890

  
4891
    app = login(get_app(pub))
4892
    resp = app.get('/backoffice/settings/data-sources/1/')
4893
    resp = resp.click(href='delete')
4894
    assert 'This datasource is still used, it cannot be deleted.' in resp.text
4895
    assert 'delete-button' not in resp.text
4896

  
4897
    formdef.fields = []
4898
    formdef.store()
4899
    resp = app.get('/backoffice/settings/data-sources/1/')
4900
    resp = resp.click(href='delete')
4901
    assert 'delete-button' in resp.text
4902

  
4875 4903
def test_data_sources_edit_slug(pub):
4876 4904
    create_superuser(pub)
4877 4905
    NamedDataSource.wipe()
......
4906 4934
    resp = resp.forms[0].submit('submit')
4907 4935
    assert resp.location == 'http://example.net/backoffice/settings/data-sources/1/'
4908 4936

  
4937
def test_data_sources_in_use_edit_slug(pub):
4938
    create_superuser(pub)
4939
    NamedDataSource.wipe()
4940
    data_source = NamedDataSource(name='foobar')
4941
    data_source.data_source = {'type': 'formula', 'value': '[]'}
4942
    data_source.store()
4943
    assert NamedDataSource.get(1).slug == 'foobar'
4944

  
4945
    FormDef.wipe()
4946
    formdef = FormDef()
4947
    formdef.name = 'form title'
4948
    formdef.fields = [
4949
        fields.ItemField(id='0', label='string', type='item',
4950
                data_source={'type': 'foobar'}),
4951
    ]
4952
    formdef.store()
4953

  
4954
    app = login(get_app(pub))
4955
    resp = app.get('/backoffice/settings/data-sources/1/')
4956
    resp = resp.click(href='edit')
4957
    assert 'form_slug' not in resp.text
4958

  
4959
    formdef.fields = []
4960
    formdef.store()
4961
    resp = app.get('/backoffice/settings/data-sources/1/')
4962
    resp = resp.click(href='edit')
4963
    assert 'form_slug' in resp.text
4964

  
4909 4965
def test_wscalls_new(pub):
4910 4966
    create_superuser(pub)
4911 4967
    NamedWsCall.wipe()
tests/test_datasource.py
477 477
        assert data_sources.get_structured_items({'type': 'foobar'}) == [
478 478
            {'id': '1', 'text': 'foo'}, {'id': '2', 'text': 'bar'}]
479 479
        assert urlopen.call_count == 3
480

  
481
def test_named_datasource_in_formdef():
482
    from wcs.formdef import FormDef
483
    datasource = NamedDataSource(name='foobar')
484
    datasource.data_source = {'type': 'json', 'value': 'http://whatever/'}
485
    datasource.store()
486
    assert datasource.slug == 'foobar'
487

  
488
    formdef = FormDef()
489
    assert not datasource.is_used_in_formdef(formdef)
490

  
491
    formdef.fields = [
492
        fields.ItemField(id='0', label='string', type='item',
493
        data_source={'type': 'foobar'}),
494
    ]
495
    assert datasource.is_used_in_formdef(formdef)
496

  
497
    datasource.slug = 'barfoo'
498
    assert not datasource.is_used_in_formdef(formdef)
wcs/admin/data_sources.py
25 25
from ..qommon.backoffice.menu import html_top
26 26
from wcs.data_sources import (NamedDataSource, DataSourceSelectionWidget,
27 27
        get_structured_items)
28
from wcs.formdef import FormDef
28
from wcs.formdef import FormDef, get_formdefs_of_all_kinds
29 29

  
30 30
class NamedDataSourceUI(object):
31 31
    def __init__(self, datasource):
......
33 33
        if self.datasource is None:
34 34
            self.datasource = NamedDataSource()
35 35

  
36
    def is_used(self):
37
        for formdef in get_formdefs_of_all_kinds():
38
            if self.datasource.is_used_in_formdef(formdef):
39
                return True
40
        return False
41

  
36 42
    def get_form(self):
37 43
        form = Form(enctype='multipart/form-data',
38 44
                advanced_label=_('Additional options'))
......
78 84
                    'data-dynamic-display-child-of': 'data_source$type',
79 85
                    'data-dynamic-display-value': 'json',
80 86
                })
81
        if self.datasource.slug:
87
        if self.datasource.slug and not self.is_used():
82 88
            form.add(StringWidget, 'slug',
83 89
                    value=self.datasource.slug,
84 90
                    title=_('Identifier'),
85
                    hint=_('Beware it is risky to change it'),
86 91
                    required=True, advanced=True,
87 92
                    )
88 93
        form.add_submit('submit', _('Submit'))
......
138 143
    def usage_in_formdefs(self):
139 144
        formdefs = []
140 145
        for formdef in FormDef.select(ignore_errors=True, ignore_migration=True, order_by='name'):
141
            for field in (formdef.fields or []):
142
                data_source = getattr(field, 'data_source', None)
143
                if not data_source:
144
                    continue
145
                if data_source.get('type') == self.datasource.slug:
146
                    formdefs.append(formdef)
147
                    break
148
            else:
149
                continue
146
            if self.datasource.is_used_in_formdef(formdef):
147
                formdefs.append(formdef)
150 148
        return formdefs
151 149

  
152 150
    def preview_block(self):
......
202 200

  
203 201
    def delete(self):
204 202
        form = Form(enctype='multipart/form-data')
205
        form.widgets.append(HtmlWidget('<p>%s</p>' % _(
203
        if not self.datasource_ui.is_used():
204
            form.widgets.append(HtmlWidget('<p>%s</p>' % _(
206 205
                        'You are about to irrevocably delete this data source.')))
207
        form.add_submit('delete', _('Submit'))
206
            form.add_submit('delete', _('Submit'))
207
        else:
208
            form.widgets.append(HtmlWidget('<p>%s</p>' % _(
209
                        'This datasource is still used, it cannot be deleted.')))
208 210
        form.add_submit('cancel', _('Cancel'))
209 211
        if form.get_widget('cancel').parse():
210 212
            return redirect('..')
wcs/data_sources.py
419 419
    def humanized_cache_duration(self):
420 420
        return seconds2humanduration(int(self.cache_duration))
421 421

  
422
    def is_used_in_formdef(self, formdef):
423
        from .fields import WidgetField
424
        for field in formdef.fields or []:
425
            data_source = getattr(field, 'data_source', None)
426
            if not data_source:
427
                continue
428
            if data_source.get('type') == self.slug:
429
                return True
430
        return False
431

  
422 432

  
423 433
class DataSourcesSubstitutionProxy(object):
424 434
    def __getattr__(self, attr):
425
-