admin: protect datasources in use from deletion or slug change (#15163)

This commit is contained in:
Nicolas Roche 2019-10-10 18:01:34 +02:00
parent b876213f44
commit 215209fb98
4 changed files with 101 additions and 14 deletions

View File

@ -4817,6 +4817,7 @@ def test_data_sources_edit(pub):
data_source.data_source = {'type': 'formula', 'value': '[]'}
data_source.store()
FormDef.wipe()
app = login(get_app(pub))
resp = app.get('/backoffice/settings/data-sources/1/')
@ -4872,6 +4873,33 @@ def test_data_sources_delete(pub):
resp = resp.follow()
assert NamedDataSource.count() == 0
def test_data_sources_in_use_delete(pub):
create_superuser(pub)
NamedDataSource.wipe()
category = NamedDataSource(name='foobar')
category.store()
FormDef.wipe()
formdef = FormDef()
formdef.name = 'form title'
formdef.fields = [
fields.ItemField(id='0', label='string', type='item',
data_source={'type': 'foobar'}),
]
formdef.store()
app = login(get_app(pub))
resp = app.get('/backoffice/settings/data-sources/1/')
resp = resp.click(href='delete')
assert 'This datasource is still used, it cannot be deleted.' in resp.text
assert 'delete-button' not in resp.text
formdef.fields = []
formdef.store()
resp = app.get('/backoffice/settings/data-sources/1/')
resp = resp.click(href='delete')
assert 'delete-button' in resp.text
def test_data_sources_edit_slug(pub):
create_superuser(pub)
NamedDataSource.wipe()
@ -4906,6 +4934,34 @@ def test_data_sources_edit_slug(pub):
resp = resp.forms[0].submit('submit')
assert resp.location == 'http://example.net/backoffice/settings/data-sources/1/'
def test_data_sources_in_use_edit_slug(pub):
create_superuser(pub)
NamedDataSource.wipe()
data_source = NamedDataSource(name='foobar')
data_source.data_source = {'type': 'formula', 'value': '[]'}
data_source.store()
assert NamedDataSource.get(1).slug == 'foobar'
FormDef.wipe()
formdef = FormDef()
formdef.name = 'form title'
formdef.fields = [
fields.ItemField(id='0', label='string', type='item',
data_source={'type': 'foobar'}),
]
formdef.store()
app = login(get_app(pub))
resp = app.get('/backoffice/settings/data-sources/1/')
resp = resp.click(href='edit')
assert 'form_slug' not in resp.text
formdef.fields = []
formdef.store()
resp = app.get('/backoffice/settings/data-sources/1/')
resp = resp.click(href='edit')
assert 'form_slug' in resp.text
def test_wscalls_new(pub):
create_superuser(pub)
NamedWsCall.wipe()

View File

@ -477,3 +477,22 @@ def test_named_datasource_json_cache(requests_pub):
assert data_sources.get_structured_items({'type': 'foobar'}) == [
{'id': '1', 'text': 'foo'}, {'id': '2', 'text': 'bar'}]
assert urlopen.call_count == 3
def test_named_datasource_in_formdef():
from wcs.formdef import FormDef
datasource = NamedDataSource(name='foobar')
datasource.data_source = {'type': 'json', 'value': 'http://whatever/'}
datasource.store()
assert datasource.slug == 'foobar'
formdef = FormDef()
assert not datasource.is_used_in_formdef(formdef)
formdef.fields = [
fields.ItemField(id='0', label='string', type='item',
data_source={'type': 'foobar'}),
]
assert datasource.is_used_in_formdef(formdef)
datasource.slug = 'barfoo'
assert not datasource.is_used_in_formdef(formdef)

View File

@ -25,7 +25,7 @@ from ..qommon.misc import json_response
from ..qommon.backoffice.menu import html_top
from wcs.data_sources import (NamedDataSource, DataSourceSelectionWidget,
get_structured_items)
from wcs.formdef import FormDef
from wcs.formdef import FormDef, get_formdefs_of_all_kinds
class NamedDataSourceUI(object):
def __init__(self, datasource):
@ -33,6 +33,12 @@ class NamedDataSourceUI(object):
if self.datasource is None:
self.datasource = NamedDataSource()
def is_used(self):
for formdef in get_formdefs_of_all_kinds():
if self.datasource.is_used_in_formdef(formdef):
return True
return False
def get_form(self):
form = Form(enctype='multipart/form-data',
advanced_label=_('Additional options'))
@ -78,11 +84,10 @@ class NamedDataSourceUI(object):
'data-dynamic-display-child-of': 'data_source$type',
'data-dynamic-display-value': 'json',
})
if self.datasource.slug:
if self.datasource.slug and not self.is_used():
form.add(StringWidget, 'slug',
value=self.datasource.slug,
title=_('Identifier'),
hint=_('Beware it is risky to change it'),
required=True, advanced=True,
)
form.add_submit('submit', _('Submit'))
@ -138,15 +143,8 @@ class NamedDataSourcePage(Directory):
def usage_in_formdefs(self):
formdefs = []
for formdef in FormDef.select(ignore_errors=True, ignore_migration=True, order_by='name'):
for field in (formdef.fields or []):
data_source = getattr(field, 'data_source', None)
if not data_source:
continue
if data_source.get('type') == self.datasource.slug:
formdefs.append(formdef)
break
else:
continue
if self.datasource.is_used_in_formdef(formdef):
formdefs.append(formdef)
return formdefs
def preview_block(self):
@ -202,9 +200,13 @@ class NamedDataSourcePage(Directory):
def delete(self):
form = Form(enctype='multipart/form-data')
form.widgets.append(HtmlWidget('<p>%s</p>' % _(
if not self.datasource_ui.is_used():
form.widgets.append(HtmlWidget('<p>%s</p>' % _(
'You are about to irrevocably delete this data source.')))
form.add_submit('delete', _('Submit'))
form.add_submit('delete', _('Submit'))
else:
form.widgets.append(HtmlWidget('<p>%s</p>' % _(
'This datasource is still used, it cannot be deleted.')))
form.add_submit('cancel', _('Cancel'))
if form.get_widget('cancel').parse():
return redirect('..')

View File

@ -419,6 +419,16 @@ class NamedDataSource(XmlStorableObject):
def humanized_cache_duration(self):
return seconds2humanduration(int(self.cache_duration))
def is_used_in_formdef(self, formdef):
from .fields import WidgetField
for field in formdef.fields or []:
data_source = getattr(field, 'data_source', None)
if not data_source:
continue
if data_source.get('type') == self.slug:
return True
return False
class DataSourcesSubstitutionProxy(object):
def __getattr__(self, attr):