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:
@ -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
|
||||||
|
@ -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:
|
||||||
|
@ -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 %}
|
||||||
—
|
—
|
||||||
{% 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',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -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'
|
||||||
|
@ -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 %}
|
||||||
|
@ -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>
|
||||||
|
@ -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">
|
||||||
|
@ -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>
|
||||||
|
@ -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">
|
||||||
|
Reference in New Issue
Block a user