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

Restore ability to assign interface when editing an IPAddress

This commit is contained in:
Jeremy Stretch
2020-06-24 11:30:28 -04:00
parent 99c72c78c1
commit 4d2c75a824
3 changed files with 105 additions and 85 deletions

View File

@ -522,11 +522,33 @@ class PrefixFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm)
#
class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldModelForm):
# TODO: Restore ability to select assigned object when editing IPAddress
# interface = forms.ModelChoiceField(
# queryset=Interface.objects.all(),
# required=False
# )
device = DynamicModelChoiceField(
queryset=Device.objects.all(),
required=False,
widget=APISelect(
filter_for={
'interface': 'device_id'
}
)
)
interface = DynamicModelChoiceField(
queryset=Interface.objects.all(),
required=False
)
virtual_machine = DynamicModelChoiceField(
queryset=VirtualMachine.objects.all(),
required=False,
widget=APISelect(
filter_for={
'vminterface': 'virtual_machine_id'
}
)
)
vminterface = DynamicModelChoiceField(
queryset=VMInterface.objects.all(),
required=False,
label='Interface'
)
vrf = DynamicModelChoiceField(
queryset=VRF.objects.all(),
required=False,
@ -611,67 +633,68 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldModel
# Initialize helper selectors
instance = kwargs.get('instance')
initial = kwargs.get('initial', {}).copy()
if instance and instance.nat_inside and instance.nat_inside.device is not None:
initial['nat_site'] = instance.nat_inside.device.site
initial['nat_rack'] = instance.nat_inside.device.rack
initial['nat_device'] = instance.nat_inside.device
if instance:
if type(instance.assigned_object) is Interface:
initial['device'] = instance.assigned_object.device
initial['interface'] = instance.assigned_object
elif type(instance.assigned_object) is VMInterface:
initial['virtual_machine'] = instance.assigned_object.virtual_machine
initial['vminterface'] = instance.assigned_object
if instance.nat_inside and instance.nat_inside.device is not None:
initial['nat_site'] = instance.nat_inside.device.site
initial['nat_rack'] = instance.nat_inside.device.rack
initial['nat_device'] = instance.nat_inside.device
kwargs['initial'] = initial
super().__init__(*args, **kwargs)
self.fields['vrf'].empty_label = 'Global'
# # Limit interface selections to those belonging to the parent device/VM
# if self.instance and self.instance.interface:
# self.fields['interface'].queryset = Interface.objects.filter(
# device=self.instance.interface.device, virtual_machine=self.instance.interface.virtual_machine
# ).prefetch_related(
# 'device__primary_ip4',
# 'device__primary_ip6',
# 'virtual_machine__primary_ip4',
# 'virtual_machine__primary_ip6',
# ) # We prefetch the primary address fields to ensure cache invalidation does not balk on the save()
# 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 (
# self.instance.address.version == 4 and parent.primary_ip4_id == self.instance.pk or
# self.instance.address.version == 6 and parent.primary_ip6_id == self.instance.pk
# ):
# self.initial['primary_for_parent'] = True
# Initialize primary_for_parent if IP address is already assigned
if self.instance.pk and self.instance.assigned_object:
parent = self.instance.assigned_object.parent
if (
self.instance.address.version == 4 and parent.primary_ip4_id == self.instance.pk or
self.instance.address.version == 6 and parent.primary_ip6_id == self.instance.pk
):
self.initial['primary_for_parent'] = True
def clean(self):
super().clean()
# Cannot select both a device interface and a VM interface
if self.cleaned_data.get('interface') and self.cleaned_data.get('vminterface'):
raise forms.ValidationError("Cannot select both a device interface and a virtual machine interface")
# Primary IP assignment is only available if an interface has been assigned.
if self.cleaned_data.get('primary_for_parent') and not self.cleaned_data.get('interface'):
interface = self.cleaned_data.get('interface') or self.cleaned_data.get('vminterface')
if self.cleaned_data.get('primary_for_parent') and not interface:
self.add_error(
'primary_for_parent', "Only IP addresses assigned to an interface can be designated as primary IPs."
)
def save(self, *args, **kwargs):
# Set assigned object
interface = self.cleaned_data.get('interface') or self.cleaned_data.get('vminterface')
if interface:
self.instance.assigned_object = interface
ipaddress = super().save(*args, **kwargs)
# Assign/clear this IPAddress as the primary for the associated Device/VirtualMachine.
if self.cleaned_data['primary_for_parent']:
parent = self.cleaned_data['interface'].parent
if interface and self.cleaned_data['primary_for_parent']:
if ipaddress.address.version == 4:
parent.primary_ip4 = ipaddress
interface.parent.primary_ip4 = ipaddress
else:
parent.primary_ip6 = ipaddress
parent.save()
# elif self.cleaned_data['interface']:
# parent = self.cleaned_data['interface'].parent
# if ipaddress.address.version == 4 and parent.primary_ip4 == ipaddress:
# parent.primary_ip4 = None
# parent.save()
# elif ipaddress.address.version == 6 and parent.primary_ip6 == ipaddress:
# parent.primary_ip6 = None
# parent.save()
interface.primary_ip6 = ipaddress
interface.parent.save()
elif interface and ipaddress.address.version == 4 and interface.parent.primary_ip4 == ipaddress:
interface.parent.primary_ip4 = None
interface.parent.save()
elif interface and ipaddress.address.version == 6 and interface.parent.primary_ip6 == ipaddress:
interface.parent.primary_ip4 = None
interface.parent.save()
return ipaddress

View File

@ -15,7 +15,7 @@ from extras.utils import extras_features
from utilities.models import ChangeLoggedModel
from utilities.querysets import RestrictedQuerySet
from utilities.utils import serialize_object
from virtualization.models import VirtualMachine
from virtualization.models import VirtualMachine, VMInterface
from .choices import *
from .constants import *
from .fields import IPNetworkField, IPAddressField
@ -717,32 +717,31 @@ class IPAddress(ChangeLoggedModel, CustomFieldModel):
)
})
if self.pk:
# Check for primary IP assignment that doesn't match the assigned device/VM
# Check for primary IP assignment that doesn't match the assigned device/VM
if self.pk and type(self.assigned_object) is Interface:
device = Device.objects.filter(Q(primary_ip4=self) | Q(primary_ip6=self)).first()
if device:
if self.interface is None:
if self.assigned_object is None:
raise ValidationError({
'interface': "IP address is primary for device {} but not assigned".format(device)
'interface': f"IP address is primary for device {device} but not assigned to an interface"
})
elif (device.primary_ip4 == self or device.primary_ip6 == self) and self.interface.device != device:
elif self.assigned_object.device != device:
raise ValidationError({
'interface': "IP address is primary for device {} but assigned to {} ({})".format(
device, self.interface.device, self.interface
)
'interface': f"IP address is primary for device {device} but assigned to "
f"{self.assigned_object.device} ({self.assigned_object})"
})
elif self.pk and type(self.assigned_object) is VMInterface:
vm = VirtualMachine.objects.filter(Q(primary_ip4=self) | Q(primary_ip6=self)).first()
if vm:
if self.interface is None:
if self.assigned_object is None:
raise ValidationError({
'interface': "IP address is primary for virtual machine {} but not assigned".format(vm)
'vminterface': f"IP address is primary for virtual machine {vm} but not assigned to an "
f"interface"
})
elif (vm.primary_ip4 == self or vm.primary_ip6 == self) and self.interface.virtual_machine != vm:
elif self.interface.virtual_machine != vm:
raise ValidationError({
'interface': "IP address is primary for virtual machine {} but assigned to {} ({})".format(
vm, self.interface.virtual_machine, self.interface
)
'vminterface': f"IP address is primary for virtual machine {vm} but assigned to "
f"{self.assigned_object.virtual_machine} ({self.assigned_object})"
})
def save(self, *args, **kwargs):

View File

@ -28,32 +28,30 @@
{% render_field form.tenant %}
</div>
</div>
{% if obj.assigned_object %}
<div class="panel panel-default">
<div class="panel-heading">
<strong>Interface Assignment</strong>
</div>
<div class="panel-body">
<div class="form-group">
<label class="col-md-3 control-label">{{ obj.assigned_object.parent|meta:"verbose_name"|bettertitle }}</label>
<div class="col-md-9">
<p class="form-control-static">
<a href="{{ obj.assigned_object.parent.get_absolute_url }}">{{ obj.assigned_object.parent }}</a>
</p>
</div>
</div>
<div class="form-group">
<label class="col-md-3 control-label">Interface</label>
<div class="col-md-9">
<p class="form-control-static">
<a href="{{ obj.assigned_object.get_absolute_url }}">{{ obj.assigned_object }}</a>
</p>
</div>
</div>
{% render_field form.primary_for_parent %}
</div>
<div class="panel panel-default">
<div class="panel-heading">
<strong>Interface Assignment</strong>
</div>
{% endif %}
<div class="panel-body">
{% with vm_tab_active=obj.vminterface.exists %}
<ul class="nav nav-tabs" role="tablist">
<li role="presentation"{% if not vm_tab_active %} class="active"{% endif %}><a href="#device" role="tab" data-toggle="tab">Device</a></li>
<li role="presentation"{% if vm_tab_active %} class="active"{% endif %}><a href="#virtualmachine" role="tab" data-toggle="tab">Virtual Machine</a></li>
</ul>
<div class="tab-content">
<div class="tab-pane{% if not vm_tab_active %} active{% endif %}" id="device">
{% render_field form.device %}
{% render_field form.interface %}
</div>
<div class="tab-pane{% if vm_tab_active %} active{% endif %}" id="virtualmachine">
{% render_field form.virtual_machine %}
{% render_field form.vminterface %}
</div>
</div>
{% endwith %}
{% render_field form.primary_for_parent %}
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading"><strong>NAT IP (Inside)</strong></div>
<div class="panel-body">