Projet

Général

Profil

0001-wcs-add-cell-to-display-card-details-46767.patch

Lauréline Guérin, 22 septembre 2020 16:04

Télécharger (18 ko)

Voir les différences:

Subject: [PATCH] wcs: add cell to display card details (#46767)

 combo/apps/wcs/__init__.py                   |   4 +-
 combo/apps/wcs/migrations/0021_card.py       |  41 +++++
 combo/apps/wcs/models.py                     | 151 +++++++++++++------
 combo/apps/wcs/templates/combo/wcs/card.html |  22 +++
 tests/test_search.py                         |   2 +-
 tests/test_wcs.py                            | 108 ++++++++++++-
 6 files changed, 271 insertions(+), 57 deletions(-)
 create mode 100644 combo/apps/wcs/migrations/0021_card.py
 create mode 100644 combo/apps/wcs/templates/combo/wcs/card.html
combo/apps/wcs/__init__.py
111 111

  
112 112
    def update_db_cache(self):
113 113
        from combo.data.models import CellBase
114
        from .models import WcsFormCell, WcsCategoryCell, WcsFormsOfCategoryCell, WcsCardsCell
115
        models_to_update = [WcsFormCell, WcsCategoryCell, WcsFormsOfCategoryCell, WcsCardsCell]
114
        from .models import WcsFormCell, WcsCategoryCell, WcsFormsOfCategoryCell, WcsCardsCell, WcsCardInfosCell
115
        models_to_update = [WcsFormCell, WcsCategoryCell, WcsFormsOfCategoryCell, WcsCardsCell, WcsCardInfosCell]
116 116
        for cell in CellBase.get_cells(cell_filter=lambda x: x in models_to_update):
117 117
            cell.save()
118 118

  
combo/apps/wcs/migrations/0021_card.py
1
# -*- coding: utf-8 -*-
2
from __future__ import unicode_literals
3

  
4
import combo.apps.wcs.models
5
from django.db import migrations, models
6
import django.db.models.deletion
7
import jsonfield.fields
8

  
9

  
10
class Migration(migrations.Migration):
11

  
12
    dependencies = [
13
        ('auth', '0008_alter_user_username_max_length'),
14
        ('data', '0045_link_list_limit'),
15
        ('wcs', '0020_cards'),
16
    ]
17

  
18
    operations = [
19
        migrations.CreateModel(
20
            name='WcsCardInfosCell',
21
            fields=[
22
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
23
                ('placeholder', models.CharField(max_length=20)),
24
                ('order', models.PositiveIntegerField()),
25
                ('slug', models.SlugField(blank=True, verbose_name='Slug')),
26
                ('extra_css_class', models.CharField(blank=True, max_length=100, verbose_name='Extra classes for CSS styling')),
27
                ('public', models.BooleanField(default=True, verbose_name='Public')),
28
                ('restricted_to_unlogged', models.BooleanField(default=False, verbose_name='Restrict to unlogged users')),
29
                ('last_update_timestamp', models.DateTimeField(auto_now=True)),
30
                ('carddef_reference', models.CharField(max_length=150, verbose_name='Card Model')),
31
                ('cached_title', models.CharField(max_length=150, verbose_name='Title')),
32
                ('cached_json', jsonfield.fields.JSONField(blank=True, default=dict)),
33
                ('groups', models.ManyToManyField(blank=True, to='auth.Group', verbose_name='Groups')),
34
                ('page', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='data.Page')),
35
            ],
36
            options={
37
                'verbose_name': 'Card Information Cell',
38
            },
39
            bases=(combo.apps.wcs.models.CardMixin, models.Model),
40
        ),
41
    ]
combo/apps/wcs/models.py
710 710
        verbose_name = _('Form Categories')
711 711

  
712 712

  
713
class CardMixin(object):
714
    invalid_reason_codes = invalid_reason_codes
715

  
716
    def populate_cache(self, store_json=False):
717
        if self.carddef_reference:
718
            wcs_key, card_slug = self.carddef_reference.split(':')
719
            wcs_site = get_wcs_services().get(wcs_key)
720
            card_models = get_wcs_json(wcs_site, 'api/cards/@list')
721

  
722
            if not card_models:
723
                # can not retrieve data, don't report cell as invalid
724
                self.mark_as_valid()
725
                return
726

  
727
            card_found = False
728
            for card in card_models.get('data', []):
729
                slug = card['slug']
730
                if slug == card_slug:
731
                    self.cached_title = card['title']
732
                    if store_json:
733
                        self.cached_json = card
734
                        self.save(update_fields=['cached_title', 'cached_json'])
735
                    else:
736
                        self.save(update_fields=['cached_title'])
737
                    card_found = True
738
                    break
739
            if card_found:
740
                self.mark_as_valid()
741
                return
742
            else:
743
                return self.mark_as_invalid('wcs_card_not_found')
744
        else:
745
            return self.mark_as_invalid('wcs_card_not_defined')
746

  
747
    def is_relevant(self, context):
748
        return bool(self.carddef_reference)
749

  
750
    def get_default_form_class(self):
751
        from .forms import WcsCardsCellForm
752
        return WcsCardsCellForm
753

  
754
    @property
755
    def wcs_site(self):
756
        return self.carddef_reference.split(':')[0]
757

  
758
    def get_additional_label(self):
759
        if not self.cached_title:
760
            return
761
        return self.cached_title
762

  
763

  
713 764
@register_cell_class
714
class WcsCardsCell(CellBase, WcsBlurpMixin):
765
class WcsCardsCell(CardMixin, WcsBlurpMixin, CellBase):
715 766
    carddef_reference = models.CharField(_('Card Model'), max_length=150)
716 767
    cached_title = models.CharField(_('Title'), max_length=150)
717 768

  
718 769
    template_name = 'combo/wcs/cards.html'
719 770
    variable_name = 'cards'
720
    invalid_reason_codes = invalid_reason_codes
721 771

  
722 772
    class Meta:
723 773
        verbose_name = _('Cards')
724 774

  
725 775
    def save(self, *args, **kwargs):
726
        if 'update_fields' in kwargs:
727
            # don't populate the cache
728
            return super().save(*args, **kwargs)
729

  
730
        def populate_cache():
731
            if self.carddef_reference:
732
                wcs_key, card_slug = self.carddef_reference.split(':')
733
                wcs_site = get_wcs_services().get(wcs_key)
734
                card_models = get_wcs_json(wcs_site, 'api/cards/@list')
735

  
736
                if not card_models:
737
                    # can not retrieve data, don't report cell as invalid
738
                    self.mark_as_valid()
739
                    return
740

  
741
                card_found = False
742
                for card in card_models.get('data', []):
743
                    slug = card['slug']
744
                    if slug == card_slug:
745
                        self.cached_title = card['title']
746
                        self.save(update_fields=['cached_title'])
747
                        card_found = True
748
                        break
749
                if card_found:
750
                    self.mark_as_valid()
751
                    return
752
                else:
753
                    return self.mark_as_invalid('wcs_card_not_found')
754
            else:
755
                return self.mark_as_invalid('wcs_card_not_defined')
756

  
757 776
        super().save(*args, **kwargs)
758
        populate_cache()
759 777

  
760
    def is_relevant(self, context):
761
        return bool(self.carddef_reference)
778
        if 'update_fields' in kwargs:
779
            # don't populate the cache
780
            return
762 781

  
763
    @property
764
    def wcs_site(self):
765
        return self.carddef_reference.split(':')[0]
782
        self.populate_cache()
766 783

  
767 784
    def get_api_url(self, context):
768 785
        return 'api/cards/%s/list' % self.carddef_reference.split(':')[1]
769 786

  
770
    def get_default_form_class(self):
771
        from .forms import WcsCardsCellForm
772
        return WcsCardsCellForm
773

  
774 787
    def get_cell_extra_context(self, context):
775 788
        extra_context = super().get_cell_extra_context(context)
776 789
        extra_context.update(WcsBlurpMixin.get_cell_extra_context(self, context))
......
790 803

  
791 804
        return extra_context
792 805

  
793
    def get_additional_label(self):
794
        if not self.cached_title:
795
            return
796
        return self.cached_title
797

  
798 806
    def render_for_search(self):
799 807
        return ''
800 808

  
801 809

  
810
@register_cell_class
811
class WcsCardInfosCell(CardMixin, CellBase):
812
    carddef_reference = models.CharField(_('Card Model'), max_length=150)
813

  
814
    cached_title = models.CharField(_('Title'), max_length=150)
815
    cached_json = JSONField(blank=True)
816

  
817
    is_enabled = classmethod(is_wcs_enabled)
818

  
819
    template_name = 'combo/wcs/card.html'
820

  
821
    class Meta:
822
        verbose_name = _('Card Information Cell')
823

  
824
    def save(self, *args, **kwargs):
825
        super().save(*args, **kwargs)
826

  
827
        if 'update_fields' in kwargs:
828
            # don't populate the cache
829
            return
830

  
831
        self.populate_cache(store_json=True)
832

  
833
    def get_cell_extra_context(self, context):
834
        extra_context = super().get_cell_extra_context(context)
835
        extra_context['title'] = self.cached_title
836

  
837
        card_id = '%s_id' % self.carddef_reference.split(':')[1]
838
        if not context.get(card_id):
839
            return extra_context
840
        api_url = 'api/cards/%s/%s/' % (self.carddef_reference.split(':')[1], context.get(card_id))
841

  
842
        wcs_site = get_wcs_services().get(self.wcs_site)
843

  
844
        response = requests.get(
845
                api_url,
846
                remote_service=wcs_site,
847
                user=getattr(context.get('request'), 'user', None),
848
                cache_duration=5,
849
                raise_if_not_cached=not(context.get('synchronous')),
850
                log_errors=False)
851
        if response.status_code == 200:
852
            extra_context['card'] = response.json()
853

  
854
        return extra_context
855

  
856

  
802 857
@register_cell_class
803 858
class TrackingCodeInputCell(CellBase):
804 859
    is_enabled = classmethod(is_wcs_enabled)
combo/apps/wcs/templates/combo/wcs/card.html
1
{% load assets i18n %}
2

  
3
{% block cell-content %}
4

  
5
<div class="card">
6
{% block cell-header %}
7
<h2>{{ title }}{% if card %} - {{ card.text }}{% endif %}</h2>
8
{% include "combo/asset_picture_fragment.html" %}
9
{% endblock %}
10

  
11
{% if card %}
12
{% for key, value in card.fields.items|dictsort:0 %}
13
    {% if not key|endswith:'_raw' and not key|endswith:'_structured' %}
14
    <p><span class="label">{{ key }}</span> <span class="value">{{ value|default:"" }}</span></p>
15
    {% endif %}
16
{% endfor %}
17
{% else %}
18
<p>{% trans 'Unknown Card' %}</p>
19
{% endif %}
20
</div>
21

  
22
{% endblock %}
tests/test_search.py
815 815
    assert IndexedCell.objects.count() == 50
816 816
    with CaptureQueriesContext(connection) as ctx:
817 817
        index_site()
818
        assert len(ctx.captured_queries) == 222
818
        assert len(ctx.captured_queries) == 223
819 819

  
820 820
    SearchCell.objects.create(
821 821
        page=page, placeholder='content', order=0,
tests/test_wcs.py
24 24
    WcsFormCell, WcsCurrentFormsCell,
25 25
    WcsFormsOfCategoryCell, WcsCurrentDraftsCell, WcsCategoryCell,
26 26
    TrackingCodeInputCell, BackofficeSubmissionCell, WcsCareFormsCell,
27
    WcsCardsCell)
27
    WcsCardsCell, WcsCardInfosCell)
28 28

  
29 29
from combo.apps.search.models import SearchCell
30 30
from combo.apps.search.utils import index_site, search_site
......
115 115
    },
116 116
]
117 117

  
118
WCS_CARD_DATA = {
119
    'id': 11,
120
    'display_id': '10-11',
121
    'display_name': 'Card Model 1 - n°10-11',
122
    'digest': 'a a a',
123
    'text': 'aa',
124
    'url': '/backoffice/data/card_model_1/11/',
125
    'fields': {
126
        'fielda': 'a',
127
        'fieldb': 'b',
128
        'related': 'Foo Bar',
129
        'related_raw': 42,
130
        'related_structured': {'id': 42, 'text': 'blah'},
131
    }
132
}
133

  
118 134

  
119 135
class MockUser(object):
120 136
    email = 'foo@example.net'
......
154 170
        return WCS_USER_DRAFTS_DATA
155 171
    if '/api/cards/@list' in url:
156 172
        return WCS_CARDDEFS_DATA
157
    if 'api/cards/' in url:
173
    if 'api/cards/card_model_1/11/' in url:
174
        return WCS_CARD_DATA
175
    if 'api/cards/card_model_1/list' in url:
158 176
        return WCS_CARDS_DATA
159 177
    return []
160 178

  
......
162 180
def mocked_requests_send(request, **kwargs):
163 181
    request_url = urlparse.urlparse(request.url)
164 182
    data = copy.deepcopy(get_data_from_url(request_url.path))
183

  
184
    if not isinstance(data, list):
185
        return MockedRequestResponse(content=json.dumps(data))
186

  
165 187
    for elem in data:
166 188
        for key in ['url', 'form_url_backoffice']:
167 189
            if key not in elem:
......
897 919
        settings.KNOWN_SERVICES = temp_settings
898 920

  
899 921

  
922
@pytest.mark.parametrize('cell_class', [WcsCardsCell, WcsCardInfosCell])
900 923
@mock.patch('combo.apps.wcs.utils.requests.send', side_effect=mocked_requests_send)
901
def test_cards_cell_setup(mock_send):
902
    cell = WcsCardsCell()
924
def test_card_cells_setup(mock_send, cell_class):
925
    cell = cell_class()
903 926
    form_class = cell.get_default_form_class()
904 927
    form = form_class()
905 928
    assert form.fields['carddef_reference'].widget.choices == [
......
936 959
    assert WcsCardsCell.objects.get(pk=cell.pk).cached_title == 'Card Model 1'
937 960

  
938 961

  
962
@pytest.mark.parametrize('cell_class', [WcsCardsCell, WcsCardInfosCell])
939 963
@mock.patch('combo.apps.wcs.utils.requests.send', side_effect=mocked_requests_send)
940
def test_cards_cell_validity(mock_send):
964
def test_card_cells_validity(mock_send, cell_class):
941 965
    page = Page.objects.create(title='xxx', template_name='standard')
942
    cell = WcsCardsCell.objects.create(page=page, placeholder='content', order=0)
966
    cell = cell_class.objects.create(page=page, placeholder='content', order=0)
943 967
    validity_info = ValidityInfo.objects.latest('pk')
944 968
    assert validity_info.invalid_reason_code == 'wcs_card_not_defined'
945 969
    assert validity_info.invalid_since is not None
......
1009 1033
    assert '<a href="/foo/13"><span class="card-title">cc</span></a>' in result
1010 1034

  
1011 1035

  
1036
@mock.patch('combo.apps.wcs.utils.requests.send', side_effect=mocked_requests_send)
1037
def test_card_cell_save_cache(mock_send):
1038
    page = Page.objects.create(title='xxx', slug='test_cards_cell_save_cache', template_name='standard')
1039
    cell = WcsCardInfosCell(page=page, placeholder='content', order=0)
1040
    assert cell.get_additional_label() is None
1041
    cell.carddef_reference = 'default:card_model_1'
1042
    cell.save()
1043
    assert cell.cached_title == 'Card Model 1'
1044
    assert cell.cached_json == {'slug': 'card_model_1', 'title': 'Card Model 1'}
1045
    assert cell.get_additional_label() == 'Card Model 1'
1046
    # make sure cached attributes are removed from serialized pages
1047
    assert 'cached_' not in json.dumps(page.get_serialized_page())
1048

  
1049
    # artificially change title and json
1050
    WcsCardInfosCell.objects.filter(pk=cell.pk).update(cached_title='XXX', cached_json={})
1051
    assert WcsCardInfosCell.objects.get(pk=cell.pk).cached_title == 'XXX'
1052
    # run update db cache
1053
    appconfig = apps.get_app_config('wcs')
1054
    appconfig.update_db_cache()
1055
    assert WcsCardInfosCell.objects.get(pk=cell.pk).cached_title == 'Card Model 1'
1056
    assert WcsCardInfosCell.objects.get(pk=cell.pk).cached_json == {'slug': 'card_model_1', 'title': 'Card Model 1'}
1057

  
1058

  
1059
@mock.patch('combo.apps.wcs.utils.requests.send', side_effect=mocked_requests_send)
1060
def test_card_cell_load(mock_send):
1061
    page = Page.objects.create(title='xxx', slug='test_cards', template_name='standard')
1062
    cell = WcsCardInfosCell(page=page, placeholder='content', order=0)
1063
    cell.carddef_reference = 'default:card_model_1'
1064
    cell.save()
1065
    site_export = [page.get_serialized_page()]
1066
    cell.delete()
1067
    assert not Page.objects.get(pk=page.pk).get_cells()
1068
    Page.load_serialized_pages(site_export)
1069
    page = Page.objects.get(slug='test_cards')
1070
    cells = page.get_cells()
1071
    assert len(cells) == 1
1072
    cell = cells[0]
1073
    assert cell.cached_title == 'Card Model 1'
1074
    assert cell.cached_json == {'slug': 'card_model_1', 'title': 'Card Model 1'}
1075

  
1076

  
1077
@mock.patch('combo.apps.wcs.utils.requests.send', side_effect=mocked_requests_send)
1078
def test_card_cell_render(mock_send, context):
1079
    page = Page.objects.create(title='xxx', template_name='standard')
1080
    cell = WcsCardInfosCell(page=page, placeholder='content', order=0)
1081
    cell.carddef_reference = u'default:card_model_1'
1082
    cell.save()
1083

  
1084
    # card id not in context
1085
    assert 'card_model_1_id' not in context
1086
    result = cell.render(context)
1087
    assert '<h2>Card Model 1</h2>' in result
1088
    assert '<p>Unknown Card</p>' in result
1089

  
1090
    context['card_model_1_id'] = 11
1091

  
1092
    # query should fail as nothing is cached
1093
    cache.clear()
1094
    with pytest.raises(NothingInCacheException):
1095
        result = cell.render(context)
1096

  
1097
    context['synchronous'] = True  # to get fresh content
1098

  
1099
    result = cell.render(context)
1100
    assert '<h2>Card Model 1 - aa</h2>' in result
1101
    assert '<span class="label">fielda</span> <span class="value">a</span>' in result
1102
    assert '<span class="label">fieldb</span> <span class="value">b</span>' in result
1103
    assert '<span class="label">related</span> <span class="value">Foo Bar</span>' in result
1104
    assert 'related_raw' not in result
1105
    assert 'related_structured' not in result
1106

  
1107

  
1012 1108
def test_tracking_code_cell(app, nocache):
1013 1109
    page = Page(title='One', slug='index', template_name='standard')
1014 1110
    page.save()
1015
-