From 1e05cfb52377ac93d261bb067c9d49509c2fab34 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Laur=C3=A9line=20Gu=C3=A9rin?= <zebuline@entrouvert.com>
Date: Thu, 16 Jun 2022 11:17:59 +0200
Subject: [PATCH] data: add display condition to cells (#66263)

and also on each item of a linklist cell
---
 .../migrations/0004_display_condition.py      | 16 ++++
 combo/apps/calendar/models.py                 |  4 +-
 .../migrations/0004_display_condition.py      | 16 ++++
 combo/apps/dashboard/views.py                 |  2 +-
 .../migrations/0024_display_condition.py      | 31 +++++++
 combo/apps/dataviz/views.py                   |  2 +-
 .../migrations/0013_display_condition.py      | 16 ++++
 combo/apps/family/models.py                   |  6 +-
 .../migrations/0006_display_condition.py      | 16 ++++
 combo/apps/fargo/models.py                    |  6 +-
 .../migrations/0005_display_condition.py      | 21 +++++
 .../kb/migrations/0004_display_condition.py   | 16 ++++
 .../migrations/0046_display_condition.py      | 46 ++++++++++
 .../maps/migrations/0020_display_condition.py | 41 +++++++++
 combo/apps/maps/views.py                      |  2 +-
 .../migrations/0007_display_condition.py      | 26 ++++++
 combo/apps/notifications/models.py            |  6 +-
 .../migrations/0013_display_condition.py      | 16 ++++
 combo/apps/search/models.py                   |  6 +-
 .../wcs/migrations/0046_display_condition.py  | 68 +++++++++++++++
 combo/apps/wcs/models.py                      | 12 +--
 combo/data/forms.py                           | 30 ++++++-
 .../data/migrations/0056_display_condition.py | 66 ++++++++++++++
 combo/data/models.py                          | 33 +++++--
 combo/manager/forms.py                        | 21 +++++
 .../migrations/0004_display_condition.py      | 16 ++++
 combo/profile/models.py                       |  6 +-
 combo/public/templatetags/combo.py            |  2 +-
 combo/public/views.py                         |  6 +-
 combo/settings.py                             |  1 +
 tests/settings.py                             |  1 +
 tests/test_cells.py                           | 10 ++-
 tests/test_manager.py                         | 32 ++++++-
 tests/test_notification.py                    |  4 +-
 tests/test_public.py                          | 30 +++++++
 tests/test_search.py                          | 10 ++-
 tests/test_wcs.py                             | 87 +++++++++++++++++--
 37 files changed, 671 insertions(+), 59 deletions(-)
 create mode 100644 combo/apps/calendar/migrations/0004_display_condition.py
 create mode 100644 combo/apps/dashboard/migrations/0004_display_condition.py
 create mode 100644 combo/apps/dataviz/migrations/0024_display_condition.py
 create mode 100644 combo/apps/family/migrations/0013_display_condition.py
 create mode 100644 combo/apps/fargo/migrations/0006_display_condition.py
 create mode 100644 combo/apps/gallery/migrations/0005_display_condition.py
 create mode 100644 combo/apps/kb/migrations/0004_display_condition.py
 create mode 100644 combo/apps/lingo/migrations/0046_display_condition.py
 create mode 100644 combo/apps/maps/migrations/0020_display_condition.py
 create mode 100644 combo/apps/notifications/migrations/0007_display_condition.py
 create mode 100644 combo/apps/search/migrations/0013_display_condition.py
 create mode 100644 combo/apps/wcs/migrations/0046_display_condition.py
 create mode 100644 combo/data/migrations/0056_display_condition.py
 create mode 100644 combo/profile/migrations/0004_display_condition.py

diff --git a/combo/apps/calendar/migrations/0004_display_condition.py b/combo/apps/calendar/migrations/0004_display_condition.py
new file mode 100644
index 00000000..682f31c7
--- /dev/null
+++ b/combo/apps/calendar/migrations/0004_display_condition.py
@@ -0,0 +1,16 @@
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('calendar', '0003_bookingcalendar_template_name'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='bookingcalendar',
+            name='condition',
+            field=models.CharField(blank=True, max_length=1000, null=True, verbose_name='Display condition'),
+        ),
+    ]
diff --git a/combo/apps/calendar/models.py b/combo/apps/calendar/models.py
index c737926a..4fe98e8a 100644
--- a/combo/apps/calendar/models.py
+++ b/combo/apps/calendar/models.py
@@ -58,8 +58,8 @@ class BookingCalendar(CellBase):
     def is_enabled(cls):
         return settings.BOOKING_CALENDAR_CELL_ENABLED and is_chrono_enabled() and is_wcs_enabled()
 
-    def is_visible(self, **kwargs):
-        return self.agenda_reference and self.formdef_reference and super().is_visible(**kwargs)
+    def is_visible(self, request, **kwargs):
+        return self.agenda_reference and self.formdef_reference and super().is_visible(request, **kwargs)
 
     def get_cell_extra_context(self, context):
         if context.get('placeholder_search_mode'):
diff --git a/combo/apps/dashboard/migrations/0004_display_condition.py b/combo/apps/dashboard/migrations/0004_display_condition.py
new file mode 100644
index 00000000..8da55f1c
--- /dev/null
+++ b/combo/apps/dashboard/migrations/0004_display_condition.py
@@ -0,0 +1,16 @@
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('dashboard', '0003_dashboardcell_template_name'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='dashboardcell',
+            name='condition',
+            field=models.CharField(blank=True, max_length=1000, null=True, verbose_name='Display condition'),
+        ),
+    ]
diff --git a/combo/apps/dashboard/views.py b/combo/apps/dashboard/views.py
index 17db04e7..0f3c803a 100644
--- a/combo/apps/dashboard/views.py
+++ b/combo/apps/dashboard/views.py
@@ -65,7 +65,7 @@ class DashboardAddTileView(View):
         cell = CellBase.get_cell(kwargs['cell_reference'])
         if not cell.page.is_visible(request.user):
             raise PermissionDenied()
-        if not cell.is_visible(user=request.user):
+        if not cell.is_visible(request):
             raise PermissionDenied()
         cell.pk = None
         cell.page = dashboard.page
diff --git a/combo/apps/dataviz/migrations/0024_display_condition.py b/combo/apps/dataviz/migrations/0024_display_condition.py
new file mode 100644
index 00000000..7e6d360d
--- /dev/null
+++ b/combo/apps/dataviz/migrations/0024_display_condition.py
@@ -0,0 +1,31 @@
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('dataviz', '0023_statistic_has_future_data'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='chartcell',
+            name='condition',
+            field=models.CharField(blank=True, max_length=1000, null=True, verbose_name='Display condition'),
+        ),
+        migrations.AddField(
+            model_name='chartfilterscell',
+            name='condition',
+            field=models.CharField(blank=True, max_length=1000, null=True, verbose_name='Display condition'),
+        ),
+        migrations.AddField(
+            model_name='chartngcell',
+            name='condition',
+            field=models.CharField(blank=True, max_length=1000, null=True, verbose_name='Display condition'),
+        ),
+        migrations.AddField(
+            model_name='gauge',
+            name='condition',
+            field=models.CharField(blank=True, max_length=1000, null=True, verbose_name='Display condition'),
+        ),
+    ]
diff --git a/combo/apps/dataviz/views.py b/combo/apps/dataviz/views.py
index f23e584a..74bc2bea 100644
--- a/combo/apps/dataviz/views.py
+++ b/combo/apps/dataviz/views.py
@@ -45,7 +45,7 @@ class DatavizGraphView(DetailView):
 
         if not self.cell.page.is_visible(request.user):
             raise PermissionDenied()
-        if not self.cell.is_visible(user=request.user):
+        if not self.cell.is_visible(request):
             raise PermissionDenied()
         if not self.cell.statistic or not self.cell.statistic.url:
             raise Http404('misconfigured cell')
diff --git a/combo/apps/family/migrations/0013_display_condition.py b/combo/apps/family/migrations/0013_display_condition.py
new file mode 100644
index 00000000..e74a6a26
--- /dev/null
+++ b/combo/apps/family/migrations/0013_display_condition.py
@@ -0,0 +1,16 @@
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('family', '0012_booking_form_url'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='weeklyagendacell',
+            name='condition',
+            field=models.CharField(blank=True, max_length=1000, null=True, verbose_name='Display condition'),
+        ),
+    ]
diff --git a/combo/apps/family/models.py b/combo/apps/family/models.py
index e054dbff..03b21fdd 100644
--- a/combo/apps/family/models.py
+++ b/combo/apps/family/models.py
@@ -94,11 +94,11 @@ class WeeklyAgendaCell(JsonCellBase):
             )
         )
 
-    def is_visible(self, **kwargs):
-        user = kwargs.get('user')
+    def is_visible(self, request, **kwargs):
+        user = getattr(request, 'user', None)
         if not user or user.is_anonymous:
             return False
-        return super().is_visible(**kwargs)
+        return super().is_visible(request, **kwargs)
 
     def get_cell_extra_context(self, context):
         if context.get('placeholder_search_mode'):
diff --git a/combo/apps/fargo/migrations/0006_display_condition.py b/combo/apps/fargo/migrations/0006_display_condition.py
new file mode 100644
index 00000000..ec55346d
--- /dev/null
+++ b/combo/apps/fargo/migrations/0006_display_condition.py
@@ -0,0 +1,16 @@
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('fargo', '0005_recentdocumentscell_template_name'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='recentdocumentscell',
+            name='condition',
+            field=models.CharField(blank=True, max_length=1000, null=True, verbose_name='Display condition'),
+        ),
+    ]
diff --git a/combo/apps/fargo/models.py b/combo/apps/fargo/models.py
index b2c86e81..ba7b3fd9 100644
--- a/combo/apps/fargo/models.py
+++ b/combo/apps/fargo/models.py
@@ -58,11 +58,11 @@ class RecentDocumentsCell(CellBase):
             self.__class__, fields=self.get_form_fields(), widgets=self.get_form_widgets()
         )
 
-    def is_visible(self, **kwargs):
-        user = kwargs.get('user')
+    def is_visible(self, request, **kwargs):
+        user = getattr(request, 'user', None)
         if not user or user.is_anonymous:
             return False
-        return super().is_visible(**kwargs)
+        return super().is_visible(request, **kwargs)
 
     @classmethod
     def is_enabled(cls):
diff --git a/combo/apps/gallery/migrations/0005_display_condition.py b/combo/apps/gallery/migrations/0005_display_condition.py
new file mode 100644
index 00000000..39f29eec
--- /dev/null
+++ b/combo/apps/gallery/migrations/0005_display_condition.py
@@ -0,0 +1,21 @@
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('gallery', '0004_auto_20210723_1318'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='gallerycell',
+            name='condition',
+            field=models.CharField(blank=True, max_length=1000, null=True, verbose_name='Display condition'),
+        ),
+        migrations.AlterField(
+            model_name='image',
+            name='image',
+            field=models.ImageField(upload_to='uploads/gallery/%Y/%m/', verbose_name='Image'),
+        ),
+    ]
diff --git a/combo/apps/kb/migrations/0004_display_condition.py b/combo/apps/kb/migrations/0004_display_condition.py
new file mode 100644
index 00000000..2f7d609d
--- /dev/null
+++ b/combo/apps/kb/migrations/0004_display_condition.py
@@ -0,0 +1,16 @@
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('kb', '0003_latestpageupdatescell_template_name'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='latestpageupdatescell',
+            name='condition',
+            field=models.CharField(blank=True, max_length=1000, null=True, verbose_name='Display condition'),
+        ),
+    ]
diff --git a/combo/apps/lingo/migrations/0046_display_condition.py b/combo/apps/lingo/migrations/0046_display_condition.py
new file mode 100644
index 00000000..3fa20837
--- /dev/null
+++ b/combo/apps/lingo/migrations/0046_display_condition.py
@@ -0,0 +1,46 @@
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('lingo', '0045_basketitem_remote_item_id'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='activeitems',
+            name='condition',
+            field=models.CharField(blank=True, max_length=1000, null=True, verbose_name='Display condition'),
+        ),
+        migrations.AddField(
+            model_name='itemshistory',
+            name='condition',
+            field=models.CharField(blank=True, max_length=1000, null=True, verbose_name='Display condition'),
+        ),
+        migrations.AddField(
+            model_name='lingobasketcell',
+            name='condition',
+            field=models.CharField(blank=True, max_length=1000, null=True, verbose_name='Display condition'),
+        ),
+        migrations.AddField(
+            model_name='lingobasketlinkcell',
+            name='condition',
+            field=models.CharField(blank=True, max_length=1000, null=True, verbose_name='Display condition'),
+        ),
+        migrations.AddField(
+            model_name='lingorecenttransactionscell',
+            name='condition',
+            field=models.CharField(blank=True, max_length=1000, null=True, verbose_name='Display condition'),
+        ),
+        migrations.AddField(
+            model_name='selfdeclaredinvoicepayment',
+            name='condition',
+            field=models.CharField(blank=True, max_length=1000, null=True, verbose_name='Display condition'),
+        ),
+        migrations.AddField(
+            model_name='tipipaymentformcell',
+            name='condition',
+            field=models.CharField(blank=True, max_length=1000, null=True, verbose_name='Display condition'),
+        ),
+    ]
diff --git a/combo/apps/maps/migrations/0020_display_condition.py b/combo/apps/maps/migrations/0020_display_condition.py
new file mode 100644
index 00000000..1aea1ff0
--- /dev/null
+++ b/combo/apps/maps/migrations/0020_display_condition.py
@@ -0,0 +1,41 @@
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('maps', '0019_auto_20211104_1603'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='map',
+            name='condition',
+            field=models.CharField(blank=True, max_length=1000, null=True, verbose_name='Display condition'),
+        ),
+        migrations.AlterField(
+            model_name='maplayer',
+            name='tiles_attribution',
+            field=models.CharField(
+                blank=True,
+                help_text=(
+                    'Par exemple\xa0: Map data &amp;copy; &lt;a href=&quot;https://openstreetmap.org&quot;&gt;OpenStreetMap&lt;/a&gt; contributors, '
+                    '&lt;a href=&quot;http://creativecommons.org/licenses/by-sa/2.0/&quot;&gt;CC-BY-SA&lt;/a&gt;'
+                ),
+                max_length=1024,
+                null=True,
+                verbose_name='Attribution',
+            ),
+        ),
+        migrations.AlterField(
+            model_name='maplayer',
+            name='tiles_template_url',
+            field=models.CharField(
+                blank=True,
+                help_text='Par exemple\xa0: https://tiles.entrouvert.org/hdm/{z}/{x}/{y}.png',
+                max_length=1024,
+                null=True,
+                verbose_name='Tiles URL',
+            ),
+        ),
+    ]
diff --git a/combo/apps/maps/views.py b/combo/apps/maps/views.py
index 84069796..681a87d5 100644
--- a/combo/apps/maps/views.py
+++ b/combo/apps/maps/views.py
@@ -27,7 +27,7 @@ class GeojsonView(View):
     def get(self, request, *args, **kwargs):
         cell = get_object_or_404(Map, pk=kwargs['cell_id'])
         layer = get_object_or_404(cell.layers.all(), kind='geojson', slug=kwargs['layer_slug'])
-        if not cell.page.is_visible(request.user) or not cell.is_visible(user=request.user):
+        if not cell.page.is_visible(request.user) or not cell.is_visible(request):
             return HttpResponseForbidden()
         options = cell.maplayeroptions_set.get(map_layer=layer)
         geojson = layer.get_geojson(request, options.properties)
diff --git a/combo/apps/notifications/migrations/0007_display_condition.py b/combo/apps/notifications/migrations/0007_display_condition.py
new file mode 100644
index 00000000..6be59a1f
--- /dev/null
+++ b/combo/apps/notifications/migrations/0007_display_condition.py
@@ -0,0 +1,26 @@
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('notifications', '0006_auto_20210723_1318'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='notificationscell',
+            name='condition',
+            field=models.CharField(blank=True, max_length=1000, null=True, verbose_name='Display condition'),
+        ),
+        migrations.AlterField(
+            model_name='notification',
+            name='body',
+            field=models.TextField(blank=True, default='', verbose_name='Body'),
+        ),
+        migrations.AlterField(
+            model_name='notification',
+            name='url',
+            field=models.URLField(blank=True, default='', verbose_name='URL'),
+        ),
+    ]
diff --git a/combo/apps/notifications/models.py b/combo/apps/notifications/models.py
index 5823cfe7..8c7a8c4a 100644
--- a/combo/apps/notifications/models.py
+++ b/combo/apps/notifications/models.py
@@ -176,11 +176,11 @@ class NotificationsCell(CellBase):
     class Meta:
         verbose_name = _('User Notifications')
 
-    def is_visible(self, **kwargs):
-        user = kwargs.get('user')
+    def is_visible(self, request, **kwargs):
+        user = getattr(request, 'user', None)
         if user is None or not user.is_authenticated:
             return False
-        return super().is_visible(**kwargs)
+        return super().is_visible(request, **kwargs)
 
     def get_cell_extra_context(self, context):
         extra_context = super().get_cell_extra_context(context)
diff --git a/combo/apps/search/migrations/0013_display_condition.py b/combo/apps/search/migrations/0013_display_condition.py
new file mode 100644
index 00000000..f12b0d69
--- /dev/null
+++ b/combo/apps/search/migrations/0013_display_condition.py
@@ -0,0 +1,16 @@
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('search', '0012_auto_20220105_0832'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='searchcell',
+            name='condition',
+            field=models.CharField(blank=True, max_length=1000, null=True, verbose_name='Display condition'),
+        ),
+    ]
diff --git a/combo/apps/search/models.py b/combo/apps/search/models.py
index d0a8a8c6..5dd612ac 100644
--- a/combo/apps/search/models.py
+++ b/combo/apps/search/models.py
@@ -60,10 +60,10 @@ class SearchCell(CellBase):
     class Meta:
         verbose_name = _('Search')
 
-    def is_visible(self, **kwargs):
+    def is_visible(self, request, **kwargs):
         if not self._search_services.get('data'):
             return False
-        return super().is_visible(**kwargs)
+        return super().is_visible(request, **kwargs)
 
     def get_default_form_class(self):
         from .forms import SearchCellForm
@@ -176,7 +176,7 @@ class SearchCell(CellBase):
     @classmethod
     def ajax_results_view(cls, request, cell_pk, service_slug):
         cell = get_object_or_404(cls, pk=cell_pk)
-        if not cell.is_visible(user=request.user) or not cell.page.is_visible(request.user):
+        if not cell.is_visible(request) or not cell.page.is_visible(request.user):
             raise PermissionDenied
 
         query = request.GET.get('q')
diff --git a/combo/apps/wcs/migrations/0046_display_condition.py b/combo/apps/wcs/migrations/0046_display_condition.py
new file mode 100644
index 00000000..a92878c8
--- /dev/null
+++ b/combo/apps/wcs/migrations/0046_display_condition.py
@@ -0,0 +1,68 @@
+# Generated by Django 2.2.26 on 2022-06-15 05:51
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('wcs', '0045_wcscareformscell_cache_duration'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='backofficesubmissioncell',
+            name='condition',
+            field=models.CharField(blank=True, max_length=1000, null=True, verbose_name='Display condition'),
+        ),
+        migrations.AddField(
+            model_name='categoriescell',
+            name='condition',
+            field=models.CharField(blank=True, max_length=1000, null=True, verbose_name='Display condition'),
+        ),
+        migrations.AddField(
+            model_name='trackingcodeinputcell',
+            name='condition',
+            field=models.CharField(blank=True, max_length=1000, null=True, verbose_name='Display condition'),
+        ),
+        migrations.AddField(
+            model_name='wcscardinfoscell',
+            name='condition',
+            field=models.CharField(blank=True, max_length=1000, null=True, verbose_name='Display condition'),
+        ),
+        migrations.AddField(
+            model_name='wcscardscell',
+            name='condition',
+            field=models.CharField(blank=True, max_length=1000, null=True, verbose_name='Display condition'),
+        ),
+        migrations.AddField(
+            model_name='wcscareformscell',
+            name='condition',
+            field=models.CharField(blank=True, max_length=1000, null=True, verbose_name='Display condition'),
+        ),
+        migrations.AddField(
+            model_name='wcscategorycell',
+            name='condition',
+            field=models.CharField(blank=True, max_length=1000, null=True, verbose_name='Display condition'),
+        ),
+        migrations.AddField(
+            model_name='wcscurrentdraftscell',
+            name='condition',
+            field=models.CharField(blank=True, max_length=1000, null=True, verbose_name='Display condition'),
+        ),
+        migrations.AddField(
+            model_name='wcscurrentformscell',
+            name='condition',
+            field=models.CharField(blank=True, max_length=1000, null=True, verbose_name='Display condition'),
+        ),
+        migrations.AddField(
+            model_name='wcsformcell',
+            name='condition',
+            field=models.CharField(blank=True, max_length=1000, null=True, verbose_name='Display condition'),
+        ),
+        migrations.AddField(
+            model_name='wcsformsofcategorycell',
+            name='condition',
+            field=models.CharField(blank=True, max_length=1000, null=True, verbose_name='Display condition'),
+        ),
+    ]
diff --git a/combo/apps/wcs/models.py b/combo/apps/wcs/models.py
index 3473dbd5..8f2f4915 100644
--- a/combo/apps/wcs/models.py
+++ b/combo/apps/wcs/models.py
@@ -414,11 +414,11 @@ class WcsUserDataBaseCell(WcsDataBaseCell):
     class Meta:
         abstract = True
 
-    def is_visible(self, **kwargs):
-        user = kwargs.get('user')
+    def is_visible(self, request, **kwargs):
+        user = getattr(request, 'user', None)
         if not user or user.is_anonymous:
             return False
-        return super().is_visible(**kwargs)
+        return super().is_visible(request, **kwargs)
 
 
 class CategoriesAndWcsSiteValidityMixin:
@@ -886,11 +886,11 @@ class WcsCardsCell(CardMixin, WcsBlurpMixin, CellBase):
 
         populate_cache()
 
-    def is_visible(self, **kwargs):
-        user = kwargs.get('user')
+    def is_visible(self, request, **kwargs):
+        user = getattr(request, 'user', None)
         if self.only_for_user and (not user or user.is_anonymous):
             return False
-        return super().is_visible(**kwargs)
+        return super().is_visible(request, **kwargs)
 
     def get_api_url(self, context):
         parts = self.carddef_reference.split(':')
diff --git a/combo/data/forms.py b/combo/data/forms.py
index 96a4059a..d07bae8c 100644
--- a/combo/data/forms.py
+++ b/combo/data/forms.py
@@ -17,6 +17,9 @@
 import copy
 
 from django import forms
+from django.conf import settings
+from django.template import Template, TemplateSyntaxError
+from django.utils.translation import ugettext_lazy as _
 
 from combo.utils import cache_during_request
 
@@ -52,7 +55,29 @@ class LinkCellForm(forms.ModelForm):
 class LinkCellForLinkListCellForm(LinkCellForm):
     class Meta:
         model = LinkCell
-        fields = ('title', 'url', 'link_page', 'anchor', 'bypass_url_validity_check', 'extra_css_class')
+        fields = (
+            'title',
+            'url',
+            'link_page',
+            'anchor',
+            'bypass_url_validity_check',
+            'extra_css_class',
+            'condition',
+        )
+
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        if not settings.CELL_CONDITIONS_ENABLED:
+            del self.fields['condition']
+
+    def clean_condition(self):
+        condition = self.cleaned_data['condition']
+        try:
+            Template('{%% if %s %%}OK{%% endif %%}' % condition)
+        except TemplateSyntaxError:
+            raise forms.ValidationError(_('Invalid syntax.'))
+
+        return condition
 
 
 class LinkListCellForm(forms.ModelForm):
@@ -60,9 +85,6 @@ class LinkListCellForm(forms.ModelForm):
         model = LinkListCell
         fields = ['limit']
 
-    def __init__(self, *args, **kwargs):
-        super().__init__(*args, **kwargs)
-
 
 class ConfigJsonForm(forms.ModelForm):
     formdef = []
diff --git a/combo/data/migrations/0056_display_condition.py b/combo/data/migrations/0056_display_condition.py
new file mode 100644
index 00000000..b3bf92a6
--- /dev/null
+++ b/combo/data/migrations/0056_display_condition.py
@@ -0,0 +1,66 @@
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('data', '0055_page_placeholer_options'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='configjsoncell',
+            name='condition',
+            field=models.CharField(blank=True, max_length=1000, null=True, verbose_name='Display condition'),
+        ),
+        migrations.AddField(
+            model_name='feedcell',
+            name='condition',
+            field=models.CharField(blank=True, max_length=1000, null=True, verbose_name='Display condition'),
+        ),
+        migrations.AddField(
+            model_name='fortunecell',
+            name='condition',
+            field=models.CharField(blank=True, max_length=1000, null=True, verbose_name='Display condition'),
+        ),
+        migrations.AddField(
+            model_name='jsoncell',
+            name='condition',
+            field=models.CharField(blank=True, max_length=1000, null=True, verbose_name='Display condition'),
+        ),
+        migrations.AddField(
+            model_name='linkcell',
+            name='condition',
+            field=models.CharField(blank=True, max_length=1000, null=True, verbose_name='Display condition'),
+        ),
+        migrations.AddField(
+            model_name='linklistcell',
+            name='condition',
+            field=models.CharField(blank=True, max_length=1000, null=True, verbose_name='Display condition'),
+        ),
+        migrations.AddField(
+            model_name='menucell',
+            name='condition',
+            field=models.CharField(blank=True, max_length=1000, null=True, verbose_name='Display condition'),
+        ),
+        migrations.AddField(
+            model_name='parentcontentcell',
+            name='condition',
+            field=models.CharField(blank=True, max_length=1000, null=True, verbose_name='Display condition'),
+        ),
+        migrations.AddField(
+            model_name='textcell',
+            name='condition',
+            field=models.CharField(blank=True, max_length=1000, null=True, verbose_name='Display condition'),
+        ),
+        migrations.AddField(
+            model_name='unlockmarkercell',
+            name='condition',
+            field=models.CharField(blank=True, max_length=1000, null=True, verbose_name='Display condition'),
+        ),
+        migrations.AlterField(
+            model_name='linkcell',
+            name='title',
+            field=models.CharField(blank=True, max_length=150, verbose_name='Label'),
+        ),
+    ]
diff --git a/combo/data/models.py b/combo/data/models.py
index c08f3f58..66bca183 100644
--- a/combo/data/models.py
+++ b/combo/data/models.py
@@ -806,6 +806,7 @@ class CellBase(models.Model, metaclass=CellMeta):
     slug = models.SlugField(_('Slug'), blank=True)
     extra_css_class = models.CharField(_('Extra classes for CSS styling'), max_length=100, blank=True)
     template_name = models.CharField(_('Cell Template'), max_length=50, blank=True, null=True)
+    condition = models.CharField(_('Display condition'), max_length=1000, blank=True, null=True)
 
     public = models.BooleanField(_('Public'), default=True)
     # restricted_to_unlogged is actually an invert switch, it is used for mark
@@ -1151,6 +1152,7 @@ class CellBase(models.Model, metaclass=CellMeta):
                 'last_update_timestamp',
                 'restricted_to_unlogged',
                 'template_name',
+                'condition',
             )
             + tuple(self.get_appearance_fields())
         ]
@@ -1325,10 +1327,25 @@ class CellBase(models.Model, metaclass=CellMeta):
             and validity_info.invalid_datetime <= now()
         )
 
-    def is_visible(self, user=None, check_validity_info=True):
+    def compute_condition(self, request):
+        condition = self.condition
+        if not condition:
+            return True
+        context = RequestContext(request)
+        if condition in self.page.extra_variables:
+            condition = self.page.extra_variables[condition].strip('{ }')
+        try:
+            return Template('{%% if %s %%}OK{%% endif %%}' % condition).render(context) == 'OK'
+        except (TemplateSyntaxError, VariableDoesNotExist):
+            return False
+
+    def is_visible(self, request, check_validity_info=True):
+        condition = self.compute_condition(request=request)
+        if not condition:
+            return False
         if check_validity_info and self.is_hidden_because_invalid():
             return False
-        return element_is_visible(self, user=user)
+        return element_is_visible(self, user=getattr(request, 'user', None))
 
     def is_relevant(self, context):
         """Return whether it's relevant to render this cell in the page
@@ -1842,9 +1859,9 @@ class LinkListCell(CellBase):
         for link in self.get_items():
             link.duplicate(page_target=new_cell.page, placeholder=new_cell.link_placeholder)
 
-    def is_visible(self, check_validity_info=True, **kwargs):
+    def is_visible(self, request, check_validity_info=True, **kwargs):
         # cell is visible even if items are invalid
-        return super().is_visible(check_validity_info=False, **kwargs)
+        return super().is_visible(request, check_validity_info=False, **kwargs)
 
     def check_validity(self):
         for link in self.get_items(prefetch_validity_info=True):
@@ -1880,8 +1897,8 @@ class FeedCell(CellBase):
     class Meta:
         verbose_name = _('RSS/Atom Feed')
 
-    def is_visible(self, **kwargs):
-        return bool(self.url) and super().is_visible(**kwargs)
+    def is_visible(self, *args, **kwargs):
+        return bool(self.url) and super().is_visible(*args, **kwargs)
 
     def save(self, *args, **kwargs):
         result = super().save(*args, **kwargs)
@@ -2017,8 +2034,8 @@ class JsonCellBase(CellBase):
             self.mark_as_valid()
         return result
 
-    def is_visible(self, **kwargs):
-        return bool(self.url) and super().is_visible(**kwargs)
+    def is_visible(self, *args, **kwargs):
+        return bool(self.url) and super().is_visible(*args, **kwargs)
 
     def is_user_dependant(self, context=None):
         urls = [self.url] + [x['url'] for x in self.additional_data or []]
diff --git a/combo/manager/forms.py b/combo/manager/forms.py
index ddd44514..9d5fc15d 100644
--- a/combo/manager/forms.py
+++ b/combo/manager/forms.py
@@ -276,6 +276,12 @@ class CellVisibilityForm(forms.Form):
         ],
     )
     groups = forms.MultipleChoiceField(label=_('Groups'), required=False, choices=get_groups_as_choices)
+    condition = forms.CharField(
+        label=_('Display condition'),
+        widget=forms.TextInput(attrs={'class': 'text-wide'}),
+        max_length=1000,
+        required=False,
+    )
 
     def __init__(self, *args, **kwargs):
         self.instance = instance = kwargs.pop('instance')
@@ -293,7 +299,21 @@ class CellVisibilityForm(forms.Form):
                     initial['visibility'] = 'groups-none'
                 else:
                     initial['visibility'] = 'groups-any'
+        initial['condition'] = instance.condition
         super().__init__(*args, **kwargs)
+        if not settings.CELL_CONDITIONS_ENABLED:
+            del self.fields['condition']
+
+    def clean_condition(self):
+        condition = self.cleaned_data['condition']
+        if not condition:
+            return condition
+        try:
+            Template('{%% if %s %%}OK{%% endif %%}' % condition)
+        except TemplateSyntaxError:
+            raise ValidationError(_('Invalid syntax.'))
+
+        return condition
 
     def save(self):
         if self.cleaned_data['visibility'] == 'all':
@@ -316,6 +336,7 @@ class CellVisibilityForm(forms.Form):
             self.instance.public = False
             self.instance.restricted_to_unlogged = True
             self.instance.groups.set(self.cleaned_data['groups'])
+        self.instance.condition = self.cleaned_data.get('condition')
         self.instance.save()
         return self.instance
 
diff --git a/combo/profile/migrations/0004_display_condition.py b/combo/profile/migrations/0004_display_condition.py
new file mode 100644
index 00000000..bc1d4092
--- /dev/null
+++ b/combo/profile/migrations/0004_display_condition.py
@@ -0,0 +1,16 @@
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('profile', '0003_profilecell_template_name'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='profilecell',
+            name='condition',
+            field=models.CharField(blank=True, max_length=1000, null=True, verbose_name='Display condition'),
+        ),
+    ]
diff --git a/combo/profile/models.py b/combo/profile/models.py
index 29cae2ed..ea467cb7 100644
--- a/combo/profile/models.py
+++ b/combo/profile/models.py
@@ -45,11 +45,11 @@ class ProfileCell(JsonCellBase):
         idp = list(settings.KNOWN_SERVICES.get('authentic').values())[0]
         return '%sapi/users/{{ concerned_user|name_id }}/' % idp.get('url')
 
-    def is_visible(self, **kwargs):
-        user = kwargs.get('user')
+    def is_visible(self, request, **kwargs):
+        user = getattr(request, 'user', None)
         if not user or user.is_anonymous:
             return False
-        return super().is_visible(**kwargs)
+        return super().is_visible(request, **kwargs)
 
     def get_cell_extra_context(self, context):
         extra_context = super().get_cell_extra_context(context)
diff --git a/combo/public/templatetags/combo.py b/combo/public/templatetags/combo.py
index 42ae69f8..947261ba 100644
--- a/combo/public/templatetags/combo.py
+++ b/combo/public/templatetags/combo.py
@@ -99,7 +99,7 @@ def placeholder(context, placeholder_name, **options):
         and (
             context.get('render_skeleton')
             or x.is_relevant(context)
-            and x.is_visible(user=context['request'].user, check_validity_info=False)
+            and x.is_visible(context['request'], check_validity_info=False)
         )
     ]
     if context.get('render_skeleton'):
diff --git a/combo/public/views.py b/combo/public/views.py
index 49101071..062f835b 100644
--- a/combo/public/views.py
+++ b/combo/public/views.py
@@ -129,7 +129,7 @@ def ajax_page_cell(request, page_pk, cell_reference):
     except ObjectDoesNotExist:
         raise Http404()
 
-    if not cell.is_visible(user=request.user):
+    if not cell.is_visible(request):
         raise PermissionDenied()
 
     exception = None
@@ -187,7 +187,7 @@ def render_cell(request, cell):
                     placeholder__in=['_auto_tile', '_dashboard', '_suggested_tile']
                 )
             )
-        other_cells = [x for x in other_cells if x.is_visible(user=request.user)]
+        other_cells = [x for x in other_cells if x.is_visible(request)]
         other_cells.sort(key=lambda x: x.order)
         for other_cell in other_cells:
             if other_cell.get_reference() != cell.get_reference():
@@ -554,7 +554,7 @@ def publish_page(request, page, status=200, template_name=None):
         cells_exclude=Q(placeholder__in=['_auto_tile', '_dashboard', '_suggested_tile']),
     )
     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(request)]
     mark_duplicated_slugs(cells)
 
     # load assets
diff --git a/combo/settings.py b/combo/settings.py
index 8ab1d055..49c81c84 100644
--- a/combo/settings.py
+++ b/combo/settings.py
@@ -367,6 +367,7 @@ PWA_NOTIFICATION_ICON_URL = None
 BOOKING_CALENDAR_CELL_ENABLED = False
 LEGACY_CHART_CELL_ENABLED = False
 PUBLIK_FAMILY_CELL_ENABLED = False
+CELL_CONDITIONS_ENABLED = False
 
 # and enable others
 CHART_FILTERS_CELL_ENABLED = True
diff --git a/tests/settings.py b/tests/settings.py
index 230a7441..2b30aaea 100644
--- a/tests/settings.py
+++ b/tests/settings.py
@@ -90,6 +90,7 @@ BOOKING_CALENDAR_CELL_ENABLED = True
 LEGACY_CHART_CELL_ENABLED = True
 PUBLIK_FAMILY_CELL_ENABLED = True
 CHART_FILTERS_CELL_ENABLED = True
+CELL_CONDITIONS_ENABLED = True
 
 USER_PROFILE_CONFIG = {
     'fields': [
diff --git a/tests/test_cells.py b/tests/test_cells.py
index e0cd2f02..ab7eca8d 100644
--- a/tests/test_cells.py
+++ b/tests/test_cells.py
@@ -330,7 +330,8 @@ def test_link_list_cell_validity():
     assert validity_info.invalid_since is not None
     validity_info.invalid_since = now() - datetime.timedelta(days=2)
     validity_info.save()
-    assert cell.is_visible()  # particular case: cell is visible
+    request = RequestFactory().get('/')
+    assert cell.is_visible(request)  # particular case: cell is visible
 
 
 def test_feed_cell_validity(context):
@@ -1559,21 +1560,22 @@ def test_link_list_cell_duplicate():
 
 
 def test_cell_is_visible():
+    request = RequestFactory().get('/')
     page = Page.objects.create()
     cell = TextCell.objects.create(page=page, order=0)
-    assert cell.is_visible() is True
+    assert cell.is_visible(request) is True
 
     # invalid cell since just now
     validity_info = ValidityInfo.objects.create(content_object=cell)
     validity_info.invalid_reason_code = 'FOO'
     validity_info.invalid_since = now()
     validity_info.save()
-    assert cell.is_visible() is True
+    assert cell.is_visible(request) is True
 
     # invalid cell since two days
     validity_info.invalid_since = now() - datetime.timedelta(days=2)
     validity_info.save()
-    assert cell.is_visible() is False
+    assert cell.is_visible(request) is False
 
 
 def test_cell_invalidity_marker():
diff --git a/tests/test_manager.py b/tests/test_manager.py
index 2b41ac85..f5495870 100644
--- a/tests/test_manager.py
+++ b/tests/test_manager.py
@@ -1491,7 +1491,9 @@ def test_duplicate_cell(app, admin_user):
     assert PageSnapshot.objects.filter(page=other_page).count() == 2
 
 
-def test_edit_cell_visibility(app, admin_user):
+def test_edit_cell_visibility(settings, app, admin_user):
+    settings.CELL_CONDITIONS_ENABLED = False
+
     Page.objects.all().delete()
     page = Page(title='One', slug='one', template_name='standard')
     page.save()
@@ -1501,6 +1503,7 @@ def test_edit_cell_visibility(app, admin_user):
     app = login(app)
     resp = app.get('/manage/pages/%s/' % page.id)
     assert resp.form['cdata_textcell-%s-visibility' % cell.id].value == 'all'
+    assert 'cdata_textcell-%s-condition' % cell.id not in resp
     resp.form['cdata_textcell-%s-visibility' % cell.id] = 'logged'
     resp = resp.form.submit('submit')
     assert TextCell.objects.get(id=cell.id).public is False
@@ -1546,6 +1549,17 @@ def test_edit_cell_visibility(app, admin_user):
     assert TextCell.objects.get(id=cell.id).groups.count() == 1
     assert TextCell.objects.get(id=cell.id).groups.all()[0].name == 'Another group'
 
+    settings.CELL_CONDITIONS_ENABLED = True
+    resp = app.get('/manage/pages/%s/' % page.id)
+    resp.form['cdata_textcell-%s-condition' % cell.pk] = 'False #'
+    resp = resp.form.submit('submit')
+    assert resp.json['errorlist']['visibility']['condition'] == ['Invalid syntax.']
+
+    resp = app.get('/manage/pages/%s/' % page.id)
+    resp.form['cdata_textcell-%s-condition' % cell.pk] = 'False'
+    resp = resp.form.submit('submit')
+    assert TextCell.objects.get(id=cell.id).condition == 'False'
+
 
 def test_edit_cell_options(app, admin_user):
     Page.objects.all().delete()
@@ -2648,7 +2662,8 @@ def test_link_cell_validation():
     assert form.errors['url'] == ["syntax error: Could not parse the remainder: '{test_url' from '{test_url'"]
 
 
-def test_add_edit_delete_list_link_item(app, admin_user):
+def test_add_edit_delete_list_link_item(settings, app, admin_user):
+    settings.CELL_CONDITIONS_ENABLED = False
     Page.objects.all().delete()
     page = Page.objects.create(title='One', slug='one', template_name='standard')
     cell = LinkListCell.objects.create(order=0, placeholder='content', page=page)
@@ -2657,6 +2672,7 @@ def test_add_edit_delete_list_link_item(app, admin_user):
     assert PageSnapshot.objects.count() == 0
 
     resp = resp.click(href='.*/add-link/link$')
+    assert 'condition' not in resp.context['form'].fields
     resp.forms[0]['title'] = 'Hello world'
     resp.forms[0]['url'] = 'http://example.com'
     resp.forms[0]['extra_css_class'] = 'foobar'
@@ -2693,6 +2709,18 @@ def test_add_edit_delete_list_link_item(app, admin_user):
     assert LinkCell.objects.count() == 0
     assert PageSnapshot.objects.count() == 3
 
+    settings.CELL_CONDITIONS_ENABLED = True
+    resp = resp.follow()
+    resp = resp.click(href='.*/add-link/link$')
+    resp.forms[0]['condition'] = 'False #'
+    resp = resp.forms[0].submit()
+    assert resp.context['form'].errors['condition'] == ['Invalid syntax.']
+
+    resp.forms[0]['condition'] = 'True'
+    resp = resp.forms[0].submit()
+    item = LinkCell.objects.get()
+    assert item.condition == 'True'
+
 
 def test_edit_link_list_order(app, admin_user):
     Page.objects.all().delete()
diff --git a/tests/test_notification.py b/tests/test_notification.py
index 3b0b5f3b..b9caed0e 100644
--- a/tests/test_notification.py
+++ b/tests/test_notification.py
@@ -93,9 +93,9 @@ def test_notification_cell(app, john_doe, jane_doe):
     context['synchronous'] = True  # to get fresh content
 
     context['request'].user = None
-    assert cell.is_visible(user=context['request'].user) is False
+    assert cell.is_visible(context['request']) is False
     context['request'].user = john_doe
-    assert cell.is_visible(user=context['request'].user) is True
+    assert cell.is_visible(context['request']) is True
     assert cell.get_badge(context) is None
 
     notification1 = Notification.notify(john_doe, 'notibar')
diff --git a/tests/test_public.py b/tests/test_public.py
index 7987d075..8d690593 100644
--- a/tests/test_public.py
+++ b/tests/test_public.py
@@ -138,6 +138,36 @@ def test_page_contents_group_absence(app, normal_user):
     assert 'Foobar' not in resp.text
 
 
+def test_page_contents_condition(app, normal_user):
+    page = Page.objects.create(title='Home', slug='index', template_name='standard')
+    TextCell.objects.create(page=page, placeholder='content', text='Foobar', order=0, condition='True')
+    TextCell.objects.create(page=page, placeholder='content', text='Foobaz', order=1, condition='False')
+    cell3 = LinkListCell.objects.create(order=1, page=page, placeholder='content')
+    LinkCell.objects.create(
+        page=page,
+        placeholder=cell3.link_placeholder,
+        title='Example Site',
+        url='http://example.net/',
+        order=0,
+        condition='True',
+    )
+    LinkCell.objects.create(
+        page=page,
+        placeholder=cell3.link_placeholder,
+        title='Other Site',
+        url='http://other.net/',
+        order=1,
+        condition='False',
+    )
+
+    app = login(app, username='normal-user', password='normal-user')
+    resp = app.get('/', status=200)
+    assert 'Foobar' in resp.text
+    assert 'Foobaz' not in resp.text
+    assert 'Example Site' in resp.text
+    assert 'Other Site' not in resp.text
+
+
 def test_page_footer_acquisition(app):
     Page.objects.all().delete()
     page = Page(title='Home', slug='index', template_name='standard')
diff --git a/tests/test_search.py b/tests/test_search.py
index ed614150..7c9a2209 100644
--- a/tests/test_search.py
+++ b/tests/test_search.py
@@ -313,14 +313,15 @@ def test_search_custom_templates(app):
 
 
 def test_search_cell_visibility(settings, app):
+    request = RequestFactory().get('/')
     page = Page.objects.create(title='example page', slug='example-page')
     settings.COMBO_SEARCH_SERVICES = SEARCH_SERVICES
 
     cell = SearchCell(page=page, order=0)
-    assert not cell.is_visible()
+    assert not cell.is_visible(request)
 
     cell._search_services = {'data': ['_text']}
-    assert cell.is_visible()
+    assert cell.is_visible(request)
 
 
 def test_search_contents():
@@ -1394,6 +1395,7 @@ def test_index_site_inactive_placeholder(app):
 
 
 def test_index_site_invalid_cell(app):
+    request = RequestFactory().get('/')
     page = Page.objects.create(title='page', slug='example-page')
     cell = TextCell.objects.create(page=page, placeholder='content', text='<p>foobar</p>', order=0)
 
@@ -1402,7 +1404,7 @@ def test_index_site_invalid_cell(app):
 
     # invalid cell since just now
     cell.mark_as_invalid(reason_code='foobar')
-    assert cell.is_visible() is True
+    assert cell.is_visible(request) is True
     index_site()
     assert IndexedCell.objects.count() == 1
 
@@ -1410,7 +1412,7 @@ def test_index_site_invalid_cell(app):
     validity_info = cell.get_validity_info()
     validity_info.invalid_since = now() - datetime.timedelta(days=2)
     validity_info.save()
-    assert cell.is_visible() is False
+    assert cell.is_visible(request) is False
     index_site()
     assert IndexedCell.objects.count() == 0
 
diff --git a/tests/test_wcs.py b/tests/test_wcs.py
index e03bc54e..a1a2f25b 100644
--- a/tests/test_wcs.py
+++ b/tests/test_wcs.py
@@ -35,7 +35,7 @@ from combo.apps.wcs.models import (
     WcsFormsOfCategoryCell,
 )
 from combo.data.library import get_cell_classes
-from combo.data.models import CellBase, LinkListCell, Page, ValidityInfo
+from combo.data.models import CellBase, LinkCell, LinkListCell, Page, ValidityInfo
 from combo.utils import NothingInCacheException
 
 from .test_manager import login
@@ -1869,16 +1869,20 @@ def test_cards_cell_only_for_user(mock_send, context):
     cell.only_for_user = False
     cell.save()
 
-    assert cell.is_visible(user=None) is True
-    assert cell.is_visible(user=MockUserWithNameId()) is True
+    assert cell.is_visible(request=context['request']) is True
+    context['request'].user = MockUserWithNameId()
+    assert cell.is_visible(request=context['request']) is True
 
     cell.only_for_user = True
     cell.save()
-    assert cell.is_visible(user=None) is False
-    assert cell.is_visible(user=MockUserWithNameId()) is True
+    context['request'].user = None
+    assert cell.is_visible(request=context['request']) is False
+    context['request'].user = MockUserWithNameId()
+    assert cell.is_visible(request=context['request']) is True
 
     cache.clear()
     context['synchronous'] = True  # to get fresh content
+    context['request'].user = None
 
     mock_send.reset_mock()
     cell.render(context)
@@ -4207,6 +4211,79 @@ def test_manager_link_list_cell_duplicate():
     assert new_item.cached_json == item.cached_json
 
 
+@mock.patch('requests.Session.send', side_effect=mocked_requests_send)
+def test_cell_condition(mock_send, nocache, app):
+    page = Page.objects.create(title='xxx', slug='foo', template_name='standard')
+    cell = WcsCardInfosCell.objects.create(
+        page=page,
+        placeholder='content',
+        order=0,
+        carddef_reference='default:card_model_1',
+        card_ids='{{ cards|objects:"card_model_1"|last|get:"id" }}',
+    )
+
+    cell.condition = 'cards|objects:"card_model_1"|getlist:"id"|list'
+    cell.save()
+    resp = app.get(page.get_online_url())
+    assert len(resp.context['cells']) == 1
+
+    cell.condition = 'cards|objects:"card_model_1"|getlist:"id"|get:42'
+    cell.save()
+    resp = app.get(page.get_online_url())
+    assert len(resp.context.get('cells') or []) == 0
+
+    page.extra_variables = {'var1': 'cards|objects:"card_model_1"|getlist:"id"|list'}
+    page.save()
+    cell.condition = 'var1'
+    cell.save()
+    resp = app.get(page.get_online_url())
+    assert len(resp.context['cells']) == 1
+
+    page.extra_variables = {'var1': 'cards|objects:"card_model_1"|getlist:"id"|get:42'}
+    page.save()
+    resp = app.get(page.get_online_url())
+    assert len(resp.context.get('cells') or []) == 0
+
+
+@mock.patch('requests.Session.send', side_effect=mocked_requests_send)
+def test_link_list_cell_condition(mock_send, nocache, app):
+    page = Page.objects.create(title='xxx', slug='foo', template_name='standard')
+    cell = LinkListCell.objects.create(order=0, placeholder='content', page=page)
+    link_cell = LinkCell.objects.create(
+        page=page,
+        placeholder=cell.link_placeholder,
+        title='Example Site',
+        url='http://example.net/',
+        order=0,
+    )
+
+    link_cell.condition = 'cards|objects:"card_model_1"|getlist:"id"|list'
+    link_cell.save()
+    resp = app.get(page.get_online_url())
+    assert len(resp.context['cells']) == 1
+    assert 'Example Site' in resp
+
+    link_cell.condition = 'cards|objects:"card_model_1"|getlist:"id"|get:42'
+    link_cell.save()
+    resp = app.get(page.get_online_url())
+    assert len(resp.context['cells']) == 1
+    assert 'Example Site' not in resp
+
+    page.extra_variables = {'var1': 'cards|objects:"card_model_1"|getlist:"id"'}
+    page.save()
+    link_cell.condition = 'var1'
+    link_cell.save()
+    resp = app.get(page.get_online_url())
+    assert len(resp.context['cells']) == 1
+    assert 'Example Site' in resp
+
+    page.extra_variables = {'var1': 'cards|objects:"card_model_1"|getlist:"id"|get:42'}
+    page.save()
+    resp = app.get(page.get_online_url())
+    assert len(resp.context['cells']) == 1
+    assert 'Example Site' not in resp
+
+
 @mock.patch('requests.Session.send', side_effect=mocked_requests_send)
 def test_manager_add_edit_delete_list_link_item(mock_send, app, admin_user):
     page = Page.objects.create(title='One', slug='one', template_name='standard')
-- 
2.35.1

