From 188e179f08cb3cdcf1c7cee62dc356681b734907 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Laur=C3=A9line=20Gu=C3=A9rin?= <zebuline@entrouvert.com>
Date: Fri, 24 Apr 2020 14:12:28 +0200
Subject: [PATCH] assets: add css classes related to assets on cells (#41995)

---
 combo/apps/assets/templatetags/assets.py      |  8 +++-
 combo/data/models.py                          | 29 ++++++++++++
 combo/public/templates/combo/placeholder.html |  2 +-
 combo/public/views.py                         | 10 ++++
 tests/test_public.py                          | 46 ++++++++++++++++++-
 5 files changed, 91 insertions(+), 4 deletions(-)

diff --git a/combo/apps/assets/templatetags/assets.py b/combo/apps/assets/templatetags/assets.py
index 52496b0..6f2e31e 100644
--- a/combo/apps/assets/templatetags/assets.py
+++ b/combo/apps/assets/templatetags/assets.py
@@ -80,12 +80,16 @@ def get_asset(context, *args, **kwargs):
 
     key = None
     if 'cell' in kwargs and 'type' in kwargs:
+        cell = kwargs['cell']
+        cell_type = kwargs['type']
         try:
-            if not kwargs['cell'].can_have_assets():
+            if not cell.can_have_assets():
                 return None
-            key = kwargs['cell'].get_asset_slot_key(kwargs['type'])
+            key = cell.get_asset_slot_key(cell_type)
         except AttributeError:
             return None
+        if hasattr(cell, '_assets'):
+            return cell._assets.get(key)
     elif len(args) == 1:
         key = args[0]
 
diff --git a/combo/data/models.py b/combo/data/models.py
index d5cd421..61c90d6 100644
--- a/combo/data/models.py
+++ b/combo/data/models.py
@@ -621,6 +621,29 @@ class CellBase(six.with_metaclass(CellMeta, models.Model)):
     def css_class_names(self):
         return ' '.join([self.class_name, self.legacy_class_name, self.extra_css_class])
 
+    @property
+    def asset_css_classes(self):
+        from combo.apps.assets.models import Asset
+
+        if not hasattr(self, '_asset_keys'):
+            self._asset_keys = self.get_asset_slot_keys()
+        if not hasattr(self, '_assets'):
+            self._assets = {a.key: a for a in Asset.objects.filter(key__in=self._asset_keys.keys())}
+
+        if not self._asset_keys or not self._assets:
+            return ''
+
+        # add has-asset-<slug> for each asset found
+        css = sorted(['has-asset-{}'.format(v) for k, v in self._asset_keys.items() if k in self._assets])
+        # add has-any-asset if at least one asset defined
+        if self._assets:
+            css.append('has-any-asset')
+        # add has-all-assets if all assets are defined
+        if self._assets.keys() == self._asset_keys.keys():
+            css.append('has-all-assets')
+
+        return ' '.join(css)
+
     def can_have_assets(self):
         return self.get_slug_for_asset() and self.get_asset_slot_templates()
 
@@ -643,6 +666,12 @@ class CellBase(six.with_metaclass(CellMeta, models.Model)):
     def get_asset_slot_templates(self):
         return settings.COMBO_CELL_ASSET_SLOTS.get(self.get_cell_type_str()) or {}
 
+    def get_asset_slot_keys(self):
+        if not self.can_have_assets():
+            return {}
+        slot_templates = self.get_asset_slot_templates()
+        return {self.get_asset_slot_key(key): key for key in slot_templates.keys()}
+
     def get_asset_slots(self):
         if not self.can_have_assets():
             return {}
diff --git a/combo/public/templates/combo/placeholder.html b/combo/public/templates/combo/placeholder.html
index 8f86b73..6a6331a 100644
--- a/combo/public/templates/combo/placeholder.html
+++ b/combo/public/templates/combo/placeholder.html
@@ -4,7 +4,7 @@
 {{ skeleton }}
 {% endif %}
 {% for cell in cells %}
-<div class="cell {{ cell.css_class_names }} {% if cell.slug %}{{cell.slug}}{% endif %} {% if cell|shown_because_admin:request %}shown-because-admin{% endif %}"
+<div class="cell {{ cell.css_class_names }} {{ cell.asset_css_classes }}{% if cell.slug %} {{cell.slug}}{% endif %}{% if cell|shown_because_admin:request %} shown-because-admin{% endif %}"
      {% if cell.slug and cell.use_slug_as_id %}id="{{ cell.slug }}"{% endif %}
      data-ajax-cell-url="{{ site_base }}{% url 'combo-public-ajax-page-cell' page_pk=cell.page_id cell_reference=cell.get_reference %}"
      data-ajax-cell-loading-message="{{ cell.loading_message }}"
diff --git a/combo/public/views.py b/combo/public/views.py
index b993645..d3fab73 100644
--- a/combo/public/views.py
+++ b/combo/public/views.py
@@ -16,6 +16,7 @@
 
 import json
 import re
+from itertools import chain
 
 import django
 from django.conf import settings
@@ -46,6 +47,7 @@ if 'mellon' in settings.INSTALLED_APPS:
 else:
     get_idps = lambda: []
 
+from combo.apps.assets.models import Asset
 from combo.data.models import (CellBase, PostException, Page, Redirect,
         ParentContentCell, TextCell, PageSnapshot)
 from combo.profile.models import Profile
@@ -536,6 +538,14 @@ def publish_page(request, page, status=200, template_name=None):
     cells = [x for x in cells if x.is_visible(user=request.user)]
     mark_duplicated_slugs(cells)
 
+    # load assets
+    for cell in cells:
+        cell._asset_keys = cell.get_asset_slot_keys()
+    asset_keys = list(set(chain(*[c._asset_keys.keys() for c in cells])))
+    assets = list(Asset.objects.filter(key__in=asset_keys))
+    for cell in cells:
+        cell._assets = {a.key: a for a in assets if a.key in cell._asset_keys.keys()}
+
     ctx = {
         'check_badges': should_check_badges(),
         'page': page,
diff --git a/tests/test_public.py b/tests/test_public.py
index 973ea87..90514a1 100644
--- a/tests/test_public.py
+++ b/tests/test_public.py
@@ -9,10 +9,11 @@ import os
 
 from django.conf import settings
 from django.contrib.auth.models import User, Group
+from django.core.files import File
 from django.urls import reverse
 from django.db import connection
 from django.utils.http import quote
-from django.utils import six
+from django.utils.six import StringIO
 from django.utils.six.moves.urllib import parse as urlparse
 from django.test import override_settings
 from django.test.utils import CaptureQueriesContext
@@ -26,6 +27,7 @@ import requests
 from combo.wsgi import application
 from combo.data.models import (Page, CellBase, TextCell, ParentContentCell,
         FeedCell, LinkCell, LinkListCell, ConfigJsonCell, Redirect, JsonCell)
+from combo.apps.assets.models import Asset
 from combo.apps.family.models import FamilyInfosCell
 
 pytestmark = pytest.mark.django_db
@@ -1008,3 +1010,45 @@ def test_missing_trailing_slashes(app):
     assert urlparse.urlparse(app.get('/foo', status=301).location).path == '/foo/'
     # don't be tricked by double slashes
     assert urlparse.urlparse(app.get('//foo', status=301).location).path == '/foo/'
+
+
+def test_cell_asset_css_classes(settings, app, admin_user):
+    settings.COMBO_CELL_ASSET_SLOTS = {}
+    page = Page.objects.create(title='Home', slug='index', template_name='standard')
+    cell = TextCell.objects.create(page=page, placeholder='content', order=0, slug='foo', text='foo')
+
+    # test direct access
+    assert cell.asset_css_classes == ''
+    # and test asset preload
+    resp = app.get('/', status=200)
+    assert 'class="cell text-cell textcell   foo"' in resp.text
+
+    settings.COMBO_CELL_ASSET_SLOTS = {'data_textcell': {'picture': {'prefix': 'Picture'}}}
+    cell = TextCell.objects.get(pk=cell.pk)
+    assert cell.asset_css_classes == ''
+    resp = app.get('/', status=200)
+    assert 'class="cell text-cell textcell   foo"' in resp.text
+
+    Asset.objects.create(key=cell.get_asset_slot_key('picture'), asset=File(StringIO('test'), 'test.png'))
+    cell = TextCell.objects.get(pk=cell.pk)
+    assert cell.asset_css_classes == 'has-asset-picture has-any-asset has-all-assets'
+    resp = app.get('/', status=200)
+    assert 'class="cell text-cell textcell  has-asset-picture has-any-asset has-all-assets foo"' in resp.text
+
+    settings.COMBO_CELL_ASSET_SLOTS = {'data_textcell': {'picture': {'prefix': 'Picture'}, 'foo': {'prefix': 'Foo'}}}
+    cell = TextCell.objects.get(pk=cell.pk)
+    assert cell.asset_css_classes == 'has-asset-picture has-any-asset'
+    resp = app.get('/', status=200)
+    assert 'class="cell text-cell textcell  has-asset-picture has-any-asset foo"' in resp.text
+
+    Asset.objects.create(key=cell.get_asset_slot_key('foo'), asset=File(StringIO('test'), 'test.png'))
+    cell = TextCell.objects.get(pk=cell.pk)
+    assert cell.asset_css_classes == 'has-asset-foo has-asset-picture has-any-asset has-all-assets'
+    resp = app.get('/', status=200)
+    assert 'class="cell text-cell textcell  has-asset-foo has-asset-picture has-any-asset has-all-assets foo"' in resp.text
+
+    Asset.objects.all().delete()
+    cell = TextCell.objects.get(pk=cell.pk)
+    assert cell.asset_css_classes == ''
+    resp = app.get('/', status=200)
+    assert 'class="cell text-cell textcell   foo"' in resp.text
-- 
2.20.1

