Repository: django-cms/django-cms

Issue:
Application namespace throws error if there are dublicates
When adding another page with the same application namespace "faq" for example, the following error occurs:

IntegrityError at /en/admin/cms/page/59/advanced-settings/
duplicate key value violates unique constraint "cms_page_publisher_is_draft_application_namespace_key"
DETAIL:  Key (publisher_is_draft, application_namespace)=(t, faq) already exists.



Discussion:

Resolving patch:
diff --git a/cms/admin/forms.py b/cms/admin/forms.py
--- a/cms/admin/forms.py
+++ b/cms/admin/forms.py
@@ -212,18 +212,24 @@ def clean(self):
                         pk=self.instance.pk).count():
                     self._errors['reverse_id'] = self.error_class(
                         [_('A page with this reverse URL id exists already.')])
-        apphook = cleaned_data['application_urls']
-        namespace = cleaned_data['application_namespace']
+        apphook = cleaned_data.get('application_urls', None)
+        namespace = cleaned_data.get('application_namespace', None)
         if apphook:
             apphook_pool.discover_apps()
             if apphook_pool.apps[apphook].app_name and not namespace:
                 self._errors['application_urls'] = ErrorList(
-                    [_('You selected an apphook with an "app_name". You must enter a namespace.')])
+                    [_('You selected an apphook with an "app_name". You must enter a instance name.')])
         if namespace and not apphook:
             self._errors['application_namespace'] = ErrorList(
-                [_("If you enter a namespace you need an application url as well.")])
+                [_("If you enter an instance name you need an application url as well.")])
         return cleaned_data
 
+    def clean_application_namespace(self):
+        namespace = self.cleaned_data['application_namespace']
+        if namespace and Page.objects.filter(publisher_is_draft=True, application_namespace=namespace).exclude(pk=self.instance.pk).count():
+            raise ValidationError(_('A instance name with this name already exists.'))
+        return namespace
+
     def clean_overwrite_url(self):
         if 'overwrite_url' in self.fields:
             url = self.cleaned_data['overwrite_url']
diff --git a/cms/models/pagemodel.py b/cms/models/pagemodel.py
--- a/cms/models/pagemodel.py
+++ b/cms/models/pagemodel.py
@@ -70,7 +70,7 @@ class Page(with_metaclass(PageMetaClass, MPTTModel)):
                                                         help_text=_("limit when this page is visible in the menu"))
     is_home = models.BooleanField(editable=False, db_index=True, default=False)
     application_urls = models.CharField(_('application'), max_length=200, blank=True, null=True, db_index=True)
-    application_namespace = models.CharField(_('application namespace'), max_length=200, blank=True, null=True)
+    application_namespace = models.CharField(_('application instance name'), max_length=200, blank=True, null=True)
     level = models.PositiveIntegerField(db_index=True, editable=False)
     lft = models.PositiveIntegerField(db_index=True, editable=False)
     rght = models.PositiveIntegerField(db_index=True, editable=False)


Test patch:
diff --git a/cms/tests/admin.py b/cms/tests/admin.py
--- a/cms/tests/admin.py
+++ b/cms/tests/admin.py
@@ -249,6 +249,50 @@ def test_edit_does_not_reset_apphook(self):
             self.assertEqual(page.reverse_id, REVERSE_ID)
             self.assertEqual(page.application_urls, '')
 
+    def test_2apphooks_with_same_namespace(self):
+        PAGE1 = 'Test Page'
+        PAGE2 = '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(PAGE1, "nav_playground.html", "en",
+                           site=site, created_by=admin)
+        page2 = create_page(PAGE2, "nav_playground.html", "en",
+                           site=site, created_by=admin)
+
+        page.application_urls = APPLICATION_URLS
+        page.application_namespace = "space1"
+        page.save()
+        page2.application_urls = APPLICATION_URLS
+        page2.save()
+
+
+        # The admin edits the page (change the page name for ex.)
+        page_data = {
+            'title': PAGE2,
+            'slug': page2.get_slug(),
+            'language': 'en',
+            'site': page.site.pk,
+            'template': page2.template,
+            'application_urls':'SampleApp',
+            'application_namespace':'space1',
+        }
+
+        with self.login_user_context(admin):
+            resp = self.client.post(base.URL_CMS_PAGE_ADVANCED_CHANGE % page.pk, page_data)
+            self.assertEqual(resp.status_code, 302)
+            self.assertEqual(Page.objects.filter(application_namespace="space1").count(), 1)
+            resp = self.client.post(base.URL_CMS_PAGE_ADVANCED_CHANGE % page2.pk, page_data)
+            self.assertEqual(resp.status_code, 200)
+            page_data['application_namespace'] = 'space2'
+            resp = self.client.post(base.URL_CMS_PAGE_ADVANCED_CHANGE % page2.pk, page_data)
+            self.assertEqual(resp.status_code, 302)
+
     def test_delete(self):
         admin = self.get_superuser()
         page = create_page("delete-page", "nav_playground.html", "en",
@@ -1190,7 +1234,7 @@ def test_tree_view_queries(self):
 
         user = self.get_superuser()
         with self.login_user_context(user):
-            with self.assertNumQueries(FuzzyInt(19, 25)):
+            with self.assertNumQueries(FuzzyInt(18, 25)):
                 output = force_unicode(self.client.get('/en/admin/cms/page/'))