admin: protect datasources in use from deletion or slug change (#15163)
This commit is contained in:
parent
b876213f44
commit
215209fb98
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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('..')
|
||||
|
|
|
@ -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):
|
||||
|
|
Loading…
Reference in New Issue