[dashboard] Modify bgpvpn network associations

This patch adds a button "create network association" in bgpvpn panels. It
opens a form to add only one network association.
The bgpvpn overview details is more complete. There is now a
network associations tab which list all the network associations. It's
possible to delete each network association. It's inspired by the network
panel which include subnets and ports tabs on the overview of a network.
It will be easier in the future to add others associations as routers and
ports with the same logical with new tab in bgpvpn overview details.
Change-Id: I6280b53558b6b52081ee6d629e8f3963d8dfc346
This commit is contained in:
Cédric Savignan
2018年01月23日 17:47:42 +01:00
committed by Thomas Morin
parent 991e2c58c5
commit 6eae473fd4

View File

@@ -68,6 +68,15 @@ def bgpvpn_delete(request, bgpvpn_id):
neutronclient(request).delete_bgpvpn(bgpvpn_id)
def network_association_get(request, bgpvpn_id, network_assoc_id, **kwargs):
LOG.debug("network_association_get(): "
"bgpvpn_id=%s, network_assoc_id=%s, kwargs=%s",
bgpvpn_id, network_assoc_id, kwargs)
network_association = neutronclient(request).show_bgpvpn_network_assoc(
bgpvpn_id, network_assoc_id).get('network_association')
return NetworkAssociation(network_association)
def network_association_list(request, bgpvpn_id, **kwargs):
LOG.debug("network_association_list(): bgpvpn_id=%s, kwargs=%s",
bgpvpn_id, kwargs)

View File

@@ -104,3 +104,12 @@ class EditDataBgpVpn(CommonData):
def __init__(self, request, *args, **kwargs):
super(EditDataBgpVpn, self).__init__(request, *args, **kwargs)
self.action = 'update'
class CreateNetworkAssociation(project_forms.CreateNetworkAssociation):
project_id = forms.CharField(widget=forms.HiddenInput())
def _set_params(self, data):
params = super(CreateNetworkAssociation, self)._set_params(data)
params['tenant_id'] = data['project_id']
return params

View File

@@ -72,6 +72,10 @@ class UpdateRouterAssociations(project_tables.UpdateRouterAssociations):
url = "horizon:admin:bgpvpn:update-associations"
class CreateNetworkAssociation(project_tables.CreateNetworkAssociation):
url = "horizon:admin:bgpvpn:create-network-association"
def get_route_targets(bgpvpn):
return ', '.join(rt for rt in bgpvpn.route_targets)
@@ -109,6 +113,7 @@ class BgpvpnTable(project_tables.BgpvpnTable):
row_actions = (EditInfoBgpVpn,
UpdateNetworkAssociations,
UpdateRouterAssociations,
CreateNetworkAssociation,
DeleteBgpvpn)
columns = ("tenant_id", "name", "type", "route_targets",
"import_targets", "export_targets", "networks", "routers")

View File

@@ -0,0 +1,27 @@
# Copyright (c) 2017 Orange.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from bgpvpn_dashboard.dashboards.project.bgpvpn.network_associations \
import tabs as network_tabs
from bgpvpn_dashboard.dashboards.project.bgpvpn import tabs as project_tabs
class OverviewTab(project_tabs.OverviewTab):
template_name = "admin/bgpvpn/_detail_overview.html"
class BgpvpnDetailsTabs(project_tabs.BgpvpnDetailsTabs):
tabs = (OverviewTab,
network_tabs.NetworkAssociationsTab,)

View File

@@ -1,34 +1,34 @@
{% load i18n %}
<div class="detail">
<dl class="dl-horizontal">
<dt>{% trans "BGPVPN Name" %}</dt>
<dd>{{ bgpvpn.name }}</dd>
<dt>{% trans "BGPVPN ID" %}</dt>
<dd>{{ bgpvpn.id }}</dd>
<dt>{% trans "Type" %}</dt>
<dd>{{ bgpvpn.type }}</dd>
<dt>{% trans "Route Targets" %}</dt>
<dd>
{% for rt in bgpvpn.route_targets %}
{{ rt }}
{% endfor %}
</dd>
<dt>{% trans "Import Targets" %}</dt>
<dd>
{% for it in bgpvpn.import_targets %}
{{ it }}
{% endfor %}
</dd>
<dt>{% trans "Export Targets" %}</dt>
<dd>
{% for et in bgpvpn.export_targets %}
{{ et }}
{% endfor %}
</dd>
<dt>{% trans "Project ID" %}</dt>
<dd>{{ bgpvpn.tenant_id }}</dd>
{% include "project/bgpvpn/_associated_networks.html" %}
{% include "project/bgpvpn/_associated_routers.html" %}
</dl>
</div>
{% load i18n %}
<div class="detail">
<dl class="dl-horizontal">
<dt>{% trans "BGPVPN Name" %}</dt>
<dd>{{ bgpvpn.name }}</dd>
<dt>{% trans "BGPVPN ID" %}</dt>
<dd>{{ bgpvpn.id }}</dd>
<dt>{% trans "Type" %}</dt>
<dd>{{ bgpvpn.type }}</dd>
<dt>{% trans "Route Targets" %}</dt>
<dd>
{% for rt in bgpvpn.route_targets %}
{{ rt }}
{% endfor %}
</dd>
<dt>{% trans "Import Targets" %}</dt>
<dd>
{% for it in bgpvpn.import_targets %}
{{ it }}
{% endfor %}
</dd>
<dt>{% trans "Export Targets" %}</dt>
<dd>
{% for et in bgpvpn.export_targets %}
{{ et }}
{% endfor %}
</dd>
<dt>{% trans "Project ID" %}</dt>
<dd>{{ bgpvpn.tenant_id }}</dd>
{% include "project/bgpvpn/_associated_networks.html" %}
{% include "project/bgpvpn/_associated_routers.html" %}
</dl>
</div>

View File

@@ -1,16 +0,0 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "BGPVPN Details" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_detail_header.html" %}
{% endblock %}
{% block main %}
<div class="row">
<div class="col-sm-12">
{% include "admin/bgpvpn/_detail_overview.html" %}
</div>
</div>
{% endblock %}

View File

@@ -26,6 +26,9 @@ urlpatterns = [
url(BGPVPN % 'update-associations',
bgpvpn_views.UpdateAssociationsView.as_view(),
name='update-associations'),
url(BGPVPN % 'create-network-association',
bgpvpn_views.CreateNetworkAssociationView.as_view(),
name='create-network-association'),
url(r'^(?P<bgpvpn_id>[^/]+)/detail/$',
bgpvpn_views.DetailProjectView.as_view(), name='detail'),
]

View File

@@ -24,6 +24,7 @@ from openstack_dashboard import api
import bgpvpn_dashboard.api.bgpvpn as bgpvpn_api
from bgpvpn_dashboard.dashboards.admin.bgpvpn import forms as bgpvpn_forms
from bgpvpn_dashboard.dashboards.admin.bgpvpn import tables as bgpvpn_tables
from bgpvpn_dashboard.dashboards.admin.bgpvpn import tabs as bgpvpn_tabs
from bgpvpn_dashboard.dashboards.admin.bgpvpn import workflows \
as bgpvpn_workflows
from bgpvpn_dashboard.dashboards.project.bgpvpn import views as project_views
@@ -69,6 +70,12 @@ class EditDataView(project_views.EditDataView):
success_url = reverse_lazy('horizon:admin:bgpvpn:index')
class CreateNetworkAssociationView(project_views.CreateNetworkAssociationView):
form_class = bgpvpn_forms.CreateNetworkAssociation
submit_url = 'horizon:admin:bgpvpn:create-network-association'
success_url = reverse_lazy('horizon:admin:bgpvpn:index')
class UpdateAssociationsView(project_views.UpdateAssociationsView):
workflow_class = bgpvpn_workflows.UpdateBgpVpnAssociations
page_title = _("Edit BGPVPN associations")
@@ -76,5 +83,5 @@ class UpdateAssociationsView(project_views.UpdateAssociationsView):
class DetailProjectView(project_views.DetailProjectView):
template_name = 'admin/bgpvpn/detail.html'
tab_group_class = bgpvpn_tabs.BgpvpnDetailsTabs
redirect_url = 'horizon:admin:bgpvpn:index'

View File

@@ -23,6 +23,8 @@ from horizon import exceptions
from horizon import forms
from horizon import messages
from openstack_dashboard import api
from bgpvpn_dashboard.api import bgpvpn as bgpvpn_api
from bgpvpn_dashboard.common import bgpvpn as bgpvpn_common
@@ -98,3 +100,54 @@ class EditDataBgpVpn(CommonData):
def __init__(self, request, *args, **kwargs):
super(EditDataBgpVpn, self).__init__(request, *args, **kwargs)
self.action = 'update'
class CreateNetworkAssociation(forms.SelfHandlingForm):
bgpvpn_id = forms.CharField(widget=forms.HiddenInput())
network_resource = forms.ChoiceField(
label=_("Associate Network"),
widget=forms.ThemableSelectWidget(
data_attrs=('name', 'id'),
transform=lambda x: "%s" % x.name_or_id))
def __init__(self, request, *args, **kwargs):
super(CreateNetworkAssociation, self).__init__(
request, *args, **kwargs)
# when an admin user uses the project panel BGPVPN, there is no
# project in data because bgpvpn_get doesn't return it
project_id = kwargs.get('initial', {}).get("project_id", None)
if request.user.is_superuser and project_id:
tenant_id = project_id
else:
tenant_id = self.request.user.tenant_id
try:
networks = api.neutron.network_list_for_tenant(request, tenant_id)
if networks:
choices = [('', _("Choose a network"))] + [(n.id, n) for n in
networks]
self.fields['network_resource'].choices = choices
else:
self.fields['network_resource'].choices = [('',
_("No network"))]
except Exception as e:
exceptions.handle(
request, _("Unable to retrieve networks: %s") % e)
def handle(self, request, data):
try:
params = self._set_params(data)
bgpvpn_api.network_association_create(
request, data['bgpvpn_id'], **params)
return True
except Exception as e:
exceptions.handle(
request, _("Unable to associate network {} : {}").format(
data["network_resource"], str(e)))
return False
def _set_params(self, data):
params = dict()
params['network_id'] = data['network_resource']
return params

View File

@@ -0,0 +1,100 @@
# Copyright (c) 2017 Orange.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import logging
from django.core.urlresolvers import reverse
from django.core.urlresolvers import reverse_lazy
from django.utils import safestring
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ungettext_lazy
from horizon import exceptions
from horizon import tables
from bgpvpn_dashboard.api import bgpvpn as bgpvpn_api
LOG = logging.getLogger(__name__)
class DeleteNetworkAssociation(tables.DeleteAction):
@staticmethod
def action_present(count):
return ungettext_lazy(u"Delete Network Association",
u"Delete Network Associations",
count)
@staticmethod
def action_past(count):
return ungettext_lazy(u"Deleted Network Association",
u"Deleted Network Associations",
count)
def delete(self, request, asso_id):
try:
bgpvpn_api.network_association_delete(
request, asso_id, self.table.kwargs['bgpvpn_id'])
except Exception:
msg = _('Failed to delete Network Association %s') % asso_id
LOG.info(msg)
redirect = reverse('horizon:project:bgpvpn:detail')
exceptions.handle(request, msg, redirect=redirect)
class CreateNetworkAssociation(tables.LinkAction):
name = "create_network_association"
verbose_name = _("Create Network Association")
url = "horizon:project:bgpvpn:create-network-association"
classes = ("ajax-modal",)
icon = "pencil"
def get_link_url(self, datum=None):
bgpvpn_id = self.table.kwargs['bgpvpn_id']
return reverse(self.url, args=(bgpvpn_id,))
class IDColumn(tables.Column):
url = "horizon:project:bgpvpn:network_assos:detail"
def get_link_url(self, asso):
bgpvpn_id = self.table.kwargs['bgpvpn_id']
return reverse(self.url, args=(bgpvpn_id, asso.id))
class NetworkColumn(tables.Column):
def get_raw_data(self, asso):
url = reverse('horizon:project:networks:detail',
args=[asso.network_id])
instance = '<a href=%s>%s</a>' % (url,
asso.network_name or asso.network_id)
return safestring.mark_safe(instance)
class NetworkAssociationsTable(tables.DataTable):
id = IDColumn("id", verbose_name=_("Association ID"),
link='horizon:project:bgpvpn:network_assos:detail')
network = NetworkColumn("network_id", verbose_name=_("Network"))
failure_url = reverse_lazy('horizon:project:bgpvpn:detail')
class Meta(object):
name = "network_associations"
verbose_name = _("Network Associations")
table_actions = (CreateNetworkAssociation,)
row_actions = (DeleteNetworkAssociation,)
hidden_title = False
def get_object_display(self, asso):
return asso.id

View File

@@ -0,0 +1,85 @@
# Copyright (c) 2017 Orange.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
from horizon import tabs
from horizon.utils import memoized
from openstack_dashboard import api
from bgpvpn_dashboard.api import bgpvpn as bgpvpn_api
from bgpvpn_dashboard.dashboards.project.bgpvpn.network_associations \
import tables as network_tables
class OverviewTab(tabs.Tab):
name = _("Overview")
slug = "overview"
template_name = "project/bgpvpn/network_associations/_detail_overview.html"
def get_context_data(self, request):
network_association = self.tab_group.kwargs['network_association']
network = self.get_network(network_association.network_id)
network_association.network_name = network.get('name')
network_association.network_url = self.get_network_detail_url(
network_association.network_id)
return {'network_association': network_association}
@memoized.memoized_method
def get_network(self, network_id):
try:
network = api.neutron.network_get(self.request, network_id)
except Exception:
network = {}
msg = _('Unable to retrieve network details.')
exceptions.handle(self.request, msg)
return network
@staticmethod
def get_network_detail_url(network_id):
return reverse('horizon:project:networks:detail', args=(network_id,))
class NetworkAssociationsTab(tabs.TableTab):
name = _("Network Associations")
slug = "network_associations_tab"
table_classes = (network_tables.NetworkAssociationsTable,)
template_name = ("horizon/common/_detail_table.html")
preload = False
def get_network_associations_data(self):
try:
bgpvpn_id = self.tab_group.kwargs['bgpvpn_id']
network_associations = bgpvpn_api.network_association_list(
self.request, bgpvpn_id)
for network_asso in network_associations:
network = api.neutron.network_get(self.request,
network_asso.network_id)
network_asso.network_name = network.name
except Exception:
network_associations = []
msg = _('Network associations list can not be retrieved.')
exceptions.handle(self.request, msg)
return network_associations
class NetworkAssociationDetailTabs(tabs.TabGroup):
slug = "network_association_details"
tabs = (OverviewTab,)

View File

@@ -0,0 +1,27 @@
# Copyright (c) 2017 Orange.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.conf.urls import url
from bgpvpn_dashboard.dashboards.project.bgpvpn.network_associations \
import views as bgpvpn_network_associations_views
NETWORK_ASSO = r'^(?P<bgpvpn_id>[^/]+)/network_assos/' \
r'(?P<network_association_id>[^/]+)/%s$'
urlpatterns = [
url(NETWORK_ASSO % 'detail',
bgpvpn_network_associations_views.DetailView.as_view(), name='detail'),
]

View File

@@ -0,0 +1,61 @@
# Copyright (c) 2017 Orange.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
from horizon import tabs
from bgpvpn_dashboard.api import bgpvpn as bgpvpn_api
from bgpvpn_dashboard.dashboards.project.bgpvpn.network_associations \
import tabs as project_tabs
class DetailView(tabs.TabView):
tab_group_class = project_tabs.NetworkAssociationDetailTabs
template_name = 'horizon/common/_detail.html'
page_title = "{{ network_association.id }}"
def get_data(self):
network_association_id = self.kwargs['network_association_id']
bgpvpn_id = self.kwargs['bgpvpn_id']
try:
network_association = bgpvpn_api.network_association_get(
self.request, bgpvpn_id, network_association_id)
return network_association
except Exception:
network_association = []
msg = _('Unable to retrieve network association details.')
exceptions.handle(self.request, msg,
redirect=self.get_redirect_url())
return network_association
def get_context_data(self, **kwargs):
context = super(DetailView, self).get_context_data(**kwargs)
network_association = self.get_data()
context["url"] = reverse(
"horizon:project:bgpvpn:network_associations_tab",
args=(self.kwargs["bgpvpn_id"], network_association.id))
return context
def get_tabs(self, request, *args, **kwargs):
network_association = self.get_data()
return self.tab_group_class(
request, network_association=network_association, **kwargs)
@staticmethod
def get_redirect_url():
return reverse('horizon:project:bgpvpn:index')

View File

@@ -57,6 +57,14 @@ class UpdateRouterAssociations(tables.LinkAction):
return "?".join([base_url, param])
class CreateNetworkAssociation(tables.LinkAction):
name = "create_network_association"
verbose_name = _("Create Network Association")
url = "horizon:project:bgpvpn:create-network-association"
classes = ("ajax-modal",)
icon = "pencil"
def get_network_url(network):
url = reverse('horizon:project:networks:detail', args=[network.id])
instance = '<a href=%s>%s</a>' % (url, html.escape(network.name_or_id))
@@ -94,4 +102,5 @@ class BgpvpnTable(tables.DataTable):
verbose_name = _("BGPVPN")
row_actions = (EditInfoBgpVpn,
UpdateNetworkAssociations,
UpdateRouterAssociations)
UpdateRouterAssociations,
CreateNetworkAssociation)

View File

@@ -0,0 +1,57 @@
# Copyright (c) 2017 Orange.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import bgpvpn_dashboard.api.bgpvpn as bgpvpn_api
from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
from horizon import tabs
from bgpvpn_dashboard.dashboards.project.bgpvpn.network_associations \
import tabs as network_tabs
class OverviewTab(tabs.Tab):
name = _("Overview")
slug = "overview"
template_name = ("project/bgpvpn/_detail_overview.html")
preload = False
def _get_data(self):
bgpvpn = {}
bgpvpn_id = None
try:
bgpvpn_id = self.tab_group.kwargs['bgpvpn_id']
bgpvpn = bgpvpn_api.bgpvpn_get(self.request, bgpvpn_id)
bgpvpn.set_id_as_name_if_empty(length=0)
except Exception:
msg = _('Unable to retrieve details for bgpvpn "%s".') % (
bgpvpn_id)
exceptions.handle(self.request, msg)
return bgpvpn
def get_context_data(self, request, **kwargs):
context = super(OverviewTab, self).get_context_data(request, **kwargs)
bgpvpn = self._get_data()
context["bgpvpn"] = bgpvpn
return context
class BgpvpnDetailsTabs(tabs.DetailTabsGroup):
slug = "bgpvpn_tabs"
tabs = (OverviewTab,
network_tabs.NetworkAssociationsTab,)
sticky = True

View File

@@ -1,15 +1,15 @@
{% load i18n %}
<dt>
{% if bgpvpn.routers|length > 1 %}
{% trans "Associated Routers" %}
{% else %}
{% trans "Associated Router" %}
{% endif %}
</dt>
{% for router in bgpvpn.routers %}
<dd>
{% url 'horizon:project:routers:detail' router as router_url %}
<a href="{{ router_url }}">{{ router }}</a>
</dd>
{% load i18n %}
<dt>
{% if bgpvpn.routers|length > 1 %}
{% trans "Associated Routers" %}
{% else %}
{% trans "Associated Router" %}
{% endif %}
</dt>
{% for router in bgpvpn.routers %}
<dd>
{% url 'horizon:project:routers:detail' router as router_url %}
<a href="{{ router_url }}">{{ router }}</a>
</dd>
{% endfor %}

View File

@@ -0,0 +1,7 @@
{% extends "horizon/common/_modal_form.html" %}
{% load i18n %}
{% block modal-body-right %}
<h3>{% trans "Description:" %}</h3>
<p>{% trans "Associate a BGPVPN to a Network" %}</p>
{% endblock %}

View File

@@ -1,14 +1,13 @@
{% load i18n %}
<div class="detail">
<dl class="dl-horizontal">
<dt>{% trans "BGPVPN Name" %}</dt>
<dd>{{ bgpvpn.name }}</dd>
<dt>{% trans "BGPVPN ID" %}</dt>
<dd>{{ bgpvpn.id }}</dd>
<dt>{% trans "Type" %}</dt>
<dd>{{ bgpvpn.type }}</dd>
{% include "project/bgpvpn/_associated_networks.html" %}
{% include "project/bgpvpn/_associated_routers.html" %}
</dl>
</div>
{% load i18n %}
<div class="detail">
<dl class="dl-horizontal">
<dt>{% trans "BGPVPN Name" %}</dt>
<dd>{{ bgpvpn.name }}</dd>
<dt>{% trans "BGPVPN ID" %}</dt>
<dd>{{ bgpvpn.id }}</dd>
<dt>{% trans "Type" %}</dt>
<dd>{{ bgpvpn.type }}</dd>
{% include "project/bgpvpn/_associated_networks.html" %}
{% include "project/bgpvpn/_associated_routers.html" %}
</dl>
</div>

View File

@@ -0,0 +1,7 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Create Network Association" %}{% endblock %}
{% block main %}
{% include "project/bgpvpn/_create_network_association.html" %}
{% endblock %}

View File

@@ -1,16 +0,0 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "BGPVPN Details" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_detail_header.html" %}
{% endblock %}
{% block main %}
<div class="row">
<div class="col-sm-12">
{% include "project/bgpvpn/_detail_overview.html" %}
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,12 @@
{% load i18n sizeformat %}
<div class="detail">
<dl class="dl-horizontal">
<dt title="{% trans 'ID' %}">{% trans "Association ID" %}</dt>
<dd data-display="{{ network_association.id }}">{{network_association.id|default:_("None") }}</dd>
<dt title="{% trans 'Network Name' %}">{% trans "Network Name" %}</dt>
<dd data-display="{{ network_association.network_name|default:_('None') }}">{{ network_association.network_name|default:_("None") }}</dd>
<dt title="{% trans 'Network ID' %}">{% trans "Network ID" %}</dt>
<dd><a href="{{ network_association.network_url }}">{{ network_association.network_id|default:_("None") }}</a></dd>
</dl>
</div>

View File

@@ -13,9 +13,14 @@
# License for the specific language governing permissions and limitations
# under the License.
from django.conf.urls import include
from django.conf.urls import url
import bgpvpn_dashboard.dashboards.project.bgpvpn.views as bgpvpn_views
from bgpvpn_dashboard.dashboards.project.bgpvpn.network_associations import \
urls as network_associations_urls
from bgpvpn_dashboard.dashboards.project.bgpvpn.network_associations import \
views as network_associations_views
from bgpvpn_dashboard.dashboards.project.bgpvpn import views as bgpvpn_views
BGPVPN = r'^(?P<bgpvpn_id>[^/]+)/%s$'
@@ -25,6 +30,16 @@ urlpatterns = [
url(BGPVPN % 'update-associations',
bgpvpn_views.UpdateAssociationsView.as_view(),
name='update-associations'),
url(BGPVPN % 'create-network-association',
bgpvpn_views.CreateNetworkAssociationView.as_view(),
name='create-network-association'),
url(r'^(?P<bgpvpn_id>[^/]+)/detail/$',
bgpvpn_views.DetailProjectView.as_view(), name='detail'),
url(r'^(?P<bgpvpn_id>[^/]+)/network_assos/'
r'(?P<network__association_id>[^/]+)/'
r'detail\?tab=bgpvpns__network__associations_tab$',
network_associations_views.DetailView.as_view(),
name='network_associations_tab'),
url(r'^network_assos/', include(network_associations_urls,
namespace='network_assos')),
]

View File

@@ -19,15 +19,16 @@ from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
from horizon import forms
from horizon import tables
from horizon import tabs
from horizon.utils import memoized
from horizon import views
from horizon import workflows
from openstack_dashboard import api
import bgpvpn_dashboard.api.bgpvpn as bgpvpn_api
from bgpvpn_dashboard.api import bgpvpn as bgpvpn_api
from bgpvpn_dashboard.common import bgpvpn as bgpvpn_common
from bgpvpn_dashboard.dashboards.project.bgpvpn import forms as bgpvpn_forms
from bgpvpn_dashboard.dashboards.project.bgpvpn import tables as bgpvpn_tables
from bgpvpn_dashboard.dashboards.project.bgpvpn import tabs as bgpvpn_tabs
from bgpvpn_dashboard.dashboards.project.bgpvpn import workflows \
as bgpvpn_workflows
@@ -91,6 +92,39 @@ class EditDataView(forms.ModalFormView):
return data
class CreateNetworkAssociationView(forms.ModalFormView):
form_class = bgpvpn_forms.CreateNetworkAssociation
form_id = "create_network_association_form"
modal_header = _("Create Network Association")
submit_label = _("Create")
submit_url = 'horizon:project:bgpvpn:create-network-association'
success_url = reverse_lazy('horizon:project:bgpvpn:index')
template_name = 'project/bgpvpn/create_network_association.html'
page_title = _("Create Network Association")
def get_context_data(self, **kwargs):
context = super(
CreateNetworkAssociationView, self).get_context_data(**kwargs)
args = (self.kwargs['bgpvpn_id'],)
context["bgpvpn_id"] = self.kwargs['bgpvpn_id']
context["submit_url"] = reverse(self.submit_url, args=args)
return context
def get_initial(self):
bgpvpn_id = self.kwargs['bgpvpn_id']
try:
# Get initial bgpvpn information
bgpvpn = bgpvpn_api.bgpvpn_get(self.request, bgpvpn_id)
data = bgpvpn.to_dict()
data['bgpvpn_id'] = data.pop('id')
return data
except Exception:
exceptions.handle(
self.request,
_('Unable to retrieve BGPVPN details.'),
redirect=self.success_url)
class UpdateAssociationsView(workflows.WorkflowView):
workflow_class = bgpvpn_workflows.UpdateBgpVpnAssociations
page_title = _("Edit BGPVPN associations")
@@ -111,8 +145,9 @@ class UpdateAssociationsView(workflows.WorkflowView):
redirect=self.failure_url)
class DetailProjectView(views.HorizonTemplateView):
template_name = 'project/bgpvpn/detail.html'
class DetailProjectView(tabs.TabbedTableView):
tab_group_class = bgpvpn_tabs.BgpvpnDetailsTabs
template_name = 'horizon/common/_detail.html'
page_title = "{{ bgpvpn.name }}"
redirect_url = 'horizon:project:bgpvpn:index'

View File

@@ -12,6 +12,7 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import logging
from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
@@ -19,6 +20,8 @@ from horizon import forms
from horizon import workflows
from openstack_dashboard import api
LOG = logging.getLogger(__name__)
from bgpvpn_dashboard.api import bgpvpn as bgpvpn_api

View File

@@ -110,6 +110,20 @@ class BgpvpnApiTests(bgpvpn_test.APITestCase):
self.mock_delete_bgpvpn.assert_called_once_with(bgpvpn.id)
@test.create_mocks({
neutronclient: ('show_bgpvpn_network_assoc',)})
def test_network_association_get(self):
bgpvpn = self.bgpvpns.first()
na = self.network_associations.first()
ret_dict = {
'network_association': self.api_network_associations.first()}
self.mock_show_bgpvpn_network_assoc.return_value = ret_dict
ret_val = api.bgpvpn.network_association_get(
self.request, bgpvpn.id, na.id)
self.assertIsInstance(ret_val, api.bgpvpn.NetworkAssociation)
@test.create_mocks({
neutronclient: ('list_bgpvpn_network_assocs',)})
def test_network_association_list(self):

View File

@@ -183,37 +183,3 @@ class TestUpdateAssociationsView(helpers.APITestCase):
self.bgpvpn_view.request, "foo-id")
for key, val in expected_data.items():
self.assertEqual(val, result[key])
class TestDetailProjectView(helpers.APITestCase):
def setUp(self):
super(TestDetailProjectView, self).setUp()
self.view = bgpvpn_views.DetailProjectView()
fake_response = {'status_code': 200}
self.mock_request = mock.Mock(return_value=fake_response)
self.view.request = self.mock_request
self.view.kwargs = {'bgpvpn_id': 'foo-id'}
self.assertEqual('project/bgpvpn/detail.html', self.view.template_name)
self.assertEqual('{{ bgpvpn.name }}', self.view.page_title)
self.assertEqual('horizon:project:bgpvpn:index',
self.view.redirect_url)
self.addCleanup(mock.patch.stopall)
@mock.patch.object(bgpvpn_views, 'bgpvpn_api', autospec=True)
def test_get_context_data(self, mock_bgpvpn_api):
expected_bgpvpn = bgpvpn_api.Bgpvpn({"id": "foo-id"})
mock_bgpvpn_api.bgpvpn_get.return_value = expected_bgpvpn
context = self.view.get_context_data()
mock_bgpvpn_api.bgpvpn_get.assert_called_once_with(
self.view.request, "foo-id")
self.assertIn('view', context)
self.assertIn('bgpvpn', context)
self.assertIn('url', context)
self.assertIsInstance(context['view'], bgpvpn_views.DetailProjectView)
self.assertEqual(expected_bgpvpn, context['bgpvpn'])
self.assertEqual(reverse('horizon:project:bgpvpn:index'),
context['url'])
Reference in New Issue
openstack/networking-bgpvpn
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.

The note is not visible to the blocked user.