cards: add shared and datasource custom views to carddef export (#42571)

This commit is contained in:
Lauréline Guérin 2020-10-16 11:42:10 +02:00
parent 24ef0f870c
commit f5fe99d347
No known key found for this signature in database
GPG Key ID: 1FAB9B9B4F93D473
5 changed files with 257 additions and 3 deletions

View File

@ -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):

View File

@ -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'

View File

@ -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):

View File

@ -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))

View File

@ -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):