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): class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldModelForm):
# TODO: Restore ability to select assigned object when editing IPAddress device = DynamicModelChoiceField(
# interface = forms.ModelChoiceField( queryset=Device.objects.all(),
# queryset=Interface.objects.all(), required=False,
# 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( vrf = DynamicModelChoiceField(
queryset=VRF.objects.all(), queryset=VRF.objects.all(),
required=False, required=False,
@ -611,67 +633,68 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldModel
# 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.nat_inside and instance.nat_inside.device is not None: if instance:
initial['nat_site'] = instance.nat_inside.device.site if type(instance.assigned_object) is Interface:
initial['nat_rack'] = instance.nat_inside.device.rack initial['device'] = instance.assigned_object.device
initial['nat_device'] = instance.nat_inside.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 kwargs['initial'] = initial
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.fields['vrf'].empty_label = 'Global' self.fields['vrf'].empty_label = 'Global'
# # Limit interface selections to those belonging to the parent device/VM # Initialize primary_for_parent if IP address is already assigned
# if self.instance and self.instance.interface: if self.instance.pk and self.instance.assigned_object:
# self.fields['interface'].queryset = Interface.objects.filter( parent = self.instance.assigned_object.parent
# device=self.instance.interface.device, virtual_machine=self.instance.interface.virtual_machine if (
# ).prefetch_related( self.instance.address.version == 4 and parent.primary_ip4_id == self.instance.pk or
# 'device__primary_ip4', self.instance.address.version == 6 and parent.primary_ip6_id == self.instance.pk
# 'device__primary_ip6', ):
# 'virtual_machine__primary_ip4', self.initial['primary_for_parent'] = True
# '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
def clean(self): def clean(self):
super().clean() 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. # 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( self.add_error(
'primary_for_parent', "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):
# 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) ipaddress = super().save(*args, **kwargs)
# Assign/clear this IPAddress as the primary for the associated Device/VirtualMachine. # Assign/clear this IPAddress as the primary for the associated Device/VirtualMachine.
if self.cleaned_data['primary_for_parent']: if interface and self.cleaned_data['primary_for_parent']:
parent = self.cleaned_data['interface'].parent
if ipaddress.address.version == 4: if ipaddress.address.version == 4:
parent.primary_ip4 = ipaddress interface.parent.primary_ip4 = ipaddress
else: else:
parent.primary_ip6 = ipaddress interface.primary_ip6 = ipaddress
parent.save() interface.parent.save()
# elif self.cleaned_data['interface']: elif interface and ipaddress.address.version == 4 and interface.parent.primary_ip4 == ipaddress:
# parent = self.cleaned_data['interface'].parent interface.parent.primary_ip4 = None
# if ipaddress.address.version == 4 and parent.primary_ip4 == ipaddress: interface.parent.save()
# parent.primary_ip4 = None elif interface and ipaddress.address.version == 6 and interface.parent.primary_ip6 == ipaddress:
# parent.save() interface.parent.primary_ip4 = None
# elif ipaddress.address.version == 6 and parent.primary_ip6 == ipaddress: interface.parent.save()
# parent.primary_ip6 = None
# parent.save()
return ipaddress return ipaddress

View File

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

View File

@ -28,32 +28,30 @@
{% render_field form.tenant %} {% render_field form.tenant %}
</div> </div>
</div> </div>
{% if obj.assigned_object %} <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 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> </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 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">