From e4e01b0b52fc3d7009306434e1de03da290fba18 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Laur=C3=A9line=20Gu=C3=A9rin?= <zebuline@entrouvert.com>
Date: Tue, 7 Dec 2021 09:48:40 +0100
Subject: [PATCH 2/5] wcs: build paths of related cards for form options
 (#58833)

---
 combo/apps/wcs/forms.py                       |  16 +-
 combo/apps/wcs/migrations/0043_card_ids.py    |   2 +-
 .../wcs/migrations/0044_related_card_path.py  |  16 ++
 combo/apps/wcs/models.py                      |  75 ++++-
 .../wcs/manager/card-infos-cell-form.html     |  12 +-
 tests/test_wcs.py                             | 264 +++++++++++++++++-
 6 files changed, 374 insertions(+), 11 deletions(-)
 create mode 100644 combo/apps/wcs/migrations/0044_related_card_path.py

diff --git a/combo/apps/wcs/forms.py b/combo/apps/wcs/forms.py
index 1aefeb32..2de4009b 100644
--- a/combo/apps/wcs/forms.py
+++ b/combo/apps/wcs/forms.py
@@ -68,10 +68,18 @@ class WcsCardsCellForm(forms.ModelForm):
 class WcsCardInfoCellForm(forms.ModelForm):
     with_user = forms.BooleanField(label=_('Restrict to cards accessible to the user'), required=False)
     customize_display = forms.BooleanField(label=_('Customize display'), required=False)
+    related_card_path = forms.ChoiceField(label=_('Card Identifier'), required=False)
 
     class Meta:
         model = WcsCardInfosCell
-        fields = ('carddef_reference', 'title_type', 'custom_title', 'card_ids', 'custom_schema')
+        fields = (
+            'carddef_reference',
+            'title_type',
+            'custom_title',
+            'related_card_path',
+            'card_ids',
+            'custom_schema',
+        )
         widgets = {
             'custom_schema': forms.HiddenInput(),
         }
@@ -89,10 +97,16 @@ class WcsCardInfoCellForm(forms.ModelForm):
             del self.fields['customize_display']
             del self.fields['custom_schema']
 
+        self.fields['related_card_path'].choices = [
+            ('', _('Other Card Identifiers'))
+        ] + self.instance.get_related_card_paths()
+
     def save(self, *args, **kwargs):
         super().save(*args, **kwargs)
         if not self.cleaned_data.get('customize_display'):
             self.instance.custom_schema = {}
+        if self.instance.related_card_path:
+            self.instance.card_ids = ''
         self.instance.without_user = not self.cleaned_data['with_user']
         self.instance.save()
         return self.instance
diff --git a/combo/apps/wcs/migrations/0043_card_ids.py b/combo/apps/wcs/migrations/0043_card_ids.py
index c6d36179..f53ee601 100644
--- a/combo/apps/wcs/migrations/0043_card_ids.py
+++ b/combo/apps/wcs/migrations/0043_card_ids.py
@@ -16,6 +16,6 @@ class Migration(migrations.Migration):
         migrations.AlterField(
             model_name='wcscardinfoscell',
             name='card_ids',
-            field=models.CharField(blank=True, max_length=1000, verbose_name='Card Identifiers'),
+            field=models.CharField(blank=True, max_length=1000, verbose_name='Other Card Identifiers'),
         ),
     ]
diff --git a/combo/apps/wcs/migrations/0044_related_card_path.py b/combo/apps/wcs/migrations/0044_related_card_path.py
new file mode 100644
index 00000000..356e9e5d
--- /dev/null
+++ b/combo/apps/wcs/migrations/0044_related_card_path.py
@@ -0,0 +1,16 @@
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('wcs', '0043_card_ids'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='wcscardinfoscell',
+            name='related_card_path',
+            field=models.CharField(blank=True, max_length=1000, verbose_name='Card Identifier'),
+        ),
+    ]
diff --git a/combo/apps/wcs/models.py b/combo/apps/wcs/models.py
index 481f7658..74756050 100644
--- a/combo/apps/wcs/models.py
+++ b/combo/apps/wcs/models.py
@@ -768,6 +768,10 @@ class CardMixin:
     def wcs_site(self):
         return self.carddef_reference.split(':')[0]
 
+    @property
+    def card_slug(self):
+        return self.carddef_reference.split(':')[1]
+
     def get_additional_label(self):
         if not self.cached_title:
             return
@@ -846,9 +850,6 @@ class WcsCardsCell(CardMixin, WcsBlurpMixin, CellBase):
 
         populate_cache()
 
-    def card_slug(self):
-        return self.carddef_reference.split(':')[1]
-
     def is_visible(self, **kwargs):
         user = kwargs.get('user')
         if self.only_for_user and (not user or user.is_anonymous):
@@ -899,7 +900,8 @@ class WcsCardsCell(CardMixin, WcsBlurpMixin, CellBase):
 @register_cell_class
 class WcsCardInfosCell(CardMixin, CellBase):
     carddef_reference = models.CharField(_('Card Model'), max_length=150)
-    card_ids = models.CharField(_('Card Identifiers'), max_length=1000, blank=True)
+    related_card_path = models.CharField(_('Card Identifier'), max_length=1000, blank=True)
+    card_ids = models.CharField(_('Other Card Identifiers'), max_length=1000, blank=True)
     without_user = models.BooleanField(_('Ignore the logged-in user'), default=False)
     custom_schema = JSONField(blank=True, default=dict)
 
@@ -973,6 +975,71 @@ class WcsCardInfosCell(CardMixin, CellBase):
     def get_repeat_template(self, context):
         return len(context[self.global_context_key])
 
+    def get_card_schema(self, card_slug):
+        wcs_site = get_wcs_services().get(self.wcs_site)
+        card_schema = get_wcs_json(wcs_site, 'api/cards/%s/@schema' % card_slug, log_errors='warn')
+        if not card_schema:
+            return None
+        if card_schema.get('err') == 1:
+            return None
+        return card_schema
+
+    def get_related_card_paths(self):
+        if not self.carddef_reference:
+            return []
+
+        def iter_relations(relations, path, label, carddefs_already_seen):
+            carddefs_already_seen = carddefs_already_seen[:]
+            for relation in relations:
+                new_path = '%s/%s%s' % (
+                    path,
+                    'reverse:' if relation['reverse'] else '',
+                    relation['varname'],
+                )
+                new_label = '%s/%s%s' % (
+                    label,
+                    relation['varname'],
+                    ' (reverse)' if relation['reverse'] else '',
+                )
+                if relation['obj'] == 'carddef:%s' % self.card_slug:
+                    # target carddef found
+                    yield (new_path, new_label)
+                if not relation['reverse'] and relation['type'] in ['item', 'computed']:
+                    # relation is not multiple, continue to search for matching relations
+                    new_card_slug = relation['obj'][8:]  # remove 'carddef:'
+                    new_card_schema = self.get_card_schema(new_card_slug)
+                    if new_card_slug not in carddefs_already_seen and new_card_schema:
+                        carddefs_already_seen.append(new_card_slug)
+                        yield from iter_relations(
+                            relations=new_card_schema['relations'],
+                            path=new_path,
+                            label=new_label,
+                            carddefs_already_seen=carddefs_already_seen,
+                        )
+
+        # get cells with explicit ids
+        results = []
+        for cell in WcsCardInfosCell.objects.filter(page=self.page_id).exclude(pk=self.pk):
+            if not cell.slug:
+                # no slug
+                continue
+            if cell.related_card_path:
+                # no explicit ids
+                continue
+            if ',' in cell.card_ids:
+                # multiple ids, can not follow relations
+                continue
+            # follow relations
+            results += list(
+                iter_relations(
+                    relations=cell.cached_json.get('relations') or [],
+                    path=cell.slug,
+                    label=cell.slug,
+                    carddefs_already_seen=[self.card_slug],
+                )
+            )
+        return results
+
     def get_card_ids(self, original_context, request):
         if self.card_ids:
             try:
diff --git a/combo/apps/wcs/templates/combo/wcs/manager/card-infos-cell-form.html b/combo/apps/wcs/templates/combo/wcs/manager/card-infos-cell-form.html
index c353e9ed..6f3c73e5 100644
--- a/combo/apps/wcs/templates/combo/wcs/manager/card-infos-cell-form.html
+++ b/combo/apps/wcs/templates/combo/wcs/manager/card-infos-cell-form.html
@@ -89,8 +89,8 @@
 
 {% endif %}
 
-{# display/hide custom_title field #}
 <script>
+  {# display/hide custom_title field #}
   $('#id_cwcs_wcscardinfoscell-{{ cell.pk }}-title_type').on('change', function() {
     if ($(this).val() == 'manual') {
       $('#id_cwcs_wcscardinfoscell-{{ cell.pk }}-custom_title').parent().show();
@@ -99,5 +99,15 @@
     }
   });
   $('#id_cwcs_wcscardinfoscell-{{ cell.pk }}-title_type').change();
+
+  {# display/hide card_ids field #}
+  $('#id_cwcs_wcscardinfoscell-{{ cell.pk }}-related_card_path').on('change', function() {
+    if (!$(this).val()) {
+      $('#id_cwcs_wcscardinfoscell-{{ cell.pk }}-card_ids').parent().show();
+    } else {
+      $('#id_cwcs_wcscardinfoscell-{{ cell.pk }}-card_ids').parent().hide();
+    }
+  });
+  $('#id_cwcs_wcscardinfoscell-{{ cell.pk }}-related_card_path').change();
 </script>
 {% endblock %}
diff --git a/tests/test_wcs.py b/tests/test_wcs.py
index 9df0f9c8..9c9ea76d 100644
--- a/tests/test_wcs.py
+++ b/tests/test_wcs.py
@@ -118,6 +118,11 @@ WCS_CARDDEFS_DATA = [
     {'title': 'Card Model 1', 'slug': 'card_model_1', 'custom_views': [{'id': 'foo', 'text': 'bar'}]},
     {'title': 'Card Model 2', 'slug': 'card_model_2'},
     {'title': 'Card Model 3', 'slug': 'card_model_3'},
+    {'title': 'Card A', 'slug': 'card_a'},
+    {'title': 'Card B', 'slug': 'card_b'},
+    {'title': 'Card C', 'slug': 'card_c'},
+    {'title': 'Card D', 'slug': 'card_d'},
+    {'title': 'Card E', 'slug': 'card_e'},
 ]
 
 WCS_CARDS_DATA = {
@@ -199,6 +204,75 @@ WCS_CARDDEF_SCHEMAS = {
             {'label': 'Empty', 'varname': None, 'type': 'string'},
         ],
     },
+    'card_a': {
+        'name': 'Card A',
+        'fields': [
+            {'label': 'Card B', 'varname': 'cardb', 'type': 'item'},
+            {'label': 'Cards B', 'varname': 'cardsb', 'type': 'items'},
+            {'label': 'Block B', 'varname': 'blockb', 'type': 'block:b'},
+            {'label': 'Card C', 'varname': 'cardc', 'type': 'item'},
+        ],
+        'relations': [
+            {'obj': 'carddef:card_b', 'varname': 'cardb', 'type': 'item', 'reverse': False},
+            {'obj': 'carddef:card_b', 'varname': 'cardsb', 'type': 'items', 'reverse': False},
+            {'obj': 'carddef:card_b', 'varname': 'blockb_cardb', 'type': 'item', 'reverse': False},
+            {'obj': 'carddef:card_c', 'varname': 'cardc', 'type': 'item', 'reverse': False},
+            {
+                'obj': 'carddef:card_z',  # unknown card model
+                'varname': 'cardz',
+                'type': 'item',
+                'reverse': False,
+            },
+        ],
+    },
+    'card_b': {
+        'name': 'Card B',
+        'fields': [],
+        'relations': [
+            {'obj': 'carddef:card_a', 'varname': 'cardb', 'type': 'item', 'reverse': True},
+            {'obj': 'carddef:card_a', 'varname': 'cardsb', 'type': 'items', 'reverse': True},
+            {'obj': 'carddef:card_a', 'varname': 'blockb_cardb', 'type': 'item', 'reverse': True},
+            {'obj': 'carddef:card_c', 'varname': 'cardb', 'type': 'item', 'reverse': True},
+            {'obj': 'carddef:card_c', 'varname': 'cardsb', 'type': 'items', 'reverse': True},
+            {'obj': 'carddef:card_c', 'varname': 'blockb_cardb', 'type': 'item', 'reverse': True},
+        ],
+    },
+    'card_c': {
+        'name': 'Card C',
+        'fields': [
+            {'label': 'Card B', 'varname': 'cardb', 'type': 'item'},
+            {'label': 'Cards B', 'varname': 'cardsb', 'type': 'items'},
+            {'label': 'Block B', 'varname': 'blockb', 'type': 'block:b'},
+        ],
+        'relations': [
+            {'obj': 'carddef:card_b', 'varname': 'cardb', 'type': 'item', 'reverse': False},
+            {'obj': 'carddef:card_b', 'varname': 'cardsb', 'type': 'items', 'reverse': False},
+            {'obj': 'carddef:card_b', 'varname': 'blockb_cardb', 'type': 'item', 'reverse': False},
+            {'obj': 'carddef:card_a', 'varname': 'cardc', 'type': 'item', 'reverse': True},
+        ],
+    },
+    'card_d': {
+        'name': 'Card D',
+        'fields': [
+            {'label': 'Card D', 'varname': 'cardd-foo', 'type': 'item'},
+            {'label': 'Card E', 'varname': 'carde-foo', 'type': 'item'},
+        ],
+        'relations': [
+            {'obj': 'carddef:card_d', 'varname': 'cardd-foo', 'type': 'item', 'reverse': False},
+            {'obj': 'carddef:card_d', 'varname': 'cardd-foo', 'type': 'item', 'reverse': True},
+            {'obj': 'carddef:card_e', 'varname': 'carde-foo', 'type': 'item', 'reverse': False},
+        ],
+    },
+    'card_e': {
+        'name': 'Card E',
+        'fields': [
+            {'label': 'Card D', 'varname': 'cardd-bar', 'type': 'item'},
+        ],
+        'relations': [
+            {'obj': 'carddef:card_d', 'varname': 'cardd-bar', 'type': 'item', 'reverse': False},
+            {'obj': 'carddef:card_d', 'varname': 'carde-foo', 'type': 'item', 'reverse': True},
+        ],
+    },
 }
 
 
@@ -1392,10 +1466,20 @@ def test_cards_cell_setup(mock_send):
         ('default:card_model_1:foo', 'test : Card Model 1 - bar'),
         ('default:card_model_2', 'test : Card Model 2'),
         ('default:card_model_3', 'test : Card Model 3'),
+        ('default:card_a', 'test : Card A'),
+        ('default:card_b', 'test : Card B'),
+        ('default:card_c', 'test : Card C'),
+        ('default:card_d', 'test : Card D'),
+        ('default:card_e', 'test : Card E'),
         ('other:card_model_1', 'test2 : Card Model 1'),
         ('other:card_model_1:foo', 'test2 : Card Model 1 - bar'),
         ('other:card_model_2', 'test2 : Card Model 2'),
         ('other:card_model_3', 'test2 : Card Model 3'),
+        ('other:card_a', 'test2 : Card A'),
+        ('other:card_b', 'test2 : Card B'),
+        ('other:card_c', 'test2 : Card C'),
+        ('other:card_d', 'test2 : Card D'),
+        ('other:card_e', 'test2 : Card E'),
     ]
 
 
@@ -1666,9 +1750,19 @@ def test_card_cell_setup(mock_send, app, admin_user):
         ('default:card_model_1', 'test : Card Model 1'),
         ('default:card_model_2', 'test : Card Model 2'),
         ('default:card_model_3', 'test : Card Model 3'),
+        ('default:card_a', 'test : Card A'),
+        ('default:card_b', 'test : Card B'),
+        ('default:card_c', 'test : Card C'),
+        ('default:card_d', 'test : Card D'),
+        ('default:card_e', 'test : Card E'),
         ('other:card_model_1', 'test2 : Card Model 1'),
         ('other:card_model_2', 'test2 : Card Model 2'),
         ('other:card_model_3', 'test2 : Card Model 3'),
+        ('other:card_a', 'test2 : Card A'),
+        ('other:card_b', 'test2 : Card B'),
+        ('other:card_c', 'test2 : Card C'),
+        ('other:card_d', 'test2 : Card D'),
+        ('other:card_e', 'test2 : Card E'),
     ]
     assert 'customize_display' not in form.fields
     assert 'custom_schema' not in form.fields
@@ -1773,7 +1867,7 @@ def test_card_cell_validity(mock_send):
 @mock.patch('combo.apps.wcs.utils.requests.send', side_effect=mocked_requests_send)
 def test_manager_card_cell(mock_send, app, admin_user):
     page = Page.objects.create(title='xxx', slug='test_cards', template_name='standard')
-    cell = WcsCardInfosCell(page=page, placeholder='content', order=0)
+    cell = WcsCardInfosCell.objects.create(page=page, placeholder='content', order=0)
 
     app = login(app)
     resp = app.get('/manage/pages/%s/' % page.pk)
@@ -1793,6 +1887,168 @@ def test_manager_card_cell(mock_send, app, admin_user):
     assert cell.without_user is True
     assert resp.forms[0]['c%s-with_user' % cell.get_reference()].value is None
 
+    # card with relations
+    cell.carddef_reference = 'default:card_a'
+    cell.save()
+    resp = app.get('/manage/pages/%s/' % page.pk)
+    # but only one cell on the page, no relations to follow
+    assert resp.forms[0]['c%s-related_card_path' % cell.get_reference()].options == [
+        ('', True, 'Other Card Identifiers')
+    ]
+
+    # add a second cell, related to the first card model
+    cell2 = WcsCardInfosCell.objects.create(
+        page=page, placeholder='content', order=1, carddef_reference='default:card_b'
+    )
+    resp = app.get('/manage/pages/%s/' % page.pk)
+    # still no relation to follow
+    assert resp.forms[0]['c%s-related_card_path' % cell.get_reference()].options == [
+        ('', True, 'Other Card Identifiers')
+    ]
+    # no cell with id and slug
+    assert resp.forms[1]['c%s-related_card_path' % cell2.get_reference()].options == [
+        ('', True, 'Other Card Identifiers')
+    ]
+
+    # set a slug on first cell
+    cell.slug = 'sluga'
+    cell.save()
+    resp = app.get('/manage/pages/%s/' % page.pk)
+    # still no relation to follow
+    assert resp.forms[0]['c%s-related_card_path' % cell.get_reference()].options == [
+        ('', True, 'Other Card Identifiers')
+    ]
+    # multiple relations to follow
+    assert resp.forms[1]['c%s-related_card_path' % cell2.get_reference()].options == [
+        ('', True, 'Other Card Identifiers'),
+        ('sluga/cardb', False, 'sluga/cardb'),
+        ('sluga/cardsb', False, 'sluga/cardsb'),
+        ('sluga/blockb_cardb', False, 'sluga/blockb_cardb'),
+        ('sluga/cardc/cardb', False, 'sluga/cardc/cardb'),
+        ('sluga/cardc/cardsb', False, 'sluga/cardc/cardsb'),
+        ('sluga/cardc/blockb_cardb', False, 'sluga/cardc/blockb_cardb'),
+    ]
+
+    # set a list of ids on first cell
+    cell.card_ids = '{{ cards|objects:"card_model_1"|getlist:"id"|join:"," }}'
+    cell.save()
+    resp = app.get('/manage/pages/%s/' % page.pk)
+    # still no relation to follow
+    assert resp.forms[0]['c%s-related_card_path' % cell.get_reference()].options == [
+        ('', True, 'Other Card Identifiers')
+    ]
+    # can not user cell with multiple ids as reference
+    assert resp.forms[1]['c%s-related_card_path' % cell2.get_reference()].options == [
+        ('', True, 'Other Card Identifiers')
+    ]
+
+    # define a slug on second cell
+    cell.card_ids = ''
+    cell.save()
+    cell2.slug = 'slugb'
+    cell2.save()
+    resp = app.get('/manage/pages/%s/' % page.pk)
+    # multiple relations to follow
+    assert resp.forms[0]['c%s-related_card_path' % cell.get_reference()].options == [
+        ('', True, 'Other Card Identifiers'),
+        ('slugb/reverse:cardb', False, 'slugb/cardb (reverse)'),
+        ('slugb/reverse:cardsb', False, 'slugb/cardsb (reverse)'),
+        ('slugb/reverse:blockb_cardb', False, 'slugb/blockb_cardb (reverse)'),
+    ]
+    # still multiple relations to follow
+    assert resp.forms[1]['c%s-related_card_path' % cell2.get_reference()].options == [
+        ('', True, 'Other Card Identifiers'),
+        ('sluga/cardb', False, 'sluga/cardb'),
+        ('sluga/cardsb', False, 'sluga/cardsb'),
+        ('sluga/blockb_cardb', False, 'sluga/blockb_cardb'),
+        ('sluga/cardc/cardb', False, 'sluga/cardc/cardb'),
+        ('sluga/cardc/cardsb', False, 'sluga/cardc/cardsb'),
+        ('sluga/cardc/blockb_cardb', False, 'sluga/cardc/blockb_cardb'),
+    ]
+
+    # set a related_path on cell2
+    resp.forms[1]['c%s-related_card_path' % cell2.get_reference()] = 'sluga/cardb'
+    resp.forms[1]['c%s-card_ids' % cell2.get_reference()] = 'foobar'
+    resp = resp.forms[1].submit()
+    cell2.refresh_from_db()
+    assert cell2.related_card_path == 'sluga/cardb'
+    assert cell2.card_ids == ''
+    resp = app.get('/manage/pages/%s/' % page.pk)
+    # no more relation to follow
+    assert resp.forms[0]['c%s-related_card_path' % cell.get_reference()].options == [
+        ('', True, 'Other Card Identifiers')
+    ]
+    # still multiple relations to follow
+    assert resp.forms[1]['c%s-related_card_path' % cell2.get_reference()].options == [
+        ('', False, 'Other Card Identifiers'),
+        ('sluga/cardb', True, 'sluga/cardb'),
+        ('sluga/cardsb', False, 'sluga/cardsb'),
+        ('sluga/blockb_cardb', False, 'sluga/blockb_cardb'),
+        ('sluga/cardc/cardb', False, 'sluga/cardc/cardb'),
+        ('sluga/cardc/cardsb', False, 'sluga/cardc/cardsb'),
+        ('sluga/cardc/blockb_cardb', False, 'sluga/cardc/blockb_cardb'),
+    ]
+
+    # check circular relations
+    cell.slug = 'sluge'
+    cell.carddef_reference = 'default:card_e'
+    cell.save()
+    cell2.carddef_reference = 'default:card_d'
+    cell2.slug = 'slugd'
+    cell2.related_card_path = ''
+    cell2.save()
+    resp = app.get('/manage/pages/%s/' % page.pk)
+    assert resp.forms[0]['c%s-related_card_path' % cell.get_reference()].options == [
+        ('', True, 'Other Card Identifiers'),
+        ('slugd/cardd-foo/carde-foo', False, 'slugd/cardd-foo/carde-foo'),
+        ('slugd/carde-foo', False, 'slugd/carde-foo'),
+    ]
+    assert resp.forms[1]['c%s-related_card_path' % cell2.get_reference()].options == [
+        ('', True, 'Other Card Identifiers'),
+        ('sluge/cardd-bar', False, 'sluge/cardd-bar'),
+        ('sluge/reverse:carde-foo', False, 'sluge/carde-foo (reverse)'),
+    ]
+
+    cell.slug = 'slugd'
+    cell.carddef_reference = 'default:card_d'
+    cell.save()
+    cell2.carddef_reference = 'default:card_d'
+    cell2.slug = 'slugd-bis'
+    cell2.related_card_path = ''
+    cell2.save()
+    resp = app.get('/manage/pages/%s/' % page.pk)
+    assert resp.forms[0]['c%s-related_card_path' % cell.get_reference()].options == [
+        ('', True, 'Other Card Identifiers'),
+        ('slugd-bis/cardd-foo', False, 'slugd-bis/cardd-foo'),
+        ('slugd-bis/reverse:cardd-foo', False, 'slugd-bis/cardd-foo (reverse)'),
+        ('slugd-bis/carde-foo/cardd-bar', False, 'slugd-bis/carde-foo/cardd-bar'),
+        ('slugd-bis/carde-foo/reverse:carde-foo', False, 'slugd-bis/carde-foo/carde-foo (reverse)'),
+    ]
+    assert resp.forms[1]['c%s-related_card_path' % cell2.get_reference()].options == [
+        ('', True, 'Other Card Identifiers'),
+        ('slugd/cardd-foo', False, 'slugd/cardd-foo'),
+        ('slugd/reverse:cardd-foo', False, 'slugd/cardd-foo (reverse)'),
+        ('slugd/carde-foo/cardd-bar', False, 'slugd/carde-foo/cardd-bar'),
+        ('slugd/carde-foo/reverse:carde-foo', False, 'slugd/carde-foo/carde-foo (reverse)'),
+    ]
+
+    cell.slug = 'sluge'
+    cell.carddef_reference = 'default:card_e'
+    cell.save()
+    cell2.carddef_reference = 'default:card_e'
+    cell2.slug = 'sluge-bis'
+    cell2.related_card_path = ''
+    cell2.save()
+    resp = app.get('/manage/pages/%s/' % page.pk)
+    assert resp.forms[0]['c%s-related_card_path' % cell.get_reference()].options == [
+        ('', True, 'Other Card Identifiers'),
+        ('sluge-bis/cardd-bar/carde-foo', False, 'sluge-bis/cardd-bar/carde-foo'),
+    ]
+    assert resp.forms[1]['c%s-related_card_path' % cell2.get_reference()].options == [
+        ('', True, 'Other Card Identifiers'),
+        ('sluge/cardd-bar/carde-foo', False, 'sluge/cardd-bar/carde-foo'),
+    ]
+
 
 @mock.patch('combo.apps.wcs.utils.requests.send', side_effect=mocked_requests_send)
 def test_card_cell_load(mock_send):
@@ -2009,9 +2265,9 @@ def test_card_cell_render_identifier(mock_send, nocache, app):
     page = Page.objects.create(
         title='xxx', slug='foo', template_name='standard', sub_slug='(?P<card_model_1_id>[a-z0-9]+)'
     )
-    cell = WcsCardInfosCell(page=page, placeholder='content', order=0)
-    cell.carddef_reference = 'default:card_model_1'
-    cell.save()
+    cell = WcsCardInfosCell.objects.create(
+        page=page, placeholder='content', order=0, carddef_reference='default:card_model_1'
+    )
 
     cell_url = reverse(
         'combo-public-ajax-page-cell',
-- 
2.33.0

