summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorwei.ying <wei.ying@easystack.cn>2017-09-30 14:10:10 +0800
committerwei.ying <wei.ying@easystack.cn>2017-09-30 15:32:01 +0800
commitb5bd7589e6fa46139d096481e01a32a4463b896c (patch)
treea2f953f48d2b6bcd9f55c7a12db301409644ca29
parenta2bf8855198355af23ac624b71212b37f3be9336 (diff)
Correct the subnets quota check in admin networks panel
Currently when checking the subnets quota in admin networks table, the current tenant subnets quota is checked, while the subnet is created, using the tenant of the selected network[1], this doesn't look the same. Similarly, in the network details subnets table, the creation of subnets actions lacks quota checking. [1] https://github.com/openstack/horizon/blob/master/openstack_dashboard/dashboards/admin/networks/subnets/workflows.py#L75 Change-Id: Ifb88b97168fc4f500e4bb15658f96363ddc7651f Closes-Bug:#1719606
Notes
Notes (review): Verified+1: Jenkins Code-Review+2: Akihiro Motoki <amotoki@gmail.com> Workflow+1: Akihiro Motoki <amotoki@gmail.com> Verified+2: Zuul Submitted-by: Zuul Submitted-at: Sat, 21 Oct 2017 01:48:20 +0000 Reviewed-on: https://review.openstack.org/508666 Project: openstack/horizon Branch: refs/heads/master
-rw-r--r--openstack_dashboard/dashboards/admin/networks/subnets/tables.py18
-rw-r--r--openstack_dashboard/dashboards/admin/networks/subnets/tests.py18
-rw-r--r--openstack_dashboard/dashboards/admin/networks/tables.py17
-rw-r--r--openstack_dashboard/dashboards/admin/networks/tests.py80
4 files changed, 84 insertions, 49 deletions
diff --git a/openstack_dashboard/dashboards/admin/networks/subnets/tables.py b/openstack_dashboard/dashboards/admin/networks/subnets/tables.py
index fc7c15b..75275c7 100644
--- a/openstack_dashboard/dashboards/admin/networks/subnets/tables.py
+++ b/openstack_dashboard/dashboards/admin/networks/subnets/tables.py
@@ -28,6 +28,7 @@ from openstack_dashboard.dashboards.project.networks.subnets \
28 import tables as proj_tables 28 import tables as proj_tables
29from openstack_dashboard.dashboards.project.networks.subnets.tabs \ 29from openstack_dashboard.dashboards.project.networks.subnets.tabs \
30 import SubnetsTab as project_tabs_subnets_tab 30 import SubnetsTab as project_tabs_subnets_tab
31from openstack_dashboard.usage import quotas
31 32
32LOG = logging.getLogger(__name__) 33LOG = logging.getLogger(__name__)
33 34
@@ -76,6 +77,23 @@ class CreateSubnet(proj_tables.SubnetPolicyTargetMixin, tables.LinkAction):
76 network_id = self.table.kwargs['network_id'] 77 network_id = self.table.kwargs['network_id']
77 return reverse(self.url, args=(network_id,)) 78 return reverse(self.url, args=(network_id,))
78 79
80 def allowed(self, request, datum=None):
81 network = self.table._get_network()
82 usages = quotas.tenant_quota_usages(
83 request, tenant_id=network.tenant_id, targets=('subnets', ))
84
85 # when Settings.OPENSTACK_NEUTRON_NETWORK['enable_quotas'] = False
86 # usages["subnets'] is empty
87 if usages.get('subnets', {}).get('available', 1) <= 0:
88 if 'disabled' not in self.classes:
89 self.classes = [c for c in self.classes] + ['disabled']
90 self.verbose_name = _('Create Subnet (Quota exceeded)')
91 else:
92 self.verbose_name = _('Create Subnet')
93 self.classes = [c for c in self.classes if c != 'disabled']
94
95 return True
96
79 97
80class UpdateSubnet(proj_tables.SubnetPolicyTargetMixin, tables.LinkAction): 98class UpdateSubnet(proj_tables.SubnetPolicyTargetMixin, tables.LinkAction):
81 name = "update" 99 name = "update"
diff --git a/openstack_dashboard/dashboards/admin/networks/subnets/tests.py b/openstack_dashboard/dashboards/admin/networks/subnets/tests.py
index a16f4e9..28b1816 100644
--- a/openstack_dashboard/dashboards/admin/networks/subnets/tests.py
+++ b/openstack_dashboard/dashboards/admin/networks/subnets/tests.py
@@ -409,18 +409,18 @@ class NetworkSubnetTests(test.BaseAdminViewTests):
409 409
410 def _test_network_detail_ip_availability_exception(self, 410 def _test_network_detail_ip_availability_exception(self,
411 mac_learning=False): 411 mac_learning=False):
412 network_id = self.networks.first().id 412 network = self.networks.first()
413 quota_data = self.quota_usages.first() 413 quota_data = self.neutron_quota_usages.first()
414 api.neutron.is_extension_supported( 414 api.neutron.is_extension_supported(
415 IsA(http.HttpRequest), 415 IsA(http.HttpRequest),
416 'network-ip-availability').AndReturn(True) 416 'network-ip-availability').AndReturn(True)
417 api.neutron.show_network_ip_availability(IsA(http.HttpRequest), 417 api.neutron.show_network_ip_availability(IsA(http.HttpRequest),
418 network_id).\ 418 network.id).\
419 MultipleTimes().AndRaise(self.exceptions.neutron) 419 MultipleTimes().AndRaise(self.exceptions.neutron)
420 api.neutron.network_get(IsA(http.HttpRequest), network_id).\ 420 api.neutron.network_get(IsA(http.HttpRequest), network.id).\
421 AndReturn(self.networks.first()) 421 MultipleTimes().AndReturn(self.networks.first())
422 422
423 api.neutron.subnet_list(IsA(http.HttpRequest), network_id=network_id).\ 423 api.neutron.subnet_list(IsA(http.HttpRequest), network_id=network.id).\
424 AndReturn([self.subnets.first()]) 424 AndReturn([self.subnets.first()])
425 api.neutron.is_extension_supported(IsA(http.HttpRequest), 425 api.neutron.is_extension_supported(IsA(http.HttpRequest),
426 'mac-learning') \ 426 'mac-learning') \
@@ -432,12 +432,12 @@ class NetworkSubnetTests(test.BaseAdminViewTests):
432 'dhcp_agent_scheduler')\ 432 'dhcp_agent_scheduler')\
433 .MultipleTimes().AndReturn(True) 433 .MultipleTimes().AndReturn(True)
434 quotas.tenant_quota_usages( 434 quotas.tenant_quota_usages(
435 IsA(http.HttpRequest), targets=('subnets',)) \ 435 IsA(http.HttpRequest), tenant_id=network.tenant_id,
436 .MultipleTimes().AndReturn(quota_data) 436 targets=('subnets',)).MultipleTimes().AndReturn(quota_data)
437 self.mox.ReplayAll() 437 self.mox.ReplayAll()
438 from django.utils.http import urlunquote 438 from django.utils.http import urlunquote
439 url = urlunquote(reverse('horizon:admin:networks:subnets_tab', 439 url = urlunquote(reverse('horizon:admin:networks:subnets_tab',
440 args=[network_id])) 440 args=[network.id]))
441 res = self.client.get(url) 441 res = self.client.get(url)
442 self.assertTemplateUsed(res, 'horizon/common/_detail.html') 442 self.assertTemplateUsed(res, 'horizon/common/_detail.html')
443 subnets = res.context['subnets_table'].data 443 subnets = res.context['subnets_table'].data
diff --git a/openstack_dashboard/dashboards/admin/networks/tables.py b/openstack_dashboard/dashboards/admin/networks/tables.py
index b0a649b..50f1505 100644
--- a/openstack_dashboard/dashboards/admin/networks/tables.py
+++ b/openstack_dashboard/dashboards/admin/networks/tables.py
@@ -27,6 +27,7 @@ from openstack_dashboard import api
27from openstack_dashboard.dashboards.project.networks \ 27from openstack_dashboard.dashboards.project.networks \
28 import tables as project_tables 28 import tables as project_tables
29from openstack_dashboard import policy 29from openstack_dashboard import policy
30from openstack_dashboard.usage import quotas
30 31
31LOG = logging.getLogger(__name__) 32LOG = logging.getLogger(__name__)
32 33
@@ -82,6 +83,22 @@ class EditNetwork(policy.PolicyTargetMixin, tables.LinkAction):
82class CreateSubnet(project_tables.CreateSubnet): 83class CreateSubnet(project_tables.CreateSubnet):
83 url = "horizon:admin:networks:createsubnet" 84 url = "horizon:admin:networks:createsubnet"
84 85
86 def allowed(self, request, datum=None):
87 usages = quotas.tenant_quota_usages(
88 request, tenant_id=datum.tenant_id, targets=('subnets', ))
89
90 # when Settings.OPENSTACK_NEUTRON_NETWORK['enable_quotas'] = False
91 # usages["subnets'] is empty
92 if usages.get('subnets', {}).get('available', 1) <= 0:
93 if 'disabled' not in self.classes:
94 self.classes = [c for c in self.classes] + ['disabled']
95 self.verbose_name = _('Create Subnet (Quota exceeded)')
96 else:
97 self.verbose_name = _('Create Subnet')
98 self.classes = [c for c in self.classes if c != 'disabled']
99
100 return True
101
85 102
86DISPLAY_CHOICES = ( 103DISPLAY_CHOICES = (
87 ("up", pgettext_lazy("Admin state of a Network", u"UP")), 104 ("up", pgettext_lazy("Admin state of a Network", u"UP")),
diff --git a/openstack_dashboard/dashboards/admin/networks/tests.py b/openstack_dashboard/dashboards/admin/networks/tests.py
index a29c7f7..b147e72 100644
--- a/openstack_dashboard/dashboards/admin/networks/tests.py
+++ b/openstack_dashboard/dashboards/admin/networks/tests.py
@@ -44,6 +44,9 @@ class NetworkTests(test.BaseAdminViewTests):
44 api.keystone.tenant_list(IsA(http.HttpRequest))\ 44 api.keystone.tenant_list(IsA(http.HttpRequest))\
45 .AndReturn([tenants, False]) 45 .AndReturn([tenants, False])
46 for network in self.networks.list(): 46 for network in self.networks.list():
47 usage.quotas.tenant_quota_usages(
48 IsA(http.HttpRequest), tenant_id=network.tenant_id,
49 targets=('subnets', )).AndReturn(quota_data)
47 api.neutron.list_dhcp_agent_hosting_networks(IsA(http.HttpRequest), 50 api.neutron.list_dhcp_agent_hosting_networks(IsA(http.HttpRequest),
48 network.id)\ 51 network.id)\
49 .AndReturn(self.agents.list()) 52 .AndReturn(self.agents.list())
@@ -54,9 +57,6 @@ class NetworkTests(test.BaseAdminViewTests):
54 api.neutron.is_extension_supported( 57 api.neutron.is_extension_supported(
55 IsA(http.HttpRequest), 58 IsA(http.HttpRequest),
56 'dhcp_agent_scheduler').AndReturn(True) 59 'dhcp_agent_scheduler').AndReturn(True)
57 usage.quotas.tenant_quota_usages(
58 IsA(http.HttpRequest), targets=('subnets', )) \
59 .MultipleTimes().AndReturn(quota_data)
60 self.mox.ReplayAll() 60 self.mox.ReplayAll()
61 61
62 res = self.client.get(INDEX_URL) 62 res = self.client.get(INDEX_URL)
@@ -106,9 +106,9 @@ class NetworkTests(test.BaseAdminViewTests):
106 'is_extension_supported'), 106 'is_extension_supported'),
107 usage.quotas: ('tenant_quota_usages',)}) 107 usage.quotas: ('tenant_quota_usages',)})
108 def test_network_detail_new(self, mac_learning=False): 108 def test_network_detail_new(self, mac_learning=False):
109 network_id = self.networks.first().id 109 network = self.networks.first()
110 quota_data = self.quota_usages.first() 110 quota_data = self.quota_usages.first()
111 api.neutron.network_get(IsA(http.HttpRequest), network_id) \ 111 api.neutron.network_get(IsA(http.HttpRequest), network.id) \
112 .MultipleTimes().AndReturn(self.networks.first()) 112 .MultipleTimes().AndReturn(self.networks.first())
113 api.neutron.is_extension_supported(IsA(http.HttpRequest), 113 api.neutron.is_extension_supported(IsA(http.HttpRequest),
114 'network-ip-availability') \ 114 'network-ip-availability') \
@@ -121,11 +121,11 @@ class NetworkTests(test.BaseAdminViewTests):
121 IsA(http.HttpRequest), 121 IsA(http.HttpRequest),
122 'dhcp_agent_scheduler').AndReturn(True) 122 'dhcp_agent_scheduler').AndReturn(True)
123 usage.quotas.tenant_quota_usages( 123 usage.quotas.tenant_quota_usages(
124 IsA(http.HttpRequest), targets=('subnets',)) \ 124 IsA(http.HttpRequest), tenant_id=network.tenant_id,
125 .MultipleTimes().AndReturn(quota_data) 125 targets=('subnets',)).MultipleTimes().AndReturn(quota_data)
126 self.mox.ReplayAll() 126 self.mox.ReplayAll()
127 url = urlunquote(reverse('horizon:admin:networks:detail', 127 url = urlunquote(reverse('horizon:admin:networks:detail',
128 args=[network_id])) 128 args=[network.id]))
129 129
130 res = self.client.get(url) 130 res = self.client.get(url)
131 network = res.context['network'] 131 network = res.context['network']
@@ -135,15 +135,15 @@ class NetworkTests(test.BaseAdminViewTests):
135 self.assertTemplateUsed(res, 'horizon/common/_detail.html') 135 self.assertTemplateUsed(res, 'horizon/common/_detail.html')
136 136
137 def _test_network_detail_subnets_tab(self, mac_learning=False): 137 def _test_network_detail_subnets_tab(self, mac_learning=False):
138 network_id = self.networks.first().id 138 network = self.networks.first()
139 ip_availability = self.ip_availability.get() 139 ip_availability = self.ip_availability.get()
140 quota_data = self.quota_usages.first() 140 quota_data = self.quota_usages.first()
141 api.neutron.show_network_ip_availability(IsA(http.HttpRequest), 141 api.neutron.show_network_ip_availability(IsA(http.HttpRequest),
142 network_id).\ 142 network.id).\
143 MultipleTimes().AndReturn(ip_availability) 143 MultipleTimes().AndReturn(ip_availability)
144 api.neutron.network_get(IsA(http.HttpRequest), network_id)\ 144 api.neutron.network_get(IsA(http.HttpRequest), network.id)\
145 .AndReturn(self.networks.first()) 145 .MultipleTimes().AndReturn(self.networks.first())
146 api.neutron.subnet_list(IsA(http.HttpRequest), network_id=network_id)\ 146 api.neutron.subnet_list(IsA(http.HttpRequest), network_id=network.id)\
147 .AndReturn([self.subnets.first()]) 147 .AndReturn([self.subnets.first()])
148 api.neutron.is_extension_supported( 148 api.neutron.is_extension_supported(
149 IsA(http.HttpRequest), 149 IsA(http.HttpRequest),
@@ -159,11 +159,11 @@ class NetworkTests(test.BaseAdminViewTests):
159 IsA(http.HttpRequest), 159 IsA(http.HttpRequest),
160 'dhcp_agent_scheduler').AndReturn(True) 160 'dhcp_agent_scheduler').AndReturn(True)
161 usage.quotas.tenant_quota_usages( 161 usage.quotas.tenant_quota_usages(
162 IsA(http.HttpRequest), targets=('subnets',)) \ 162 IsA(http.HttpRequest), tenant_id=network.tenant_id,
163 .MultipleTimes().AndReturn(quota_data) 163 targets=('subnets',)).MultipleTimes().AndReturn(quota_data)
164 self.mox.ReplayAll() 164 self.mox.ReplayAll()
165 url = urlunquote(reverse('horizon:admin:networks:subnets_tab', 165 url = urlunquote(reverse('horizon:admin:networks:subnets_tab',
166 args=[network_id])) 166 args=[network.id]))
167 res = self.client.get(url) 167 res = self.client.get(url)
168 168
169 self.assertTemplateUsed(res, 'horizon/common/_detail.html') 169 self.assertTemplateUsed(res, 'horizon/common/_detail.html')
@@ -196,8 +196,8 @@ class NetworkTests(test.BaseAdminViewTests):
196 IsA(http.HttpRequest), 196 IsA(http.HttpRequest),
197 'dhcp_agent_scheduler').AndReturn(True) 197 'dhcp_agent_scheduler').AndReturn(True)
198 usage.quotas.tenant_quota_usages( 198 usage.quotas.tenant_quota_usages(
199 IsA(http.HttpRequest), targets=('subnets',)) \ 199 IsA(http.HttpRequest), tenant_id=network.tenant_id,
200 .MultipleTimes().AndReturn(quota_data) 200 targets=('subnets',)).MultipleTimes().AndReturn(quota_data)
201 201
202 self.mox.ReplayAll() 202 self.mox.ReplayAll()
203 url = reverse('horizon:admin:networks:ports_tab', 203 url = reverse('horizon:admin:networks:ports_tab',
@@ -215,7 +215,7 @@ class NetworkTests(test.BaseAdminViewTests):
215 'list_dhcp_agent_hosting_networks',), 215 'list_dhcp_agent_hosting_networks',),
216 usage.quotas: ('tenant_quota_usages',)}) 216 usage.quotas: ('tenant_quota_usages',)})
217 def test_network_detail_agents_tab(self, mac_learning=False): 217 def test_network_detail_agents_tab(self, mac_learning=False):
218 network_id = self.networks.first().id 218 network = self.networks.first()
219 quota_data = self.quota_usages.first() 219 quota_data = self.quota_usages.first()
220 api.neutron.is_extension_supported(IsA(http.HttpRequest), 220 api.neutron.is_extension_supported(IsA(http.HttpRequest),
221 'network-ip-availability') \ 221 'network-ip-availability') \
@@ -225,10 +225,10 @@ class NetworkTests(test.BaseAdminViewTests):
225 'mac-learning')\ 225 'mac-learning')\
226 .AndReturn(mac_learning) 226 .AndReturn(mac_learning)
227 api.neutron.list_dhcp_agent_hosting_networks(IsA(http.HttpRequest), 227 api.neutron.list_dhcp_agent_hosting_networks(IsA(http.HttpRequest),
228 network_id)\ 228 network.id)\
229 .AndReturn(self.agents.list()) 229 .AndReturn(self.agents.list())
230 api.neutron.network_get(IsA(http.HttpRequest), network_id)\ 230 api.neutron.network_get(IsA(http.HttpRequest), network.id)\
231 .AndReturn(self.networks.first()) 231 .MultipleTimes().AndReturn(self.networks.first())
232 api.neutron.is_extension_supported( 232 api.neutron.is_extension_supported(
233 IsA(http.HttpRequest), 233 IsA(http.HttpRequest),
234 'dhcp_agent_scheduler').AndReturn(True) 234 'dhcp_agent_scheduler').AndReturn(True)
@@ -236,10 +236,10 @@ class NetworkTests(test.BaseAdminViewTests):
236 IsA(http.HttpRequest), 236 IsA(http.HttpRequest),
237 'dhcp_agent_scheduler').AndReturn(True) 237 'dhcp_agent_scheduler').AndReturn(True)
238 usage.quotas.tenant_quota_usages( 238 usage.quotas.tenant_quota_usages(
239 IsA(http.HttpRequest), targets=('subnets', )) \ 239 IsA(http.HttpRequest), tenant_id=network.tenant_id,
240 .MultipleTimes().AndReturn(quota_data) 240 targets=('subnets',)).MultipleTimes().AndReturn(quota_data)
241 self.mox.ReplayAll() 241 self.mox.ReplayAll()
242 url = reverse('horizon:admin:networks:agents_tab', args=[network_id]) 242 url = reverse('horizon:admin:networks:agents_tab', args=[network.id])
243 res = self.client.get(urlunquote(url)) 243 res = self.client.get(urlunquote(url))
244 244
245 self.assertTemplateUsed(res, 'horizon/common/_detail.html') 245 self.assertTemplateUsed(res, 'horizon/common/_detail.html')
@@ -318,11 +318,11 @@ class NetworkTests(test.BaseAdminViewTests):
318 318
319 def _test_network_detail_subnets_tab_subnet_exception(self, 319 def _test_network_detail_subnets_tab_subnet_exception(self,
320 mac_learning=False): 320 mac_learning=False):
321 network_id = self.networks.first().id 321 network = self.networks.first()
322 quota_data = self.quota_usages.first() 322 quota_data = self.quota_usages.first()
323 api.neutron.network_get(IsA(http.HttpRequest), network_id).\ 323 api.neutron.network_get(IsA(http.HttpRequest), network.id)\
324 AndReturn(self.networks.first()) 324 .MultipleTimes().AndReturn(self.networks.first())
325 api.neutron.subnet_list(IsA(http.HttpRequest), network_id=network_id).\ 325 api.neutron.subnet_list(IsA(http.HttpRequest), network_id=network.id).\
326 AndRaise(self.exceptions.neutron) 326 AndRaise(self.exceptions.neutron)
327 api.neutron.is_extension_supported( 327 api.neutron.is_extension_supported(
328 IsA(http.HttpRequest), 328 IsA(http.HttpRequest),
@@ -337,11 +337,11 @@ class NetworkTests(test.BaseAdminViewTests):
337 IsA(http.HttpRequest), 337 IsA(http.HttpRequest),
338 'dhcp_agent_scheduler').AndReturn(True) 338 'dhcp_agent_scheduler').AndReturn(True)
339 usage.quotas.tenant_quota_usages( 339 usage.quotas.tenant_quota_usages(
340 IsA(http.HttpRequest), targets=('subnets',)) \ 340 IsA(http.HttpRequest), tenant_id=network.tenant_id,
341 .MultipleTimes().AndReturn(quota_data) 341 targets=('subnets',)).MultipleTimes().AndReturn(quota_data)
342 self.mox.ReplayAll() 342 self.mox.ReplayAll()
343 url = urlunquote(reverse('horizon:admin:networks:subnets_tab', 343 url = urlunquote(reverse('horizon:admin:networks:subnets_tab',
344 args=[network_id])) 344 args=[network.id]))
345 res = self.client.get(url) 345 res = self.client.get(url)
346 346
347 self.assertTemplateUsed(res, 'horizon/common/_detail.html') 347 self.assertTemplateUsed(res, 'horizon/common/_detail.html')
@@ -370,15 +370,15 @@ class NetworkTests(test.BaseAdminViewTests):
370 370
371 def _test_network_detail_subnets_tab_port_exception(self, 371 def _test_network_detail_subnets_tab_port_exception(self,
372 mac_learning=False): 372 mac_learning=False):
373 network_id = self.networks.first().id 373 network = self.networks.first()
374 ip_availability = self.ip_availability.get() 374 ip_availability = self.ip_availability.get()
375 quota_data = self.quota_usages.first() 375 quota_data = self.quota_usages.first()
376 api.neutron.show_network_ip_availability(IsA(http.HttpRequest), 376 api.neutron.show_network_ip_availability(IsA(http.HttpRequest),
377 network_id). \ 377 network.id). \
378 MultipleTimes().AndReturn(ip_availability) 378 MultipleTimes().AndReturn(ip_availability)
379 api.neutron.network_get(IsA(http.HttpRequest), network_id).\ 379 api.neutron.network_get(IsA(http.HttpRequest), network.id)\
380 AndReturn(self.networks.first()) 380 .MultipleTimes().AndReturn(self.networks.first())
381 api.neutron.subnet_list(IsA(http.HttpRequest), network_id=network_id).\ 381 api.neutron.subnet_list(IsA(http.HttpRequest), network_id=network.id).\
382 AndReturn([self.subnets.first()]) 382 AndReturn([self.subnets.first()])
383 api.neutron.is_extension_supported( 383 api.neutron.is_extension_supported(
384 IsA(http.HttpRequest), 384 IsA(http.HttpRequest),
@@ -393,11 +393,11 @@ class NetworkTests(test.BaseAdminViewTests):
393 'dhcp_agent_scheduler')\ 393 'dhcp_agent_scheduler')\
394 .AndReturn(True) 394 .AndReturn(True)
395 usage.quotas.tenant_quota_usages( 395 usage.quotas.tenant_quota_usages(
396 IsA(http.HttpRequest), targets=('subnets',)) \ 396 IsA(http.HttpRequest), tenant_id=network.tenant_id,
397 .MultipleTimes().AndReturn(quota_data) 397 targets=('subnets',)).MultipleTimes().AndReturn(quota_data)
398 self.mox.ReplayAll() 398 self.mox.ReplayAll()
399 url = urlunquote(reverse('horizon:admin:networks:subnets_tab', 399 url = urlunquote(reverse('horizon:admin:networks:subnets_tab',
400 args=[network_id])) 400 args=[network.id]))
401 res = self.client.get(url) 401 res = self.client.get(url)
402 402
403 self.assertTemplateUsed(res, 'horizon/common/_detail.html') 403 self.assertTemplateUsed(res, 'horizon/common/_detail.html')