general: expose custom views as data sources (#44155)
This commit is contained in:
parent
4f8903b6e9
commit
3f60f3c8e1
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue