assets: add generic assets for cells (#40223)

This commit is contained in:
Lauréline Guérin 2020-03-10 09:52:41 +01:00
parent 00131e4868
commit 5d6d0a86fc
No known key found for this signature in database
GPG Key ID: 1FAB9B9B4F93D473
16 changed files with 203 additions and 11 deletions

View File

@ -62,6 +62,7 @@ def asset_url(*args, **kwargs):
return get_thumbnail(asset, geometry_string, **kwargs).url return get_thumbnail(asset, geometry_string, **kwargs).url
@register.simple_tag @register.simple_tag
def asset_css_url(*args, **kwargs): def asset_css_url(*args, **kwargs):
url = asset_url(*args, **kwargs) url = asset_url(*args, **kwargs)
@ -72,7 +73,21 @@ def asset_css_url(*args, **kwargs):
@register.simple_tag @register.simple_tag
def get_asset(key): def get_asset(*args, **kwargs):
key = None
if 'cell' in kwargs and 'type' in kwargs:
try:
if not kwargs['cell'].can_have_assets():
return None
key = kwargs['cell'].get_asset_slot_key(kwargs['type'])
except AttributeError:
return None
elif len(args) == 1:
key = args[0]
if not key:
return None
try: try:
return Asset.objects.get(key=key) return Asset.objects.get(key=key)
except Asset.DoesNotExist: except Asset.DoesNotExist:

View File

@ -103,8 +103,10 @@ class SlotAsset(object):
assets = dict([(x.key, x) for x in Asset.objects.all()]) assets = dict([(x.key, x) for x in Asset.objects.all()])
uniq_slots = {} uniq_slots = {}
uniq_slots.update(settings.COMBO_ASSET_SLOTS) uniq_slots.update(settings.COMBO_ASSET_SLOTS)
for cell in CellBase.get_cells( cells = CellBase.get_cells(select_related={
cell_filter=lambda x: bool(x.get_asset_slots)): '__all__': ['page'],
'data_linkcell': ['link_page']})
for cell in cells:
uniq_slots.update(cell.get_asset_slots()) uniq_slots.update(cell.get_asset_slots())
for key, value in uniq_slots.items(): for key, value in uniq_slots.items():
yield cls(key, yield cls(key,

View File

@ -577,9 +577,6 @@ class CellBase(six.with_metaclass(CellMeta, models.Model)):
# get_badge(self, context); set to None so cell types can be skipped easily # get_badge(self, context); set to None so cell types can be skipped easily
get_badge = None get_badge = None
# get_asset_slots(self); set to None so cell types can be skipped easily
get_asset_slots = None
# message displayed when the cell is loaded asynchronously # message displayed when the cell is loaded asynchronously
loading_message = _('Loading...') loading_message = _('Loading...')
@ -623,6 +620,50 @@ class CellBase(six.with_metaclass(CellMeta, models.Model)):
def css_class_names(self): def css_class_names(self):
return ' '.join([self.class_name, self.legacy_class_name, self.extra_css_class]) return ' '.join([self.class_name, self.legacy_class_name, self.extra_css_class])
def can_have_assets(self):
return self.get_slug_for_asset() and self.get_asset_slot_templates()
def get_slug_for_asset(self):
return self.slug
def get_label_for_asset(self):
return _(u'%(cell_label)s on page %(page_name)s (%(page_slug)s)') % {
'cell_label': str(self),
'page_name': str(self.page),
'page_slug': self.page.slug,
}
def get_asset_slot_key(self, key):
return 'cell:%s:%s:%s' % (
self.get_cell_type_str(),
key,
self.get_slug_for_asset())
def get_asset_slot_templates(self):
return settings.COMBO_CELL_ASSET_SLOTS.get(self.get_cell_type_str()) or {}
def get_asset_slots(self):
if not self.can_have_assets():
return {}
slot_templates = self.get_asset_slot_templates()
slots = {}
for slot_template_key, slot_template_data in slot_templates.items():
suffix = ''
if slot_template_data.get('suffix'):
suffix = ' (%s)' % slot_template_data['suffix']
slot_key = self.get_asset_slot_key(slot_template_key)
label = u'%(prefix)s%(label)s%(suffix)s' % {
'prefix': slot_template_data['prefix'],
'label': self.get_label_for_asset(),
'suffix': suffix
}
slots[slot_key] = {
'label': label
}
slots[slot_key].update(slot_template_data)
return slots
@classmethod @classmethod
def get_cell_classes(cls, class_filter=lambda x: True): def get_cell_classes(cls, class_filter=lambda x: True):
for klass in get_cell_classes(): for klass in get_cell_classes():
@ -673,7 +714,9 @@ class CellBase(six.with_metaclass(CellMeta, models.Model)):
continue continue
cells_queryset = klass.objects.filter(**kwargs) cells_queryset = klass.objects.filter(**kwargs)
if select_related: if select_related:
cells_queryset = cells_queryset.select_related(*select_related) cells_queryset = cells_queryset.select_related(
*select_related.get('__all__', []),
*select_related.get(klass.get_cell_type_str(), []))
cells.extend(cells_queryset) cells.extend(cells_queryset)
if prefetch_validity_info: if prefetch_validity_info:
validity_info_list = list(ValidityInfo.objects.select_related('content_type')) validity_info_list = list(ValidityInfo.objects.select_related('content_type'))
@ -1059,6 +1102,18 @@ class LinkCell(CellBase):
return None return None
return utils.ellipsize(title) return utils.ellipsize(title)
def get_slug_for_asset(self):
if self.placeholder and self.placeholder.startswith('_'):
return
if self.link_page:
return self.link_page.slug
return self.slug
def get_label_for_asset(self):
if self.link_page:
return str(self)
return super().get_label_for_asset()
def get_url(self, context=None): def get_url(self, context=None):
context = context or {} context = context or {}
if self.link_page: if self.link_page:

View File

@ -95,7 +95,7 @@ site_import = SiteImportView.as_view()
def invalid_cell_report(request): def invalid_cell_report(request):
invalid_cells = CellBase.get_cells( invalid_cells = CellBase.get_cells(
select_related=['page'], select_related={'__all__': ['page']},
page__snapshot__isnull=True, page__snapshot__isnull=True,
validity_info__invalid_since__isnull=False) validity_info__invalid_since__isnull=False)
invalid_cells = [c for c in invalid_cells if c.placeholder and not c.placeholder.startswith('_')] invalid_cells = [c for c in invalid_cells if c.placeholder and not c.placeholder.startswith('_')]

View File

@ -0,0 +1,7 @@
{% load assets %}
{% get_asset cell=cell type='picture' as asset %}
{% if asset %}
<picture>
<img src="{% asset_url asset size="660x360" crop="center" upscale=False %}" alt="">
</picture>
{% endif %}

View File

@ -3,6 +3,7 @@
{% if cell.title %} {% if cell.title %}
<h2>{{ cell.title }}</h2> <h2>{{ cell.title }}</h2>
{% endif %} {% endif %}
{% include "combo/asset_picture_fragment.html" %}
<div class="feed-content"> <div class="feed-content">
{% for entry in feed.entries %} {% for entry in feed.entries %}
{% if entry.link %} {% if entry.link %}

View File

@ -1,5 +1,6 @@
{% block cell-content %} {% block cell-content %}
{% if title %}<h2>{{title}}</h2>{% endif %} {% if title %}<h2>{{title}}</h2>{% endif %}
{% include "combo/asset_picture_fragment.html" %}
<!-- <!--
{{json}} {{json}}
--> -->

View File

@ -1,6 +1,7 @@
{% block cell-content %} {% block cell-content %}
<div class="links-list"> <div class="links-list">
{% if title %}<h2>{{title}}</h2>{% endif %} {% if title %}<h2>{{title}}</h2>{% endif %}
{% include "combo/asset_picture_fragment.html" %}
{% for row in json.data %} {% for row in json.data %}
<ul> <ul>
<li><a href="{{row.url}}">{{row.text}}</a></li> <li><a href="{{row.url}}">{{row.text}}</a></li>

View File

@ -1,3 +1,4 @@
{% block cell-content %} {% block cell-content %}
{% include "combo/asset_picture_fragment.html" %}
<a href="{{url}}">{{title}}</a> <a href="{{url}}">{{title}}</a>
{% endblock %} {% endblock %}

View File

@ -2,6 +2,7 @@
{% spaceless %} {% spaceless %}
<div class="links-list"> <div class="links-list">
{% if title %}<h2>{{title}}</h2>{% endif %} {% if title %}<h2>{{title}}</h2>{% endif %}
{% include "combo/asset_picture_fragment.html" %}
<ul> <ul>
{% for link in links %} {% for link in links %}
<li><a href="{{ link.url }}">{{ link.title }}</a></li> <li><a href="{{ link.url }}">{{ link.title }}</a></li>

View File

@ -1,3 +1,4 @@
{% block cell-content %} {% block cell-content %}
{% include "combo/asset_picture_fragment.html" %}
{{text}} {{text}}
{% endblock %} {% endblock %}

View File

@ -499,6 +499,7 @@ def page(request):
return publish_page(request, page) return publish_page(request, page)
def publish_page(request, page, status=200, template_name=None): def publish_page(request, page, status=200, template_name=None):
pages = page.get_parents_and_self() pages = page.get_parents_and_self()
@ -518,7 +519,10 @@ def publish_page(request, page, status=200, template_name=None):
if redirect_url: if redirect_url:
return HttpResponseRedirect(redirect_url) return HttpResponseRedirect(redirect_url)
cells = CellBase.get_cells(page=page, prefetch_validity_info=True) cells = CellBase.get_cells(
page=page,
select_related={'data_linkcell': ['link_page']},
prefetch_validity_info=True)
extend_with_parent_cells(cells, hierarchy=pages) extend_with_parent_cells(cells, hierarchy=pages)
cells = [x for x in cells if x.is_visible(user=request.user)] cells = [x for x in cells if x.is_visible(user=request.user)]
mark_duplicated_slugs(cells) mark_duplicated_slugs(cells)

View File

@ -324,6 +324,19 @@ WCS_CATEGORY_ASSET_SLOTS = {
WCS_FORM_ASSET_SLOTS = {} WCS_FORM_ASSET_SLOTS = {}
COMBO_CELL_ASSET_SLOTS = {
'data_feedcell': {
'picture': {
'prefix': _('Picture'),
},
},
'data_linklistcell': {
'picture': {
'prefix': _('Picture'),
},
},
}
# known services # known services
KNOWN_SERVICES = {} KNOWN_SERVICES = {}

View File

@ -1051,3 +1051,65 @@ def test_hourly():
assert check_validity.call_args_list == [mock.call()] assert check_validity.call_args_list == [mock.call()]
else: else:
assert hasattr(klass, 'check_validity') is False assert hasattr(klass, 'check_validity') is False
def test_cell_assets(settings, app, admin_user):
page = Page.objects.create(title='xxx', slug='test_cell_assets', template_name='standard')
text_cell = TextCell.objects.create(page=page, order=0, slug='foo')
list_cell = LinkListCell.objects.create(page=page, order=2, slug='bar')
item = LinkCell.objects.create(
page=page,
placeholder=list_cell.link_placeholder,
title='Example Site',
link_page=page,
order=1,
)
app = login(app)
settings.COMBO_CELL_ASSET_SLOTS = {}
resp = app.get('/manage/assets/')
assert 'have any asset yet.' in resp.text
# only text cells are defined for assets
settings.COMBO_CELL_ASSET_SLOTS = {'data_textcell': {'picture': {'prefix': 'Picture'}}}
resp = app.get('/manage/assets/')
assert u'Picture — %s' % text_cell.get_label_for_asset() in resp.text
assert u'Picture — %s' % list_cell.get_label_for_asset() not in resp.text
assert u'Picture — %s' % item.get_label_for_asset() not in resp.text
# text and list link cells are defined for assets
settings.COMBO_CELL_ASSET_SLOTS = {
'data_textcell': {'picture': {'prefix': 'Picture'}},
'data_linklistcell': {'picture': {'prefix': 'Picture'}},
'data_linkcell': {'picture': {'prefix': 'Picture', 'suffix': 'test'}},
}
resp = app.get('/manage/assets/')
assert u'Picture — %s' % text_cell.get_label_for_asset() in resp.text
assert u'Picture — %s' % list_cell.get_label_for_asset() in resp.text
# but items are excluded
assert item.get_slug_for_asset() is None # slug for asset is always None for items
assert u'Picture — %s' % item.get_label_for_asset() not in resp.text
# test slugs
link_cell = LinkCell.objects.create(
page=page,
url='http://example.net/',
order=1,
)
resp = app.get('/manage/assets/')
assert link_cell.get_slug_for_asset() == ''
assert u'Picture — %s' % link_cell.get_label_for_asset() not in resp.text
link_cell.slug = 'foo'
link_cell.save()
resp = app.get('/manage/assets/')
assert link_cell.get_slug_for_asset() == 'foo'
assert u'Picture — %s (test)' % link_cell.get_label_for_asset() in resp.text
link_cell.slug = ''
link_cell.url = ''
link_cell.link_page = page
link_cell.save()
resp = app.get('/manage/assets/')
assert link_cell.get_slug_for_asset() == 'test_cell_assets'
assert u'Picture — %s (test)' % link_cell.get_label_for_asset() in resp.text

View File

@ -352,7 +352,8 @@ def test_edit_page_navigation(app, admin_user):
assert '<li class="nav-right"' not in resp.text assert '<li class="nav-right"' not in resp.text
def test_edit_page_num_queries(app, admin_user): def test_edit_page_num_queries(settings, app, admin_user):
settings.COMBO_CELL_ASSET_SLOTS = {}
page = Page.objects.create(title='One', slug='one', parent=None, template_name='standard') page = Page.objects.create(title='One', slug='one', parent=None, template_name='standard')
page2 = Page.objects.create(title='Two', slug='two', parent=page, template_name='standard') page2 = Page.objects.create(title='Two', slug='two', parent=page, template_name='standard')
MenuCell.objects.create(page=page, order=0) MenuCell.objects.create(page=page, order=0)

View File

@ -13,7 +13,7 @@ from django.test.client import RequestFactory
from django.contrib.auth.models import User, Group, AnonymousUser from django.contrib.auth.models import User, Group, AnonymousUser
from django.utils.six import StringIO from django.utils.six import StringIO
from combo.data.models import Page from combo.data.models import Page, TextCell
from combo.apps.assets.models import Asset from combo.apps.assets.models import Asset
pytestmark = pytest.mark.django_db pytestmark = pytest.mark.django_db
@ -141,6 +141,7 @@ def test_get_group():
'{% for c in country_list|get_group:"USA" %}{{c.name}},{% endfor %}') '{% for c in country_list|get_group:"USA" %}{{c.name}},{% endfor %}')
assert t.render(context) == 'New York,Chicago,' assert t.render(context) == 'New York,Chicago,'
def test_asset_template_tags(): def test_asset_template_tags():
for path in ('uploads', 'assets'): for path in ('uploads', 'assets'):
if os.path.exists(default_storage.path(path)): if os.path.exists(default_storage.path(path)):
@ -194,6 +195,32 @@ def test_asset_template_tags():
t = Template('''{% load assets %}{% asset_url page.picture "collectivity:banner" size="200x200" %}''') t = Template('''{% load assets %}{% asset_url page.picture "collectivity:banner" size="200x200" %}''')
assert t.render(Context({'page': page})) == '/media/page-pictures/test2.svg' assert t.render(Context({'page': page})) == '/media/page-pictures/test2.svg'
cell = TextCell()
with override_settings(COMBO_CELL_ASSET_SLOTS={'data_textcell': {'picture': {'prefix': 'Picture'}}}):
# no slug
t = Template('''{% load assets %}{% get_asset cell=cell type='picture' as banner %}{% if banner %}BANNER{% endif %}''')
assert t.render(Context({'cell': cell})) == ''
# no asset
cell.slug = 'foobar'
t = Template('''{% load assets %}{% get_asset cell=cell type='picture' as banner %}{% if banner %}BANNER{% endif %}''')
assert t.render(Context({'cell': cell})) == ''
# ok
Asset.objects.create(key=cell.get_asset_slot_key('picture'), asset=File(StringIO('test'), 'test.png'))
t = Template('''{% load assets %}{% get_asset cell=cell type='picture' as banner %}{% if banner %}BANNER{% endif %}''')
assert t.render(Context({'cell': cell})) == 'BANNER'
# no context: AttributeError
t = Template('''{% load assets %}{% get_asset cell=cell type='picture' as banner %}{% if banner %}BANNER{% endif %}''')
assert t.render(Context()) == ''
with override_settings(COMBO_CELL_ASSET_SLOTS={}):
# cell type not defined in COMBO_CELL_ASSET_SLOTS
t = Template('''{% load assets %}{% get_asset cell=cell type='picture' as banner %}{% if banner %}BANNER{% endif %}''')
assert t.render(Context({'cell': cell})) == ''
def test_startswith(): def test_startswith():
t = Template('{% if foo|startswith:"bar" %}ok{% endif %}') t = Template('{% if foo|startswith:"bar" %}ok{% endif %}')
context = Context({'foo': None}) context = Context({'foo': None})