From aa93056953a5528de0524417c0819441d5708f1c Mon Sep 17 00:00:00 2001
From: Ana Krivokapic <akrivoka@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
---
 .../dashboards/admin/networks/tests.py             |  13 ++
 .../dashboards/admin/networks/views.py             |  16 ++-
 .../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, 407 insertions(+), 124 deletions(-)

diff --git a/openstack_dashboard/dashboards/admin/networks/tests.py b/openstack_dashboard/dashboards/admin/networks/tests.py
index fc47ec2..7676d6a 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 a224550..65ba978 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,16 @@ 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 +165,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 9cfaa3c..42f33db 100644
--- a/openstack_dashboard/dashboards/project/networks/views.py
+++ b/openstack_dashboard/dashboards/project/networks/views.py
@@ -96,7 +96,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:
@@ -132,10 +131,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
-- 
1.9.1

