Repository: django-cms/django-cms

Issue:
Apphooks are removed if "Can change advanced settings" permission not given 
I think this bug already existed in 2.1.x and still occurs in 2.2rc1.post2. 
- Set CMS_PERMISSION = True
- Create a cms group with full page permissions (don't bother about User & Group permissions and Page permissions management)
- Create a global permission (all default values except "Can change advanced settings" set to false) and choose the group created previously
- Create a cms user with full page permissions and add the user to the group created previously

If there is a page with an app hook (i.e. created by a superuser or a user with "Can change advanced settings" permissions) and the restricted user from above changes this page, the app hook gets lost.

The field "Redirect" is removed too.

The fields "ID", "Login required" and "Menu visibilty" keep their values.

I haven't tested the field "Overwrite URL".



Discussion:

Resolving patch:
diff --git a/cms/models/managers.py b/cms/models/managers.py
--- a/cms/models/managers.py
+++ b/cms/models/managers.py
@@ -188,13 +188,15 @@ def set_or_create(self, request, page, form, language):
         base_fields = [
             'slug',
             'title',
-            'application_urls',
-            'redirect',
             'meta_description',
             'meta_keywords',
             'page_title',
             'menu_title'
         ]
+        advanced_fields = [
+            'application_urls',
+            'redirect',
+        ]
         cleaned_data = form.cleaned_data
         try:
             obj = self.get(page=page, language=language)
@@ -210,6 +212,10 @@ def set_or_create(self, request, page, form, language):
                 if overwrite_url:
                     data['has_url_overwrite'] = True
                     data['path'] = overwrite_url
+                for field in advanced_fields:
+                    value = cleaned_data.get(field, None)
+                    if value:
+                        data[field] = value
             return self.create(**data)
         
         for name in base_fields:
@@ -219,6 +225,8 @@ def set_or_create(self, request, page, form, language):
             overwrite_url = cleaned_data.get('overwrite_url', None)
             obj.has_url_overwrite = bool(overwrite_url)
             obj.path = overwrite_url
+            for field in advanced_fields:
+                setattr(obj, field, cleaned_data.get(field, None))
         obj.save()
         return obj
 


Test patch:
diff --git a/cms/tests/admin.py b/cms/tests/admin.py
--- a/cms/tests/admin.py
+++ b/cms/tests/admin.py
@@ -158,6 +158,91 @@ def test_edit_does_not_reset_page_adv_fields(self):
             self.assertEqual(page.reverse_id, REVERSE_ID)
             title = page.get_title_obj()
             self.assertEqual(title.overwrite_url, None)
+            
+    def test_edit_does_not_reset_apphook(self):
+        """
+        Makes sure that if a non-superuser with no rights to edit advanced page
+        fields edits a page, those advanced fields are not touched.
+        """
+        OLD_PAGE_NAME = 'Test Page'
+        NEW_PAGE_NAME = 'Test page 2'
+        REVERSE_ID = 'Test'
+        APPLICATION_URLS = 'project.sampleapp.urls'
+        
+        admin, normal_guy = self._get_guys()
+        
+        site = Site.objects.get(pk=1)
+    
+        # The admin creates the page
+        page = create_page(OLD_PAGE_NAME, "nav_playground.html", "en",
+                           site=site, created_by=admin)
+        page.reverse_id = REVERSE_ID
+        page.save()
+        title = page.get_title_obj()
+        title.has_url_overwrite = True
+        title.application_urls = APPLICATION_URLS
+        title.save()
+        
+        self.assertEqual(page.get_title(), OLD_PAGE_NAME)
+        self.assertEqual(page.reverse_id, REVERSE_ID)
+        self.assertEqual(title.application_urls, APPLICATION_URLS)
+        
+        # The user edits the page (change the page name for ex.)
+        page_data = {
+            'title': NEW_PAGE_NAME, 
+            'slug': page.get_slug(), 
+            'language': title.language,
+            'site': page.site.pk, 
+            'template': page.template,
+        }
+        # required only if user haves can_change_permission
+        page_data['pagepermission_set-TOTAL_FORMS'] = 0
+        page_data['pagepermission_set-INITIAL_FORMS'] = 0
+        page_data['pagepermission_set-MAX_NUM_FORMS'] = 0
+        page_data['pagepermission_set-2-TOTAL_FORMS'] = 0
+        page_data['pagepermission_set-2-INITIAL_FORMS'] = 0
+        page_data['pagepermission_set-2-MAX_NUM_FORMS'] = 0
+        
+        with self.login_user_context(normal_guy):
+            resp = self.client.post(base.URL_CMS_PAGE_CHANGE % page.pk, page_data, 
+                                    follow=True)
+            self.assertEqual(resp.status_code, 200)
+            self.assertTemplateNotUsed(resp, 'admin/login.html')
+            page = Page.objects.get(pk=page.pk)
+            
+            self.assertEqual(page.get_title(), NEW_PAGE_NAME)
+            self.assertEqual(page.reverse_id, REVERSE_ID)
+            title = page.get_title_obj()
+            self.assertEqual(title.application_urls, APPLICATION_URLS)
+            
+            # The admin edits the page (change the page name for ex.)
+            page_data = {
+                'title': OLD_PAGE_NAME, 
+                'slug': page.get_slug(), 
+                'language': title.language,
+                'site': page.site.pk, 
+                'template': page.template,
+                'reverse_id': page.reverse_id,
+            }
+            # required only if user haves can_change_permission
+            page_data['pagepermission_set-TOTAL_FORMS'] = 0
+            page_data['pagepermission_set-INITIAL_FORMS'] = 0
+            page_data['pagepermission_set-MAX_NUM_FORMS'] = 0
+            page_data['pagepermission_set-2-TOTAL_FORMS'] = 0
+            page_data['pagepermission_set-2-INITIAL_FORMS'] = 0
+            page_data['pagepermission_set-2-MAX_NUM_FORMS'] = 0
+        
+        with self.login_user_context(admin):
+            resp = self.client.post(base.URL_CMS_PAGE_CHANGE % page.pk, page_data, 
+                                    follow=True)
+            self.assertEqual(resp.status_code, 200)
+            self.assertTemplateNotUsed(resp, 'admin/login.html')
+            page = Page.objects.get(pk=page.pk)
+            
+            self.assertEqual(page.get_title(), OLD_PAGE_NAME)
+            self.assertEqual(page.reverse_id, REVERSE_ID)
+            title = page.get_title_obj()
+            self.assertEqual(title.application_urls, '')
 
     def test_delete(self):
         admin = self._get_guys(True)