Projet

Général

Profil

0002-general-expose-custom-views-as-data-sources-44155.patch

Frédéric Péters, 25 août 2020 13:09

Télécharger (16,2 ko)

Voir les différences:

Subject: [PATCH 2/2] general: expose custom views as data sources (#44155)

 tests/test_admin_pages.py           | 36 ++++++++++++++
 tests/test_backoffice_pages.py      |  3 ++
 tests/test_form_pages.py            | 74 +++++++++++++++++++++++++++++
 wcs/backoffice/management.py        | 53 ++++++++++++++-------
 wcs/carddef.py                      | 36 ++++++++++++--
 wcs/qommon/static/css/dc2/admin.css |  4 ++
 6 files changed, 185 insertions(+), 21 deletions(-)
tests/test_admin_pages.py
3681 3681
    assert 'a block field' in resp.text
3682 3682
    resp = resp.click('Edit', href='1/')
3683 3683
    assert resp.form['max_items'].value == '1'
3684

  
3685

  
3686
def test_card_custom_view_data_source(pub, studio):
3687
    from test_backoffice_pages import create_environment, test_carddata_custom_view, create_superuser
3688
    create_environment(pub)
3689
    test_carddata_custom_view(pub, studio)
3690

  
3691
    carddef = CardDef.select()[0]
3692
    carddef.digest_template = '{{ form_var_foo }}'
3693
    carddef.store()
3694

  
3695
    create_superuser(pub)
3696
    app = login(get_app(pub))
3697
    resp = app.get('/backoffice/data/foo/user-card-view/')
3698
    resp.forms['listing-settings']['filter-1'].checked = True
3699
    resp = resp.forms['listing-settings'].submit()
3700
    resp.forms['listing-settings']['filter-1-value'] = 'FOO 0'
3701
    resp = resp.forms['listing-settings'].submit()
3702
    resp.forms['save-custom-view']['visibility'] = 'datasource'
3703
    resp = resp.forms['save-custom-view'].submit()
3704
    assert pub.custom_view_class.count() == 1
3705
    assert pub.custom_view_class.select()[0].visibility == 'datasource'
3706

  
3707
    formdef = FormDef.get_by_urlname('form-title')
3708
    resp = app.get('/backoffice/forms/%s/fields/' % formdef.id)
3709

  
3710
    resp.forms[0]['label'] = 'foobar'
3711
    resp.forms[0]['type'] = 'item'
3712
    resp = resp.forms[0].submit()
3713
    resp = resp.follow()
3714
    formdef = FormDef.get_by_urlname('form-title')
3715
    resp = resp.click(href='%s/' % formdef.fields[-1].id, index=0)
3716
    assert 'carddef:foo' in [x[0] for x in resp.form['data_source$type'].options]
3717
    assert 'carddef:foo:card-view' in [x[0] for x in resp.form['data_source$type'].options]
3718
    assert len(CardDef.get_data_source_items('carddef:foo')) == 50
3719
    assert len(CardDef.get_data_source_items('carddef:foo:card-view')) == 1
tests/test_backoffice_pages.py
6228 6228

  
6229 6229
    card = carddef.data_class()()
6230 6230
    card.data = {'1': 'plop'}
6231
    card.just_created()
6231 6232
    card.store()
6232 6233

  
6233 6234
    carddef2 = CardDef()
......
7228 7229

  
7229 7230
    card = carddef.data_class()()
7230 7231
    card.data = {'1': 'plop'}
7232
    card.just_created()
7231 7233
    card.store()
7232 7234

  
7233 7235
    card2 = carddef.data_class()()
7234 7236
    card2.data = {'1': 'plop2'}
7237
    card2.just_created()
7235 7238
    card2.store()
7236 7239

  
7237 7240
    BlockDef.wipe()
tests/test_form_pages.py
5598 5598
    assert formdef.data_class().select()[0].data['0_structured']['name'] == 'bar'
5599 5599

  
5600 5600

  
5601
def test_item_field_from_custom_view_on_cards(pub):
5602
    Role.wipe()
5603
    pub.custom_view_class.wipe()
5604

  
5605
    user = create_user(pub)
5606
    role = Role(name='xxx')
5607
    role.store()
5608
    user.roles = [role.id]
5609
    user.is_admin = True
5610
    user.store()
5611

  
5612
    formdef = create_formdef()
5613
    formdef.data_class().wipe()
5614

  
5615
    CardDef.wipe()
5616
    carddef = CardDef()
5617
    carddef.name = 'items'
5618
    carddef.digest_template = '{{form_var_attr}}'
5619
    carddef.workflow_roles = {'_editor': user.roles[0]}
5620
    carddef.fields = [
5621
        fields.ItemField(id='0', type='item', label='item', varname='item', items=['foo', 'bar', 'baz']),
5622
        fields.StringField(id='1', type='string', label='string', varname='attr'),
5623
    ]
5624
    carddef.store()
5625
    carddef.data_class().wipe()
5626
    baz_ids = set()
5627
    for i, value in enumerate(['foo', 'bar', 'baz'] * 10):
5628
        carddata = carddef.data_class()()
5629
        carddata.data = {
5630
            '0': value,
5631
            '0_display': value,
5632
            '1': 'attr%s' % i,
5633
        }
5634
        carddata.just_created()
5635
        carddata.store()
5636
        if value == 'baz':
5637
            baz_ids.add(str(carddata.id))
5638

  
5639
    # create custom view
5640
    app = login(get_app(pub), username='foo', password='foo')
5641

  
5642
    resp = app.get('/backoffice/data/items/')
5643
    if pub.is_using_postgresql():
5644
        assert resp.text.count('<tr') == 21  # thead + 20 items (max per page)
5645
    else:
5646
        assert resp.text.count('<tr') == 31  # thead + all items
5647
    resp.forms['listing-settings']['filter-0'].checked = True
5648
    resp = resp.forms['listing-settings'].submit()
5649

  
5650
    resp.forms['listing-settings']['filter-0-value'] = 'baz'
5651
    resp = resp.forms['listing-settings'].submit()
5652
    assert resp.text.count('<tr') == 11  # thead + 10 items
5653

  
5654
    resp.forms['save-custom-view']['title'] = 'as data source'
5655
    resp.forms['save-custom-view']['visibility'] = 'datasource'
5656
    resp = resp.forms['save-custom-view'].submit()
5657

  
5658
    custom_view = pub.custom_view_class.select()[0]
5659

  
5660
    # use custom view as source
5661
    ds = {'type': 'carddef:%s:%s' % (carddef.url_name, custom_view.slug)}
5662
    formdef.fields = [fields.ItemField(id='0', label='string', type='item',
5663
        data_source=ds, display_disabled_items=True)]
5664
    formdef.store()
5665

  
5666
    resp = get_app(pub).get('/test/')
5667
    assert len(resp.form['f0'].options) == 10
5668
    assert set([x[0] for x in resp.form['f0'].options]) == baz_ids
5669
    resp = resp.form.submit('submit')  # -> validation page
5670
    resp = resp.form.submit('submit')  # -> submit
5671
    assert formdef.data_class().select()[0].data['0'] in baz_ids
5672
    assert formdef.data_class().select()[0].data['0_structured']['item'] == 'baz'
5673

  
5674

  
5601 5675
def test_item_field_with_disabled_items(http_requests, pub):
5602 5676
    user = create_user(pub)
5603 5677
    formdef = create_formdef()
wcs/backoffice/management.py
1030 1030
    view = None
1031 1031
    admin_permission = 'forms'
1032 1032

  
1033
    def __init__(self, component=None, formdef=None, view=None):
1033
    def __init__(self, component=None, formdef=None, view=None, update_breadcrumbs=True):
1034 1034
        self.view_type = None
1035 1035
        if component:
1036 1036
            try:
1037 1037
                self.formdef = FormDef.get_by_urlname(component)
1038 1038
            except KeyError:
1039 1039
                raise errors.TraversalError()
1040
            get_response().breadcrumb.append((component + '/', self.formdef.name))
1040
            if update_breadcrumbs:
1041
                get_response().breadcrumb.append((component + '/', self.formdef.name))
1041 1042
        else:
1042 1043
            self.formdef = formdef
1043 1044
            self.view = view
1044
            get_response().breadcrumb.append((view.slug + '/', view.title))
1045
            if update_breadcrumbs:
1046
                get_response().breadcrumb.append((view.slug + '/', view.title))
1045 1047

  
1046 1048
    def check_access(self, api_name=None):
1047 1049
        session = get_session()
......
1092 1094
                if self.view:
1093 1095
                    active = bool(self.view.get_url_slug() == view.get_url_slug())
1094 1096
                    r += htmltext('<li class="active">' if active else '<li>')
1095
                    r += htmltext('<a href="../%s/%s">%s</a></li>') % (view.get_url_slug(), view_type, view.title)
1097
                    r += htmltext('<a href="../%s/%s">%s</a>') % (view.get_url_slug(), view_type, view.title)
1096 1098
                else:
1097
                    r += htmltext('<li><a href="%s/%s">%s</a></li>') % (view.get_url_slug(), view_type, view.title)
1099
                    r += htmltext('<li><a href="%s/%s">%s</a>') % (view.get_url_slug(), view_type, view.title)
1100
                if view.visibility == 'datasource':
1101
                    r += htmltext(' <span class="as-data-source">(%s)</span>') % _('for data sources')
1102
                r += htmltext('</li>')
1098 1103
            r += htmltext('</ul>')
1099 1104
        return r.getvalue()
1100 1105

  
......
1464 1469
                value=self.view.title if self.view else None)
1465 1470
        if get_publisher().get_backoffice_root().is_accessible(self.admin_permission):
1466 1471
            # admins can create views accessible to everyone
1472
            options = [
1473
                ('owner', _('to me only'), 'owner'),
1474
                ('any', _('to any users'), 'any'),
1475
            ]
1476

  
1477
            if isinstance(self.formdef, CardDef) and self.formdef.digest_template:
1478
                options.append(('datasource', _('as data source'), 'datasource'))
1479

  
1467 1480
            form.add(RadiobuttonsWidget, 'visibility', title=_('Visibility'),
1468 1481
                    value=self.view.visibility if self.view else 'owner',
1469
                    options=[
1470
                        ('owner', _('to me only'), 'owner'),
1471
                        ('any', _('to any users'), 'any')
1472
                    ])
1482
                    options=options)
1473 1483
        if self.view and (self.view.user_id == get_request().user.id or
1474 1484
                          get_publisher().get_backoffice_root().is_accessible(self.admin_permission)):
1475 1485
            form.add(CheckboxWidget, 'update', title=_('Update existing view settings'), value=True)
......
1611 1621
        return 'all'
1612 1622

  
1613 1623
    def get_criterias_from_query(self):
1624
        query_overrides = get_request().form
1625
        return self.get_view_criterias(query_overrides, request=get_request())
1626

  
1627
    def get_view_criterias(self, query_overrides=None, request=None):
1614 1628
        fake_fields = [
1615 1629
            FakeField('start', 'period-date', _('Start')),
1616 1630
            FakeField('end', 'period-date', _('End')),
......
1623 1637

  
1624 1638
        filters_dict = {}
1625 1639
        if self.view:
1626
            filters_dict.update(self.view.get_filters_dict())
1627
        filters_dict.update(get_request().form)
1640
            filters_dict.update(self.view.get_filters_dict() or {})
1641
        filters_dict.update(query_overrides or {})
1642

  
1643
        if request and request.form:
1644
            request_form = request.form
1645
        else:
1646
            request_form = {}
1628 1647

  
1629 1648
        for filter_field in fake_fields + list(self.get_formdef_fields()):
1630 1649
            if filter_field.type not in self.get_filterable_field_types():
......
1643 1662
                name_id = filters_dict.get('filter-user-uuid')
1644 1663
                if name_id:
1645 1664
                    nameid_users = get_publisher().user_class.get_users_with_name_identifier(name_id)
1646
                    get_request().form['filter-user'] = filters_dict['filter-user'] = 'on'
1665
                    request_form['filter-user'] = filters_dict['filter-user'] = 'on'
1647 1666
                    if nameid_users:
1648 1667
                        filters_dict['filter-user-value'] = str(nameid_users[0].id)
1649
                        get_request().form['filter-user-value'] = filters_dict['filter-user-value']
1668
                        request_form['filter-user-value'] = filters_dict['filter-user-value']
1650 1669
                    else:
1651 1670
                        filters_dict['filter-user-value'] = '-1'
1652
                        get_request().form['filter-user-value'] = '-1'
1671
                        request_form['filter-user-value'] = '-1'
1653 1672

  
1654 1673
            if filter_field.type == 'submission-agent-id':
1655 1674
                # convert uuid based filter into local id filter
1656 1675
                name_id = filters_dict.get('filter-submission-agent-uuid')
1657 1676
                if name_id:
1658 1677
                    nameid_users = get_publisher().user_class.get_users_with_name_identifier(name_id)
1659
                    get_request().form['filter-submission-agent'] = filters_dict['filter-submission-agent'] = 'on'
1678
                    request_form['filter-submission-agent'] = filters_dict['filter-submission-agent'] = 'on'
1660 1679
                    if nameid_users:
1661 1680
                        filters_dict['filter-submission-agent-value'] = str(nameid_users[0].id)
1662
                        get_request().form['filter-submission-agent-value'] = filters_dict['filter-submission-agent-value']
1681
                        request_form['filter-submission-agent-value'] = filters_dict['filter-submission-agent-value']
1663 1682
                    else:
1664 1683
                        filters_dict['filter-submission-agent-value'] = '-1'
1665
                        get_request().form['filter-submission-agent-value'] = '-1'
1684
                        request_form['filter-submission-agent-value'] = '-1'
1666 1685

  
1667 1686
            if filters_dict.get('filter-%s' % filter_field.id):
1668 1687
                # if there's a filter-%(id)s, it is used to enable the actual
wcs/carddef.py
19 19

  
20 20
from quixote import get_publisher
21 21
from .qommon import _, N_, misc
22
from .qommon.storage import Equal, NotEqual
22 23

  
23 24
from wcs.carddata import CardData
24 25
from wcs.formdef import FormDef
......
137 138

  
138 139
    @classmethod
139 140
    def get_as_data_source_options(cls):
141
        carddefs = {}
140 142
        for carddef in cls.select(lightweight=True, ignore_errors=True, order_by='name'):
141 143
            if not carddef.digest_template:
142 144
                continue
143 145
            data_source_id = 'carddef:%s' % carddef.url_name
146
            carddefs[carddef.id] = {'url_name': carddef.url_name, 'name': carddef.name}
144 147
            yield (data_source_id, carddef.name, data_source_id)
148
        clauses = [Equal('formdef_type', 'carddef'), Equal('visibility', 'datasource')]
149
        for custom_view in get_publisher().custom_view_class.select(clauses):
150
            carddef = carddefs.get(custom_view.formdef_id)
151
            data_source_id = 'carddef:%s:%s' % (carddef['url_name'], custom_view.slug)
152
            yield (data_source_id, '%s - %s' % (carddef['name'], custom_view.title), data_source_id)
145 153

  
146 154
    @classmethod
147 155
    def get_data_source_items(cls, data_source_id):
148 156
        assert data_source_id.startswith('carddef:')
157
        parts = data_source_id.split(':')
149 158
        try:
150
            carddef = cls.get_by_urlname(data_source_id[8:])
159
            carddef = cls.get_by_urlname(parts[1])
151 160
        except KeyError:
152 161
            return []
162
        criterias = [NotEqual('status', 'draft')]
163
        order_by = None
164
        if len(parts) > 2:
165
            lookup_criterias = [
166
                Equal('formdef_type', 'carddef'),
167
                Equal('visibility', 'datasource'),
168
                Equal('slug', parts[2]),
169
            ]
170
            try:
171
                custom_view = get_publisher().custom_view_class.select(lookup_criterias)[0]
172
            except IndexError:
173
                return []
174
            from wcs.backoffice.management import FormPage
175
            form_page = FormPage(formdef=carddef, view=custom_view, update_breadcrumbs=False)
176
            criterias.extend(form_page.get_view_criterias())
177
            order_by = custom_view.order_by
178

  
153 179
        items = [x.get_data_source_structured_item()
154
                 for x in carddef.data_class().select()
155
                 if not x.is_draft()]
156
        items.sort(key=lambda x: misc.simplify(x['text']))
180
                 for x in carddef.data_class().select(
181
                     clause=criterias,
182
                     order_by=order_by)]
183
        if order_by is None:
184
            items.sort(key=lambda x: misc.simplify(x['text']))
157 185
        return items
wcs/qommon/static/css/dc2/admin.css
1937 1937
#sidebar-custom-views .active {
1938 1938
	font-weight: bold;
1939 1939
}
1940

  
1941
#sidebar-custom-views .as-data-source {
1942
	font-weight: normal;
1943
}
1940
-