1
0
mirror of https://github.com/netbox-community/netbox.git synced 2024-05-10 07:54:54 +00:00

Allowed assigning an IP address to either a device or a VM

This commit is contained in:
Jeremy Stretch
2017-08-29 15:26:35 -04:00
parent e945aafd7b
commit fa95191792
9 changed files with 106 additions and 85 deletions

View File

@ -1241,6 +1241,10 @@ class Interface(models.Model):
) )
}) })
@property
def parent(self):
return self.device or self.virtual_machine
@property @property
def is_virtual(self): def is_virtual(self):
return self.form_factor in VIRTUAL_IFACE_TYPES return self.form_factor in VIRTUAL_IFACE_TYPES

View File

@ -377,50 +377,9 @@ class PrefixFilterForm(BootstrapMixin, CustomFieldFilterForm):
# #
class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldForm): class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldForm):
interface_site = forms.ModelChoiceField( interface = forms.ModelChoiceField(
queryset=Site.objects.all(),
required=False,
label='Site',
widget=forms.Select(
attrs={'filter-for': 'interface_rack'}
)
)
interface_rack = ChainedModelChoiceField(
queryset=Rack.objects.all(),
chains=(
('site', 'interface_site'),
),
required=False,
label='Rack',
widget=APISelect(
api_url='/api/dcim/racks/?site_id={{interface_site}}',
display_field='display_name',
attrs={'filter-for': 'interface_device', 'nullable': 'true'}
)
)
interface_device = ChainedModelChoiceField(
queryset=Device.objects.all(),
chains=(
('site', 'interface_site'),
('rack', 'interface_rack'),
),
required=False,
label='Device',
widget=APISelect(
api_url='/api/dcim/devices/?site_id={{interface_site}}&rack_id={{interface_rack}}',
display_field='display_name',
attrs={'filter-for': 'interface'}
)
)
interface = ChainedModelChoiceField(
queryset=Interface.objects.all(), queryset=Interface.objects.all(),
chains=( required=False
('device', 'interface_device'),
),
required=False,
widget=APISelect(
api_url='/api/dcim/interfaces/?device_id={{interface_device}}'
)
) )
nat_site = forms.ModelChoiceField( nat_site = forms.ModelChoiceField(
queryset=Site.objects.all(), queryset=Site.objects.all(),
@ -479,13 +438,13 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldForm)
obj_label='address' obj_label='address'
) )
) )
primary_for_device = forms.BooleanField(required=False, label='Make this the primary IP for the device') primary_for_parent = forms.BooleanField(required=False, label='Make this the primary IP for the device/VM')
class Meta: class Meta:
model = IPAddress model = IPAddress
fields = [ fields = [
'address', 'vrf', 'status', 'role', 'description', 'interface', 'primary_for_device', 'nat_site', 'nat_rack', 'address', 'vrf', 'status', 'role', 'description', 'interface', 'primary_for_parent', 'nat_site',
'nat_inside', 'tenant_group', 'tenant', 'nat_rack', 'nat_inside', 'tenant_group', 'tenant',
] ]
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@ -493,10 +452,6 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldForm)
# Initialize helper selectors # Initialize helper selectors
instance = kwargs.get('instance') instance = kwargs.get('instance')
initial = kwargs.get('initial', {}).copy() initial = kwargs.get('initial', {}).copy()
if instance and instance.interface is not None:
initial['interface_site'] = instance.interface.device.site
initial['interface_rack'] = instance.interface.device.rack
initial['interface_device'] = instance.interface.device
if instance and instance.nat_inside and instance.nat_inside.device is not None: if instance and instance.nat_inside and instance.nat_inside.device is not None:
initial['nat_site'] = instance.nat_inside.device.site initial['nat_site'] = instance.nat_inside.device.site
initial['nat_rack'] = instance.nat_inside.device.rack initial['nat_rack'] = instance.nat_inside.device.rack
@ -507,22 +462,30 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldForm)
self.fields['vrf'].empty_label = 'Global' self.fields['vrf'].empty_label = 'Global'
# Initialize primary_for_device if IP address is already assigned # Limit interface selections to those belonging to the parent device/VM
if self.instance.interface is not None: if self.instance and self.instance.interface:
device = self.instance.interface.device self.fields['interface'].queryset = Interface.objects.filter(
device=self.instance.interface.device, virtual_machine=self.instance.interface.virtual_machine
)
else:
self.fields['interface'].choices = []
# Initialize primary_for_parent if IP address is already assigned
if self.instance.pk and self.instance.interface is not None:
parent = self.instance.interface.parent
if ( if (
self.instance.address.version == 4 and device.primary_ip4 == self.instance or self.instance.address.version == 4 and parent.primary_ip4_id == self.instance.pk or
self.instance.address.version == 6 and device.primary_ip6 == self.instance self.instance.address.version == 6 and parent.primary_ip6_id == self.instance.pk
): ):
self.initial['primary_for_device'] = True self.initial['primary_for_parent'] = True
def clean(self): def clean(self):
super(IPAddressForm, self).clean() super(IPAddressForm, self).clean()
# Primary IP assignment is only available if an interface has been assigned. # Primary IP assignment is only available if an interface has been assigned.
if self.cleaned_data.get('primary_for_device') and not self.cleaned_data.get('interface'): if self.cleaned_data.get('primary_for_parent') and not self.cleaned_data.get('interface'):
self.add_error( self.add_error(
'primary_for_device', "Only IP addresses assigned to an interface can be designated as primary IPs." 'primary_for_parent', "Only IP addresses assigned to an interface can be designated as primary IPs."
) )
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
@ -530,13 +493,13 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldForm)
ipaddress = super(IPAddressForm, self).save(*args, **kwargs) ipaddress = super(IPAddressForm, self).save(*args, **kwargs)
# Assign this IPAddress as the primary for the associated Device. # Assign this IPAddress as the primary for the associated Device.
if self.cleaned_data['primary_for_device']: if self.cleaned_data['primary_for_parent']:
device = self.cleaned_data['interface'].device parent = self.cleaned_data['interface'].parent
if ipaddress.address.version == 4: if ipaddress.address.version == 4:
device.primary_ip4 = ipaddress parent.primary_ip4 = ipaddress
else: else:
device.primary_ip6 = ipaddress parent.primary_ip6 = ipaddress
device.save() parent.save()
# Clear assignment as primary for device if set. # Clear assignment as primary for device if set.
else: else:

View File

@ -77,9 +77,9 @@ IPADDRESS_LINK = """
{% endif %} {% endif %}
""" """
IPADDRESS_DEVICE = """ IPADDRESS_PARENT = """
{% if record.interface %} {% if record.interface %}
<a href="{{ record.interface.device.get_absolute_url }}">{{ record.interface.device }}</a> <a href="{{ record.interface.parent.get_absolute_url }}">{{ record.interface.parent }}</a>
{% else %} {% else %}
&mdash; &mdash;
{% endif %} {% endif %}
@ -265,12 +265,12 @@ class IPAddressTable(BaseTable):
status = tables.TemplateColumn(STATUS_LABEL) status = tables.TemplateColumn(STATUS_LABEL)
vrf = tables.TemplateColumn(VRF_LINK, verbose_name='VRF') vrf = tables.TemplateColumn(VRF_LINK, verbose_name='VRF')
tenant = tables.TemplateColumn(TENANT_LINK) tenant = tables.TemplateColumn(TENANT_LINK)
device = tables.TemplateColumn(IPADDRESS_DEVICE, orderable=False) parent = tables.TemplateColumn(IPADDRESS_PARENT, orderable=False)
interface = tables.Column(orderable=False) interface = tables.Column(orderable=False)
class Meta(BaseTable.Meta): class Meta(BaseTable.Meta):
model = IPAddress model = IPAddress
fields = ('pk', 'address', 'vrf', 'status', 'role', 'tenant', 'device', 'interface', 'description') fields = ('pk', 'address', 'vrf', 'status', 'role', 'tenant', 'parent', 'interface', 'description')
row_attrs = { row_attrs = {
'class': lambda record: 'success' if not isinstance(record, IPAddress) else '', 'class': lambda record: 'success' if not isinstance(record, IPAddress) else '',
} }
@ -283,7 +283,7 @@ class IPAddressDetailTable(IPAddressTable):
class Meta(IPAddressTable.Meta): class Meta(IPAddressTable.Meta):
fields = ( fields = (
'pk', 'address', 'vrf', 'status', 'role', 'tenant', 'nat_inside', 'device', 'interface', 'description', 'pk', 'address', 'vrf', 'status', 'role', 'tenant', 'nat_inside', 'parent', 'interface', 'description',
) )

View File

@ -10,7 +10,7 @@ from django.shortcuts import get_object_or_404, render
from django.urls import reverse from django.urls import reverse
from django.views.generic import View from django.views.generic import View
from dcim.models import Device from dcim.models import Device, Interface
from utilities.paginator import EnhancedPaginator from utilities.paginator import EnhancedPaginator
from utilities.views import ( from utilities.views import (
BulkCreateView, BulkDeleteView, BulkEditView, BulkImportView, ObjectDeleteView, ObjectEditView, ObjectListView, BulkCreateView, BulkDeleteView, BulkEditView, BulkImportView, ObjectDeleteView, ObjectEditView, ObjectListView,
@ -597,7 +597,7 @@ class IPAddressListView(ObjectListView):
queryset = IPAddress.objects.select_related( queryset = IPAddress.objects.select_related(
'vrf__tenant', 'tenant', 'nat_inside' 'vrf__tenant', 'tenant', 'nat_inside'
).prefetch_related( ).prefetch_related(
'interface__device' 'interface__device', 'interface__virtual_machine'
) )
filter = filters.IPAddressFilter filter = filters.IPAddressFilter
filter_form = forms.IPAddressFilterForm filter_form = forms.IPAddressFilterForm
@ -657,6 +657,17 @@ class IPAddressCreateView(PermissionRequiredMixin, ObjectEditView):
template_name = 'ipam/ipaddress_edit.html' template_name = 'ipam/ipaddress_edit.html'
default_return_url = 'ipam:ipaddress_list' default_return_url = 'ipam:ipaddress_list'
def alter_obj(self, obj, request, url_args, url_kwargs):
interface_id = request.GET.get('interface')
if interface_id:
try:
obj.interface = Interface.objects.get(pk=interface_id)
except (ValueError, Interface.DoesNotExist):
pass
return obj
class IPAddressEditView(IPAddressCreateView): class IPAddressEditView(IPAddressCreateView):
permission_required = 'ipam.change_ipaddress' permission_required = 'ipam.change_ipaddress'

View File

@ -64,7 +64,7 @@
{% endif %} {% endif %}
{% endif %} {% endif %}
{% if perms.ipam.add_ipaddress %} {% if perms.ipam.add_ipaddress %}
<a href="{% url 'ipam:ipaddress_add' %}?interface_site={{ device.site.pk }}&interface_rack={{ device.rack.pk }}&interface_device={{ device.pk }}&interface={{ iface.pk }}&return_url={{ device.get_absolute_url }}" class="btn btn-xs btn-success" title="Add IP address"> <a href="{% url 'ipam:ipaddress_add' %}?interface={{ iface.pk }}&return_url={{ device.get_absolute_url }}" class="btn btn-xs btn-success" title="Add IP address">
<i class="glyphicon glyphicon-plus" aria-hidden="true"></i> <i class="glyphicon glyphicon-plus" aria-hidden="true"></i>
</a> </a>
{% endif %} {% endif %}

View File

@ -100,7 +100,7 @@
<td>Assignment</td> <td>Assignment</td>
<td> <td>
{% if ipaddress.interface %} {% if ipaddress.interface %}
<span><a href="{% url 'dcim:device' pk=ipaddress.interface.device.pk %}">{{ ipaddress.interface.device }}</a> ({{ ipaddress.interface }})</span> <span><a href="{{ ipaddress.interface.parent.get_absolute_url }}">{{ ipaddress.interface.parent }}</a> ({{ ipaddress.interface }})</span>
{% else %} {% else %}
<span class="text-muted">None</span> <span class="text-muted">None</span>
{% endif %} {% endif %}
@ -112,7 +112,7 @@
{% if ipaddress.nat_inside %} {% if ipaddress.nat_inside %}
<a href="{% url 'ipam:ipaddress' pk=ipaddress.nat_inside.pk %}">{{ ipaddress.nat_inside }}</a> <a href="{% url 'ipam:ipaddress' pk=ipaddress.nat_inside.pk %}">{{ ipaddress.nat_inside }}</a>
{% if ipaddress.nat_inside.interface %} {% if ipaddress.nat_inside.interface %}
(<a href="{% url 'dcim:device' pk=ipaddress.nat_inside.interface.device.pk %}">{{ ipaddress.nat_inside.interface.device }}</a>) (<a href="{{ ipaddress.nat_inside.interface.parent.get_absolute_url }}">{{ ipaddress.nat_inside.interface.parent }}</a>)
{% endif %} {% endif %}
{% else %} {% else %}
<span class="text-muted">None</span> <span class="text-muted">None</span>

View File

@ -1,6 +1,7 @@
{% extends 'utilities/obj_edit.html' %} {% extends 'utilities/obj_edit.html' %}
{% load static from staticfiles %} {% load static from staticfiles %}
{% load form_helpers %} {% load form_helpers %}
{% load helpers %}
{% block tabs %} {% block tabs %}
{% if not obj.pk %} {% if not obj.pk %}
@ -26,18 +27,25 @@
{% render_field form.tenant %} {% render_field form.tenant %}
</div> </div>
</div> </div>
{% if obj.interface %}
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
<strong>Interface Assignment</strong> <strong>Interface Assignment</strong>
</div> </div>
<div class="panel-body"> <div class="panel-body">
{% render_field form.interface_site %} <div class="form-group">
{% render_field form.interface_rack %} <label class="col-md-3 control-label">{{ obj.interface.parent|model_name|bettertitle }}</label>
{% render_field form.interface_device %} <div class="col-md-9">
<p class="form-control-static">
<a href="{{ obj.interface.parent.get_absolute_url }}">{{ obj.interface.parent }}</a>
</p>
</div>
</div>
{% render_field form.interface %} {% render_field form.interface %}
{% render_field form.primary_for_device %} {% render_field form.primary_for_parent %}
</div> </div>
</div> </div>
{% endif %}
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"><strong>NAT IP (Inside)</strong></div> <div class="panel-heading"><strong>NAT IP (Inside)</strong></div>
<div class="panel-body"> <div class="panel-body">

View File

@ -13,6 +13,11 @@
<td>{{ iface.mtu|default:"" }}</td> <td>{{ iface.mtu|default:"" }}</td>
<td>{{ iface.mac_address|default:"" }}</td> <td>{{ iface.mac_address|default:"" }}</td>
<td class="text-right"> <td class="text-right">
{% if perms.ipam.add_ipaddress %}
<a href="{% url 'ipam:ipaddress_add' %}?interface={{ iface.pk }}&return_url={{ vm.get_absolute_url }}" class="btn btn-xs btn-success" title="Add IP address">
<i class="glyphicon glyphicon-plus" aria-hidden="true"></i>
</a>
{% endif %}
{% if perms.dcim.change_interface %} {% if perms.dcim.change_interface %}
<a href="{% url 'virtualization:interface_edit' pk=iface.pk %}" class="btn btn-info btn-xs" title="Edit interface"> <a href="{% url 'virtualization:interface_edit' pk=iface.pk %}" class="btn btn-info btn-xs" title="Edit interface">
<i class="glyphicon glyphicon-pencil" aria-hidden="true"></i> <i class="glyphicon glyphicon-pencil" aria-hidden="true"></i>
@ -30,7 +35,7 @@
{% if selectable and perms.dcim.change_interface or perms.dcim.delete_interface %} {% if selectable and perms.dcim.change_interface or perms.dcim.delete_interface %}
<td></td> <td></td>
{% endif %} {% endif %}
<td colspan="3"> <td>
<a href="{% url 'ipam:ipaddress' pk=ip.pk %}">{{ ip }}</a> <a href="{% url 'ipam:ipaddress' pk=ip.pk %}">{{ ip }}</a>
{% if ip.description %} {% if ip.description %}
<i class="fa fa-fw fa-comment-o" title="{{ ip.description }}"></i> <i class="fa fa-fw fa-comment-o" title="{{ ip.description }}"></i>

View File

@ -79,6 +79,36 @@
{% endif %} {% endif %}
</td> </td>
</tr> </tr>
<tr>
<td>Primary IPv4</td>
<td>
{% if vm.primary_ip4 %}
<a href="{% url 'ipam:ipaddress' pk=vm.primary_ip4.pk %}">{{ vm.primary_ip4.address.ip }}</a>
{% if vm.primary_ip4.nat_inside %}
<span>(NAT for {{ vm.primary_ip4.nat_inside.address.ip }})</span>
{% elif vm.primary_ip4.nat_outside %}
<span>(NAT: {{ vm.primary_ip4.nat_outside.address.ip }})</span>
{% endif %}
{% else %}
<span class="text-muted">N/A</span>
{% endif %}
</td>
</tr>
<tr>
<td>Primary IPv6</td>
<td>
{% if vm.primary_ip6 %}
<a href="{% url 'ipam:ipaddress' pk=vm.primary_ip6.pk %}">{{ vm.primary_ip6.address.ip }}</a>
{% if vm.primary_ip6.nat_inside %}
<span>(NAT for {{ vm.primary_ip6.nat_inside.address.ip }})</span>
{% elif vm.primary_ip6.nat_outside %}
<span>(NAT: {{ vm.primary_ip6.nat_outside.address.ip }})</span>
{% endif %}
{% else %}
<span class="text-muted">N/A</span>
{% endif %}
</td>
</tr>
</table> </table>
</div> </div>
<div class="panel panel-default"> <div class="panel panel-default">