cards: add shared and datasource custom views to carddef export (#42571)
This commit is contained in:
parent
24ef0f870c
commit
f5fe99d347
|
@ -77,13 +77,86 @@ def test_xml_export_import(pub):
|
|||
data_source={'type': 'carddef:foo'})
|
||||
]
|
||||
carddef.store()
|
||||
|
||||
# define also custom views
|
||||
pub.custom_view_class.wipe()
|
||||
|
||||
custom_view = pub.custom_view_class()
|
||||
custom_view.title = 'datasource card view'
|
||||
custom_view.formdef = carddef
|
||||
custom_view.columns = {'list': [{'id': 'id'}, {'id': 'time'}, {'id': 'status'}, {'id': '1'}, {'id': '2'}]}
|
||||
custom_view.filters = {'filter': 'recorded', 'filter-1': 'on', 'filter-status': 'on', 'filter-1-value': 'a'}
|
||||
custom_view.visibility = 'datasource'
|
||||
custom_view.order_by = '-receipt_time'
|
||||
custom_view.store()
|
||||
|
||||
custom_view = pub.custom_view_class()
|
||||
custom_view.title = 'shared card view'
|
||||
custom_view.formdef = carddef
|
||||
custom_view.columns = {'list': [{'id': 'id'}, {'id': 'time'}, {'id': 'status'}]}
|
||||
custom_view.filters = {'filter': 'done', 'filter-1': 'on', 'filter-status': 'on', 'filter-1-value': 'b'}
|
||||
custom_view.visibility = 'any'
|
||||
custom_view.order_by = 'receipt_time'
|
||||
custom_view.store()
|
||||
|
||||
custom_view = pub.custom_view_class()
|
||||
custom_view.title = 'private card view'
|
||||
custom_view.formdef = carddef
|
||||
custom_view.columns = {'list': [{'id': 'id'}]}
|
||||
custom_view.filters = {}
|
||||
custom_view.visibility = 'owner'
|
||||
custom_view.usier_id = 42
|
||||
custom_view.order_by = 'id'
|
||||
custom_view.store()
|
||||
|
||||
carddef_xml = carddef.export_to_xml()
|
||||
assert carddef_xml.tag == 'carddef'
|
||||
carddef.data_class().wipe()
|
||||
pub.custom_view_class.wipe()
|
||||
|
||||
carddef2 = CardDef.import_from_xml(BytesIO(ET.tostring(carddef_xml)))
|
||||
assert carddef2.name == 'foo'
|
||||
assert carddef2.fields[1].data_source == {'type': 'carddef:foo'}
|
||||
assert carddef2._custom_views
|
||||
|
||||
custom_views = sorted(carddef2._custom_views, key=lambda a: a.visibility)
|
||||
assert len(custom_views) == 2
|
||||
assert custom_views[0].title == 'shared card view'
|
||||
assert custom_views[0].slug == 'shared-card-view'
|
||||
assert custom_views[0].columns == {'list': [{'id': 'id'}, {'id': 'time'}, {'id': 'status'}]}
|
||||
assert custom_views[0].filters == {'filter': 'done', 'filter-1': 'on', 'filter-status': 'on', 'filter-1-value': 'b'}
|
||||
assert custom_views[0].visibility == 'any'
|
||||
assert custom_views[0].order_by == 'receipt_time'
|
||||
assert custom_views[0].formdef_id is None
|
||||
assert custom_views[0].formdef_type is None
|
||||
assert custom_views[1].title == 'datasource card view'
|
||||
assert custom_views[1].slug == 'datasource-card-view'
|
||||
assert custom_views[1].columns == {'list': [{'id': 'id'}, {'id': 'time'}, {'id': 'status'}, {'id': '1'}, {'id': '2'}]}
|
||||
assert custom_views[1].filters == {'filter': 'recorded', 'filter-1': 'on', 'filter-status': 'on', 'filter-1-value': 'a'}
|
||||
assert custom_views[1].visibility == 'datasource'
|
||||
assert custom_views[1].order_by == '-receipt_time'
|
||||
assert custom_views[1].formdef_id is None
|
||||
assert custom_views[1].formdef_type is None
|
||||
|
||||
carddef2.store()
|
||||
custom_views = sorted(pub.custom_view_class.select(), key=lambda a: a.visibility)
|
||||
assert len(custom_views) == 2
|
||||
assert custom_views[0].title == 'shared card view'
|
||||
assert custom_views[0].slug == 'shared-card-view'
|
||||
assert custom_views[0].columns == {'list': [{'id': 'id'}, {'id': 'time'}, {'id': 'status'}]}
|
||||
assert custom_views[0].filters == {'filter': 'done', 'filter-1': 'on', 'filter-status': 'on', 'filter-1-value': 'b'}
|
||||
assert custom_views[0].visibility == 'any'
|
||||
assert custom_views[0].order_by == 'receipt_time'
|
||||
assert custom_views[0].formdef_id == carddef2.id
|
||||
assert custom_views[0].formdef_type == 'carddef'
|
||||
assert custom_views[1].title == 'datasource card view'
|
||||
assert custom_views[1].slug == 'datasource-card-view'
|
||||
assert custom_views[1].columns == {'list': [{'id': 'id'}, {'id': 'time'}, {'id': 'status'}, {'id': '1'}, {'id': '2'}]}
|
||||
assert custom_views[1].filters == {'filter': 'recorded', 'filter-1': 'on', 'filter-status': 'on', 'filter-1-value': 'a'}
|
||||
assert custom_views[1].visibility == 'datasource'
|
||||
assert custom_views[1].order_by == '-receipt_time'
|
||||
assert custom_views[1].formdef_id == carddef2.id
|
||||
assert custom_views[1].formdef_type == 'carddef'
|
||||
|
||||
|
||||
def test_template_access(pub):
|
||||
|
|
|
@ -697,3 +697,67 @@ def test_field_prefill():
|
|||
f2 = FormDef.import_from_xml_tree(formdef_xml)
|
||||
assert len(f2.fields) == len(formdef.fields)
|
||||
assert f2.fields[0].prefill == {'type': 'string', 'value': None}
|
||||
|
||||
|
||||
def test_custom_views():
|
||||
formdef = FormDef()
|
||||
formdef.name = 'foo'
|
||||
formdef.fields = [
|
||||
fields.StringField(id='1', label='Foo', type='string', varname='foo'),
|
||||
fields.StringField(id='2', label='Bar', type='string', varname='bar'),
|
||||
]
|
||||
formdef.store()
|
||||
|
||||
# define also custom views
|
||||
pub.custom_view_class.wipe()
|
||||
|
||||
custom_view = pub.custom_view_class()
|
||||
custom_view.title = 'shared form view'
|
||||
custom_view.formdef = formdef
|
||||
custom_view.columns = {'list': [{'id': 'id'}, {'id': 'time'}, {'id': 'status'}]}
|
||||
custom_view.filters = {'filter': 'done', 'filter-1': 'on', 'filter-status': 'on', 'filter-1-value': 'b'}
|
||||
custom_view.visibility = 'any'
|
||||
custom_view.order_by = 'receipt_time'
|
||||
custom_view.store()
|
||||
|
||||
custom_view = pub.custom_view_class()
|
||||
custom_view.title = 'private form view'
|
||||
custom_view.formdef = formdef
|
||||
custom_view.columns = {'list': [{'id': 'id'}]}
|
||||
custom_view.filters = {}
|
||||
custom_view.visibility = 'owner'
|
||||
custom_view.usier_id = 42
|
||||
custom_view.order_by = 'id'
|
||||
custom_view.store()
|
||||
|
||||
formdef_xml = formdef.export_to_xml()
|
||||
assert formdef_xml.tag == 'formdef'
|
||||
formdef.data_class().wipe()
|
||||
pub.custom_view_class.wipe()
|
||||
|
||||
formdef2 = FormDef.import_from_xml(BytesIO(ET.tostring(formdef_xml)))
|
||||
assert formdef2.name == 'foo'
|
||||
assert formdef2._custom_views
|
||||
|
||||
custom_views = formdef2._custom_views
|
||||
assert len(custom_views) == 1
|
||||
assert custom_views[0].title == 'shared form view'
|
||||
assert custom_views[0].slug == 'shared-form-view'
|
||||
assert custom_views[0].columns == {'list': [{'id': 'id'}, {'id': 'time'}, {'id': 'status'}]}
|
||||
assert custom_views[0].filters == {'filter': 'done', 'filter-1': 'on', 'filter-status': 'on', 'filter-1-value': 'b'}
|
||||
assert custom_views[0].visibility == 'any'
|
||||
assert custom_views[0].order_by == 'receipt_time'
|
||||
assert custom_views[0].formdef_id is None
|
||||
assert custom_views[0].formdef_type is None
|
||||
|
||||
formdef2.store()
|
||||
custom_views = pub.custom_view_class.select()
|
||||
assert len(custom_views) == 1
|
||||
assert custom_views[0].title == 'shared form view'
|
||||
assert custom_views[0].slug == 'shared-form-view'
|
||||
assert custom_views[0].columns == {'list': [{'id': 'id'}, {'id': 'time'}, {'id': 'status'}]}
|
||||
assert custom_views[0].filters == {'filter': 'done', 'filter-1': 'on', 'filter-status': 'on', 'filter-1-value': 'b'}
|
||||
assert custom_views[0].visibility == 'any'
|
||||
assert custom_views[0].order_by == 'receipt_time'
|
||||
assert custom_views[0].formdef_id == formdef2.id
|
||||
assert custom_views[0].formdef_type == 'formdef'
|
||||
|
|
|
@ -251,6 +251,22 @@ def test_card_snapshot_browse(pub):
|
|||
carddef.fields = []
|
||||
carddef.store()
|
||||
|
||||
pub.custom_view_class.wipe()
|
||||
custom_view = pub.custom_view_class()
|
||||
custom_view.title = 'shared form view'
|
||||
custom_view.formdef = carddef
|
||||
custom_view.columns = {'list': [{'id': 'id'}]}
|
||||
custom_view.filters = {}
|
||||
custom_view.visibility = 'any'
|
||||
custom_view.store()
|
||||
|
||||
# new version has custom views
|
||||
carddef.name = 'test 1'
|
||||
carddef.store()
|
||||
|
||||
# delete custom views
|
||||
pub.custom_view_class.wipe()
|
||||
|
||||
app = login(get_app(pub))
|
||||
|
||||
resp = app.get('/backoffice/cards/%s/history/' % carddef.id)
|
||||
|
@ -259,6 +275,7 @@ def test_card_snapshot_browse(pub):
|
|||
assert 'This card model is readonly' in resp
|
||||
resp = resp.click('Geolocation')
|
||||
assert [x[0].name for x in resp.form.fields.values() if x[0].tag == 'button'] == ['cancel']
|
||||
assert pub.custom_view_class.count() == 0 # custom views are not restore on preview
|
||||
|
||||
|
||||
def test_datasource_snapshot_browse(pub):
|
||||
|
@ -286,13 +303,31 @@ def test_form_snapshot_browse(pub, formdef_with_history):
|
|||
create_role()
|
||||
app = login(get_app(pub))
|
||||
|
||||
pub.custom_view_class.wipe()
|
||||
custom_view = pub.custom_view_class()
|
||||
custom_view.title = 'shared form view'
|
||||
custom_view.formdef = formdef_with_history
|
||||
custom_view.columns = {'list': [{'id': 'id'}]}
|
||||
custom_view.filters = {}
|
||||
custom_view.visibility = 'any'
|
||||
custom_view.store()
|
||||
|
||||
# version 5 has custom views
|
||||
formdef_with_history.name = 'testform 5'
|
||||
formdef_with_history.description = 'this is a description (5)'
|
||||
formdef_with_history.store()
|
||||
|
||||
# delete custom views
|
||||
pub.custom_view_class.wipe()
|
||||
|
||||
resp = app.get('/backoffice/forms/%s/history/' % formdef_with_history.id)
|
||||
snapshot = pub.snapshot_class.select_object_history(formdef_with_history)[2]
|
||||
snapshot = pub.snapshot_class.select_object_history(formdef_with_history)[0]
|
||||
resp = resp.click(href='%s/view/' % snapshot.id)
|
||||
assert 'This form is readonly' in resp
|
||||
resp = resp.click('Description')
|
||||
assert resp.form['description'].value == 'this is a description (2)'
|
||||
assert resp.form['description'].value == 'this is a description (5)'
|
||||
assert [x[0].name for x in resp.form.fields.values() if x[0].tag == 'button'] == ['cancel']
|
||||
assert pub.custom_view_class.count() == 0 # custom views are not restore on preview
|
||||
|
||||
|
||||
def test_workflow_snapshot_browse(pub):
|
||||
|
|
|
@ -14,13 +14,17 @@
|
|||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
from django.utils.six.moves.urllib import parse as urlparse
|
||||
from django.utils.encoding import force_text
|
||||
from quixote import get_publisher
|
||||
|
||||
from wcs.carddef import CardDef
|
||||
from wcs.formdef import FormDef
|
||||
from wcs.qommon.storage import StorableObject, Equal
|
||||
from wcs.qommon.misc import simplify
|
||||
from .qommon.misc import xml_node_text
|
||||
|
||||
|
||||
class CustomView(StorableObject):
|
||||
|
@ -37,6 +41,8 @@ class CustomView(StorableObject):
|
|||
filters = None
|
||||
order_by = None
|
||||
|
||||
xml_root_node = 'custom_view'
|
||||
|
||||
@property
|
||||
def user(self):
|
||||
return get_publisher().user_class.get(self.user_id)
|
||||
|
@ -62,7 +68,7 @@ class CustomView(StorableObject):
|
|||
return False
|
||||
if self.formdef_id != str(formdef.id):
|
||||
return False
|
||||
if self.visibility == 'owner' and self.user_id != str(user.id):
|
||||
if self.visibility == 'owner' and (user is None or self.user_id != str(user.id)):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
@ -135,3 +141,60 @@ class CustomView(StorableObject):
|
|||
|
||||
def get_default_filters(self):
|
||||
return [key[7:] for key in self.filters if key.startswith('filter-')]
|
||||
|
||||
def export_to_xml(self, charset=None):
|
||||
root = ET.Element(self.xml_root_node)
|
||||
fields = [
|
||||
'title',
|
||||
'slug',
|
||||
'visibility',
|
||||
'filters',
|
||||
'columns',
|
||||
'order_by',
|
||||
]
|
||||
for attribute in fields:
|
||||
if getattr(self, attribute, None) is not None:
|
||||
val = getattr(self, attribute)
|
||||
el = ET.SubElement(root, attribute)
|
||||
if attribute == 'columns':
|
||||
for field_dict in self.columns.get('list') or []:
|
||||
if not isinstance(field_dict, dict):
|
||||
continue
|
||||
for k, v in sorted(field_dict.items()):
|
||||
text_value = force_text(v, charset, errors='replace')
|
||||
ET.SubElement(el, k).text = text_value
|
||||
elif type(val) is dict:
|
||||
for k, v in sorted(val.items()):
|
||||
text_value = force_text(v, charset, errors='replace')
|
||||
ET.SubElement(el, k).text = text_value
|
||||
elif isinstance(val, str):
|
||||
el.text = force_text(val, charset, errors='replace')
|
||||
else:
|
||||
el.text = str(val)
|
||||
return root
|
||||
|
||||
def init_with_xml(self, elem, charset):
|
||||
fields = [
|
||||
'title',
|
||||
'slug',
|
||||
'visibility',
|
||||
'filters',
|
||||
'columns',
|
||||
'order_by',
|
||||
]
|
||||
for attribute in fields:
|
||||
el = elem.find(attribute)
|
||||
if el is None:
|
||||
continue
|
||||
if attribute == 'filters':
|
||||
v = {}
|
||||
for e in el:
|
||||
v[e.tag] = xml_node_text(e)
|
||||
setattr(self, attribute, v)
|
||||
elif attribute == 'columns':
|
||||
v = []
|
||||
for e in el:
|
||||
v.append({e.tag: xml_node_text(e)})
|
||||
setattr(self, attribute, {'list': v})
|
||||
else:
|
||||
setattr(self, attribute, xml_node_text(el))
|
||||
|
|
|
@ -389,8 +389,14 @@ class FormDef(StorableObject):
|
|||
from . import sql
|
||||
sql.do_formdef_tables(self, rebuild_views=True,
|
||||
rebuild_global_views=True)
|
||||
self.store_related_custom_views()
|
||||
return t
|
||||
|
||||
def store_related_custom_views(self):
|
||||
for view in getattr(self, '_custom_views', []):
|
||||
view.formdef = self
|
||||
view.store()
|
||||
|
||||
def get_all_fields(self):
|
||||
return (self.fields or []) + self.workflow.get_backoffice_fields()
|
||||
|
||||
|
@ -1000,6 +1006,11 @@ class FormDef(StorableObject):
|
|||
else:
|
||||
pass # TODO: extend support to other types
|
||||
|
||||
custom_views = ET.SubElement(root, 'custom_views')
|
||||
for view in get_publisher().custom_view_class.select():
|
||||
if view.match(user=None, formdef=self):
|
||||
custom_views.append(view.export_to_xml(charset=charset))
|
||||
|
||||
geolocations = ET.SubElement(root, 'geolocations')
|
||||
for geoloc_key, geoloc_label in (self.geolocations or {}).items():
|
||||
element = ET.SubElement(geolocations, 'geolocation')
|
||||
|
@ -1153,6 +1164,12 @@ class FormDef(StorableObject):
|
|||
option_value.set_content(base64.decodebytes(force_bytes(option.find('content').text)))
|
||||
formdef.workflow_options[option.attrib.get('varname')] = option_value
|
||||
|
||||
formdef._custom_views = []
|
||||
for view in tree.findall('custom_views/%s' % get_publisher().custom_view_class.xml_root_node):
|
||||
view_o = get_publisher().custom_view_class()
|
||||
view_o.init_with_xml(view, charset)
|
||||
formdef._custom_views.append(view_o)
|
||||
|
||||
if tree.find('last_modification') is not None:
|
||||
node = tree.find('last_modification')
|
||||
formdef.last_modification_time = time.strptime(node.text, '%Y-%m-%d %H:%M:%S')
|
||||
|
@ -1492,6 +1509,8 @@ class FormDef(StorableObject):
|
|||
if self.lightweight and 'fields' in odict:
|
||||
# will be stored independently
|
||||
del odict['fields']
|
||||
if '_custom_views' in odict:
|
||||
del odict['_custom_views']
|
||||
return odict
|
||||
|
||||
def __setstate__(self, dict):
|
||||
|
|
Loading…
Reference in New Issue