Repository: django-cms/django-cms

Issue:
Wrong handling of multiple apphooks instances in new apphooks config implementation



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
@@ -264,15 +264,16 @@ def __init__(self, *args, **kwargs):
             )
             self.fields['application_urls'].choices = [('', "---------")] + apphook_pool.get_apphooks()
 
+            page_data = self.data if self.data else self.initial
             if app_configs:
                 self.fields['application_configs'].widget = ApplicationConfigSelect(
                     attrs={'id': 'application_configs'},
                     app_configs=app_configs)
 
-                if self.data.get('application_urls', False) and self.data['application_urls'] in app_configs:
-                    self.fields['application_configs'].choices = [(config.pk, force_text(config)) for config in app_configs[self.data['application_urls']].get_configs()]
+                if page_data.get('application_urls', False) and page_data['application_urls'] in app_configs:
+                    self.fields['application_configs'].choices = [(config.pk, force_text(config)) for config in app_configs[page_data['application_urls']].get_configs()]
 
-                    apphook = self.data.get('application_urls', False)
+                    apphook = page_data.get('application_urls', False)
                     try:
                         config = apphook_pool.get_apphook(apphook).get_configs().get(namespace=self.initial['application_namespace'])
                         self.fields['application_configs'].initial = config.pk
@@ -283,7 +284,7 @@ def __init__(self, *args, **kwargs):
                         pass
                 else:
                     # If app_config apphook is not selected, drop any value
-                    # for application_configs do avoid the field dato for
+                    # for application_configs to avoid the field data from
                     # being validated by the field itself
                     try:
                         del self.data['application_configs']
@@ -293,6 +294,12 @@ def __init__(self, *args, **kwargs):
         if 'redirect' in self.fields:
             self.fields['redirect'].widget.language = self.fields['language'].initial
 
+    def _check_unique_namespace_instance(self, namespace):
+        return Page.objects.filter(
+            publisher_is_draft=True,
+            application_namespace=namespace
+        ).exclude(pk=self.instance.pk).exists()
+
     def clean(self):
         cleaned_data = super(AdvancedSettingsForm, self).clean()
         if 'reverse_id' in self.fields:
@@ -315,31 +322,39 @@ def clean(self):
                 # the 'usual' namespace field to be backward compatible
                 # with existing apphooks
                 config = apphook_pool.get_apphook(apphook).get_configs().get(pk=int(application_config))
-                self.cleaned_data['application_namespace'] = config.namespace
+                if self._check_unique_namespace_instance(config.namespace):
+                    # Looks like there's already one with the default instance
+                    # namespace defined.
+                    self._errors['application_configs'] = ErrorList([
+                        _('An application instance using this configuration already exists.')
+                    ])
+                else:
+                    self.cleaned_data['application_namespace'] = config.namespace
             else:
-                # The attribute on the apps 'app_name' is a misnomer, it should be
-                # 'application_namespace'.
-                application_namespace = apphook_pool.get_apphook(apphook).app_name
-                if application_namespace and not instance_namespace:
-                    if Page.objects.filter(
-                        publisher_is_draft=True,
-                        application_urls=apphook,
-                        application_namespace=application_namespace
-                    ).exclude(pk=self.instance.pk).count():
-                        # Looks like there's already one with the default instance
-                        # namespace defined.
-                        self._errors['application_urls'] = ErrorList([
-                            _('''You selected an apphook with an "app_name".
-                                You must enter a unique instance name.''')
+                if instance_namespace:
+                    if self._check_unique_namespace_instance(instance_namespace):
+                        self._errors['application_namespace'] = ErrorList([
+                            _('An application instance with this name already exists.')
                         ])
-                    else:
-                        # OK, there are zero instances of THIS app that use the
-                        # default instance namespace, so, since the user didn't
-                        # provide one, we'll use the default. NOTE: The following
-                        # line is really setting the "instance namespace" of the
-                        # new app to the app’s "application namespace", which is
-                        # the default instance namespace.
-                        self.cleaned_data['application_namespace'] = application_namespace
+                else:
+                    # The attribute on the apps 'app_name' is a misnomer, it should be
+                    # 'application_namespace'.
+                    application_namespace = apphook_pool.get_apphook(apphook).app_name
+                    if application_namespace and not instance_namespace:
+                        if self._check_unique_namespace_instance(application_namespace):
+                            # Looks like there's already one with the default instance
+                            # namespace defined.
+                            self._errors['application_namespace'] = ErrorList([
+                                _('An application instance with this name already exists.')
+                            ])
+                        else:
+                            # OK, there are zero instances of THIS app that use the
+                            # default instance namespace, so, since the user didn't
+                            # provide one, we'll use the default. NOTE: The following
+                            # line is really setting the "instance namespace" of the
+                            # new app to the app’s "application namespace", which is
+                            # the default instance namespace.
+                            self.cleaned_data['application_namespace'] = application_namespace
 
         if instance_namespace and not apphook:
             self.cleaned_data['application_namespace'] = None
@@ -347,17 +362,11 @@ def clean(self):
         if application_config and not apphook:
             self.cleaned_data['application_configs'] = None
 
-        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
+        return self.cleaned_data
 
     def clean_xframe_options(self):
         if 'xframe_options' not in self.fields:
-            return # nothing to do, field isn't present
+            return  # nothing to do, field isn't present
 
         xframe_options = self.cleaned_data['xframe_options']
         if xframe_options == '':


Test patch: