general: expose custom views as data sources (#44155)

This commit is contained in:
Frédéric Péters 2020-08-24 14:27:35 +02:00
parent 4f8903b6e9
commit 3f60f3c8e1
6 changed files with 187 additions and 21 deletions

View File

@ -2012,3 +2012,39 @@ def test_block_use_in_formdef(pub, blocks_feature):
assert 'a block field' in resp.text
resp = resp.click('Edit', href='1/')
assert resp.form['max_items'].value == '1'
def test_card_custom_view_data_source(pub, studio):
from test_backoffice_pages import create_environment, test_carddata_custom_view, create_superuser
create_environment(pub)
test_carddata_custom_view(pub, studio)
carddef = CardDef.select()[0]
carddef.digest_template = '{{ form_var_foo }}'
carddef.store()
create_superuser(pub)
app = login(get_app(pub))
resp = app.get('/backoffice/data/foo/user-card-view/')
resp.forms['listing-settings']['filter-1'].checked = True
resp = resp.forms['listing-settings'].submit()
resp.forms['listing-settings']['filter-1-value'] = 'FOO 0'
resp = resp.forms['listing-settings'].submit()
resp.forms['save-custom-view']['visibility'] = 'datasource'
resp = resp.forms['save-custom-view'].submit()
assert pub.custom_view_class.count() == 1
assert pub.custom_view_class.select()[0].visibility == 'datasource'
formdef = FormDef.get_by_urlname('form-title')
resp = app.get('/backoffice/forms/%s/fields/' % formdef.id)
resp.forms[0]['label'] = 'foobar'
resp.forms[0]['type'] = 'item'
resp = resp.forms[0].submit()
resp = resp.follow()
formdef = FormDef.get_by_urlname('form-title')
resp = resp.click(href='%s/' % formdef.fields[-1].id, index=0)
assert 'carddef:foo' in [x[0] for x in resp.form['data_source$type'].options]
assert 'carddef:foo:card-view' in [x[0] for x in resp.form['data_source$type'].options]
assert len(CardDef.get_data_source_items('carddef:foo')) == 50
assert len(CardDef.get_data_source_items('carddef:foo:card-view')) == 1

View File

@ -6501,6 +6501,7 @@ def test_studio_card_item_link(pub, studio):
card = carddef.data_class()()
card.data = {'1': 'plop'}
card.just_created()
card.store()
carddef2 = CardDef()
@ -7501,10 +7502,12 @@ def test_block_card_item_link(pub, studio, blocks_feature):
card = carddef.data_class()()
card.data = {'1': 'plop'}
card.just_created()
card.store()
card2 = carddef.data_class()()
card2.data = {'1': 'plop2'}
card2.just_created()
card2.store()
BlockDef.wipe()

View File

@ -5598,6 +5598,80 @@ def test_item_field_from_cards(pub):
assert formdef.data_class().select()[0].data['0_structured']['name'] == 'bar'
def test_item_field_from_custom_view_on_cards(pub):
Role.wipe()
pub.custom_view_class.wipe()
user = create_user(pub)
role = Role(name='xxx')
role.store()
user.roles = [role.id]
user.is_admin = True
user.store()
formdef = create_formdef()
formdef.data_class().wipe()
CardDef.wipe()
carddef = CardDef()
carddef.name = 'items'
carddef.digest_template = '{{form_var_attr}}'
carddef.workflow_roles = {'_editor': user.roles[0]}
carddef.fields = [
fields.ItemField(id='0', type='item', label='item', varname='item', items=['foo', 'bar', 'baz']),
fields.StringField(id='1', type='string', label='string', varname='attr'),
]
carddef.store()
carddef.data_class().wipe()
baz_ids = set()
for i, value in enumerate(['foo', 'bar', 'baz'] * 10):
carddata = carddef.data_class()()
carddata.data = {
'0': value,
'0_display': value,
'1': 'attr%s' % i,
}
carddata.just_created()
carddata.store()
if value == 'baz':
baz_ids.add(str(carddata.id))
# create custom view
app = login(get_app(pub), username='foo', password='foo')
resp = app.get('/backoffice/data/items/')
if pub.is_using_postgresql():
assert resp.text.count('<tr') == 21 # thead + 20 items (max per page)
else:
assert resp.text.count('<tr') == 31 # thead + all items
resp.forms['listing-settings']['filter-0'].checked = True
resp = resp.forms['listing-settings'].submit()
resp.forms['listing-settings']['filter-0-value'] = 'baz'
resp = resp.forms['listing-settings'].submit()
assert resp.text.count('<tr') == 11 # thead + 10 items
resp.forms['save-custom-view']['title'] = 'as data source'
resp.forms['save-custom-view']['visibility'] = 'datasource'
resp = resp.forms['save-custom-view'].submit()
custom_view = pub.custom_view_class.select()[0]
# use custom view as source
ds = {'type': 'carddef:%s:%s' % (carddef.url_name, custom_view.slug)}
formdef.fields = [fields.ItemField(id='0', label='string', type='item',
data_source=ds, display_disabled_items=True)]
formdef.store()
resp = get_app(pub).get('/test/')
assert len(resp.form['f0'].options) == 10
assert set([x[0] for x in resp.form['f0'].options]) == baz_ids
resp = resp.form.submit('submit') # -> validation page
resp = resp.form.submit('submit') # -> submit
assert formdef.data_class().select()[0].data['0'] in baz_ids
assert formdef.data_class().select()[0].data['0_structured']['item'] == 'baz'
def test_item_field_with_disabled_items(http_requests, pub):
user = create_user(pub)
formdef = create_formdef()

View File

@ -1032,18 +1032,20 @@ class FormPage(Directory):
formdef_class = FormDef
search_label = N_('Search in form content')
def __init__(self, component=None, formdef=None, view=None):
def __init__(self, component=None, formdef=None, view=None, update_breadcrumbs=True):
self.view_type = None
if component:
try:
self.formdef = self.formdef_class.get_by_urlname(component)
except KeyError:
raise errors.TraversalError()
get_response().breadcrumb.append((component + '/', self.formdef.name))
if update_breadcrumbs:
get_response().breadcrumb.append((component + '/', self.formdef.name))
else:
self.formdef = formdef
self.view = view
get_response().breadcrumb.append((view.get_url_slug() + '/', view.title))
if update_breadcrumbs:
get_response().breadcrumb.append((view.get_url_slug() + '/', view.title))
def check_access(self, api_name=None):
session = get_session()
@ -1094,9 +1096,12 @@ class FormPage(Directory):
if self.view:
active = bool(self.view.get_url_slug() == view.get_url_slug())
r += htmltext('<li class="active">' if active else '<li>')
r += htmltext('<a href="../%s/%s">%s</a></li>') % (view.get_url_slug(), view_type, view.title)
r += htmltext('<a href="../%s/%s">%s</a>') % (view.get_url_slug(), view_type, view.title)
else:
r += htmltext('<li><a href="%s/%s">%s</a></li>') % (view.get_url_slug(), view_type, view.title)
r += htmltext('<li><a href="%s/%s">%s</a>') % (view.get_url_slug(), view_type, view.title)
if view.visibility == 'datasource':
r += htmltext(' <span class="as-data-source">(%s)</span>') % _('for data sources')
r += htmltext('</li>')
r += htmltext('</ul>')
return r.getvalue()
@ -1466,12 +1471,17 @@ class FormPage(Directory):
value=self.view.title if self.view else None)
if get_publisher().get_backoffice_root().is_accessible(self.admin_permission):
# admins can create views accessible to everyone
options = [
('owner', _('to me only'), 'owner'),
('any', _('to any users'), 'any'),
]
if isinstance(self.formdef, CardDef) and self.formdef.digest_template:
options.append(('datasource', _('as data source'), 'datasource'))
form.add(RadiobuttonsWidget, 'visibility', title=_('Visibility'),
value=self.view.visibility if self.view else 'owner',
options=[
('owner', _('to me only'), 'owner'),
('any', _('to any users'), 'any')
])
options=options)
if self.view and (self.view.user_id == get_request().user.id or
get_publisher().get_backoffice_root().is_accessible(self.admin_permission)):
form.add(CheckboxWidget, 'update', title=_('Update existing view settings'), value=True)
@ -1622,6 +1632,10 @@ class FormPage(Directory):
return 'all'
def get_criterias_from_query(self):
query_overrides = get_request().form
return self.get_view_criterias(query_overrides, request=get_request())
def get_view_criterias(self, query_overrides=None, request=None):
fake_fields = [
FakeField('start', 'period-date', _('Start')),
FakeField('end', 'period-date', _('End')),
@ -1634,8 +1648,13 @@ class FormPage(Directory):
filters_dict = {}
if self.view:
filters_dict.update(self.view.get_filters_dict())
filters_dict.update(get_request().form)
filters_dict.update(self.view.get_filters_dict() or {})
filters_dict.update(query_overrides or {})
if request and request.form:
request_form = request.form
else:
request_form = {}
for filter_field in fake_fields + list(self.get_formdef_fields()):
if filter_field.type not in self.get_filterable_field_types():
@ -1654,26 +1673,26 @@ class FormPage(Directory):
name_id = filters_dict.get('filter-user-uuid')
if name_id:
nameid_users = get_publisher().user_class.get_users_with_name_identifier(name_id)
get_request().form['filter-user'] = filters_dict['filter-user'] = 'on'
request_form['filter-user'] = filters_dict['filter-user'] = 'on'
if nameid_users:
filters_dict['filter-user-value'] = str(nameid_users[0].id)
get_request().form['filter-user-value'] = filters_dict['filter-user-value']
request_form['filter-user-value'] = filters_dict['filter-user-value']
else:
filters_dict['filter-user-value'] = '-1'
get_request().form['filter-user-value'] = '-1'
request_form['filter-user-value'] = '-1'
if filter_field.type == 'submission-agent-id':
# convert uuid based filter into local id filter
name_id = filters_dict.get('filter-submission-agent-uuid')
if name_id:
nameid_users = get_publisher().user_class.get_users_with_name_identifier(name_id)
get_request().form['filter-submission-agent'] = filters_dict['filter-submission-agent'] = 'on'
request_form['filter-submission-agent'] = filters_dict['filter-submission-agent'] = 'on'
if nameid_users:
filters_dict['filter-submission-agent-value'] = str(nameid_users[0].id)
get_request().form['filter-submission-agent-value'] = filters_dict['filter-submission-agent-value']
request_form['filter-submission-agent-value'] = filters_dict['filter-submission-agent-value']
else:
filters_dict['filter-submission-agent-value'] = '-1'
get_request().form['filter-submission-agent-value'] = '-1'
request_form['filter-submission-agent-value'] = '-1'
if filters_dict.get('filter-%s' % filter_field.id):
# if there's a filter-%(id)s, it is used to enable the actual

View File

@ -19,6 +19,7 @@ import types
from quixote import get_publisher
from .qommon import _, N_, misc
from .qommon.storage import Equal, NotEqual
from wcs.carddata import CardData
from wcs.formdef import FormDef
@ -137,21 +138,50 @@ class CardDef(FormDef):
@classmethod
def get_as_data_source_options(cls):
carddefs = {}
for carddef in cls.select(lightweight=True, ignore_errors=True, order_by='name'):
if not carddef.digest_template:
continue
data_source_id = 'carddef:%s' % carddef.url_name
carddefs[carddef.id] = {'url_name': carddef.url_name, 'name': carddef.name}
yield (data_source_id, carddef.name, data_source_id)
clauses = [Equal('formdef_type', 'carddef'), Equal('visibility', 'datasource')]
for custom_view in get_publisher().custom_view_class.select(clauses):
carddef = carddefs.get(custom_view.formdef_id)
if not carddef:
continue
data_source_id = 'carddef:%s:%s' % (carddef['url_name'], custom_view.slug)
yield (data_source_id, '%s - %s' % (carddef['name'], custom_view.title), data_source_id)
@classmethod
def get_data_source_items(cls, data_source_id):
assert data_source_id.startswith('carddef:')
parts = data_source_id.split(':')
try:
carddef = cls.get_by_urlname(data_source_id[8:])
carddef = cls.get_by_urlname(parts[1])
except KeyError:
return []
criterias = [NotEqual('status', 'draft')]
order_by = None
if len(parts) > 2:
lookup_criterias = [
Equal('formdef_type', 'carddef'),
Equal('visibility', 'datasource'),
Equal('slug', parts[2]),
]
try:
custom_view = get_publisher().custom_view_class.select(lookup_criterias)[0]
except IndexError:
return []
from wcs.backoffice.management import FormPage
form_page = FormPage(formdef=carddef, view=custom_view, update_breadcrumbs=False)
criterias.extend(form_page.get_view_criterias())
order_by = custom_view.order_by
items = [x.get_data_source_structured_item()
for x in carddef.data_class().select()
if not x.is_draft()]
items.sort(key=lambda x: misc.simplify(x['text']))
for x in carddef.data_class().select(
clause=criterias,
order_by=order_by)]
if order_by is None:
items.sort(key=lambda x: misc.simplify(x['text']))
return items

View File

@ -1945,3 +1945,7 @@ ul.snapshots-list .collapsed {
ul.snapshots-list .new-day, ul.snapshots-list .has-label {
display: block;
}
#sidebar-custom-views .as-data-source {
font-weight: normal;
}