Projet

Général

Profil

0002-wcs-add-cell-to-list-cards-of-a-card-model-46768.patch

Lauréline Guérin, 22 septembre 2020 10:43

Télécharger (19,1 ko)

Voir les différences:

Subject: [PATCH 2/2] wcs: add cell to list cards of a card model (#46768)

 combo/apps/wcs/__init__.py                    |   6 +-
 combo/apps/wcs/forms.py                       |  13 +-
 combo/apps/wcs/migrations/0020_cards.py       |  39 +++++
 combo/apps/wcs/models.py                      |  93 ++++++++++-
 combo/apps/wcs/templates/combo/wcs/cards.html |  23 +++
 tests/test_search.py                          |   6 +-
 tests/test_wcs.py                             | 155 +++++++++++++++++-
 7 files changed, 325 insertions(+), 10 deletions(-)
 create mode 100644 combo/apps/wcs/migrations/0020_cards.py
 create mode 100644 combo/apps/wcs/templates/combo/wcs/cards.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
115
        for cell in CellBase.get_cells(
116
                cell_filter=lambda x: x in (WcsFormCell, WcsCategoryCell, WcsFormsOfCategoryCell)):
114
        from .models import WcsFormCell, WcsCategoryCell, WcsFormsOfCategoryCell, WcsCardsCell
115
        models_to_update = [WcsFormCell, WcsCategoryCell, WcsFormsOfCategoryCell, WcsCardsCell]
116
        for cell in CellBase.get_cells(cell_filter=lambda x: x in models_to_update):
117 117
            cell.save()
118 118

  
119 119

  
combo/apps/wcs/forms.py
23 23
from combo.utils.forms import MultiSortWidget
24 24

  
25 25
from .models import (WcsFormCell, WcsCategoryCell, WcsFormsOfCategoryCell,
26
                     WcsCurrentFormsCell, WcsCurrentDraftsCell)
26
                     WcsCurrentFormsCell, WcsCurrentDraftsCell, WcsCardsCell)
27 27
from .utils import get_wcs_options, get_wcs_services
28 28

  
29 29

  
......
38 38
        self.fields['formdef_reference'].widget = forms.Select(choices=formdef_references)
39 39

  
40 40

  
41
class WcsCardsCellForm(forms.ModelForm):
42
    class Meta:
43
        model = WcsCardsCell
44
        fields = ('carddef_reference',)
45

  
46
    def __init__(self, *args, **kwargs):
47
        super().__init__(*args, **kwargs)
48
        card_models = get_wcs_options('/api/cards/@list')
49
        self.fields['carddef_reference'].widget = forms.Select(choices=card_models)
50

  
51

  
41 52
class WcsCategoryCellForm(forms.ModelForm):
42 53
    class Meta:
43 54
        model = WcsCategoryCell
combo/apps/wcs/migrations/0020_cards.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

  
8

  
9
class Migration(migrations.Migration):
10

  
11
    dependencies = [
12
        ('data', '0045_link_list_limit'),
13
        ('auth', '0008_alter_user_username_max_length'),
14
        ('wcs', '0019_wcscurrentformscell_include_drafts'),
15
    ]
16

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

  
29 29
from jsonfield import JSONField
30 30

  
31
from combo.data.models import CellBase
31
from combo.data.models import CellBase, Page
32 32
from combo.data.library import register_cell_class
33 33
from combo.utils import requests
34 34

  
......
38 38
invalid_reason_codes = {
39 39
    'wcs_form_not_defined': _('No form set'),
40 40
    'wcs_form_not_found': _('Invalid form'),
41
    'wcs_card_not_defined': _('No card model set'),
42
    'wcs_card_not_found': _('Invalid card model'),
41 43
    'wcs_category_not_defined': _('No category set'),
42 44
    'wcs_category_not_found': _('Invalid category'),
43 45
    'wcs_data_failure': _('Failed to get data'),
......
708 710
        verbose_name = _('Form Categories')
709 711

  
710 712

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

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

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

  
725
    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
        super().save(*args, **kwargs)
758
        populate_cache()
759

  
760
    def is_relevant(self, context):
761
        return bool(self.carddef_reference)
762

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

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

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

  
774
    def get_cell_extra_context(self, context):
775
        extra_context = super().get_cell_extra_context(context)
776
        extra_context.update(WcsBlurpMixin.get_cell_extra_context(self, context))
777
        extra_context['title'] = self.cached_title
778

  
779
        pages_with_sub_slug = Page.objects.exclude(sub_slug='')
780
        matching_pages = ([p for p in pages_with_sub_slug if '<%s_id>' % self.carddef_reference.split(':')[1] in p.sub_slug])
781
        if matching_pages:
782
            card_page = matching_pages[0]
783
            extra_context['card_page_base_url'] = card_page.get_online_url()
784

  
785
        try:
786
            extra_context['cards'] = list(extra_context['cards'][self.wcs_site]['data'])
787
        except (KeyError, TypeError):
788
            # an error occured when getting the data
789
            extra_context['cards'] = []
790

  
791
        return extra_context
792

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

  
798
    def render_for_search(self):
799
        return ''
800

  
801

  
711 802
@register_cell_class
712 803
class TrackingCodeInputCell(CellBase):
713 804
    is_enabled = classmethod(is_wcs_enabled)
combo/apps/wcs/templates/combo/wcs/cards.html
1
{% load assets i18n %}
2

  
3
{% block cell-content %}
4

  
5
{% block cell-header %}
6
<h2>{{ title }}</h2>
7
{% include "combo/asset_picture_fragment.html" %}
8
{% endblock %}
9

  
10
<div class="links-list cards-{{ slug }} list-of-cards">
11
  <ul>
12
    {% for data in cards %}
13
    <li>
14
      <a href="{% if card_page_base_url %}{{ card_page_base_url }}{{ data.id }}{% else %}{{ data.url }}{% endif %}"><span class="card-title">{{ data.text }}</span></a>
15
    </li>
16
    {% endfor %}
17
  </ul>
18
</div>
19

  
20
{% if not cards %}<div class="empty-message"><p>{% trans "There are no cards." %}</p></div>{% endif %}
21
{% include "combo/pagination.html" %}
22

  
23
{% endblock %}
tests/test_search.py
658 658
            assert '&include-anonymised=off' in engine['url']
659 659

  
660 660
    # create a page with sub_slug to enable card engines
661
    Page.objects.create(slug='foo', title='Foo', sub_slug='(?P<foo_id>[a-z0-9]+')
661
    Page.objects.create(slug='foo', title='Foo', sub_slug='(?P<foo_id>[a-z0-9]+)')
662 662
    with mock.patch('combo.apps.wcs.utils.get_wcs_json') as mock_wcs:
663 663
        # no card model found
664 664
        mock_wcs.return_value = {}
......
675 675
        assert len([x for x in search_engines.keys() if x.startswith('cards:')]) == 0
676 676

  
677 677
        # related page exists
678
        Page.objects.create(slug='bar', title='Bar', sub_slug='(?P<bar_id>[a-z0-9]+')
678
        Page.objects.create(slug='bar', title='Bar', sub_slug='(?P<bar_id>[a-z0-9]+)')
679 679
        search_engines = engines.get_engines()
680 680
        assert len([x for x in search_engines.keys() if x.startswith('cards:')]) == 1
681 681
        assert 'cards:c21f969b:bar' in search_engines.keys()
......
815 815
    assert IndexedCell.objects.count() == 50
816 816
    with CaptureQueriesContext(connection) as ctx:
817 817
        index_site()
818
        assert len(ctx.captured_queries) == 221
818
        assert len(ctx.captured_queries) == 222
819 819

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

  
28 29
from combo.apps.search.models import SearchCell
29 30
from combo.apps.search.utils import index_site, search_site
......
81 82
     'form_status_is_endpoint': True, 'category_slug': 'test-9'},
82 83
]
83 84

  
85
WCS_CARDDEFS_DATA = [
86
    {'title': 'Card Model 1', 'slug': 'card_model_1'},
87
    {'title': 'Card Model 2', 'slug': 'card_model_2'},
88
    {'title': 'Card Model 3', 'slug': 'card_model_3'},
89
]
90

  
91
WCS_CARDS_DATA = [
92
    {
93
        'id': 11,
94
        'display_id': '10-11',
95
        'display_name': 'Card Model 1 - n°10-11',
96
        'digest': 'a a a',
97
        'text': 'aa',
98
        'url': '/backoffice/data/card_model_1/11/'
99
    },
100
    {
101
        'id': 12,
102
        'display_id': '10-12',
103
        'display_name': 'Card Model 1 - n°10-12',
104
        'digest': 'b b b',
105
        'text': 'bb',
106
        'url': '/backoffice/data/card_model_1/12/'
107
    },
108
    {
109
        'id': 13,
110
        'display_id': '10-13',
111
        'display_name': 'Card Model 1 - n°10-13',
112
        'digest': 'c c c',
113
        'text': 'cc',
114
        'url': '/backoffice/data/card_model_1/13/'
115
    },
116
]
117

  
84 118

  
85 119
class MockUser(object):
86 120
    email = 'foo@example.net'
......
118 152
        return WCS_FORMS_DATA
119 153
    if '/api/user/drafts' in url:
120 154
        return WCS_USER_DRAFTS_DATA
155
    if '/api/cards/@list' in url:
156
        return WCS_CARDDEFS_DATA
157
    if 'api/cards/' in url:
158
        return WCS_CARDS_DATA
121 159
    return []
122 160

  
123 161

  
......
213 251
    assert validity_info.invalid_since is not None
214 252

  
215 253

  
216
def test_form_cell_load():
254
@mock.patch('combo.apps.wcs.utils.requests.send', side_effect=mocked_requests_send)
255
def test_form_cell_load(mock_send):
217 256
    page = Page(title='xxx', slug='test_form_cell_save_cache', template_name='standard')
218 257
    page.save()
219 258
    cell = WcsFormCell(page=page, placeholder='content', order=0)
......
858 897
        settings.KNOWN_SERVICES = temp_settings
859 898

  
860 899

  
900
@mock.patch('combo.apps.wcs.utils.requests.send', side_effect=mocked_requests_send)
901
def test_cards_cell_setup(mock_send):
902
    cell = WcsCardsCell()
903
    form_class = cell.get_default_form_class()
904
    form = form_class()
905
    assert form.fields['carddef_reference'].widget.choices == [
906
        ('default:card_model_1', 'test : Card Model 1'),
907
        ('default:card_model_2', 'test : Card Model 2'),
908
        ('default:card_model_3', 'test : Card Model 3'),
909
        ('other:card_model_1', 'test2 : Card Model 1'),
910
        ('other:card_model_2', 'test2 : Card Model 2'),
911
        ('other:card_model_3', 'test2 : Card Model 3'),
912
    ]
913

  
914

  
915
@mock.patch('combo.apps.wcs.utils.requests.send', side_effect=mocked_requests_send)
916
def test_cards_cell_save_cache(mock_send):
917
    page = Page.objects.create(title='xxx', slug='test_cards_cell_save_cache', template_name='standard')
918
    cell = WcsCardsCell(page=page, placeholder='content', order=0)
919
    assert cell.get_additional_label() is None
920
    cell.carddef_reference = 'default:card_model_1'
921
    cell.save()
922
    assert cell.cached_title == 'Card Model 1'
923
    assert cell.get_additional_label() == 'Card Model 1'
924
    # make sure cached attributes are removed from serialized pages
925
    assert 'cached_' not in json.dumps(page.get_serialized_page())
926

  
927
    # check content provided to search engine
928
    assert cell.render_for_search() == ''
929

  
930
    # artificially change title
931
    WcsCardsCell.objects.filter(pk=cell.pk).update(cached_title='XXX')
932
    assert WcsCardsCell.objects.get(pk=cell.pk).cached_title == 'XXX'
933
    # run update db cache
934
    appconfig = apps.get_app_config('wcs')
935
    appconfig.update_db_cache()
936
    assert WcsCardsCell.objects.get(pk=cell.pk).cached_title == 'Card Model 1'
937

  
938

  
939
@mock.patch('combo.apps.wcs.utils.requests.send', side_effect=mocked_requests_send)
940
def test_cards_cell_validity(mock_send):
941
    page = Page.objects.create(title='xxx', template_name='standard')
942
    cell = WcsCardsCell.objects.create(page=page, placeholder='content', order=0)
943
    validity_info = ValidityInfo.objects.latest('pk')
944
    assert validity_info.invalid_reason_code == 'wcs_card_not_defined'
945
    assert validity_info.invalid_since is not None
946

  
947
    cell.carddef_reference = 'default:card_model_1'
948
    cell.save()
949
    assert ValidityInfo.objects.exists() is False
950

  
951
    # can not retrieve carddefs, don't set cell as invalid
952
    with mock.patch('combo.apps.wcs.models.requests.get') as requests_get:
953
        mock_json = mock.Mock(status_code=404)
954
        requests_get.return_value = mock_json
955
        cell.save()
956
    assert ValidityInfo.objects.exists() is False
957

  
958
    cell.carddef_reference = 'default:foobar'
959
    cell.save()
960
    validity_info = ValidityInfo.objects.latest('pk')
961
    assert validity_info.invalid_reason_code == 'wcs_card_not_found'
962
    assert validity_info.invalid_since is not None
963

  
964

  
965
@mock.patch('combo.apps.wcs.utils.requests.send', side_effect=mocked_requests_send)
966
def test_cards_cell_load(mock_send):
967
    page = Page.objects.create(title='xxx', slug='test_cards', template_name='standard')
968
    cell = WcsCardsCell(page=page, placeholder='content', order=0)
969
    cell.carddef_reference = 'default:card_model_1'
970
    cell.save()
971
    site_export = [page.get_serialized_page()]
972
    cell.delete()
973
    assert not Page.objects.get(pk=page.pk).get_cells()
974
    Page.load_serialized_pages(site_export)
975
    page = Page.objects.get(slug='test_cards')
976
    cells = page.get_cells()
977
    assert len(cells) == 1
978
    cell = cells[0]
979
    assert cell.cached_title == 'Card Model 1'
980

  
981

  
982
@mock.patch('combo.apps.wcs.utils.requests.send', side_effect=mocked_requests_send)
983
def test_cards_cell_render(mock_send, context):
984
    page = Page.objects.create(title='xxx', template_name='standard')
985
    cell = WcsCardsCell(page=page, placeholder='content', order=0)
986
    cell.carddef_reference = u'default:card_model_1'
987
    cell.save()
988

  
989
    # query should fail as nothing is cached
990
    cache.clear()
991
    with pytest.raises(NothingInCacheException):
992
        result = cell.render(context)
993

  
994
    context['synchronous'] = True  # to get fresh content
995

  
996
    result = cell.render(context)
997
    assert '<h2>Card Model 1</h2>' in result
998
    assert '<a href="http://127.0.0.1:8999/backoffice/data/card_model_1/11/"><span class="card-title">aa</span></a>' in result
999
    assert '<a href="http://127.0.0.1:8999/backoffice/data/card_model_1/12/"><span class="card-title">bb</span></a>' in result
1000
    assert '<a href="http://127.0.0.1:8999/backoffice/data/card_model_1/13/"><span class="card-title">cc</span></a>' in result
1001

  
1002
    # create a page with the correct subslug
1003
    Page.objects.create(slug='foo', title='Foo', sub_slug='(?P<card_model_1_id>[a-z0-9]+)')
1004

  
1005
    result = cell.render(context)
1006
    assert '<h2>Card Model 1</h2>' in result
1007
    assert '<a href="/foo/11"><span class="card-title">aa</span></a>' in result
1008
    assert '<a href="/foo/12"><span class="card-title">bb</span></a>' in result
1009
    assert '<a href="/foo/13"><span class="card-title">cc</span></a>' in result
1010

  
1011

  
861 1012
def test_tracking_code_cell(app, nocache):
862 1013
    page = Page(title='One', slug='index', template_name='standard')
863 1014
    page.save()
864
-