diff --git a/horizon/base.py b/horizon/base.py
index a2b3e8b480..8e6c10dfbf 100644
--- a/horizon/base.py
+++ b/horizon/base.py
@@ -46,6 +46,8 @@ from horizon.decorators import require_perms # noqa
from horizon import loaders
+# Name of the panel group for panels to be displayed without a group.
+DEFAULT_PANEL_GROUP = 'default'
LOG = logging.getLogger(__name__)
@@ -335,7 +337,7 @@ class PanelGroup(object):
"""
def __init__(self, dashboard, slug=None, name=None, panels=None):
self.dashboard = dashboard
- self.slug = slug or getattr(self, "slug", "default")
+ self.slug = slug or getattr(self, "slug", DEFAULT_PANEL_GROUP)
self.name = name or getattr(self, "name", None)
# Our panels must be mutable so it can be extended by others.
self.panels = list(panels or getattr(self, "panels", []))
@@ -561,6 +563,7 @@ class Dashboard(Registry, HorizonComponent):
self.panels = [self.panels]
# Now iterate our panel sets.
+ default_created = False
for panel_set in self.panels:
# Instantiate PanelGroup classes.
if not isinstance(panel_set, collections.Iterable) and \
@@ -573,7 +576,14 @@ class Dashboard(Registry, HorizonComponent):
# Put our results into their appropriate places
panels_to_discover.extend(panel_group.panels)
panel_groups.append((panel_group.slug, panel_group))
+ if panel_group.slug == DEFAULT_PANEL_GROUP:
+ default_created = True
+ # Plugin panels can be added to a default panel group. Make sure such a
+ # default group exists.
+ if not default_created:
+ default_group = PanelGroup(self)
+ panel_groups.insert(0, (default_group.slug, default_group))
self._panel_groups = collections.OrderedDict(panel_groups)
# Do the actual discovery
diff --git a/openstack_dashboard/test/test_panels/another_panel/__init__.py b/openstack_dashboard/test/test_panels/another_panel/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/openstack_dashboard/test/test_panels/another_panel/panel.py b/openstack_dashboard/test/test_panels/another_panel/panel.py
new file mode 100644
index 0000000000..e78c32d201
--- /dev/null
+++ b/openstack_dashboard/test/test_panels/another_panel/panel.py
@@ -0,0 +1,18 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import horizon
+
+
+class AnotherPanel(horizon.Panel):
+ name = "Another Plugin Panel"
+ slug = 'another_panel'
diff --git a/openstack_dashboard/test/test_panels/another_panel/templates/another_panel/index.html b/openstack_dashboard/test/test_panels/another_panel/templates/another_panel/index.html
new file mode 100644
index 0000000000..0d824f23ec
--- /dev/null
+++ b/openstack_dashboard/test/test_panels/another_panel/templates/another_panel/index.html
@@ -0,0 +1,11 @@
+{% extends 'base.html' %}
+{% load i18n %}
+{% block title %}Another Plugin-based Panel{% endblock %}
+
+{% block main %}
+
+
+ Another Plugin-based Panel
+
+
+{% endblock %}
diff --git a/openstack_dashboard/test/test_panels/another_panel/urls.py b/openstack_dashboard/test/test_panels/another_panel/urls.py
new file mode 100644
index 0000000000..862857fd3b
--- /dev/null
+++ b/openstack_dashboard/test/test_panels/another_panel/urls.py
@@ -0,0 +1,21 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from django.conf.urls import patterns
+from django.conf.urls import url
+
+from openstack_dashboard.test.test_panels.another_panel import views
+
+urlpatterns = patterns(
+ '',
+ url(r'^$', views.IndexView.as_view(), name='index'),
+)
diff --git a/openstack_dashboard/test/test_panels/another_panel/views.py b/openstack_dashboard/test/test_panels/another_panel/views.py
new file mode 100644
index 0000000000..35882683ed
--- /dev/null
+++ b/openstack_dashboard/test/test_panels/another_panel/views.py
@@ -0,0 +1,20 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from django.utils.translation import ugettext_lazy as _
+
+from horizon import views
+
+
+class IndexView(views.HorizonTemplateView):
+ template_name = 'admin/another_panel/index.html'
+ page_title = _("Another Plugin-based Panel")
diff --git a/openstack_dashboard/test/test_plugins/panel_group_config/_50_admin_add_panel_to_default_group.py b/openstack_dashboard/test/test_plugins/panel_group_config/_50_admin_add_panel_to_default_group.py
new file mode 100644
index 0000000000..6e6a97f593
--- /dev/null
+++ b/openstack_dashboard/test/test_plugins/panel_group_config/_50_admin_add_panel_to_default_group.py
@@ -0,0 +1,10 @@
+# The name of the panel to be added to HORIZON_CONFIG. Required.
+PANEL = 'another_panel'
+# The name of the dashboard the PANEL associated with. Required.
+PANEL_DASHBOARD = 'admin'
+# The name of the panel group the PANEL is associated with.
+PANEL_GROUP = 'default'
+
+# Python panel class of the PANEL to be added.
+ADD_PANEL = \
+ 'openstack_dashboard.test.test_panels.another_panel.panel.AnotherPanel'
diff --git a/openstack_dashboard/test/test_plugins/panel_group_tests.py b/openstack_dashboard/test/test_plugins/panel_group_tests.py
index 7e85802de6..4b8046693d 100644
--- a/openstack_dashboard/test/test_plugins/panel_group_tests.py
+++ b/openstack_dashboard/test/test_plugins/panel_group_tests.py
@@ -18,6 +18,8 @@ from django.test.utils import override_settings
import horizon
from openstack_dashboard.test import helpers as test
+from openstack_dashboard.test.test_panels.another_panel \
+ import panel as another_panel
from openstack_dashboard.test.test_panels.plugin_panel \
import panel as plugin_panel
from openstack_dashboard.test.test_panels.second_panel \
@@ -76,3 +78,10 @@ class PanelGroupPluginTests(test.PluginTestCase):
def test_unregistered_panel_group(self):
dashboard = horizon.get_dashboard("admin")
self.assertIsNone(dashboard.get_panel_group("nonexistent_panel"))
+
+ def test_add_panel_to_default_panel_group(self):
+ dashboard = horizon.get_dashboard('admin')
+ default_panel_group = dashboard.get_panel_group('default')
+ self.assertIsNotNone(default_panel_group)
+ self.assertIn(another_panel.AnotherPanel,
+ [p.__class__ for p in default_panel_group])