wcs: add cell to list cards of a card model (#46768)

This commit is contained in:
Lauréline Guérin 2020-09-21 13:53:39 +02:00
parent d7f4f2c4ee
commit e642164112
No known key found for this signature in database
GPG Key ID: 1FAB9B9B4F93D473
7 changed files with 325 additions and 10 deletions

View File

@ -111,9 +111,9 @@ class AppConfig(django.apps.AppConfig):
def update_db_cache(self):
from combo.data.models import CellBase
from .models import WcsFormCell, WcsCategoryCell, WcsFormsOfCategoryCell
for cell in CellBase.get_cells(
cell_filter=lambda x: x in (WcsFormCell, WcsCategoryCell, WcsFormsOfCategoryCell)):
from .models import WcsFormCell, WcsCategoryCell, WcsFormsOfCategoryCell, WcsCardsCell
models_to_update = [WcsFormCell, WcsCategoryCell, WcsFormsOfCategoryCell, WcsCardsCell]
for cell in CellBase.get_cells(cell_filter=lambda x: x in models_to_update):
cell.save()

View File

@ -23,7 +23,7 @@ from django.utils.translation import ugettext_lazy as _
from combo.utils.forms import MultiSortWidget
from .models import (WcsFormCell, WcsCategoryCell, WcsFormsOfCategoryCell,
WcsCurrentFormsCell, WcsCurrentDraftsCell)
WcsCurrentFormsCell, WcsCurrentDraftsCell, WcsCardsCell)
from .utils import get_wcs_options, get_wcs_services
@ -38,6 +38,17 @@ class WcsFormCellForm(forms.ModelForm):
self.fields['formdef_reference'].widget = forms.Select(choices=formdef_references)
class WcsCardsCellForm(forms.ModelForm):
class Meta:
model = WcsCardsCell
fields = ('carddef_reference',)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
card_models = get_wcs_options('/api/cards/@list')
self.fields['carddef_reference'].widget = forms.Select(choices=card_models)
class WcsCategoryCellForm(forms.ModelForm):
class Meta:
model = WcsCategoryCell

View File

@ -0,0 +1,39 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import combo.apps.wcs.models
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('data', '0045_link_list_limit'),
('auth', '0008_alter_user_username_max_length'),
('wcs', '0019_wcscurrentformscell_include_drafts'),
]
operations = [
migrations.CreateModel(
name='WcsCardsCell',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('placeholder', models.CharField(max_length=20)),
('order', models.PositiveIntegerField()),
('slug', models.SlugField(blank=True, verbose_name='Slug')),
('extra_css_class', models.CharField(blank=True, max_length=100, verbose_name='Extra classes for CSS styling')),
('public', models.BooleanField(default=True, verbose_name='Public')),
('restricted_to_unlogged', models.BooleanField(default=False, verbose_name='Restrict to unlogged users')),
('last_update_timestamp', models.DateTimeField(auto_now=True)),
('carddef_reference', models.CharField(max_length=150, verbose_name='Card Model')),
('cached_title', models.CharField(max_length=150, verbose_name='Title')),
('groups', models.ManyToManyField(blank=True, to='auth.Group', verbose_name='Groups')),
('page', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='data.Page')),
],
options={
'verbose_name': 'Cards',
},
bases=(models.Model, combo.apps.wcs.models.WcsBlurpMixin),
),
]

View File

@ -28,7 +28,7 @@ from django.utils.translation import ugettext_lazy as _
from jsonfield import JSONField
from combo.data.models import CellBase
from combo.data.models import CellBase, Page
from combo.data.library import register_cell_class
from combo.utils import requests
@ -38,6 +38,8 @@ from .utils import get_wcs_json, is_wcs_enabled, get_wcs_services
invalid_reason_codes = {
'wcs_form_not_defined': _('No form set'),
'wcs_form_not_found': _('Invalid form'),
'wcs_card_not_defined': _('No card model set'),
'wcs_card_not_found': _('Invalid card model'),
'wcs_category_not_defined': _('No category set'),
'wcs_category_not_found': _('Invalid category'),
'wcs_data_failure': _('Failed to get data'),
@ -708,6 +710,95 @@ class CategoriesCell(WcsDataBaseCell):
verbose_name = _('Form Categories')
@register_cell_class
class WcsCardsCell(CellBase, WcsBlurpMixin):
carddef_reference = models.CharField(_('Card Model'), max_length=150)
cached_title = models.CharField(_('Title'), max_length=150)
template_name = 'combo/wcs/cards.html'
variable_name = 'cards'
invalid_reason_codes = invalid_reason_codes
class Meta:
verbose_name = _('Cards')
def save(self, *args, **kwargs):
if 'update_fields' in kwargs:
# don't populate the cache
return super().save(*args, **kwargs)
def populate_cache():
if self.carddef_reference:
wcs_key, card_slug = self.carddef_reference.split(':')
wcs_site = get_wcs_services().get(wcs_key)
card_models = get_wcs_json(wcs_site, 'api/cards/@list')
if not card_models:
# can not retrieve data, don't report cell as invalid
self.mark_as_valid()
return
card_found = False
for card in card_models.get('data', []):
slug = card['slug']
if slug == card_slug:
self.cached_title = card['title']
self.save(update_fields=['cached_title'])
card_found = True
break
if card_found:
self.mark_as_valid()
return
else:
return self.mark_as_invalid('wcs_card_not_found')
else:
return self.mark_as_invalid('wcs_card_not_defined')
super().save(*args, **kwargs)
populate_cache()
def is_relevant(self, context):
return bool(self.carddef_reference)
@property
def wcs_site(self):
return self.carddef_reference.split(':')[0]
def get_api_url(self, context):
return 'api/cards/%s/list' % self.carddef_reference.split(':')[1]
def get_default_form_class(self):
from .forms import WcsCardsCellForm
return WcsCardsCellForm
def get_cell_extra_context(self, context):
extra_context = super().get_cell_extra_context(context)
extra_context.update(WcsBlurpMixin.get_cell_extra_context(self, context))
extra_context['title'] = self.cached_title
pages_with_sub_slug = Page.objects.exclude(sub_slug='')
matching_pages = ([p for p in pages_with_sub_slug if '<%s_id>' % self.carddef_reference.split(':')[1] in p.sub_slug])
if matching_pages:
card_page = matching_pages[0]
extra_context['card_page_base_url'] = card_page.get_online_url()
try:
extra_context['cards'] = list(extra_context['cards'][self.wcs_site]['data'])
except (KeyError, TypeError):
# an error occured when getting the data
extra_context['cards'] = []
return extra_context
def get_additional_label(self):
if not self.cached_title:
return
return self.cached_title
def render_for_search(self):
return ''
@register_cell_class
class TrackingCodeInputCell(CellBase):
is_enabled = classmethod(is_wcs_enabled)

View File

@ -0,0 +1,23 @@
{% load assets i18n %}
{% block cell-content %}
{% block cell-header %}
<h2>{{ title }}</h2>
{% include "combo/asset_picture_fragment.html" %}
{% endblock %}
<div class="links-list cards-{{ slug }} list-of-cards">
<ul>
{% for data in cards %}
<li>
<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>
</li>
{% endfor %}
</ul>
</div>
{% if not cards %}<div class="empty-message"><p>{% trans "There are no cards." %}</p></div>{% endif %}
{% include "combo/pagination.html" %}
{% endblock %}

View File

@ -658,7 +658,7 @@ def test_wcs_search_engines(settings, app):
assert '&include-anonymised=off' in engine['url']
# create a page with sub_slug to enable card engines
Page.objects.create(slug='foo', title='Foo', sub_slug='(?P<foo_id>[a-z0-9]+')
Page.objects.create(slug='foo', title='Foo', sub_slug='(?P<foo_id>[a-z0-9]+)')
with mock.patch('combo.apps.wcs.utils.get_wcs_json') as mock_wcs:
# no card model found
mock_wcs.return_value = {}
@ -675,7 +675,7 @@ def test_wcs_search_engines(settings, app):
assert len([x for x in search_engines.keys() if x.startswith('cards:')]) == 0
# related page exists
Page.objects.create(slug='bar', title='Bar', sub_slug='(?P<bar_id>[a-z0-9]+')
Page.objects.create(slug='bar', title='Bar', sub_slug='(?P<bar_id>[a-z0-9]+)')
search_engines = engines.get_engines()
assert len([x for x in search_engines.keys() if x.startswith('cards:')]) == 1
assert 'cards:c21f969b:bar' in search_engines.keys()
@ -815,7 +815,7 @@ def test_index_site_num_queries(settings, app):
assert IndexedCell.objects.count() == 50
with CaptureQueriesContext(connection) as ctx:
index_site()
assert len(ctx.captured_queries) == 221
assert len(ctx.captured_queries) == 222
SearchCell.objects.create(
page=page, placeholder='content', order=0,

View File

@ -23,7 +23,8 @@ from combo.apps.search.engines import engines
from combo.apps.wcs.models import (
WcsFormCell, WcsCurrentFormsCell,
WcsFormsOfCategoryCell, WcsCurrentDraftsCell, WcsCategoryCell,
TrackingCodeInputCell, BackofficeSubmissionCell, WcsCareFormsCell)
TrackingCodeInputCell, BackofficeSubmissionCell, WcsCareFormsCell,
WcsCardsCell)
from combo.apps.search.models import SearchCell
from combo.apps.search.utils import index_site, search_site
@ -81,6 +82,39 @@ WCS_USER_DRAFTS_DATA = [
'form_status_is_endpoint': True, 'category_slug': 'test-9'},
]
WCS_CARDDEFS_DATA = [
{'title': 'Card Model 1', 'slug': 'card_model_1'},
{'title': 'Card Model 2', 'slug': 'card_model_2'},
{'title': 'Card Model 3', 'slug': 'card_model_3'},
]
WCS_CARDS_DATA = [
{
'id': 11,
'display_id': '10-11',
'display_name': 'Card Model 1 - n°10-11',
'digest': 'a a a',
'text': 'aa',
'url': '/backoffice/data/card_model_1/11/'
},
{
'id': 12,
'display_id': '10-12',
'display_name': 'Card Model 1 - n°10-12',
'digest': 'b b b',
'text': 'bb',
'url': '/backoffice/data/card_model_1/12/'
},
{
'id': 13,
'display_id': '10-13',
'display_name': 'Card Model 1 - n°10-13',
'digest': 'c c c',
'text': 'cc',
'url': '/backoffice/data/card_model_1/13/'
},
]
class MockUser(object):
email = 'foo@example.net'
@ -118,6 +152,10 @@ def get_data_from_url(url):
return WCS_FORMS_DATA
if '/api/user/drafts' in url:
return WCS_USER_DRAFTS_DATA
if '/api/cards/@list' in url:
return WCS_CARDDEFS_DATA
if 'api/cards/' in url:
return WCS_CARDS_DATA
return []
@ -213,7 +251,8 @@ def test_form_cell_validity(mock_send):
assert validity_info.invalid_since is not None
def test_form_cell_load():
@mock.patch('combo.apps.wcs.utils.requests.send', side_effect=mocked_requests_send)
def test_form_cell_load(mock_send):
page = Page(title='xxx', slug='test_form_cell_save_cache', template_name='standard')
page.save()
cell = WcsFormCell(page=page, placeholder='content', order=0)
@ -858,6 +897,118 @@ def test_manager_current_forms(app, admin_user):
settings.KNOWN_SERVICES = temp_settings
@mock.patch('combo.apps.wcs.utils.requests.send', side_effect=mocked_requests_send)
def test_cards_cell_setup(mock_send):
cell = WcsCardsCell()
form_class = cell.get_default_form_class()
form = form_class()
assert form.fields['carddef_reference'].widget.choices == [
('default:card_model_1', 'test : Card Model 1'),
('default:card_model_2', 'test : Card Model 2'),
('default:card_model_3', 'test : Card Model 3'),
('other:card_model_1', 'test2 : Card Model 1'),
('other:card_model_2', 'test2 : Card Model 2'),
('other:card_model_3', 'test2 : Card Model 3'),
]
@mock.patch('combo.apps.wcs.utils.requests.send', side_effect=mocked_requests_send)
def test_cards_cell_save_cache(mock_send):
page = Page.objects.create(title='xxx', slug='test_cards_cell_save_cache', template_name='standard')
cell = WcsCardsCell(page=page, placeholder='content', order=0)
assert cell.get_additional_label() is None
cell.carddef_reference = 'default:card_model_1'
cell.save()
assert cell.cached_title == 'Card Model 1'
assert cell.get_additional_label() == 'Card Model 1'
# make sure cached attributes are removed from serialized pages
assert 'cached_' not in json.dumps(page.get_serialized_page())
# check content provided to search engine
assert cell.render_for_search() == ''
# artificially change title
WcsCardsCell.objects.filter(pk=cell.pk).update(cached_title='XXX')
assert WcsCardsCell.objects.get(pk=cell.pk).cached_title == 'XXX'
# run update db cache
appconfig = apps.get_app_config('wcs')
appconfig.update_db_cache()
assert WcsCardsCell.objects.get(pk=cell.pk).cached_title == 'Card Model 1'
@mock.patch('combo.apps.wcs.utils.requests.send', side_effect=mocked_requests_send)
def test_cards_cell_validity(mock_send):
page = Page.objects.create(title='xxx', template_name='standard')
cell = WcsCardsCell.objects.create(page=page, placeholder='content', order=0)
validity_info = ValidityInfo.objects.latest('pk')
assert validity_info.invalid_reason_code == 'wcs_card_not_defined'
assert validity_info.invalid_since is not None
cell.carddef_reference = 'default:card_model_1'
cell.save()
assert ValidityInfo.objects.exists() is False
# can not retrieve carddefs, don't set cell as invalid
with mock.patch('combo.apps.wcs.models.requests.get') as requests_get:
mock_json = mock.Mock(status_code=404)
requests_get.return_value = mock_json
cell.save()
assert ValidityInfo.objects.exists() is False
cell.carddef_reference = 'default:foobar'
cell.save()
validity_info = ValidityInfo.objects.latest('pk')
assert validity_info.invalid_reason_code == 'wcs_card_not_found'
assert validity_info.invalid_since is not None
@mock.patch('combo.apps.wcs.utils.requests.send', side_effect=mocked_requests_send)
def test_cards_cell_load(mock_send):
page = Page.objects.create(title='xxx', slug='test_cards', template_name='standard')
cell = WcsCardsCell(page=page, placeholder='content', order=0)
cell.carddef_reference = 'default:card_model_1'
cell.save()
site_export = [page.get_serialized_page()]
cell.delete()
assert not Page.objects.get(pk=page.pk).get_cells()
Page.load_serialized_pages(site_export)
page = Page.objects.get(slug='test_cards')
cells = page.get_cells()
assert len(cells) == 1
cell = cells[0]
assert cell.cached_title == 'Card Model 1'
@mock.patch('combo.apps.wcs.utils.requests.send', side_effect=mocked_requests_send)
def test_cards_cell_render(mock_send, context):
page = Page.objects.create(title='xxx', template_name='standard')
cell = WcsCardsCell(page=page, placeholder='content', order=0)
cell.carddef_reference = u'default:card_model_1'
cell.save()
# query should fail as nothing is cached
cache.clear()
with pytest.raises(NothingInCacheException):
result = cell.render(context)
context['synchronous'] = True # to get fresh content
result = cell.render(context)
assert '<h2>Card Model 1</h2>' in result
assert '<a href="http://127.0.0.1:8999/backoffice/data/card_model_1/11/"><span class="card-title">aa</span></a>' in result
assert '<a href="http://127.0.0.1:8999/backoffice/data/card_model_1/12/"><span class="card-title">bb</span></a>' in result
assert '<a href="http://127.0.0.1:8999/backoffice/data/card_model_1/13/"><span class="card-title">cc</span></a>' in result
# create a page with the correct subslug
Page.objects.create(slug='foo', title='Foo', sub_slug='(?P<card_model_1_id>[a-z0-9]+)')
result = cell.render(context)
assert '<h2>Card Model 1</h2>' in result
assert '<a href="/foo/11"><span class="card-title">aa</span></a>' in result
assert '<a href="/foo/12"><span class="card-title">bb</span></a>' in result
assert '<a href="/foo/13"><span class="card-title">cc</span></a>' in result
def test_tracking_code_cell(app, nocache):
page = Page(title='One', slug='index', template_name='standard')
page.save()