From 61c4c63d8d0cbd2c20bc9950afa7df021693a8a2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20P=C3=A9ters?= <fpeters@entrouvert.com>
Date: Sat, 10 Sep 2022 20:48:43 +0200
Subject: [PATCH] manager: add view to edit a single attribute with an ajax
 request (#64575)

---
 combo/manager/urls.py  |  5 +++++
 combo/manager/views.py | 44 +++++++++++++++++++++++++++++++++++++
 tests/test_manager.py  | 49 ++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 98 insertions(+)

diff --git a/combo/manager/urls.py b/combo/manager/urls.py
index e250cac8..9b9ab838 100644
--- a/combo/manager/urls.py
+++ b/combo/manager/urls.py
@@ -126,6 +126,11 @@ urlpatterns = [
         views.page_duplicate_cell,
         name='combo-manager-page-duplicate-cell',
     ),
+    re_path(
+        r'^pages/(?P<page_pk>\d+)/cell/(?P<cell_reference>[\w_-]+)/attribute/(?P<attribute>[\w_]+)/$',
+        views.page_edit_cell_attribute,
+        name='combo-manager-page-edit-cell-attribute',
+    ),
     re_path(
         r'^pages/(?P<page_pk>\d+)/cell/(?P<cell_reference>[\w_-]+)/add-link/(?P<link_code>[\w-]+)$',
         views.page_list_cell_add_link,
diff --git a/combo/manager/views.py b/combo/manager/views.py
index c0a4400c..1a146f5d 100644
--- a/combo/manager/views.py
+++ b/combo/manager/views.py
@@ -868,6 +868,50 @@ class PageDuplicateCellView(ManagedPageMixin, FormView):
 page_duplicate_cell = PageDuplicateCellView.as_view()
 
 
+class PageEditCellAttributeView(ManagedPageMixin, UpdateView):
+    http_method_names = ['post']
+
+    def get_object(self, queryset=None):
+        page_pk = self.kwargs.get('page_pk')
+        cell_reference = self.kwargs.get('cell_reference')
+        try:
+            return CellBase.get_cell(cell_reference, page_id=page_pk)
+        except ObjectDoesNotExist:
+            raise Http404()
+
+    def post(self, request, *args, **kwargs):
+        self.object = self.get_object()
+        attr = kwargs['attribute']
+
+        if attr not in self.object.get_default_form_fields():
+            return JsonResponse({'err': 1, 'err_desc': 'not-attribute-from-default-form'})
+
+        form_class = self.object.get_default_form_class(fields=[attr])
+        form = form_class(**self.get_form_kwargs())
+        response = {}
+        if form.is_valid():
+            response['err'] = 0
+            self.object = form.save()
+            PageSnapshot.take(
+                self.object.page,
+                request=self.request,
+                comment=_('changed cell "%(cell)s" (attribute: %(attr)s)')
+                % {
+                    'cell': self.object,
+                    'attr': attr,
+                },
+            )
+        else:
+            response['err'] = 1
+            response['err_desc'] = 'form-error-see-errorlist'
+            response['errorlist'] = form.errors
+
+        return JsonResponse(response)
+
+
+page_edit_cell_attribute = PageEditCellAttributeView.as_view()
+
+
 class PageCellOrder(ManagedPageMixin, View):
     def get(self, *args, **kwargs):
         request = self.request
diff --git a/tests/test_manager.py b/tests/test_manager.py
index 2b6db9e2..736afd2c 100644
--- a/tests/test_manager.py
+++ b/tests/test_manager.py
@@ -33,6 +33,7 @@ from combo.data.forms import LinkCellForm
 from combo.data.models import (
     CellBase,
     ConfigJsonCell,
+    FeedCell,
     JsonCell,
     LinkCell,
     LinkListCell,
@@ -1636,6 +1637,54 @@ def test_edit_cell_order(app, admin_user):
         assert TextCell.objects.get(id=cell.id).order == new_order[i]
 
 
+def test_edit_cell_attribute_ajax(settings, app, admin_user):
+    Page.objects.all().delete()
+    page = Page(title='One', slug='one', template_name='standard')
+    page.save()
+    cell = TextCell(page=page, placeholder='content', text='Foobar', order=0)
+    cell.save()
+
+    app = login(app)
+    params = {
+        'text': 'new text',
+    }
+    app.post(
+        f'/manage/pages/{page.id}/cell/data_textcell-{cell.id}/attribute/text/', params=params, status=403
+    )
+
+    resp = app.get(f'/manage/pages/{page.id}/')
+    params = {
+        'text': 'new text',
+        'csrfmiddlewaretoken': PyQuery(resp.text).find('[name=csrfmiddlewaretoken]').val(),
+    }
+    resp = app.post(f'/manage/pages/{page.id}/cell/data_textcell-{cell.id}/attribute/text/', params=params)
+    assert resp.json == {'err': 0}
+    assert TextCell.objects.get(id=cell.id).text == 'new text'
+
+    params = {
+        'placeholder': '',
+        'csrfmiddlewaretoken': params['csrfmiddlewaretoken'],
+    }
+    resp = app.post(
+        f'/manage/pages/{page.id}/cell/data_textcell-{cell.id}/attribute/placeholder/', params=params
+    )
+    assert resp.json == {'err': 1, 'err_desc': 'not-attribute-from-default-form'}
+
+    feedcell = FeedCell.objects.create(page=page, placeholder='content', order=1)
+    params = {
+        'limit': 'foo',
+        'csrfmiddlewaretoken': params['csrfmiddlewaretoken'],
+    }
+    resp = app.post(
+        f'/manage/pages/{page.id}/cell/data_feedcell-{feedcell.id}/attribute/limit/', params=params
+    )
+    assert resp.json == {
+        'err': 1,
+        'err_desc': 'form-error-see-errorlist',
+        'errorlist': {'limit': ['Enter a whole number.']},
+    }
+
+
 def test_edit_text_cell(app, admin_user):
     Page.objects.all().delete()
     page = Page(title='One', slug='one', template_name='standard')
-- 
2.37.2

