[python-django-horizon] Updated patches from master-patches
Matthias Runge
mrunge at fedoraproject.org
Thu Oct 16 10:19:20 UTC 2014
commit 65c8e21fb5d6ffb87407228be4a6dd45c6644b28
Author: Matthias Runge <mrunge at redhat.com>
Date: Thu Oct 16 10:30:56 2014 +0200
Updated patches from master-patches
0001-disable-debug-move-web-root.patch | 61 ++
...file-location-to-tmp-and-also-add-localho.patch | 4 +-
...-Add-a-customization-module-based-on-RHOS.patch | 8 +-
...olicy-files-and-checks-to-etc-openstack-d.patch | 4 +-
...5-move-SECRET_KEY-secret_key_store-to-tmp.patch | 4 +-
...atch => 0006-RCUE-navbar-and-login-screen.patch | 8 +-
...e8-issues.patch => 0007-fix-flake8-issues.patch | 2 +-
... => 0008-remove-runtime-dep-to-python-pbr.patch | 2 +-
...dd-Change-password-link-to-the-RCUE-theme.patch | 2 +-
...rcue.patch => 0010-.less-replaced-in-rcue.patch | 15 +-
0010-Theme-fixes.patch | 23 -
0011-re-add-lesscpy-to-compile-.less.patch | 6 +-
...gration-of-LESS-to-SCSS-and-various-fixes.patch | 2 +-
...redundant-Settings-button-on-downstream-t.patch | 35 +
0014-Change-page-header-heading-to-H1.patch | 143 +++
0015-Add-dropdown-actions-to-detail-page.patch | 203 ++++
...Add-dropdown-actions-to-all-details-pages.patch | 1038 ++++++++++++++++++++
...d-support-for-row-actions-to-detail-pages.patch | 205 ++++
0018-Clean-up-test-output.patch | 116 +++
python-django-horizon.spec | 26 +-
20 files changed, 1847 insertions(+), 60 deletions(-)
---
diff --git a/0001-disable-debug-move-web-root.patch b/0001-disable-debug-move-web-root.patch
new file mode 100644
index 0000000..f2e81f1
--- /dev/null
+++ b/0001-disable-debug-move-web-root.patch
@@ -0,0 +1,61 @@
+From 28e1333c3052545015639fb0805eba3e63fd080b Mon Sep 17 00:00:00 2001
+From: Matthias Runge <mrunge at redhat.com>
+Date: Fri, 5 Apr 2013 10:07:53 +0200
+Subject: [PATCH] disable debug, move web root
+
+---
+ openstack_dashboard/local/local_settings.py.example | 2 +-
+ openstack_dashboard/settings.py | 10 ++++++----
+ 2 files changed, 7 insertions(+), 5 deletions(-)
+
+diff --git a/openstack_dashboard/local/local_settings.py.example b/openstack_dashboard/local/local_settings.py.example
+index 5e988da..78f2858 100644
+--- a/openstack_dashboard/local/local_settings.py.example
++++ b/openstack_dashboard/local/local_settings.py.example
+@@ -4,7 +4,7 @@ from django.utils.translation import ugettext_lazy as _
+
+ from openstack_dashboard import exceptions
+
+-DEBUG = True
++DEBUG = False
+ TEMPLATE_DEBUG = DEBUG
+
+ # Required for Django 1.5.
+diff --git a/openstack_dashboard/settings.py b/openstack_dashboard/settings.py
+index ad9e691..786978e 100644
+--- a/openstack_dashboard/settings.py
++++ b/openstack_dashboard/settings.py
+@@ -48,7 +48,7 @@ warnings.formatwarning = lambda message, category, *args, **kwargs: \
+ '%s: %s' % (category.__name__, message)
+
+ ROOT_PATH = os.path.dirname(os.path.abspath(__file__))
+-BIN_DIR = os.path.abspath(os.path.join(ROOT_PATH, '..', 'bin'))
++BIN_DIR = '/usr/bin'
+
+ if ROOT_PATH not in sys.path:
+ sys.path.append(ROOT_PATH)
+@@ -58,12 +58,13 @@ TEMPLATE_DEBUG = DEBUG
+
+ SITE_BRANDING = 'OpenStack Dashboard'
+
+-LOGIN_URL = '/auth/login/'
+-LOGOUT_URL = '/auth/logout/'
++WEBROOT = '/dashboard'
++LOGIN_URL = WEBROOT + '/auth/login/'
++LOGOUT_URL = WEBROOT + '/auth/logout/'
+ # LOGIN_REDIRECT_URL can be used as an alternative for
+ # HORIZON_CONFIG.user_home, if user_home is not set.
+ # Do not set it to '/home/', as this will cause circular redirect loop
+-LOGIN_REDIRECT_URL = '/'
++LOGIN_REDIRECT_URL = WEBROOT
+
+ MEDIA_ROOT = os.path.abspath(os.path.join(ROOT_PATH, '..', 'media'))
+ MEDIA_URL = '/media/'
+@@ -211,6 +212,7 @@ COMPRESS_ENABLED = True
+ COMPRESS_OUTPUT_DIR = 'dashboard'
+ COMPRESS_CSS_HASHING_METHOD = 'hash'
+ COMPRESS_PARSER = 'compressor.parser.HtmlParser'
++COMPRESS_OFFLINE = True
+
+ INSTALLED_APPS = [
+ 'openstack_dashboard',
diff --git a/0001-change-lockfile-location-to-tmp-and-also-add-localho.patch b/0002-change-lockfile-location-to-tmp-and-also-add-localho.patch
similarity index 93%
rename from 0001-change-lockfile-location-to-tmp-and-also-add-localho.patch
rename to 0002-change-lockfile-location-to-tmp-and-also-add-localho.patch
index 6c7afa5..f237e49 100644
--- a/0001-change-lockfile-location-to-tmp-and-also-add-localho.patch
+++ b/0002-change-lockfile-location-to-tmp-and-also-add-localho.patch
@@ -1,4 +1,4 @@
-From 197ad035e3ca1edfdbef701150c07827ddc9b504 Mon Sep 17 00:00:00 2001
+From 854be29fe112581280f1126de6e5b31bb0070ed3 Mon Sep 17 00:00:00 2001
From: Matthias Runge <mrunge at redhat.com>
Date: Thu, 25 Jul 2013 11:32:38 +0200
Subject: [PATCH] change lockfile location to '/tmp' and also add localhost to
@@ -9,7 +9,7 @@ Subject: [PATCH] change lockfile location to '/tmp' and also add localhost to
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/openstack_dashboard/local/local_settings.py.example b/openstack_dashboard/local/local_settings.py.example
-index 5e988da..dc03d5b 100644
+index 78f2858..3feab86 100644
--- a/openstack_dashboard/local/local_settings.py.example
+++ b/openstack_dashboard/local/local_settings.py.example
@@ -12,7 +12,7 @@ TEMPLATE_DEBUG = DEBUG
diff --git a/0002-Add-a-customization-module-based-on-RHOS.patch b/0003-Add-a-customization-module-based-on-RHOS.patch
similarity index 97%
rename from 0002-Add-a-customization-module-based-on-RHOS.patch
rename to 0003-Add-a-customization-module-based-on-RHOS.patch
index 87e34ef..85c7a16 100644
--- a/0002-Add-a-customization-module-based-on-RHOS.patch
+++ b/0003-Add-a-customization-module-based-on-RHOS.patch
@@ -1,4 +1,4 @@
-From 882123ae68de71f80dd98c850b1fd25408e0551c Mon Sep 17 00:00:00 2001
+From ff582f08e91c9c7b4b1095b590ffe9dc7fea864c Mon Sep 17 00:00:00 2001
From: Matthias Runge <mrunge at redhat.com>
Date: Thu, 14 Feb 2013 12:55:54 +0100
Subject: [PATCH] Add a customization module based on RHOS
@@ -28,10 +28,10 @@ Conflicts:
create mode 100644 openstack_dashboard_theme/templates/splash.html
diff --git a/openstack_dashboard/settings.py b/openstack_dashboard/settings.py
-index ad9e691..8f4a32a 100644
+index 786978e..1fad2e9 100644
--- a/openstack_dashboard/settings.py
+++ b/openstack_dashboard/settings.py
-@@ -137,14 +137,13 @@ TEMPLATE_CONTEXT_PROCESSORS = (
+@@ -138,14 +138,13 @@ TEMPLATE_CONTEXT_PROCESSORS = (
)
TEMPLATE_LOADERS = (
@@ -47,7 +47,7 @@ index ad9e691..8f4a32a 100644
STATICFILES_FINDERS = (
'django.contrib.staticfiles.finders.FileSystemFinder',
-@@ -227,6 +226,15 @@ INSTALLED_APPS = [
+@@ -229,6 +228,15 @@ INSTALLED_APPS = [
'openstack_auth',
]
diff --git a/0003-move-RBAC-policy-files-and-checks-to-etc-openstack-d.patch b/0004-move-RBAC-policy-files-and-checks-to-etc-openstack-d.patch
similarity index 91%
rename from 0003-move-RBAC-policy-files-and-checks-to-etc-openstack-d.patch
rename to 0004-move-RBAC-policy-files-and-checks-to-etc-openstack-d.patch
index 1f54103..d32c6db 100644
--- a/0003-move-RBAC-policy-files-and-checks-to-etc-openstack-d.patch
+++ b/0004-move-RBAC-policy-files-and-checks-to-etc-openstack-d.patch
@@ -1,4 +1,4 @@
-From e347fd14730a22a91dc6e334a6751fff70297e22 Mon Sep 17 00:00:00 2001
+From 2e93cdcb50651eacc3bea504ad4bceebfeaa2980 Mon Sep 17 00:00:00 2001
From: Matthias Runge <mrunge at redhat.com>
Date: Thu, 31 Jul 2014 10:13:12 +0200
Subject: [PATCH] move RBAC policy files and checks to /etc/openstack-dashboard
@@ -10,7 +10,7 @@ Conflicts:
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/openstack_dashboard/local/local_settings.py.example b/openstack_dashboard/local/local_settings.py.example
-index dc03d5b..37910da 100644
+index 3feab86..ed547eb 100644
--- a/openstack_dashboard/local/local_settings.py.example
+++ b/openstack_dashboard/local/local_settings.py.example
@@ -273,7 +273,8 @@ TIME_ZONE = "UTC"
diff --git a/0004-move-SECRET_KEY-secret_key_store-to-tmp.patch b/0005-move-SECRET_KEY-secret_key_store-to-tmp.patch
similarity index 91%
rename from 0004-move-SECRET_KEY-secret_key_store-to-tmp.patch
rename to 0005-move-SECRET_KEY-secret_key_store-to-tmp.patch
index 2868040..a85d273 100644
--- a/0004-move-SECRET_KEY-secret_key_store-to-tmp.patch
+++ b/0005-move-SECRET_KEY-secret_key_store-to-tmp.patch
@@ -1,4 +1,4 @@
-From c1f1bc006a5d8b8441b83398477a85910af2c2b4 Mon Sep 17 00:00:00 2001
+From 16ea98344b5005843ba8ffe97bae04d2ff4b28f2 Mon Sep 17 00:00:00 2001
From: Matthias Runge <mrunge at redhat.com>
Date: Mon, 9 Sep 2013 20:52:51 +0200
Subject: [PATCH] move SECRET_KEY secret_key_store to /tmp
@@ -8,7 +8,7 @@ Subject: [PATCH] move SECRET_KEY secret_key_store to /tmp
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/openstack_dashboard/local/local_settings.py.example b/openstack_dashboard/local/local_settings.py.example
-index 37910da..0aa1a02 100644
+index ed547eb..ae84041 100644
--- a/openstack_dashboard/local/local_settings.py.example
+++ b/openstack_dashboard/local/local_settings.py.example
@@ -83,7 +83,6 @@ HORIZON_CONFIG = {
diff --git a/0005-RCUE-navbar-and-login-screen.patch b/0006-RCUE-navbar-and-login-screen.patch
similarity index 99%
rename from 0005-RCUE-navbar-and-login-screen.patch
rename to 0006-RCUE-navbar-and-login-screen.patch
index 1fced63..369953d 100644
--- a/0005-RCUE-navbar-and-login-screen.patch
+++ b/0006-RCUE-navbar-and-login-screen.patch
@@ -1,4 +1,4 @@
-From 0b1f097a679bf26e4c7bf51ecfc61845b0905499 Mon Sep 17 00:00:00 2001
+From 313c2529670282ffa9f8d59de97c0bccf966f8dc Mon Sep 17 00:00:00 2001
From: Matthias Runge <mrunge at redhat.com>
Date: Tue, 14 Oct 2014 11:33:00 +0200
Subject: [PATCH] RCUE navbar and login screen
@@ -19092,10 +19092,10 @@ index 0000000..f37f848
+ADD_INSTALLED_APPS = ['openstack_dashboard.dashboards.theme']
+
diff --git a/openstack_dashboard/settings.py b/openstack_dashboard/settings.py
-index 8f4a32a..4406706 100644
+index 1fad2e9..0e5084a 100644
--- a/openstack_dashboard/settings.py
+++ b/openstack_dashboard/settings.py
-@@ -143,7 +143,7 @@ TEMPLATE_LOADERS = (
+@@ -144,7 +144,7 @@ TEMPLATE_LOADERS = (
TEMPLATE_DIRS = (
os.path.join(ROOT_PATH, 'templates'),
@@ -19104,7 +19104,7 @@ index 8f4a32a..4406706 100644
STATICFILES_FINDERS = (
'django.contrib.staticfiles.finders.FileSystemFinder',
-@@ -226,15 +226,6 @@ INSTALLED_APPS = [
+@@ -228,15 +228,6 @@ INSTALLED_APPS = [
'openstack_auth',
]
diff --git a/0006-fix-flake8-issues.patch b/0007-fix-flake8-issues.patch
similarity index 95%
rename from 0006-fix-flake8-issues.patch
rename to 0007-fix-flake8-issues.patch
index c574f1d..6de3417 100644
--- a/0006-fix-flake8-issues.patch
+++ b/0007-fix-flake8-issues.patch
@@ -1,4 +1,4 @@
-From b8ea41c83fbb730a2147491c18c98fbefd316e71 Mon Sep 17 00:00:00 2001
+From 6532985169afd46649062a6ae6decfef68bb33e9 Mon Sep 17 00:00:00 2001
From: Matthias Runge <mrunge at redhat.com>
Date: Thu, 10 Apr 2014 09:27:21 +0200
Subject: [PATCH] fix flake8 issues
diff --git a/0007-remove-runtime-dep-to-python-pbr.patch b/0008-remove-runtime-dep-to-python-pbr.patch
similarity index 93%
rename from 0007-remove-runtime-dep-to-python-pbr.patch
rename to 0008-remove-runtime-dep-to-python-pbr.patch
index f648344..8e0ad0d 100644
--- a/0007-remove-runtime-dep-to-python-pbr.patch
+++ b/0008-remove-runtime-dep-to-python-pbr.patch
@@ -1,4 +1,4 @@
-From aad4fa47000b8c8e9eb3908f477a6a65d1c88bca Mon Sep 17 00:00:00 2001
+From 5c081060b90598fa9f30a0449a3d14be05cf265d Mon Sep 17 00:00:00 2001
From: Matthias Runge <mrunge at redhat.com>
Date: Fri, 15 Nov 2013 09:42:08 +0100
Subject: [PATCH] remove runtime dep to python-pbr
diff --git a/0008-Add-Change-password-link-to-the-RCUE-theme.patch b/0009-Add-Change-password-link-to-the-RCUE-theme.patch
similarity index 96%
rename from 0008-Add-Change-password-link-to-the-RCUE-theme.patch
rename to 0009-Add-Change-password-link-to-the-RCUE-theme.patch
index 08d8957..c48ec79 100644
--- a/0008-Add-Change-password-link-to-the-RCUE-theme.patch
+++ b/0009-Add-Change-password-link-to-the-RCUE-theme.patch
@@ -1,4 +1,4 @@
-From af0c8b76218cfce952a88a43763b46d7443dd492 Mon Sep 17 00:00:00 2001
+From 6f017c146ba566dc28f71abe7e7d07c88cba8c90 Mon Sep 17 00:00:00 2001
From: Julie Pichon <jpichon at redhat.com>
Date: Tue, 20 May 2014 16:22:31 +0100
Subject: [PATCH] Add "Change password" link to the RCUE theme.
diff --git a/0009-.less-replaced-in-rcue.patch b/0010-.less-replaced-in-rcue.patch
similarity index 73%
rename from 0009-.less-replaced-in-rcue.patch
rename to 0010-.less-replaced-in-rcue.patch
index ca42981..f10049c 100644
--- a/0009-.less-replaced-in-rcue.patch
+++ b/0010-.less-replaced-in-rcue.patch
@@ -1,17 +1,17 @@
-From 128a3b2adb6c1a97dd30f256d8de01aa264ae172 Mon Sep 17 00:00:00 2001
+From f5196bd426ccc7444cd33d88626807a4523f89d9 Mon Sep 17 00:00:00 2001
From: Matthias Runge <mrunge at redhat.com>
Date: Thu, 31 Jul 2014 11:17:29 +0200
Subject: [PATCH] .less replaced in rcue
---
- openstack_dashboard/dashboards/theme/templates/_stylesheets.html | 5 ++++-
- 1 file changed, 4 insertions(+), 1 deletion(-)
+ openstack_dashboard/dashboards/theme/templates/_stylesheets.html | 8 +++++---
+ 1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/openstack_dashboard/dashboards/theme/templates/_stylesheets.html b/openstack_dashboard/dashboards/theme/templates/_stylesheets.html
-index c3899cf..29c4b69 100644
+index c3899cf..8f9c16b 100644
--- a/openstack_dashboard/dashboards/theme/templates/_stylesheets.html
+++ b/openstack_dashboard/dashboards/theme/templates/_stylesheets.html
-@@ -1,8 +1,11 @@
+@@ -1,10 +1,12 @@
{% load compress %}
{% compress css %}
@@ -21,6 +21,9 @@ index c3899cf..29c4b69 100644
+<link href='{{ STATIC_URL }}dashboard/css/rickshaw.css' type='text/css' media='screen' rel='stylesheet' />
+<link href='{{ STATIC_URL }}dashboard/scss/horizon_charts.scss' type='text/scss' media='screen' rel='stylesheet' />
+<link href='{{ STATIC_URL }}dashboard/scss/horizon_workflow.scss' type='text/scss' media='screen' rel='stylesheet' />
++<link href='{{ STATIC_URL }}horizon/lib/font-awesome/scss/font-awesome.scss' type='text/scss' media='screen' rel='stylesheet' />
{% endcompress %}
- <link href='{{ STATIC_URL }}dashboard/css/font-awesome.min.css' type='text/css' media='screen' rel='stylesheet' />
+-<link href='{{ STATIC_URL }}dashboard/css/font-awesome.min.css' type='text/css' media='screen' rel='stylesheet' />
+-
+ <link rel="shortcut icon" href="{{ STATIC_URL }}dashboard/img/rhfavicon.ico"/>
diff --git a/0011-re-add-lesscpy-to-compile-.less.patch b/0011-re-add-lesscpy-to-compile-.less.patch
index f8f82af..5e684f7 100644
--- a/0011-re-add-lesscpy-to-compile-.less.patch
+++ b/0011-re-add-lesscpy-to-compile-.less.patch
@@ -1,4 +1,4 @@
-From 2f5adea6e0e29d700d9f5f61e9321e0a1dd1a0a6 Mon Sep 17 00:00:00 2001
+From 75e41ad9bb3b945967774462132ae5b565355287 Mon Sep 17 00:00:00 2001
From: Matthias Runge <mrunge at redhat.com>
Date: Mon, 6 Oct 2014 12:54:03 +0200
Subject: [PATCH] re-add lesscpy to compile .less
@@ -8,10 +8,10 @@ Subject: [PATCH] re-add lesscpy to compile .less
1 file changed, 1 insertion(+)
diff --git a/openstack_dashboard/settings.py b/openstack_dashboard/settings.py
-index 4406706..bb938cb 100644
+index 0e5084a..1d79451 100644
--- a/openstack_dashboard/settings.py
+++ b/openstack_dashboard/settings.py
-@@ -199,6 +199,7 @@ else:
+@@ -200,6 +200,7 @@ else:
xstatic.main.XStatic(xstatic.pkg.jquery_ui).base_dir))
COMPRESS_PRECOMPILERS = (
diff --git a/0012-Migration-of-LESS-to-SCSS-and-various-fixes.patch b/0012-Migration-of-LESS-to-SCSS-and-various-fixes.patch
index 68299f5..50e35fe 100644
--- a/0012-Migration-of-LESS-to-SCSS-and-various-fixes.patch
+++ b/0012-Migration-of-LESS-to-SCSS-and-various-fixes.patch
@@ -1,4 +1,4 @@
-From d038dc74ffec92b7cf28957d27ec4d024b4e20df Mon Sep 17 00:00:00 2001
+From 7ad131e92325f940d701a1021baec726ede0ebb6 Mon Sep 17 00:00:00 2001
From: Robb Hamilton <rhamilto at localhost.localdomain>
Date: Mon, 29 Sep 2014 11:32:19 -0400
Subject: [PATCH] Migration of LESS to SCSS and various fixes
diff --git a/0013-Remove-the-redundant-Settings-button-on-downstream-t.patch b/0013-Remove-the-redundant-Settings-button-on-downstream-t.patch
new file mode 100644
index 0000000..077ecd9
--- /dev/null
+++ b/0013-Remove-the-redundant-Settings-button-on-downstream-t.patch
@@ -0,0 +1,35 @@
+From 93fc96f4ba91d5be0c382c13c6b00f051b44a576 Mon Sep 17 00:00:00 2001
+From: Julie Pichon <jpichon at redhat.com>
+Date: Wed, 15 Oct 2014 11:06:59 +0100
+Subject: [PATCH] Remove the redundant 'Settings' button on downstream theme
+
+Resolves rhbz: 1114292
+
+Change-Id: I725094a9c803c7d82bdfb3b6888e944d008e8d6f
+---
+ .../dashboards/theme/templates/horizon/_nav_list.html | 12 +++++++-----
+ 1 file changed, 7 insertions(+), 5 deletions(-)
+
+diff --git a/openstack_dashboard/dashboards/theme/templates/horizon/_nav_list.html b/openstack_dashboard/dashboards/theme/templates/horizon/_nav_list.html
+index 6648421..ab97b79 100644
+--- a/openstack_dashboard/dashboards/theme/templates/horizon/_nav_list.html
++++ b/openstack_dashboard/dashboards/theme/templates/horizon/_nav_list.html
+@@ -2,10 +2,12 @@
+
+ <ul class="nav nav-tabs">
+ {% for component in components %}
+- {% if user|has_permissions:component %}
+- <li{% if current.slug == component.slug %} class="active"{% endif %}>
+- <a href="{{ component.get_absolute_url }}" tabindex='1'>{{ component.name }}</a>
+- </li>
++ {% if component.slug != 'settings' %}
++ {% if user|has_permissions:component %}
++ <li{% if current.slug == component.slug %} class="active"{% endif %}>
++ <a href="{{ component.get_absolute_url }}" tabindex='1'>{{ component.name }}</a>
++ </li>
++ {% endif %}
+ {% endif %}
+ {% endfor %}
+-</ul>
+\ No newline at end of file
++</ul>
diff --git a/0014-Change-page-header-heading-to-H1.patch b/0014-Change-page-header-heading-to-H1.patch
new file mode 100644
index 0000000..56605df
--- /dev/null
+++ b/0014-Change-page-header-heading-to-H1.patch
@@ -0,0 +1,143 @@
+From dfe519cce7c2715c9d9761b8935730289259c45b Mon Sep 17 00:00:00 2001
+From: Ana Krivokapic <akrivoka at redhat.com>
+Date: Tue, 19 Aug 2014 17:28:37 +0200
+Subject: [PATCH] Change page header heading to H1
+
+Partially implements: blueprint detail-pages-ia
+
+Change-Id: I42b58e73fd3990c5b5e197e5694eb77eeb42f77c
+---
+ horizon/templates/horizon/common/_domain_page_header.html | 4 ++--
+ horizon/templates/horizon/common/_page_header.html | 2 +-
+ openstack_dashboard/dashboards/admin/volumes/snapshots/tests.py | 2 +-
+ openstack_dashboard/dashboards/project/images/images/tests.py | 2 +-
+ openstack_dashboard/dashboards/project/volumes/backups/tests.py | 4 ++--
+ openstack_dashboard/dashboards/project/volumes/snapshots/tests.py | 2 +-
+ openstack_dashboard/dashboards/project/volumes/volumes/tests.py | 2 +-
+ openstack_dashboard/static/dashboard/scss/horizon.scss | 6 +++---
+ 8 files changed, 12 insertions(+), 12 deletions(-)
+
+diff --git a/horizon/templates/horizon/common/_domain_page_header.html b/horizon/templates/horizon/common/_domain_page_header.html
+index 63ab048..e24c933 100644
+--- a/horizon/templates/horizon/common/_domain_page_header.html
++++ b/horizon/templates/horizon/common/_domain_page_header.html
+@@ -1,11 +1,11 @@
+ {% load i18n %}
+ {% block page_header %}
+ <div class='page-header'>
+- <h2>
++ <h1>
+ {% if request.session.domain_context_name %}
+ <em>{{ request.session.domain_context_name }}:</em>
+ {% endif %}
+ {{ title }}
+- </h2>
++ </h1>
+ </div>
+ {% endblock %}
+diff --git a/horizon/templates/horizon/common/_page_header.html b/horizon/templates/horizon/common/_page_header.html
+index adeb06e..01a9210 100644
+--- a/horizon/templates/horizon/common/_page_header.html
++++ b/horizon/templates/horizon/common/_page_header.html
+@@ -1,6 +1,6 @@
+ {% load i18n %}
+ {% block page_header %}
+ <div class='page-header'>
+- <h2>{{ title }}</h2>
++ <h1>{{ title }}</h1>
+ </div>
+ {% endblock %}
+diff --git a/openstack_dashboard/dashboards/admin/volumes/snapshots/tests.py b/openstack_dashboard/dashboards/admin/volumes/snapshots/tests.py
+index 84a5abc..e9d76fb 100644
+--- a/openstack_dashboard/dashboards/admin/volumes/snapshots/tests.py
++++ b/openstack_dashboard/dashboards/admin/volumes/snapshots/tests.py
+@@ -59,7 +59,7 @@ class VolumeSnapshotsViewTests(test.BaseAdminViewTests):
+ res = self.client.get(url)
+
+ self.assertContains(res,
+- "<h2>Volume Snapshot Details: %s</h2>" %
++ "<h1>Volume Snapshot Details: %s</h1>" %
+ snapshot.name,
+ 1, 200)
+ self.assertContains(res, "<dd>test snapshot</dd>", 1, 200)
+diff --git a/openstack_dashboard/dashboards/project/images/images/tests.py b/openstack_dashboard/dashboards/project/images/images/tests.py
+index 3507b87..afccd41 100644
+--- a/openstack_dashboard/dashboards/project/images/images/tests.py
++++ b/openstack_dashboard/dashboards/project/images/images/tests.py
+@@ -229,7 +229,7 @@ class ImageViewTests(test.TestCase):
+ 'project/images/images/detail.html')
+ self.assertEqual(res.context['image'].name, image.name)
+ self.assertEqual(res.context['image'].protected, image.protected)
+- self.assertContains(res, "<h2>Image Details: %s</h2>" % image.name,
++ self.assertContains(res, "<h1>Image Details: %s</h1>" % image.name,
+ 1, 200)
+
+ @test.create_stubs({api.glance: ('image_get',)})
+diff --git a/openstack_dashboard/dashboards/project/volumes/backups/tests.py b/openstack_dashboard/dashboards/project/volumes/backups/tests.py
+index 088be05..651b79f 100644
+--- a/openstack_dashboard/dashboards/project/volumes/backups/tests.py
++++ b/openstack_dashboard/dashboards/project/volumes/backups/tests.py
+@@ -100,7 +100,7 @@ class VolumeBackupsViewTests(test.TestCase):
+ res = self.client.get(url)
+
+ self.assertContains(res,
+- "<h2>Volume Backup Details: %s</h2>" %
++ "<h1>Volume Backup Details: %s</h1>" %
+ backup.name,
+ 1, 200)
+ self.assertContains(res, "<dd>%s</dd>" % backup.name, 1, 200)
+@@ -141,7 +141,7 @@ class VolumeBackupsViewTests(test.TestCase):
+ res = self.client.get(url)
+
+ self.assertContains(res,
+- "<h2>Volume Backup Details: %s</h2>" %
++ "<h1>Volume Backup Details: %s</h1>" %
+ backup.name,
+ 1, 200)
+ self.assertContains(res, "<dd>%s</dd>" % backup.name, 1, 200)
+diff --git a/openstack_dashboard/dashboards/project/volumes/snapshots/tests.py b/openstack_dashboard/dashboards/project/volumes/snapshots/tests.py
+index 25a948a..ab46569 100644
+--- a/openstack_dashboard/dashboards/project/volumes/snapshots/tests.py
++++ b/openstack_dashboard/dashboards/project/volumes/snapshots/tests.py
+@@ -152,7 +152,7 @@ class VolumeSnapshotsViewTests(test.TestCase):
+ res = self.client.get(url)
+
+ self.assertContains(res,
+- "<h2>Volume Snapshot Details: %s</h2>" %
++ "<h1>Volume Snapshot Details: %s</h1>" %
+ snapshot.name,
+ 1, 200)
+ self.assertContains(res, "<dd>test snapshot</dd>", 1, 200)
+diff --git a/openstack_dashboard/dashboards/project/volumes/volumes/tests.py b/openstack_dashboard/dashboards/project/volumes/volumes/tests.py
+index 58cbc1b..ec304a2 100644
+--- a/openstack_dashboard/dashboards/project/volumes/volumes/tests.py
++++ b/openstack_dashboard/dashboards/project/volumes/volumes/tests.py
+@@ -972,7 +972,7 @@ class VolumeViewTests(test.TestCase):
+ args=[volume.id])
+ res = self.client.get(url)
+
+- self.assertContains(res, "<h2>Volume Details: Volume name</h2>",
++ self.assertContains(res, "<h1>Volume Details: Volume name</h1>",
+ 1, 200)
+ self.assertContains(res, "<dd>Volume name</dd>", 1, 200)
+ self.assertContains(res, "<dd>%s</dd>" % volume.id, 1, 200)
+diff --git a/openstack_dashboard/static/dashboard/scss/horizon.scss b/openstack_dashboard/static/dashboard/scss/horizon.scss
+index 20aadaf..496e58d 100755
+--- a/openstack_dashboard/static/dashboard/scss/horizon.scss
++++ b/openstack_dashboard/static/dashboard/scss/horizon.scss
+@@ -172,12 +172,12 @@ dt {
+
+ .page-header {
+ margin: 0 0 5px 0;
+- padding: 0 0 5px 0;
+- border-bottom: 2px solid $headings-color;
++ padding: 10px 0 5px 0;
++ border-bottom: 0;
+ font-family: anivers;
+ height: auto;
+ width: 100%;
+- h2 {
++ h1 {
+ margin: 0;
+ }
+ }
diff --git a/0015-Add-dropdown-actions-to-detail-page.patch b/0015-Add-dropdown-actions-to-detail-page.patch
new file mode 100644
index 0000000..a41868d
--- /dev/null
+++ b/0015-Add-dropdown-actions-to-detail-page.patch
@@ -0,0 +1,203 @@
+From 4beb6924bc7530f0a8c52c87070e0ab91d8235e7 Mon Sep 17 00:00:00 2001
+From: Ana Krivokapic <akrivoka at redhat.com>
+Date: Tue, 26 Aug 2014 13:19:12 +0200
+Subject: [PATCH] Add dropdown actions to detail page
+
+Add support for performing actions on an object from the object's
+detail page. Actions are displayed in the page header, in a
+dropdown menu. As a POC, this patch enables dropdown actions on a
+detail page of a volume (/project/volumes/volume-id/).
+
+Partially implements: blueprint detail-pages-ia
+
+Change-Id: Ie21c63d86c2806d9689ae7f1a67b67c83710c108
+---
+ horizon/tables/base.py | 7 ++--
+ .../horizon/common/_data_table_row_actions.html | 2 +-
+ horizon/templates/horizon/common/_page_header.html | 6 +++
+ .../dashboards/project/volumes/volumes/views.py | 6 ++-
+ .../static/dashboard/scss/horizon.scss | 47 ++++++++++++----------
+ 5 files changed, 42 insertions(+), 26 deletions(-)
+
+diff --git a/horizon/tables/base.py b/horizon/tables/base.py
+index 16e3fd9..2f76b79 100644
+--- a/horizon/tables/base.py
++++ b/horizon/tables/base.py
+@@ -676,7 +676,7 @@ class Cell(html.HTMLElement):
+ form_field_attributes)
+ table._data_cache[column][table.get_object_id(datum)] = data
+ elif column.auto == "actions":
+- data = table.render_row_actions(datum)
++ data = table.render_row_actions(datum, pull_right=False)
+ table._data_cache[column][table.get_object_id(datum)] = data
+ else:
+ data = column.get_data(datum)
+@@ -1388,7 +1388,7 @@ class DataTable(object):
+ self.set_multiselect_column_visibility(len(bound_actions) > 0)
+ return table_actions_template.render(context)
+
+- def render_row_actions(self, datum):
++ def render_row_actions(self, datum, pull_right=True):
+ """Renders the actions specified in ``Meta.row_actions`` using the
+ current row data.
+ """
+@@ -1396,7 +1396,8 @@ class DataTable(object):
+ row_actions_template = template.loader.get_template(template_path)
+ bound_actions = self.get_row_actions(datum)
+ extra_context = {"row_actions": bound_actions,
+- "row_id": self.get_object_id(datum)}
++ "row_id": self.get_object_id(datum),
++ "pull_right": pull_right}
+ context = template.RequestContext(self.request, extra_context)
+ return row_actions_template.render(context)
+
+diff --git a/horizon/templates/horizon/common/_data_table_row_actions.html b/horizon/templates/horizon/common/_data_table_row_actions.html
+index 9508152..3f3368b 100644
+--- a/horizon/templates/horizon/common/_data_table_row_actions.html
++++ b/horizon/templates/horizon/common/_data_table_row_actions.html
+@@ -2,7 +2,7 @@
+
+ {% spaceless %} {# This makes sure whitespace doesn't affect positioning for dropdown. #}
+ {% if row_actions|length > 1 %}
+-<div class="btn-group">
++<div class="btn-group {% if pull_right %}pull-right{% endif %}">
+ {% for action in row_actions %}
+ {% if forloop.first %}
+ {% include "horizon/common/_data_table_row_action.html" %}
+diff --git a/horizon/templates/horizon/common/_page_header.html b/horizon/templates/horizon/common/_page_header.html
+index 01a9210..9491a55 100644
+--- a/horizon/templates/horizon/common/_page_header.html
++++ b/horizon/templates/horizon/common/_page_header.html
+@@ -2,5 +2,11 @@
+ {% block page_header %}
+ <div class='page-header'>
+ <h1>{{ title }}</h1>
++ {% if actions %}
++ <form class='actions_column pull-right' action='{{ url }}' method="POST">
++ {% csrf_token %}
++ {{ actions }}
++ </form>
++ {% endif %}
+ </div>
+ {% endblock %}
+diff --git a/openstack_dashboard/dashboards/project/volumes/volumes/views.py b/openstack_dashboard/dashboards/project/volumes/volumes/views.py
+index f068e9e..c78cd09 100644
+--- a/openstack_dashboard/dashboards/project/volumes/volumes/views.py
++++ b/openstack_dashboard/dashboards/project/volumes/volumes/views.py
+@@ -44,8 +44,12 @@ class DetailView(tabs.TabView):
+ template_name = 'project/volumes/volumes/detail.html'
+
+ def get_context_data(self, **kwargs):
++ datum = self.get_data()
++ table = project_tables.VolumesTable(self.request)
+ context = super(DetailView, self).get_context_data(**kwargs)
+- context["volume"] = self.get_data()
++ context["volume"] = datum
++ context["url"] = self.get_redirect_url()
++ context["actions"] = table.render_row_actions(datum)
+ return context
+
+ @memoized.memoized_method
+diff --git a/openstack_dashboard/static/dashboard/scss/horizon.scss b/openstack_dashboard/static/dashboard/scss/horizon.scss
+index 496e58d..961850a 100755
+--- a/openstack_dashboard/static/dashboard/scss/horizon.scss
++++ b/openstack_dashboard/static/dashboard/scss/horizon.scss
+@@ -1090,20 +1090,25 @@ td.select {
+
+ /* Actions dropdown */
+
+-td.actions_column {
++.actions_column {
+ white-space: nowrap;
+ padding: 10px;
+ position: relative;
+ width: 200px;
+ }
+
+-td.actions_column .btn-group {
++form.actions_column {
++ width: auto;
++ font-family: $font-family-base;
++}
++
++.actions_column .btn-group {
+ display: inline-block;
+ }
+
+-td.actions_column .row_actions a,
+-td.actions_column .row_actions input,
+-td.actions_column .row_actions button,
++.actions_column .row_actions a,
++.actions_column .row_actions input,
++.actions_column .row_actions button,
+ div.table_actions_menu .dropdown-menu a,
+ div.table_actions_menu .dropdown-menu input,
+ div.table_actions_menu .dropdown-menu button {
+@@ -1118,11 +1123,11 @@ div.table_actions_menu .dropdown-menu button {
+ @include box-shadow(none);
+ }
+
+-td.actions_column .row_actions .hide {
++.actions_column .row_actions .hide {
+ display: none;
+ }
+
+-td.actions_column .btn-action-required {
++.actions_column .btn-action-required {
+ font-weight: bold;
+ }
+
+@@ -1156,8 +1161,8 @@ td.actions_column .btn-action-required {
+ background: none;
+ }
+
+-td.actions_column .dropdown-menu a:hover,
+-td.actions_column .dropdown-menu button:hover,
++.actions_column .dropdown-menu a:hover,
++.actions_column .dropdown-menu button:hover,
+ div.table_actions_menu .dropdown-menu a:hover,
+ div.table_actions_menu .dropdown-menu button:hover {
+ background-color: $gray-lighter;
+@@ -1172,30 +1177,30 @@ div.table_actions_menu .dropdown-menu button:hover {
+
+ /* Overrides for single-action rows (no dropdown) */
+
+-tr td.actions_column ul.row_actions.single,
+-tr:hover td.actions_column ul.row_actions.single,
+-td.actions_column ul.row_actions.single,
+-td.actions_column ul.row_actions.single:hover {
++tr .actions_column ul.row_actions.single,
++tr:hover .actions_column ul.row_actions.single,
++.actions_column ul.row_actions.single,
++.actions_column ul.row_actions.single:hover {
+ border: none;
+ }
+
+-td.actions_column ul.row_actions.single li.action {
++.actions_column ul.row_actions.single li.action {
+ display: block;
+ }
+
+-td.actions_column ul.row_actions.single li.action:hover {
++.actions_column ul.row_actions.single li.action:hover {
+ background-color: transparent;
+ }
+
+-td.actions_column ul.row_actions.single a,
+-td.actions_column ul.row_actions.single input,
+-td.actions_column ul.row_actions.single button {
++.actions_column ul.row_actions.single a,
++.actions_column ul.row_actions.single input,
++.actions_column ul.row_actions.single button {
+ color: $brand-info;
+ }
+
+-td.actions_column ul.row_actions.single a:hover,
+-td.actions_column ul.row_actions.single input:hover,
+-td.actions_column ul.row_actions.single button:hover {
++.actions_column ul.row_actions.single a:hover,
++.actions_column ul.row_actions.single input:hover,
++.actions_column ul.row_actions.single button:hover {
+ color: $text-color;
+ }
+
diff --git a/0016-Add-dropdown-actions-to-all-details-pages.patch b/0016-Add-dropdown-actions-to-all-details-pages.patch
new file mode 100644
index 0000000..6c372d8
--- /dev/null
+++ b/0016-Add-dropdown-actions-to-all-details-pages.patch
@@ -0,0 +1,1038 @@
+From dd6d7410af06051533aac01049c778267ea1d8f2 Mon Sep 17 00:00:00 2001
+From: Ana Krivokapic <akrivoka at redhat.com>
+Date: Wed, 3 Sep 2014 12:09:02 +0200
+Subject: [PATCH] Add dropdown actions to all details pages
+
+Partially implements: blueprint detail-pages-ia
+
+Change-Id: Ic09385d19b417b256cbc51d044d66ecbb54c7d52
+
+Conflicts:
+ openstack_dashboard/dashboards/admin/networks/views.py
+---
+ .../dashboards/admin/networks/tests.py | 13 ++
+ .../dashboards/admin/networks/views.py | 15 ++-
+ .../dashboards/admin/volumes/snapshots/views.py | 3 +-
+ .../dashboards/project/images/images/views.py | 15 ++-
+ .../dashboards/project/instances/views.py | 6 +-
+ .../dashboards/project/loadbalancers/tabs.py | 28 +----
+ .../dashboards/project/loadbalancers/views.py | 97 ++++++++++++++-
+ .../dashboards/project/networks/ports/tabs.py | 15 +--
+ .../dashboards/project/networks/ports/views.py | 38 ++++++
+ .../dashboards/project/networks/subnets/tabs.py | 17 +--
+ .../dashboards/project/networks/subnets/views.py | 38 ++++++
+ .../dashboards/project/networks/tests.py | 3 +
+ .../dashboards/project/networks/views.py | 14 ++-
+ .../dashboards/project/routers/views.py | 8 +-
+ .../dashboards/project/stacks/views.py | 13 +-
+ .../dashboards/project/volumes/backups/views.py | 16 ++-
+ .../dashboards/project/volumes/snapshots/views.py | 11 +-
+ .../dashboards/project/volumes/volumes/views.py | 8 +-
+ openstack_dashboard/dashboards/project/vpn/tabs.py | 41 +------
+ .../dashboards/project/vpn/views.py | 131 ++++++++++++++++++++-
+ 20 files changed, 406 insertions(+), 124 deletions(-)
+
+diff --git a/openstack_dashboard/dashboards/admin/networks/tests.py b/openstack_dashboard/dashboards/admin/networks/tests.py
+index b7f5c93..e9f4890 100644
+--- a/openstack_dashboard/dashboards/admin/networks/tests.py
++++ b/openstack_dashboard/dashboards/admin/networks/tests.py
+@@ -103,6 +103,8 @@ class NetworkTests(test.BaseAdminViewTests):
+ .AndReturn(mac_learning)
+ api.neutron.is_extension_supported(IsA(http.HttpRequest),
+ 'dhcp_agent_scheduler').AndReturn(True)
++ api.neutron.is_extension_supported(IsA(http.HttpRequest),
++ 'dhcp_agent_scheduler').AndReturn(True)
+
+ self.mox.ReplayAll()
+
+@@ -186,6 +188,8 @@ class NetworkTests(test.BaseAdminViewTests):
+ .AndReturn(mac_learning)
+ api.neutron.is_extension_supported(IsA(http.HttpRequest),
+ 'dhcp_agent_scheduler').AndReturn(True)
++ api.neutron.is_extension_supported(IsA(http.HttpRequest),
++ 'dhcp_agent_scheduler').AndReturn(True)
+
+ self.mox.ReplayAll()
+
+@@ -228,6 +232,12 @@ class NetworkTests(test.BaseAdminViewTests):
+ api.neutron.is_extension_supported(IsA(http.HttpRequest),
+ 'mac-learning')\
+ .AndReturn(mac_learning)
++ api.neutron.is_extension_supported(IsA(http.HttpRequest),
++ 'dhcp_agent_scheduler')\
++ .AndReturn(True)
++ api.neutron.is_extension_supported(IsA(http.HttpRequest),
++ 'dhcp_agent_scheduler')\
++ .AndReturn(True)
+
+ self.mox.ReplayAll()
+
+@@ -945,6 +955,9 @@ class NetworkPortTests(test.BaseAdminViewTests):
+ api.neutron.is_extension_supported(IsA(http.HttpRequest),
+ 'mac-learning')\
+ .AndReturn(mac_learning)
++ api.neutron.is_extension_supported(IsA(http.HttpRequest),
++ 'mac-learning')\
++ .AndReturn(mac_learning)
+
+ self.mox.ReplayAll()
+
+diff --git a/openstack_dashboard/dashboards/admin/networks/views.py b/openstack_dashboard/dashboards/admin/networks/views.py
+index ab29478..2337424 100644
+--- a/openstack_dashboard/dashboards/admin/networks/views.py
++++ b/openstack_dashboard/dashboards/admin/networks/views.py
+@@ -103,7 +103,6 @@ class DetailView(tables.MultiTableView):
+ ports_tables.PortsTable,
+ agents_tables.DHCPAgentsTable)
+ template_name = 'project/networks/detail.html'
+- failure_url = reverse_lazy('horizon:admin:networks:index')
+
+ def get_subnets_data(self):
+ try:
+@@ -148,16 +147,15 @@ class DetailView(tables.MultiTableView):
+ network = api.neutron.network_get(self.request, network_id)
+ network.set_id_as_name_if_empty(length=0)
+ except Exception:
+- redirect = self.failure_url
+ exceptions.handle(self.request,
+ _('Unable to retrieve details for '
+ 'network "%s".') % network_id,
+- redirect=redirect)
++ redirect=self.get_redirect_url())
+ return network
+
+ def get_context_data(self, **kwargs):
+ context = super(DetailView, self).get_context_data(**kwargs)
+- context["network"] = self._get_data()
++ network = self._get_data()
+ # Needs to exclude agents table if dhcp-agent-scheduler extension
+ # is not supported.
+ try:
+@@ -166,8 +164,17 @@ class DetailView(tables.MultiTableView):
+ context['dhcp_agent_support'] = dhcp_agent_support
+ except Exception:
+ context['dhcp_agent_support'] = False
++
++ table = networks_tables.NetworksTable(self.request)
++ context["network"] = network
++ context["url"] = self.get_redirect_url()
++ context["actions"] = table.render_row_actions(network)
+ return context
+
++ @staticmethod
++ def get_redirect_url():
++ return reverse_lazy('horizon:admin:networks:index')
++
+
+ class UpdateView(user_views.UpdateView):
+ form_class = project_forms.UpdateNetwork
+diff --git a/openstack_dashboard/dashboards/admin/volumes/snapshots/views.py b/openstack_dashboard/dashboards/admin/volumes/snapshots/views.py
+index a068d92..399c49b 100644
+--- a/openstack_dashboard/dashboards/admin/volumes/snapshots/views.py
++++ b/openstack_dashboard/dashboards/admin/volumes/snapshots/views.py
+@@ -59,5 +59,6 @@ class UpdateStatusView(forms.ModalFormView):
+ class DetailView(views.DetailView):
+ tab_group_class = vol_snapshot_tabs.SnapshotDetailsTabs
+
+- def get_redirect_url(self):
++ @staticmethod
++ def get_redirect_url():
+ return reverse('horizon:admin:volumes:index')
+diff --git a/openstack_dashboard/dashboards/project/images/images/views.py b/openstack_dashboard/dashboards/project/images/images/views.py
+index 36966dd..58fd5ae 100644
+--- a/openstack_dashboard/dashboards/project/images/images/views.py
++++ b/openstack_dashboard/dashboards/project/images/images/views.py
+@@ -33,6 +33,8 @@ from openstack_dashboard import api
+ from openstack_dashboard.dashboards.project.images.images \
+ import forms as project_forms
+ from openstack_dashboard.dashboards.project.images.images \
++ import tables as project_tables
++from openstack_dashboard.dashboards.project.images.images \
+ import tabs as project_tabs
+
+
+@@ -84,18 +86,25 @@ class DetailView(tabs.TabView):
+
+ def get_context_data(self, **kwargs):
+ context = super(DetailView, self).get_context_data(**kwargs)
+- context["image"] = self.get_data()
++ image = self.get_data()
++ table = project_tables.ImagesTable(self.request)
++ context["image"] = image
++ context["url"] = self.get_redirect_url()
++ context["actions"] = table.render_row_actions(image)
+ return context
+
++ @staticmethod
++ def get_redirect_url():
++ return reverse_lazy('horizon:project:images:index')
++
+ @memoized.memoized_method
+ def get_data(self):
+ try:
+ return api.glance.image_get(self.request, self.kwargs['image_id'])
+ except Exception:
+- url = reverse('horizon:project:images:index')
+ exceptions.handle(self.request,
+ _('Unable to retrieve image details.'),
+- redirect=url)
++ redirect=self.get_redirect_url())
+
+ def get_tabs(self, request, *args, **kwargs):
+ image = self.get_data()
+diff --git a/openstack_dashboard/dashboards/project/instances/views.py b/openstack_dashboard/dashboards/project/instances/views.py
+index 954e2e1..07c8cc6 100644
+--- a/openstack_dashboard/dashboards/project/instances/views.py
++++ b/openstack_dashboard/dashboards/project/instances/views.py
+@@ -253,7 +253,11 @@ class DetailView(tabs.TabView):
+
+ def get_context_data(self, **kwargs):
+ context = super(DetailView, self).get_context_data(**kwargs)
+- context["instance"] = self.get_data()
++ instance = self.get_data()
++ context["instance"] = instance
++ table = project_tables.InstancesTable(self.request)
++ context["url"] = reverse(self.redirect_url)
++ context["actions"] = table.render_row_actions(instance)
+ return context
+
+ @memoized.memoized_method
+diff --git a/openstack_dashboard/dashboards/project/loadbalancers/tabs.py b/openstack_dashboard/dashboards/project/loadbalancers/tabs.py
+index 940b754..af48a07 100644
+--- a/openstack_dashboard/dashboards/project/loadbalancers/tabs.py
++++ b/openstack_dashboard/dashboards/project/loadbalancers/tabs.py
+@@ -21,7 +21,6 @@ from horizon import tabs
+ from openstack_dashboard import api
+
+ from openstack_dashboard.dashboards.project.loadbalancers import tables
+-from openstack_dashboard.dashboards.project.loadbalancers import utils
+
+
+ class PoolsTab(tabs.TableTab):
+@@ -94,16 +93,7 @@ class PoolDetailsTab(tabs.Tab):
+ template_name = "project/loadbalancers/_pool_details.html"
+
+ def get_context_data(self, request):
+- pid = self.tab_group.kwargs['pool_id']
+- try:
+- pool = api.lbaas.pool_get(request, pid)
+- except Exception:
+- pool = []
+- exceptions.handle(request,
+- _('Unable to retrieve pool details.'))
+- for monitor in pool.health_monitors:
+- display_name = utils.get_monitor_display_name(monitor)
+- setattr(monitor, 'display_name', display_name)
++ pool = self.tab_group.kwargs['pool']
+ return {'pool': pool}
+
+
+@@ -129,13 +119,7 @@ class MemberDetailsTab(tabs.Tab):
+ template_name = "project/loadbalancers/_member_details.html"
+
+ def get_context_data(self, request):
+- mid = self.tab_group.kwargs['member_id']
+- try:
+- member = api.lbaas.member_get(request, mid)
+- except Exception:
+- member = []
+- exceptions.handle(self.tab_group.request,
+- _('Unable to retrieve member details.'))
++ member = self.tab_group.kwargs['member']
+ return {'member': member}
+
+
+@@ -145,13 +129,7 @@ class MonitorDetailsTab(tabs.Tab):
+ template_name = "project/loadbalancers/_monitor_details.html"
+
+ def get_context_data(self, request):
+- mid = self.tab_group.kwargs['monitor_id']
+- try:
+- monitor = api.lbaas.pool_health_monitor_get(request, mid)
+- except Exception:
+- monitor = []
+- exceptions.handle(self.tab_group.request,
+- _('Unable to retrieve monitor details.'))
++ monitor = self.tab_group.kwargs['monitor']
+ return {'monitor': monitor}
+
+
+diff --git a/openstack_dashboard/dashboards/project/loadbalancers/views.py b/openstack_dashboard/dashboards/project/loadbalancers/views.py
+index be731a4..de55889 100644
+--- a/openstack_dashboard/dashboards/project/loadbalancers/views.py
++++ b/openstack_dashboard/dashboards/project/loadbalancers/views.py
+@@ -26,7 +26,10 @@ from openstack_dashboard import api
+ from openstack_dashboard.dashboards.project.loadbalancers \
+ import forms as project_forms
+ from openstack_dashboard.dashboards.project.loadbalancers \
++ import tables as project_tables
++from openstack_dashboard.dashboards.project.loadbalancers \
+ import tabs as project_tabs
++from openstack_dashboard.dashboards.project.loadbalancers import utils
+ from openstack_dashboard.dashboards.project.loadbalancers \
+ import workflows as project_workflows
+
+@@ -115,24 +118,110 @@ class AddMonitorView(workflows.WorkflowView):
+
+
+ class PoolDetailsView(tabs.TabView):
+- tab_group_class = (project_tabs.PoolDetailsTabs)
++ tab_group_class = project_tabs.PoolDetailsTabs
+ template_name = 'project/loadbalancers/details_tabs.html'
+
++ @memoized.memoized_method
++ def get_data(self):
++ pid = self.kwargs['pool_id']
++
++ try:
++ pool = api.lbaas.pool_get(self.request, pid)
++ except Exception:
++ pool = []
++ exceptions.handle(self.request,
++ _('Unable to retrieve pool details.'))
++ else:
++ for monitor in pool.health_monitors:
++ display_name = utils.get_monitor_display_name(monitor)
++ setattr(monitor, 'display_name', display_name)
++
++ return pool
++
++ def get_context_data(self, **kwargs):
++ context = super(PoolDetailsView, self).get_context_data(**kwargs)
++ pool = self.get_data()
++ context['pool'] = pool
++ table = project_tables.PoolsTable(self.request)
++ context["url"] = self.get_redirect_url()
++ context["actions"] = table.render_row_actions(pool)
++ return context
++
++ def get_tabs(self, request, *args, **kwargs):
++ pool = self.get_data()
++ return self.tab_group_class(self.request, pool=pool, **kwargs)
++
++ @staticmethod
++ def get_redirect_url():
++ return reverse_lazy("horizon:project:loadbalancers:index")
++
+
+ class VipDetailsView(tabs.TabView):
+- tab_group_class = (project_tabs.VipDetailsTabs)
++ tab_group_class = project_tabs.VipDetailsTabs
+ template_name = 'project/loadbalancers/details_tabs.html'
+
+
+ class MemberDetailsView(tabs.TabView):
+- tab_group_class = (project_tabs.MemberDetailsTabs)
++ tab_group_class = project_tabs.MemberDetailsTabs
+ template_name = 'project/loadbalancers/details_tabs.html'
+
++ @memoized.memoized_method
++ def get_data(self):
++ mid = self.kwargs['member_id']
++ try:
++ return api.lbaas.member_get(self.request, mid)
++ except Exception:
++ exceptions.handle(self.request,
++ _('Unable to retrieve member details.'))
++
++ def get_context_data(self, **kwargs):
++ context = super(MemberDetailsView, self).get_context_data(**kwargs)
++ member = self.get_data()
++ context['member'] = member
++ table = project_tables.MembersTable(self.request)
++ context["url"] = self.get_redirect_url()
++ context["actions"] = table.render_row_actions(member)
++ return context
++
++ def get_tabs(self, request, *args, **kwargs):
++ member = self.get_data()
++ return self.tab_group_class(request, member=member, **kwargs)
++
++ @staticmethod
++ def get_redirect_url():
++ return reverse_lazy("horizon:project:loadbalancers:index")
++
+
+ class MonitorDetailsView(tabs.TabView):
+- tab_group_class = (project_tabs.MonitorDetailsTabs)
++ tab_group_class = project_tabs.MonitorDetailsTabs
+ template_name = 'project/loadbalancers/details_tabs.html'
+
++ @memoized.memoized_method
++ def get_data(self):
++ mid = self.kwargs['monitor_id']
++ try:
++ return api.lbaas.pool_health_monitor_get(self.request, mid)
++ except Exception:
++ exceptions.handle(self.request,
++ _('Unable to retrieve monitor details.'))
++
++ def get_context_data(self, **kwargs):
++ context = super(MonitorDetailsView, self).get_context_data(**kwargs)
++ monitor = self.get_data()
++ context['monitor'] = monitor
++ table = project_tables.MonitorsTable(self.request)
++ context["url"] = self.get_redirect_url()
++ context["actions"] = table.render_row_actions(monitor)
++ return context
++
++ def get_tabs(self, request, *args, **kwargs):
++ monitor = self.get_data()
++ return self.tab_group_class(request, monitor=monitor, **kwargs)
++
++ @staticmethod
++ def get_redirect_url():
++ return reverse_lazy("horizon:project:loadbalancers:index")
++
+
+ class UpdatePoolView(forms.ModalFormView):
+ form_class = project_forms.UpdatePool
+diff --git a/openstack_dashboard/dashboards/project/networks/ports/tabs.py b/openstack_dashboard/dashboards/project/networks/ports/tabs.py
+index b4b4183..5e12e0d 100644
+--- a/openstack_dashboard/dashboards/project/networks/ports/tabs.py
++++ b/openstack_dashboard/dashboards/project/networks/ports/tabs.py
+@@ -12,14 +12,10 @@
+ # License for the specific language governing permissions and limitations
+ # under the License.
+
+-from django.core.urlresolvers import reverse
+ from django.utils.translation import ugettext_lazy as _
+
+-from horizon import exceptions
+ from horizon import tabs
+
+-from openstack_dashboard import api
+-
+
+ class OverviewTab(tabs.Tab):
+ name = _("Overview")
+@@ -27,16 +23,7 @@ class OverviewTab(tabs.Tab):
+ template_name = "project/networks/ports/_detail_overview.html"
+
+ def get_context_data(self, request):
+- port_id = self.tab_group.kwargs['port_id']
+- try:
+- port = api.neutron.port_get(self.request, port_id)
+- except Exception:
+- redirect = reverse('horizon:project:networks:index')
+- msg = _('Unable to retrieve port details.')
+- exceptions.handle(request, msg, redirect=redirect)
+- if (api.neutron.is_extension_supported(request, 'mac-learning') and
+- not hasattr(port, 'mac_state')):
+- port.mac_state = api.neutron.OFF_STATE
++ port = self.tab_group.kwargs['port']
+ return {'port': port}
+
+
+diff --git a/openstack_dashboard/dashboards/project/networks/ports/views.py b/openstack_dashboard/dashboards/project/networks/ports/views.py
+index 39fe073..9d0e7c7 100644
+--- a/openstack_dashboard/dashboards/project/networks/ports/views.py
++++ b/openstack_dashboard/dashboards/project/networks/ports/views.py
+@@ -25,6 +25,8 @@ from openstack_dashboard import api
+ from openstack_dashboard.dashboards.project.networks.ports \
+ import forms as project_forms
+ from openstack_dashboard.dashboards.project.networks.ports \
++ import tables as project_tables
++from openstack_dashboard.dashboards.project.networks.ports \
+ import tabs as project_tabs
+
+
+@@ -32,6 +34,42 @@ class DetailView(tabs.TabView):
+ tab_group_class = project_tabs.PortDetailTabs
+ template_name = 'project/networks/ports/detail.html'
+
++ @memoized.memoized_method
++ def get_data(self):
++ port_id = self.kwargs['port_id']
++
++ try:
++ port = api.neutron.port_get(self.request, port_id)
++ except Exception:
++ port = []
++ redirect = self.get_redirect_url()
++ msg = _('Unable to retrieve port details.')
++ exceptions.handle(self.request, msg, redirect=redirect)
++
++ if (api.neutron.is_extension_supported(self.request, 'mac-learning')
++ and not hasattr(port, 'mac_state')):
++ port.mac_state = api.neutron.OFF_STATE
++
++ return port
++
++ def get_context_data(self, **kwargs):
++ context = super(DetailView, self).get_context_data(**kwargs)
++ port = self.get_data()
++ table = project_tables.PortsTable(self.request,
++ network_id=port.network_id)
++ context["port"] = port
++ context["url"] = self.get_redirect_url()
++ context["actions"] = table.render_row_actions(port)
++ return context
++
++ def get_tabs(self, request, *args, **kwargs):
++ port = self.get_data()
++ return self.tab_group_class(request, port=port, **kwargs)
++
++ @staticmethod
++ def get_redirect_url():
++ return reverse('horizon:project:networks:index')
++
+
+ class UpdateView(forms.ModalFormView):
+ form_class = project_forms.UpdatePort
+diff --git a/openstack_dashboard/dashboards/project/networks/subnets/tabs.py b/openstack_dashboard/dashboards/project/networks/subnets/tabs.py
+index 049d6bd..1052e8c 100644
+--- a/openstack_dashboard/dashboards/project/networks/subnets/tabs.py
++++ b/openstack_dashboard/dashboards/project/networks/subnets/tabs.py
+@@ -12,15 +12,10 @@
+ # License for the specific language governing permissions and limitations
+ # under the License.
+
+-from django.core.urlresolvers import reverse
+ from django.utils.translation import ugettext_lazy as _
+
+-from horizon import exceptions
+ from horizon import tabs
+
+-from openstack_dashboard import api
+-from openstack_dashboard.dashboards.project.networks.subnets import utils
+-
+
+ class OverviewTab(tabs.Tab):
+ name = _("Overview")
+@@ -28,17 +23,7 @@ class OverviewTab(tabs.Tab):
+ template_name = "project/networks/subnets/_detail_overview.html"
+
+ def get_context_data(self, request):
+- subnet_id = self.tab_group.kwargs['subnet_id']
+- try:
+- subnet = api.neutron.subnet_get(self.request, subnet_id)
+- except Exception:
+- redirect = reverse('horizon:project:networks:index')
+- msg = _('Unable to retrieve subnet details.')
+- exceptions.handle(request, msg, redirect=redirect)
+- if subnet.ip_version == 6:
+- ipv6_modes = utils.get_ipv6_modes_menu_from_attrs(
+- subnet.ipv6_ra_mode, subnet.ipv6_address_mode)
+- subnet.ipv6_modes_desc = utils.IPV6_MODE_MAP.get(ipv6_modes)
++ subnet = self.tab_group.kwargs['subnet']
+ return {'subnet': subnet}
+
+
+diff --git a/openstack_dashboard/dashboards/project/networks/subnets/views.py b/openstack_dashboard/dashboards/project/networks/subnets/views.py
+index 5c28d51..8d4b5e0 100644
+--- a/openstack_dashboard/dashboards/project/networks/subnets/views.py
++++ b/openstack_dashboard/dashboards/project/networks/subnets/views.py
+@@ -26,6 +26,8 @@ from horizon import workflows
+ from openstack_dashboard import api
+
+ from openstack_dashboard.dashboards.project.networks.subnets \
++ import tables as project_tables
++from openstack_dashboard.dashboards.project.networks.subnets \
+ import tabs as project_tabs
+ from openstack_dashboard.dashboards.project.networks.subnets import utils
+ from openstack_dashboard.dashboards.project.networks.subnets \
+@@ -99,3 +101,39 @@ class UpdateView(workflows.WorkflowView):
+ class DetailView(tabs.TabView):
+ tab_group_class = project_tabs.SubnetDetailTabs
+ template_name = 'project/networks/subnets/detail.html'
++
++ @memoized.memoized_method
++ def get_data(self):
++ subnet_id = self.kwargs['subnet_id']
++ try:
++ subnet = api.neutron.subnet_get(self.request, subnet_id)
++ except Exception:
++ subnet = []
++ msg = _('Unable to retrieve subnet details.')
++ exceptions.handle(self.request, msg,
++ redirect=self.get_redirect_url())
++ else:
++ if subnet.ip_version == 6:
++ ipv6_modes = utils.get_ipv6_modes_menu_from_attrs(
++ subnet.ipv6_ra_mode, subnet.ipv6_address_mode)
++ subnet.ipv6_modes_desc = utils.IPV6_MODE_MAP.get(ipv6_modes)
++
++ return subnet
++
++ def get_context_data(self, **kwargs):
++ context = super(DetailView, self).get_context_data(**kwargs)
++ subnet = self.get_data()
++ table = project_tables.SubnetsTable(self.request,
++ network_id=subnet.network_id)
++ context["subnet"] = subnet
++ context["url"] = self.get_redirect_url()
++ context["actions"] = table.render_row_actions(subnet)
++ return context
++
++ def get_tabs(self, request, *args, **kwargs):
++ subnet = self.get_data()
++ return self.tab_group_class(request, subnet=subnet, **kwargs)
++
++ @staticmethod
++ def get_redirect_url():
++ return reverse('horizon:project:networks:index')
+diff --git a/openstack_dashboard/dashboards/project/networks/tests.py b/openstack_dashboard/dashboards/project/networks/tests.py
+index 802f5df..221431d 100644
+--- a/openstack_dashboard/dashboards/project/networks/tests.py
++++ b/openstack_dashboard/dashboards/project/networks/tests.py
+@@ -1609,6 +1609,9 @@ class NetworkPortTests(test.TestCase):
+ api.neutron.is_extension_supported(IsA(http.HttpRequest),
+ 'mac-learning')\
+ .AndReturn(mac_learning)
++ api.neutron.is_extension_supported(IsA(http.HttpRequest),
++ 'mac-learning')\
++ .AndReturn(mac_learning)
+ self.mox.ReplayAll()
+
+ res = self.client.get(reverse('horizon:project:networks:ports:detail',
+diff --git a/openstack_dashboard/dashboards/project/networks/views.py b/openstack_dashboard/dashboards/project/networks/views.py
+index d4a3613..0337322 100644
+--- a/openstack_dashboard/dashboards/project/networks/views.py
++++ b/openstack_dashboard/dashboards/project/networks/views.py
+@@ -95,7 +95,6 @@ class UpdateView(forms.ModalFormView):
+ class DetailView(tables.MultiTableView):
+ table_classes = (subnet_tables.SubnetsTable, port_tables.PortsTable)
+ template_name = 'project/networks/detail.html'
+- failure_url = reverse_lazy('horizon:project:networks:index')
+
+ def get_subnets_data(self):
+ try:
+@@ -131,10 +130,19 @@ class DetailView(tables.MultiTableView):
+ except Exception:
+ msg = _('Unable to retrieve details for network "%s".') \
+ % (network_id)
+- exceptions.handle(self.request, msg, redirect=self.failure_url)
++ exceptions.handle(self.request, msg,
++ redirect=self.get_redirect_url())
+ return network
+
+ def get_context_data(self, **kwargs):
+ context = super(DetailView, self).get_context_data(**kwargs)
+- context["network"] = self._get_data()
++ network = self._get_data()
++ context["network"] = network
++ table = project_tables.NetworksTable(self.request)
++ context["url"] = self.get_redirect_url()
++ context["actions"] = table.render_row_actions(network)
+ return context
++
++ @staticmethod
++ def get_redirect_url():
++ return reverse_lazy('horizon:project:networks:index')
+diff --git a/openstack_dashboard/dashboards/project/routers/views.py b/openstack_dashboard/dashboards/project/routers/views.py
+index 62ed37a..3bca6af 100644
+--- a/openstack_dashboard/dashboards/project/routers/views.py
++++ b/openstack_dashboard/dashboards/project/routers/views.py
+@@ -125,11 +125,17 @@ class DetailView(tabs.TabbedTableView):
+
+ def get_context_data(self, **kwargs):
+ context = super(DetailView, self).get_context_data(**kwargs)
+- context["router"] = self._get_data()
++ router = self._get_data()
++ table = rtables.RoutersTable(self.request)
++
++ context["router"] = router
++ context["url"] = self.failure_url
++ context["actions"] = table.render_row_actions(router)
+ context['dvr_supported'] = api.neutron.get_feature_permission(
+ self.request, "dvr", "get")
+ context['ha_supported'] = api.neutron.get_feature_permission(
+ self.request, "l3-ha", "get")
++
+ return context
+
+ def get(self, request, *args, **kwargs):
+diff --git a/openstack_dashboard/dashboards/project/stacks/views.py b/openstack_dashboard/dashboards/project/stacks/views.py
+index e43d9db..53b9bc5 100644
+--- a/openstack_dashboard/dashboards/project/stacks/views.py
++++ b/openstack_dashboard/dashboards/project/stacks/views.py
+@@ -197,7 +197,11 @@ class DetailView(tabs.TabView):
+
+ def get_context_data(self, **kwargs):
+ context = super(DetailView, self).get_context_data(**kwargs)
+- context["stack"] = self.get_data(self.request, **kwargs)
++ stack = self.get_data(self.request, **kwargs)
++ table = project_tables.StacksTable(self.request)
++ context["stack"] = stack
++ context["url"] = self.get_redirect_url()
++ context["actions"] = table.render_row_actions(stack)
+ return context
+
+ @memoized.memoized_method
+@@ -210,13 +214,16 @@ class DetailView(tabs.TabView):
+ return stack
+ except Exception:
+ msg = _("Unable to retrieve stack.")
+- redirect = reverse('horizon:project:stacks:index')
+- exceptions.handle(request, msg, redirect=redirect)
++ exceptions.handle(request, msg, redirect=self.get_redirect_url())
+
+ def get_tabs(self, request, **kwargs):
+ stack = self.get_data(request, **kwargs)
+ return self.tab_group_class(request, stack=stack, **kwargs)
+
++ @staticmethod
++ def get_redirect_url():
++ return reverse('horizon:project:stacks:index')
++
+
+ class ResourceView(tabs.TabView):
+ tab_group_class = project_tabs.ResourceDetailTabs
+diff --git a/openstack_dashboard/dashboards/project/volumes/backups/views.py b/openstack_dashboard/dashboards/project/volumes/backups/views.py
+index a04fee8..7a1eb9e 100644
+--- a/openstack_dashboard/dashboards/project/volumes/backups/views.py
++++ b/openstack_dashboard/dashboards/project/volumes/backups/views.py
+@@ -23,6 +23,8 @@ from openstack_dashboard import api
+ from openstack_dashboard.dashboards.project.volumes.backups \
+ import forms as backup_forms
+ from openstack_dashboard.dashboards.project.volumes.backups \
++ import tables as backup_tables
++from openstack_dashboard.dashboards.project.volumes.backups \
+ import tabs as backup_tabs
+
+
+@@ -46,7 +48,12 @@ class BackupDetailView(tabs.TabView):
+
+ def get_context_data(self, **kwargs):
+ context = super(BackupDetailView, self).get_context_data(**kwargs)
+- context["backup"] = self.get_data()
++ backup = self.get_data()
++ table = backup_tables.BackupsTable(self.request)
++ context["backup"] = backup
++ context["url"] = self.get_redirect_url()
++ context["actions"] = table.render_row_actions(backup)
++
+ return context
+
+ @memoized.memoized_method
+@@ -56,16 +63,19 @@ class BackupDetailView(tabs.TabView):
+ backup = api.cinder.volume_backup_get(self.request,
+ backup_id)
+ except Exception:
+- redirect = reverse('horizon:project:volumes:index')
+ exceptions.handle(self.request,
+ _('Unable to retrieve backup details.'),
+- redirect=redirect)
++ redirect=self.get_redirect_url())
+ return backup
+
+ def get_tabs(self, request, *args, **kwargs):
+ backup = self.get_data()
+ return self.tab_group_class(request, backup=backup, **kwargs)
+
++ @staticmethod
++ def get_redirect_url():
++ return reverse('horizon:project:volumes:index')
++
+
+ class RestoreBackupView(forms.ModalFormView):
+ form_class = backup_forms.RestoreBackupForm
+diff --git a/openstack_dashboard/dashboards/project/volumes/snapshots/views.py b/openstack_dashboard/dashboards/project/volumes/snapshots/views.py
+index 4b50660..2e2227a 100644
+--- a/openstack_dashboard/dashboards/project/volumes/snapshots/views.py
++++ b/openstack_dashboard/dashboards/project/volumes/snapshots/views.py
+@@ -24,6 +24,8 @@ from openstack_dashboard import api
+ from openstack_dashboard.dashboards.project.volumes \
+ .snapshots import forms as vol_snapshot_forms
+ from openstack_dashboard.dashboards.project.volumes \
++ .snapshots import tables as vol_snapshot_tables
++from openstack_dashboard.dashboards.project.volumes \
+ .snapshots import tabs as vol_snapshot_tabs
+
+
+@@ -62,7 +64,11 @@ class DetailView(tabs.TabView):
+
+ def get_context_data(self, **kwargs):
+ context = super(DetailView, self).get_context_data(**kwargs)
+- context["snapshot"] = self.get_data()
++ snapshot = self.get_data()
++ table = vol_snapshot_tables.VolumeSnapshotsTable(self.request)
++ context["snapshot"] = snapshot
++ context["url"] = self.get_redirect_url()
++ context["actions"] = table.render_row_actions(snapshot)
+ return context
+
+ @memoized.memoized_method
+@@ -78,7 +84,8 @@ class DetailView(tabs.TabView):
+ redirect=redirect)
+ return snapshot
+
+- def get_redirect_url(self):
++ @staticmethod
++ def get_redirect_url():
+ return reverse('horizon:project:volumes:index')
+
+ def get_tabs(self, request, *args, **kwargs):
+diff --git a/openstack_dashboard/dashboards/project/volumes/volumes/views.py b/openstack_dashboard/dashboards/project/volumes/volumes/views.py
+index c78cd09..81449ec 100644
+--- a/openstack_dashboard/dashboards/project/volumes/volumes/views.py
++++ b/openstack_dashboard/dashboards/project/volumes/volumes/views.py
+@@ -44,12 +44,12 @@ class DetailView(tabs.TabView):
+ template_name = 'project/volumes/volumes/detail.html'
+
+ def get_context_data(self, **kwargs):
+- datum = self.get_data()
+- table = project_tables.VolumesTable(self.request)
+ context = super(DetailView, self).get_context_data(**kwargs)
+- context["volume"] = datum
++ volume = self.get_data()
++ table = project_tables.VolumesTable(self.request)
++ context["volume"] = volume
+ context["url"] = self.get_redirect_url()
+- context["actions"] = table.render_row_actions(datum)
++ context["actions"] = table.render_row_actions(volume)
+ return context
+
+ @memoized.memoized_method
+diff --git a/openstack_dashboard/dashboards/project/vpn/tabs.py b/openstack_dashboard/dashboards/project/vpn/tabs.py
+index dd836c8..f5d5f8e 100644
+--- a/openstack_dashboard/dashboards/project/vpn/tabs.py
++++ b/openstack_dashboard/dashboards/project/vpn/tabs.py
+@@ -15,7 +15,6 @@
+ # @author: Tatiana Mazur
+
+
+-from django.core.urlresolvers import reverse_lazy
+ from django.utils.translation import ugettext_lazy as _
+
+ from horizon import exceptions
+@@ -117,15 +116,9 @@ class IKEPolicyDetailsTab(tabs.Tab):
+ name = _("IKE Policy Details")
+ slug = "ikepolicydetails"
+ template_name = "project/vpn/_ikepolicy_details.html"
+- failure_url = reverse_lazy('horizon:project:vpn:index')
+
+ def get_context_data(self, request):
+- pid = self.tab_group.kwargs['ikepolicy_id']
+- try:
+- ikepolicy = api.vpn.ikepolicy_get(request, pid)
+- except Exception:
+- msg = _('Unable to retrieve IKE Policy details.')
+- exceptions.handle(request, msg, redirect=self.failure_url)
++ ikepolicy = self.tab_group.kwargs['ikepolicy']
+ return {'ikepolicy': ikepolicy}
+
+
+@@ -138,15 +131,9 @@ class IPSecPolicyDetailsTab(tabs.Tab):
+ name = _("IPSec Policy Details")
+ slug = "ipsecpolicydetails"
+ template_name = "project/vpn/_ipsecpolicy_details.html"
+- failure_url = reverse_lazy('horizon:project:vpn:index')
+
+ def get_context_data(self, request):
+- pid = self.tab_group.kwargs['ipsecpolicy_id']
+- try:
+- ipsecpolicy = api.vpn.ipsecpolicy_get(request, pid)
+- except Exception:
+- msg = _('Unable to retrieve IPSec Policy details.')
+- exceptions.handle(request, msg, redirect=self.failure_url)
++ ipsecpolicy = self.tab_group.kwargs['ipsecpolicy']
+ return {'ipsecpolicy': ipsecpolicy}
+
+
+@@ -159,21 +146,9 @@ class VPNServiceDetailsTab(tabs.Tab):
+ name = _("VPN Service Details")
+ slug = "vpnservicedetails"
+ template_name = "project/vpn/_vpnservice_details.html"
+- failure_url = reverse_lazy('horizon:project:vpn:index')
+
+ def get_context_data(self, request):
+- sid = self.tab_group.kwargs['vpnservice_id']
+- try:
+- vpnservice = api.vpn.vpnservice_get(request, sid)
+- except Exception:
+- msg = _('Unable to retrieve VPN Service details.')
+- exceptions.handle(request, msg, redirect=self.failure_url)
+- try:
+- connections = api.vpn.ipsecsiteconnection_list(
+- request, vpnservice_id=sid)
+- vpnservice.vpnconnections = connections
+- except Exception:
+- vpnservice.vpnconnections = []
++ vpnservice = self.tab_group.kwargs['vpnservice']
+ return {'vpnservice': vpnservice}
+
+
+@@ -186,16 +161,10 @@ class IPSecSiteConnectionDetailsTab(tabs.Tab):
+ name = _("IPSec Site Connection Details")
+ slug = "ipsecsiteconnectiondetails"
+ template_name = "project/vpn/_ipsecsiteconnection_details.html"
+- failure_url = reverse_lazy('horizon:project:vpn:index')
+
+ def get_context_data(self, request):
+- cid = self.tab_group.kwargs['ipsecsiteconnection_id']
+- try:
+- ipsecsiteconn = api.vpn.ipsecsiteconnection_get(request, cid)
+- except Exception:
+- msg = _('Unable to retrieve IPSec Site Connection details.')
+- exceptions.handle(request, msg, redirect=self.failure_url)
+- return {'ipsecsiteconnection': ipsecsiteconn}
++ ipsecsiteconnection = self.tab_group.kwargs['ipsecsiteconnection']
++ return {'ipsecsiteconnection': ipsecsiteconnection}
+
+
+ class IPSecSiteConnectionDetailsTabs(tabs.TabGroup):
+diff --git a/openstack_dashboard/dashboards/project/vpn/views.py b/openstack_dashboard/dashboards/project/vpn/views.py
+index 53e9f63..7dc22da 100644
+--- a/openstack_dashboard/dashboards/project/vpn/views.py
++++ b/openstack_dashboard/dashboards/project/vpn/views.py
+@@ -29,6 +29,7 @@ from openstack_dashboard import api
+
+ from openstack_dashboard.dashboards.project.vpn \
+ import forms as vpn_forms
++from openstack_dashboard.dashboards.project.vpn import tables as vpn_tables
+ from openstack_dashboard.dashboards.project.vpn import tabs as vpn_tabs
+ from openstack_dashboard.dashboards.project.vpn \
+ import workflows as vpn_workflows
+@@ -122,24 +123,146 @@ class AddIPSecPolicyView(workflows.WorkflowView):
+
+
+ class IKEPolicyDetailsView(tabs.TabView):
+- tab_group_class = (vpn_tabs.IKEPolicyDetailsTabs)
++ tab_group_class = vpn_tabs.IKEPolicyDetailsTabs
+ template_name = 'project/vpn/details_tabs.html'
+
++ @memoized.memoized_method
++ def get_data(self):
++ pid = self.kwargs['ikepolicy_id']
++ try:
++ return api.vpn.ikepolicy_get(self.request, pid)
++ except Exception:
++ msg = _('Unable to retrieve IKE Policy details.')
++ exceptions.handle(self.request, msg,
++ redirect=self.get_redirect_url())
++
++ def get_context_data(self, **kwargs):
++ context = super(IKEPolicyDetailsView, self).get_context_data(**kwargs)
++ ikepolicy = self.get_data()
++ table = vpn_tables.IKEPoliciesTable(self.request)
++ context["ikepolicy"] = ikepolicy
++ context["url"] = self.get_redirect_url()
++ context["actions"] = table.render_row_actions(ikepolicy)
++ return context
++
++ def get_tabs(self, request, *args, **kwargs):
++ ikepolicy = self.get_data()
++ return self.tab_group_class(request, ikepolicy=ikepolicy, **kwargs)
++
++ @staticmethod
++ def get_redirect_url():
++ return reverse_lazy('horizon:project:vpn:index')
++
+
+ class IPSecPolicyDetailsView(tabs.TabView):
+- tab_group_class = (vpn_tabs.IPSecPolicyDetailsTabs)
++ tab_group_class = vpn_tabs.IPSecPolicyDetailsTabs
+ template_name = 'project/vpn/details_tabs.html'
+
++ @memoized.memoized_method
++ def get_data(self):
++ pid = self.kwargs['ipsecpolicy_id']
++ try:
++ return api.vpn.ipsecpolicy_get(self.request, pid)
++ except Exception:
++ msg = _('Unable to retrieve IPSec Policy details.')
++ exceptions.handle(self.request, msg,
++ redirect=self.get_redirect_url())
++
++ def get_context_data(self, **kwargs):
++ context = super(IPSecPolicyDetailsView, self).get_context_data(
++ **kwargs)
++ ipsecpolicy = self.get_data()
++ table = vpn_tables.IPSecPoliciesTable(self.request)
++ context["ipsecpolicy"] = ipsecpolicy
++ context["url"] = self.get_redirect_url()
++ context["actions"] = table.render_row_actions(ipsecpolicy)
++ return context
++
++ def get_tabs(self, request, *args, **kwargs):
++ ipsecpolicy = self.get_data()
++ return self.tab_group_class(request, ipsecpolicy=ipsecpolicy, **kwargs)
++
++ @staticmethod
++ def get_redirect_url():
++ return reverse_lazy('horizon:project:vpn:index')
++
+
+ class VPNServiceDetailsView(tabs.TabView):
+- tab_group_class = (vpn_tabs.VPNServiceDetailsTabs)
++ tab_group_class = vpn_tabs.VPNServiceDetailsTabs
+ template_name = 'project/vpn/details_tabs.html'
+
++ @memoized.memoized_method
++ def get_data(self):
++ sid = self.kwargs['vpnservice_id']
++
++ try:
++ vpnservice = api.vpn.vpnservice_get(self.request, sid)
++ except Exception:
++ vpnservice = []
++ msg = _('Unable to retrieve VPN Service details.')
++ exceptions.handle(self.request, msg,
++ redirect=self.get_redirect_url())
++ try:
++ connections = api.vpn.ipsecsiteconnection_list(
++ self.request, vpnservice_id=sid)
++ vpnservice.vpnconnections = connections
++ except Exception:
++ vpnservice.vpnconnections = []
++
++ return vpnservice
++
++ def get_context_data(self, **kwargs):
++ context = super(VPNServiceDetailsView, self).get_context_data(**kwargs)
++ vpnservice = self.get_data()
++ table = vpn_tables.VPNServicesTable(self.request)
++ context["vpnservice"] = vpnservice
++ context["url"] = self.get_redirect_url()
++ context["actions"] = table.render_row_actions(vpnservice)
++ return context
++
++ def get_tabs(self, request, *args, **kwargs):
++ vpnservice = self.get_data()
++ return self.tab_group_class(request, vpnservice=vpnservice, **kwargs)
++
++ @staticmethod
++ def get_redirect_url():
++ return reverse_lazy('horizon:project:vpn:index')
++
+
+ class IPSecSiteConnectionDetailsView(tabs.TabView):
+- tab_group_class = (vpn_tabs.IPSecSiteConnectionDetailsTabs)
++ tab_group_class = vpn_tabs.IPSecSiteConnectionDetailsTabs
+ template_name = 'project/vpn/details_tabs.html'
+
++ @memoized.memoized_method
++ def get_data(self):
++ cid = self.kwargs['ipsecsiteconnection_id']
++ try:
++ return api.vpn.ipsecsiteconnection_get(self.request, cid)
++ except Exception:
++ msg = _('Unable to retrieve IPSec Site Connection details.')
++ exceptions.handle(self.request, msg,
++ redirect=self.get_redirect_url())
++
++ def get_context_data(self, **kwargs):
++ context = super(IPSecSiteConnectionDetailsView, self).get_context_data(
++ **kwargs)
++ ipsecsiteconnection = self.get_data()
++ table = vpn_tables.IPSecSiteConnectionsTable(self.request)
++ context["ipsecsiteconnection"] = ipsecsiteconnection
++ context["url"] = self.get_redirect_url()
++ context["actions"] = table.render_row_actions(ipsecsiteconnection)
++ return context
++
++ def get_tabs(self, request, *args, **kwargs):
++ ipsecsiteconnection = self.get_data()
++ return self.tab_group_class(request,
++ ipsecsiteconnection=ipsecsiteconnection,
++ **kwargs)
++
++ @staticmethod
++ def get_redirect_url():
++ return reverse_lazy('horizon:project:vpn:index')
++
+
+ class UpdateVPNServiceView(forms.ModalFormView):
+ form_class = vpn_forms.UpdateVPNService
diff --git a/0017-Add-support-for-row-actions-to-detail-pages.patch b/0017-Add-support-for-row-actions-to-detail-pages.patch
new file mode 100644
index 0000000..3f55fa8
--- /dev/null
+++ b/0017-Add-support-for-row-actions-to-detail-pages.patch
@@ -0,0 +1,205 @@
+From 726e5915295251bc7a3180efa753c5d6abb6f677 Mon Sep 17 00:00:00 2001
+From: Ana Krivokapic <akrivoka at redhat.com>
+Date: Thu, 11 Sep 2014 13:31:45 +0200
+Subject: [PATCH] Add support for row actions to detail pages
+
+Add support for performing actions on an object from the object's
+detail page. Actions are displayed in the page header, in a
+row of buttons. To use this newly added capability, `row=True`
+must be passed as a parameter to the render_row_actions method,
+when called from a DetailView.
+
+Change-Id: I4fd96433f9514242c21d6c47f79bf83f3a5e4fc0
+Implements: blueprint detail-pages-ia
+---
+ horizon/static/horizon/js/horizon.tables.js | 6 +++--
+ horizon/tables/base.py | 17 +++++++++----
+ .../horizon/common/_data_table_row_action.html | 5 ----
+ .../common/_data_table_row_action_dropdown.html | 5 ++++
+ .../horizon/common/_data_table_row_action_row.html | 11 ++++++++
+ .../horizon/common/_data_table_row_actions.html | 29 ----------------------
+ .../common/_data_table_row_actions_dropdown.html | 27 ++++++++++++++++++++
+ .../common/_data_table_row_actions_row.html | 8 ++++++
+ 8 files changed, 67 insertions(+), 41 deletions(-)
+ delete mode 100644 horizon/templates/horizon/common/_data_table_row_action.html
+ create mode 100644 horizon/templates/horizon/common/_data_table_row_action_dropdown.html
+ create mode 100644 horizon/templates/horizon/common/_data_table_row_action_row.html
+ delete mode 100644 horizon/templates/horizon/common/_data_table_row_actions.html
+ create mode 100644 horizon/templates/horizon/common/_data_table_row_actions_dropdown.html
+ create mode 100644 horizon/templates/horizon/common/_data_table_row_actions_row.html
+
+diff --git a/horizon/static/horizon/js/horizon.tables.js b/horizon/static/horizon/js/horizon.tables.js
+index 861f915..70c3d0a 100644
+--- a/horizon/static/horizon/js/horizon.tables.js
++++ b/horizon/static/horizon/js/horizon.tables.js
+@@ -138,8 +138,10 @@ horizon.datatables = {
+ var action_buttons = $(this).find(".table_actions button.btn-danger");
+
+ // Buttons should be enabled only if there are checked checkboxes
+- action_buttons.toggleClass("disabled",
+- !checkboxes.filter(":checked").length);
++ if (checkboxes.length) {
++ action_buttons.toggleClass("disabled",
++ !checkboxes.filter(":checked").length);
++ }
+ });
+ },
+
+diff --git a/horizon/tables/base.py b/horizon/tables/base.py
+index 2f76b79..7e5d1b4 100644
+--- a/horizon/tables/base.py
++++ b/horizon/tables/base.py
+@@ -982,8 +982,10 @@ class DataTableOptions(object):
+ self.template = getattr(options,
+ 'template',
+ 'horizon/common/_data_table.html')
+- self.row_actions_template = \
+- 'horizon/common/_data_table_row_actions.html'
++ self.row_actions_dropdown_template = ('horizon/common/_data_table_'
++ 'row_actions_dropdown.html')
++ self.row_actions_row_template = ('horizon/common/_data_table_'
++ 'row_actions_row.html')
+ self.table_actions_template = \
+ 'horizon/common/_data_table_table_actions.html'
+ self.context_var_name = unicode(getattr(options,
+@@ -1388,11 +1390,16 @@ class DataTable(object):
+ self.set_multiselect_column_visibility(len(bound_actions) > 0)
+ return table_actions_template.render(context)
+
+- def render_row_actions(self, datum, pull_right=True):
++ def render_row_actions(self, datum, pull_right=True, row=False):
+ """Renders the actions specified in ``Meta.row_actions`` using the
+- current row data.
++ current row data. If `row` is True, the actions are rendered in a row
++ of buttons. Otherwise they are rendered in a dropdown box.
+ """
+- template_path = self._meta.row_actions_template
++ if row:
++ template_path = self._meta.row_actions_row_template
++ else:
++ template_path = self._meta.row_actions_dropdown_template
++
+ row_actions_template = template.loader.get_template(template_path)
+ bound_actions = self.get_row_actions(datum)
+ extra_context = {"row_actions": bound_actions,
+diff --git a/horizon/templates/horizon/common/_data_table_row_action.html b/horizon/templates/horizon/common/_data_table_row_action.html
+deleted file mode 100644
+index d766fb3..0000000
+--- a/horizon/templates/horizon/common/_data_table_row_action.html
++++ /dev/null
+@@ -1,5 +0,0 @@
+-{% if action.method != "GET" %}
+- <button {{ action.attr_string|safe }} name="action" value="{{ action.table.name }}__{{ action.name }}__{{ row_id }}" type="submit">{{ action.verbose_name }}</button>
+-{% else %}
+- <a href='{{ action.bound_url }}' {{ action.attr_string|safe }}>{{ action.verbose_name }}</a>
+-{% endif %}
+diff --git a/horizon/templates/horizon/common/_data_table_row_action_dropdown.html b/horizon/templates/horizon/common/_data_table_row_action_dropdown.html
+new file mode 100644
+index 0000000..d766fb3
+--- /dev/null
++++ b/horizon/templates/horizon/common/_data_table_row_action_dropdown.html
+@@ -0,0 +1,5 @@
++{% if action.method != "GET" %}
++ <button {{ action.attr_string|safe }} name="action" value="{{ action.table.name }}__{{ action.name }}__{{ row_id }}" type="submit">{{ action.verbose_name }}</button>
++{% else %}
++ <a href='{{ action.bound_url }}' {{ action.attr_string|safe }}>{{ action.verbose_name }}</a>
++{% endif %}
+diff --git a/horizon/templates/horizon/common/_data_table_row_action_row.html b/horizon/templates/horizon/common/_data_table_row_action_row.html
+new file mode 100644
+index 0000000..8602439
+--- /dev/null
++++ b/horizon/templates/horizon/common/_data_table_row_action_row.html
+@@ -0,0 +1,11 @@
++{% if action.method != "GET" %}
++ <button {{ action.attr_string|safe }} name="action" value="{{ action.table.name }}__{{ action.name }}__{{ row_id }}" type="submit">
++ {% if action.icon != None %}<span class="glyphicon glyphicon-{{ action.icon }}"></span> {% endif %}
++ {{ action.verbose_name }}
++ </button>
++{% else %}
++ <a href='{{ action.bound_url }}' title='{{ action.verbose_name }}' {{ action.attr_string|safe }}>
++ {% if action.icon != None %}<span class="glyphicon glyphicon-{{ action.icon }}"></span> {% endif %}
++ {{ action.verbose_name }}
++ </a>
++{% endif %}
+diff --git a/horizon/templates/horizon/common/_data_table_row_actions.html b/horizon/templates/horizon/common/_data_table_row_actions.html
+deleted file mode 100644
+index 3f3368b..0000000
+--- a/horizon/templates/horizon/common/_data_table_row_actions.html
++++ /dev/null
+@@ -1,29 +0,0 @@
+-{% load horizon i18n %}
+-
+-{% spaceless %} {# This makes sure whitespace doesn't affect positioning for dropdown. #}
+-{% if row_actions|length > 1 %}
+-<div class="btn-group {% if pull_right %}pull-right{% endif %}">
+- {% for action in row_actions %}
+- {% if forloop.first %}
+- {% include "horizon/common/_data_table_row_action.html" %}
+- <a class="btn btn-default btn-sm dropdown-toggle" data-toggle="dropdown" href="#">
+- <span class="caret"></span>
+- </a>
+- <ul class="dropdown-menu row_actions clearfix">
+- {% else %}
+- <li class="clearfix">
+- {% include "horizon/common/_data_table_row_action.html" %}
+- </li>
+- {% endif %}
+- {% if forloop.last %}
+- </ul>
+- {% endif %}
+- {% endfor %}
+-</div>
+-{% endif %}
+-{% if row_actions|length == 1%}
+- {% for action in row_actions %}
+- {% include "horizon/common/_data_table_row_action.html" %}
+- {% endfor %}
+-{% endif %}
+-{% endspaceless %}
+diff --git a/horizon/templates/horizon/common/_data_table_row_actions_dropdown.html b/horizon/templates/horizon/common/_data_table_row_actions_dropdown.html
+new file mode 100644
+index 0000000..781a30e
+--- /dev/null
++++ b/horizon/templates/horizon/common/_data_table_row_actions_dropdown.html
+@@ -0,0 +1,27 @@
++{% load horizon i18n %}
++
++{% spaceless %} {# This makes sure whitespace doesn't affect positioning for dropdown. #}
++{% if row_actions|length > 1 %}
++<div class="btn-group {% if pull_right %}pull-right{% endif %}">
++ {% for action in row_actions %}
++ {% if forloop.first %}
++ {% include "horizon/common/_data_table_row_action_dropdown.html" %}
++ <a class="btn btn-default btn-sm dropdown-toggle" data-toggle="dropdown" href="#">
++ <span class="caret"></span>
++ </a>
++ <ul class="dropdown-menu row_actions clearfix">
++ {% else %}
++ <li class="clearfix">
++ {% include "horizon/common/_data_table_row_action_dropdown.html" %}
++ </li>
++ {% endif %}
++ {% if forloop.last %}
++ </ul>
++ {% endif %}
++ {% endfor %}
++</div>
++{% endif %}
++{% if row_actions|length == 1%}
++ {% include "horizon/common/_data_table_row_action_dropdown.html" with action=row_actions.0%}
++{% endif %}
++{% endspaceless %}
+diff --git a/horizon/templates/horizon/common/_data_table_row_actions_row.html b/horizon/templates/horizon/common/_data_table_row_actions_row.html
+new file mode 100644
+index 0000000..dd2a5f0
+--- /dev/null
++++ b/horizon/templates/horizon/common/_data_table_row_actions_row.html
+@@ -0,0 +1,8 @@
++{% load i18n %}
++<div class="table_actions clearfix">
++{% block table_actions %}
++ {% for action in row_actions %}
++ {% include "horizon/common/_data_table_row_action_row.html" %}
++ {% endfor %}
++{% endblock table_actions %}
++</div>
diff --git a/0018-Clean-up-test-output.patch b/0018-Clean-up-test-output.patch
new file mode 100644
index 0000000..b4b4d21
--- /dev/null
+++ b/0018-Clean-up-test-output.patch
@@ -0,0 +1,116 @@
+From ca952c4ce36bdcb0d02445c31255e974da81d407 Mon Sep 17 00:00:00 2001
+From: Ana Krivokapic <akrivoka at redhat.com>
+Date: Thu, 9 Oct 2014 15:20:09 +0200
+Subject: [PATCH] Clean up test output
+
+Some of the API calls weren't mocked properly. Add the missing mocks so
+the test output is again clean.
+
+Change-Id: I83fff8ee3f63e79ef5786602d6516703a22f0702
+Partial-bug: #1378907
+---
+ .../dashboards/admin/networks/tests.py | 8 +++++--
+ .../dashboards/project/instances/tests.py | 25 +++++++++++++++++-----
+ .../dashboards/project/networks/tests.py | 8 +++++--
+ openstack_dashboard/test/test_data/cinder_data.py | 1 +
+ 4 files changed, 33 insertions(+), 9 deletions(-)
+
+diff --git a/openstack_dashboard/dashboards/admin/networks/tests.py b/openstack_dashboard/dashboards/admin/networks/tests.py
+index e9f4890..2235c10 100644
+--- a/openstack_dashboard/dashboards/admin/networks/tests.py
++++ b/openstack_dashboard/dashboards/admin/networks/tests.py
+@@ -643,11 +643,15 @@ class NetworkTests(test.BaseAdminViewTests):
+
+ class NetworkSubnetTests(test.BaseAdminViewTests):
+
+- @test.create_stubs({api.neutron: ('subnet_get',)})
++ @test.create_stubs({api.neutron: ('network_get', 'subnet_get',)})
+ def test_subnet_detail(self):
++ network = self.networks.first()
+ subnet = self.subnets.first()
++
++ api.neutron.network_get(IsA(http.HttpRequest), network.id)\
++ .AndReturn(network)
+ api.neutron.subnet_get(IsA(http.HttpRequest), subnet.id)\
+- .AndReturn(self.subnets.first())
++ .AndReturn(subnet)
+
+ self.mox.ReplayAll()
+
+diff --git a/openstack_dashboard/dashboards/project/instances/tests.py b/openstack_dashboard/dashboards/project/instances/tests.py
+index 6ee921d..163af69 100644
+--- a/openstack_dashboard/dashboards/project/instances/tests.py
++++ b/openstack_dashboard/dashboards/project/instances/tests.py
+@@ -652,11 +652,20 @@ class InstanceTests(helpers.TestCase):
+
+ self.assertRedirectsNoFollow(res, INDEX_URL)
+
+- @helpers.create_stubs({api.nova: ("server_get",
+- "instance_volumes_list",
+- "flavor_get"),
+- api.network: ("server_security_groups",
+- "servers_update_addresses")})
++ @helpers.create_stubs({
++ api.nova: (
++ "server_get",
++ "instance_volumes_list",
++ "flavor_get",
++ "extension_supported"
++ ),
++ api.network: (
++ "server_security_groups",
++ "servers_update_addresses",
++ "floating_ip_simple_associate_supported",
++ "floating_ip_supported"
++ )
++ })
+ def _get_instance_details(self, server, qs=None,
+ flavor_return=None, volumes_return=None,
+ security_groups_return=None, ):
+@@ -683,6 +692,12 @@ class InstanceTests(helpers.TestCase):
+ .AndReturn(flavor_return)
+ api.network.server_security_groups(IsA(http.HttpRequest), server.id) \
+ .AndReturn(security_groups_return)
++ api.network.floating_ip_simple_associate_supported(
++ IsA(http.HttpRequest)).MultipleTimes().AndReturn(True)
++ api.network.floating_ip_supported(IsA(http.HttpRequest)) \
++ .MultipleTimes().AndReturn(True)
++ api.nova.extension_supported('AdminActions', IsA(http.HttpRequest)) \
++ .MultipleTimes().AndReturn(True)
+
+ self.mox.ReplayAll()
+
+diff --git a/openstack_dashboard/dashboards/project/networks/tests.py b/openstack_dashboard/dashboards/project/networks/tests.py
+index 221431d..0224dba 100644
+--- a/openstack_dashboard/dashboards/project/networks/tests.py
++++ b/openstack_dashboard/dashboards/project/networks/tests.py
+@@ -788,11 +788,15 @@ class NetworkTests(test.TestCase):
+
+ class NetworkSubnetTests(test.TestCase):
+
+- @test.create_stubs({api.neutron: ('subnet_get',)})
++ @test.create_stubs({api.neutron: ('network_get', 'subnet_get',)})
+ def test_subnet_detail(self):
++ network = self.networks.first()
+ subnet = self.subnets.first()
++
++ api.neutron.network_get(IsA(http.HttpRequest), network.id)\
++ .AndReturn(network)
+ api.neutron.subnet_get(IsA(http.HttpRequest), subnet.id)\
+- .AndReturn(self.subnets.first())
++ .AndReturn(subnet)
+
+ self.mox.ReplayAll()
+
+diff --git a/openstack_dashboard/test/test_data/cinder_data.py b/openstack_dashboard/test/test_data/cinder_data.py
+index a26f742..3a04e13 100644
+--- a/openstack_dashboard/test/test_data/cinder_data.py
++++ b/openstack_dashboard/test/test_data/cinder_data.py
+@@ -161,6 +161,7 @@ def data(TEST):
+
+ TEST.cinder_volume_snapshots.add(api.cinder.VolumeSnapshot(snapshot))
+ TEST.cinder_volume_snapshots.add(api.cinder.VolumeSnapshot(snapshot2))
++ TEST.cinder_volume_snapshots.first()._volume = volume
+
+ volume_backup1 = vol_backups.VolumeBackup(vol_backups.
+ VolumeBackupManager(None),
diff --git a/python-django-horizon.spec b/python-django-horizon.spec
index 02c346e..ff9b897 100644
--- a/python-django-horizon.spec
+++ b/python-django-horizon.spec
@@ -25,18 +25,24 @@ Source5: python-django-horizon-logrotate.conf
#
# patches_base=2014.2.rc2
#
-Patch0001: 0001-change-lockfile-location-to-tmp-and-also-add-localho.patch
-Patch0002: 0002-Add-a-customization-module-based-on-RHOS.patch
-Patch0003: 0003-move-RBAC-policy-files-and-checks-to-etc-openstack-d.patch
-Patch0004: 0004-move-SECRET_KEY-secret_key_store-to-tmp.patch
-Patch0005: 0005-RCUE-navbar-and-login-screen.patch
-Patch0006: 0006-fix-flake8-issues.patch
-Patch0007: 0007-remove-runtime-dep-to-python-pbr.patch
-Patch0008: 0008-Add-Change-password-link-to-the-RCUE-theme.patch
-Patch0009: 0009-.less-replaced-in-rcue.patch
-Patch0010: 0010-Theme-fixes.patch
+Patch0001: 0001-disable-debug-move-web-root.patch
+Patch0002: 0002-change-lockfile-location-to-tmp-and-also-add-localho.patch
+Patch0003: 0003-Add-a-customization-module-based-on-RHOS.patch
+Patch0004: 0004-move-RBAC-policy-files-and-checks-to-etc-openstack-d.patch
+Patch0005: 0005-move-SECRET_KEY-secret_key_store-to-tmp.patch
+Patch0006: 0006-RCUE-navbar-and-login-screen.patch
+Patch0007: 0007-fix-flake8-issues.patch
+Patch0008: 0008-remove-runtime-dep-to-python-pbr.patch
+Patch0009: 0009-Add-Change-password-link-to-the-RCUE-theme.patch
+Patch0010: 0010-.less-replaced-in-rcue.patch
Patch0011: 0011-re-add-lesscpy-to-compile-.less.patch
Patch0012: 0012-Migration-of-LESS-to-SCSS-and-various-fixes.patch
+Patch0013: 0013-Remove-the-redundant-Settings-button-on-downstream-t.patch
+Patch0014: 0014-Change-page-header-heading-to-H1.patch
+Patch0015: 0015-Add-dropdown-actions-to-detail-page.patch
+Patch0016: 0016-Add-dropdown-actions-to-all-details-pages.patch
+Patch0017: 0017-Add-support-for-row-actions-to-detail-pages.patch
+Patch0018: 0018-Clean-up-test-output.patch
#
# BuildArch needs to be located below patches in the spec file. Don't ask!
More information about the scm-commits
mailing list