0002-wcs-add-cell-to-list-cards-of-a-card-model-46768.patch
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 |
- |