Projet

Général

Profil

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

Nicolas Roche, 13 octobre 2019 14:33

Télécharger (8,53 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  | 27 +++++++++++++++++++
 wcs/admin/data_sources.py | 30 +++++++++++----------
 wcs/data_sources.py       | 10 +++++++
 4 files changed, 109 insertions(+), 14 deletions(-)
tests/test_admin_pages.py
4800 4800
    data_source.data_source = {'type': 'formula', 'value': '[]'}
4801 4801
    data_source.store()
4802 4802

  
4803
    FormDef.wipe()
4803 4804
    app = login(get_app(pub))
4804 4805
    resp = app.get('/backoffice/settings/data-sources/1/')
4805 4806

  
......
4855 4856
    resp = resp.follow()
4856 4857
    assert NamedDataSource.count() == 0
4857 4858

  
4859
def test_data_sources_in_use_delete(pub):
4860
    create_superuser(pub)
4861
    NamedDataSource.wipe()
4862
    category = NamedDataSource(name='foobar')
4863
    category.store()
4864

  
4865
    FormDef.wipe()
4866
    formdef = FormDef()
4867
    formdef.name = 'form title'
4868
    formdef.fields = [
4869
        fields.ItemField(id='0', label='string', type='item',
4870
                data_source={'type': 'foobar'}),
4871
    ]
4872
    formdef.store()
4873

  
4874
    app = login(get_app(pub))
4875
    resp = app.get('/backoffice/settings/data-sources/1/')
4876
    resp = resp.click(href='delete')
4877
    assert 'This datasource is still used in some formularies.' in resp.text
4878
    assert 'delete-button' not in resp.text
4879

  
4880
    formdef.fields = []
4881
    formdef.store()
4882
    resp = app.get('/backoffice/settings/data-sources/1/')
4883
    resp = resp.click(href='delete')
4884
    assert 'delete-button' in resp.text
4885

  
4858 4886
def test_data_sources_edit_slug(pub):
4859 4887
    create_superuser(pub)
4860 4888
    NamedDataSource.wipe()
......
4889 4917
    resp = resp.forms[0].submit('submit')
4890 4918
    assert resp.location == 'http://example.net/backoffice/settings/data-sources/1/'
4891 4919

  
4920
def test_data_sources_in_use_edit_slug(pub):
4921
    create_superuser(pub)
4922
    NamedDataSource.wipe()
4923
    data_source = NamedDataSource(name='foobar')
4924
    data_source.data_source = {'type': 'formula', 'value': '[]'}
4925
    data_source.store()
4926
    assert NamedDataSource.get(1).slug == 'foobar'
4927

  
4928
    FormDef.wipe()
4929
    formdef = FormDef()
4930
    formdef.name = 'form title'
4931
    formdef.fields = [
4932
        fields.ItemField(id='0', label='string', type='item',
4933
                data_source={'type': 'foobar'}),
4934
    ]
4935
    formdef.store()
4936

  
4937
    app = login(get_app(pub))
4938
    resp = app.get('/backoffice/settings/data-sources/1/')
4939
    resp = resp.click(href='edit')
4940
    assert 'form_slug' not in resp.text
4941

  
4942
    formdef.fields = []
4943
    formdef.store()
4944
    resp = app.get('/backoffice/settings/data-sources/1/')
4945
    resp = resp.click(href='edit')
4946
    assert 'form_slug' in resp.text
4947

  
4892 4948
def test_wscalls_new(pub):
4893 4949
    create_superuser(pub)
4894 4950
    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
    from wcs.carddef import CardDef
484
    from wcs.wf.form import WorkflowFormFieldsFormDef
485
    from wcs.workflows import (Workflow, WorkflowBackofficeFieldsFormDef,
486
                               WorkflowVariablesFieldsFormDef)
487
    class NotWidget(object):
488
        data_source={'type': 'foobar'}
489

  
490
    datasource = NamedDataSource(name='foobar')
491
    datasource.data_source = {'type': 'json', 'value': 'http://whatever/'}
492
    datasource.store()
493

  
494
    wf = Workflow('foo')
495
    for formdef in [FormDef(), CardDef(),
496
                    WorkflowFormFieldsFormDef(item=None),
497
                    WorkflowVariablesFieldsFormDef(wf),
498
                    WorkflowVariablesFieldsFormDef(wf)]:
499
        assert not datasource.is_used_in_formdef(formdef)
500
        formdef.fields = [
501
            fields.ItemField(id='0', label='string', type='item',
502
                data_source={'type': 'foobar'}),
503
        ]
504
        assert datasource.is_used_in_formdef(formdef)
505
        formdef.fields = [NotWidget()]
506
        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(ignore_errors=True, ignore_migration=True):
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'))
......
135 140
    def usage_in_formdefs(self):
136 141
        formdefs = []
137 142
        for formdef in FormDef.select(ignore_errors=True, ignore_migration=True, order_by='name'):
138
            for field in (formdef.fields or []):
139
                data_source = getattr(field, 'data_source', None)
140
                if not data_source:
141
                    continue
142
                if data_source.get('type') == self.datasource.slug:
143
                    formdefs.append(formdef)
144
                    break
145
            else:
146
                continue
143
            if self.datasource.is_used_in_formdef(formdef):
144
                formdefs.append(formdef)
147 145
        return formdefs
148 146

  
149 147
    def preview_block(self):
......
199 197

  
200 198
    def delete(self):
201 199
        form = Form(enctype='multipart/form-data')
202
        form.widgets.append(HtmlWidget('<p>%s</p>' % _(
200
        if not self.datasource_ui.is_used():
201
            form.widgets.append(HtmlWidget('<p>%s</p>' % _(
203 202
                        'You are about to irrevocably delete this data source.')))
204
        form.add_submit('delete', _('Submit'))
203
            form.add_submit('delete', _('Submit'))
204
        else:
205
            form.widgets.append(HtmlWidget('<p>%s</p>' % _(
206
                        'This datasource is still used in some formularies.')))
205 207
        form.add_submit('cancel', _('Cancel'))
206 208
        if form.get_widget('cancel').parse():
207 209
            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 (f for f in formdef.fields or [] if isinstance(f, WidgetField)):
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
-