From fc6f8c6e80eb1d62a4844f7d63e27e91cbeec513 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Laur=C3=A9line=20Gu=C3=A9rin?= <zebuline@entrouvert.com>
Date: Tue, 30 Aug 2022 23:51:14 +0200
Subject: [PATCH] manager: associate a page to a card model (#68675)

---
 combo/apps/wcs/utils.py                      |  8 ++++
 combo/data/models.py                         | 12 ++++++
 combo/manager/forms.py                       | 43 ++++++++++++++++++++
 combo/manager/templates/combo/page_view.html | 12 +++++-
 combo/manager/urls.py                        |  5 +++
 combo/manager/views.py                       | 11 +++++
 tests/wcs/test_card.py                       | 42 +++++++++++++++++++
 7 files changed, 132 insertions(+), 1 deletion(-)

diff --git a/combo/apps/wcs/utils.py b/combo/apps/wcs/utils.py
index 62c6373d..ccf97f86 100644
--- a/combo/apps/wcs/utils.py
+++ b/combo/apps/wcs/utils.py
@@ -85,3 +85,11 @@ def get_wcs_options(url, include_category_slug=False, include_custom_views=False
                         ('%s:%s' % (reference, custom_view['id']), '%s - %s' % (label, custom_view['text']))
                     )
     return references
+
+
+def get_wcs_matching_card_model(sub_slug):
+    card_models = get_wcs_options('/api/cards/@list')
+    for carddef_reference, card_label in card_models:
+        card_id = '%s_id' % carddef_reference.split(':')[1]
+        if '<%s>' % card_id in sub_slug or sub_slug == card_id:
+            return carddef_reference, card_label
diff --git a/combo/data/models.py b/combo/data/models.py
index 70488db1..056c4740 100644
--- a/combo/data/models.py
+++ b/combo/data/models.py
@@ -64,6 +64,7 @@ from django.utils.timezone import now
 from django.utils.translation import gettext_lazy as _
 
 from combo import utils
+from combo.apps.wcs.utils import get_wcs_matching_card_model, is_wcs_enabled
 from combo.utils import NothingInCacheException
 
 from .fields import RichTextField, TemplatableURLField
@@ -268,6 +269,17 @@ class Page(models.Model):
             return None
         return os.path.splitext(self.picture.name)[-1]
 
+    def get_sub_slug_details(self):
+        if not self.sub_slug:
+            return
+
+        if not is_wcs_enabled(None):
+            return self.sub_slug, None
+        result = get_wcs_matching_card_model(self.sub_slug)
+        if not result:
+            return self.sub_slug, None
+        return self.sub_slug.replace('-', '_'), result[1]
+
     def save(self, *args, **kwargs):
         if 'update_fields' in kwargs:
             return super().save(*args, **kwargs)
diff --git a/combo/manager/forms.py b/combo/manager/forms.py
index c6375cc8..b5a5e576 100644
--- a/combo/manager/forms.py
+++ b/combo/manager/forms.py
@@ -25,6 +25,7 @@ from django.template import Template, TemplateSyntaxError
 from django.template.loader import TemplateDoesNotExist, get_template
 from django.utils.translation import gettext_lazy as _
 
+from combo.apps.wcs.utils import get_wcs_matching_card_model, get_wcs_options
 from combo.data.forms import get_page_choices
 from combo.data.models import Page, ParentContentCell, SiteSettings, compile_sub_slug
 
@@ -149,6 +150,14 @@ class PageEditSlugForm(forms.ModelForm):
         model = Page
         fields = ('slug', 'sub_slug')
 
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+
+        if self.instance.sub_slug:
+            result = get_wcs_matching_card_model(self.instance.sub_slug)
+            if result:
+                self.initial['sub_slug'] = ''
+
     def clean_slug(self):
         value = self.cleaned_data.get('slug')
         if self.instance.slug == value:
@@ -170,6 +179,40 @@ class PageEditSlugForm(forms.ModelForm):
         return value
 
 
+class PageEditLinkedCardForm(forms.ModelForm):
+    carddef_reference = forms.ChoiceField(
+        label=_('Linked card model'),
+        required=False,
+    )
+
+    class Meta:
+        model = Page
+        fields = []
+
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+
+        card_models = get_wcs_options('/api/cards/@list')
+        self.fields['carddef_reference'].choices = [('', '-----')] + card_models
+
+        if self.instance.sub_slug:
+            result = get_wcs_matching_card_model(self.instance.sub_slug)
+            if result:
+                self.initial['carddef_reference'] = result[0]
+
+    def save(self, commit=True, *args, **kwargs):
+        super().save(commit=False, *args, **kwargs)
+
+        if self.cleaned_data.get('carddef_reference'):
+            self.instance.sub_slug = '%s_id' % self.cleaned_data['carddef_reference'].split(':')[1]
+        else:
+            self.instance.sub_slug = ''
+
+        if commit:
+            self.instance.save()
+        return self.instance
+
+
 class PageEditDescriptionForm(forms.ModelForm):
     class Meta:
         model = Page
diff --git a/combo/manager/templates/combo/page_view.html b/combo/manager/templates/combo/page_view.html
index 4732b207..a12bd6b9 100644
--- a/combo/manager/templates/combo/page_view.html
+++ b/combo/manager/templates/combo/page_view.html
@@ -40,12 +40,22 @@
 (<a rel="popup" href="{% url 'combo-manager-page-edit-title' pk=object.id %}">{% trans 'change' %}</a>)
 </p>
 
+{% with object.get_sub_slug_details as sub_slug_details %}
 <p>
 <label>{% trans 'Slug:' %}</label>
-<tt>{{ object.slug }}{% if object.sub_slug %}/<span class="subslug">{{ object.sub_slug }}</span>{% endif %}</tt>
+<tt>{{ object.slug }}{% if sub_slug_details and not sub_slug_details.1 %}/<span class="subslug">{{ sub_slug_details.0 }}</span>{% endif %}</tt>
 (<a rel="popup" href="{% url 'combo-manager-page-edit-slug' pk=object.id %}">{% trans 'change' %}</a>)
 </p>
 
+{% if with_wcs %}
+<p>
+<label>{% trans 'Linked card model:' %}</label>
+<tt>{% if sub_slug_details.1 %}{{ sub_slug_details.1 }} <span class="subslug">{{ sub_slug_details.0 }}</span>{% endif %}</tt>
+(<a rel="popup" href="{% url 'combo-manager-page-edit-linked-card' pk=object.id %}">{% trans 'change' %}</a>)
+</p>
+{% endif %}
+{% endwith %}
+
 <p>
 <label>{% trans 'Description:' %}</label>
 {% if object.description %}{{ object.description|truncatewords:32 }}{% else %}<i>{% trans 'none' %}</i>{% endif %}
diff --git a/combo/manager/urls.py b/combo/manager/urls.py
index e250cac8..a4ad7f10 100644
--- a/combo/manager/urls.py
+++ b/combo/manager/urls.py
@@ -52,6 +52,11 @@ urlpatterns = [
         name='combo-manager-page-edit-include-in-navigation',
     ),
     re_path(r'^pages/(?P<pk>\d+)/slug$', views.page_edit_slug, name='combo-manager-page-edit-slug'),
+    re_path(
+        r'^pages/(?P<pk>\d+)/linked-card$',
+        views.page_edit_linked_card,
+        name='combo-manager-page-edit-linked-card',
+    ),
     re_path(r'^pages/(?P<pk>\d+)/title$', views.page_edit_title, name='combo-manager-page-edit-title'),
     re_path(
         r'^pages/(?P<pk>\d+)/description$',
diff --git a/combo/manager/views.py b/combo/manager/views.py
index 11cb2ba4..2b173944 100644
--- a/combo/manager/views.py
+++ b/combo/manager/views.py
@@ -54,6 +54,7 @@ from django.views.generic import (
 )
 
 from combo import plugins
+from combo.apps.wcs.utils import is_wcs_enabled
 from combo.data.library import get_cell_class
 from combo.data.models import CellBase, LinkListCell, Page, PageSnapshot, ParentContentCell, SiteSettings
 from combo.data.utils import (
@@ -75,6 +76,7 @@ from .forms import (
     PageEditDescriptionForm,
     PageEditExtraVariablesFormSet,
     PageEditIncludeInNavigationForm,
+    PageEditLinkedCardForm,
     PageEditPictureForm,
     PageEditRedirectionForm,
     PageEditRolesForm,
@@ -335,6 +337,14 @@ class PageEditSlugView(PageEditView):
 page_edit_slug = PageEditSlugView.as_view()
 
 
+class PageEditLinkedCardView(PageEditView):
+    form_class = PageEditLinkedCardForm
+    comment = _('changed linked card')
+
+
+page_edit_linked_card = PageEditLinkedCardView.as_view()
+
+
 class PageEditDescriptionView(PageEditView):
     form_class = PageEditDescriptionForm
     comment = _('changed description')
@@ -436,6 +446,7 @@ class PageView(ManagedPageMixin, DetailView):
         context['cell_type_groups'] = list(cell_type_groups.items())
         context['cell_type_groups'].sort(key=lambda x: x[0])
         context['page_has_subpages'] = self.object.get_children().exists()
+        context['with_wcs'] = is_wcs_enabled(None)
 
         self.object.prefetched_cells = cells
         placeholders = []
diff --git a/tests/wcs/test_card.py b/tests/wcs/test_card.py
index 55bd4e7e..c09418c1 100644
--- a/tests/wcs/test_card.py
+++ b/tests/wcs/test_card.py
@@ -2727,3 +2727,45 @@ def test_page_snapshot_with_old_card_cells():
     assert cell2.only_for_user is False
     assert cell2.carddef_reference == 'default:card_model_1'
     assert cell2.related_card_path == '__all__'
+
+
+@mock.patch('requests.Session.send', side_effect=mocked_requests_send)
+def test_page_edit_linked_card(mock_send, app, admin_user):
+    page = Page.objects.create(title='One', slug='one', template_name='two')
+
+    app = login(app)
+    resp = app.get('/manage/pages/%s/' % page.pk)
+
+    resp = resp.click(href='.*/linked-card')
+    assert [o[0] for o in resp.form['carddef_reference'].options] == [
+        '',
+        'default:card_model_1',
+        'default:card_model_2',
+        'default:card_model_3',
+        'default:card_a',
+        'default:card_b',
+        'default:card_c',
+        'default:card_d',
+        'default:card_e',
+        'other:card_model_1',
+        'other:card_model_2',
+        'other:card_model_3',
+        'other:card_a',
+        'other:card_b',
+        'other:card_c',
+        'other:card_d',
+        'other:card_e',
+    ]
+    resp.form['carddef_reference'] = 'default:card_model_3'
+    resp = resp.form.submit().follow()
+    page.refresh_from_db()
+    assert page.sub_slug == 'card_model_3_id'
+    resp = app.get('/manage/pages/%s/slug' % page.pk)
+    assert resp.form['sub_slug'].value == ''
+
+    resp = app.get('/manage/pages/%s/linked-card' % page.pk)
+    assert resp.form['carddef_reference'].value == 'default:card_model_3'
+    resp.form['carddef_reference'] = ''
+    resp = resp.form.submit().follow()
+    page.refresh_from_db()
+    assert page.sub_slug == ''
-- 
2.35.1

