Closes #7769: Enable assignment of IP addresses to an existing FHRP group

This commit is contained in:
jeremystretch 2021-11-11 14:05:35 -05:00
parent 34f24de3e4
commit 83b2102705
6 changed files with 63 additions and 49 deletions

View File

@ -3,6 +3,7 @@
### Enhancements
* [#7619](https://github.com/netbox-community/netbox/issues/7619) - Permit custom validation rules to be defined as plain data or dotted path to class
* [#7769](https://github.com/netbox-community/netbox/issues/7769) - Enable assignment of IP addresses to an existing FHRP group
* [#7775](https://github.com/netbox-community/netbox/issues/7775) - Enable dynamic config for `CHANGELOG_RETENTION`, `CUSTOM_VALIDATORS`, and `GRAPHQL_ENABLED`
### Bug Fixes

View File

@ -321,6 +321,11 @@ class IPAddressForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
'virtual_machine_id': '$virtual_machine'
}
)
fhrpgroup = DynamicModelChoiceField(
queryset=FHRPGroup.objects.all(),
required=False,
label='FHRP Group'
)
vrf = DynamicModelChoiceField(
queryset=VRF.objects.all(),
required=False,
@ -428,6 +433,8 @@ class IPAddressForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
initial['interface'] = instance.assigned_object
elif type(instance.assigned_object) is VMInterface:
initial['vminterface'] = instance.assigned_object
elif type(instance.assigned_object) is FHRPGroup:
initial['fhrpgroup'] = instance.assigned_object
if instance.nat_inside:
nat_inside_parent = instance.nat_inside.assigned_object
if type(nat_inside_parent) is Interface:
@ -454,10 +461,13 @@ class IPAddressForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
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")
self.instance.assigned_object = self.cleaned_data.get('interface') or self.cleaned_data.get('vminterface')
# Handle object assignment
if self.cleaned_data['interface']:
self.instance.assigned_object = self.cleaned_data['interface']
elif self.cleaned_data['vminterface']:
self.instance.assigned_object = self.cleaned_data['vminterface']
elif self.cleaned_data['fhrpgroup']:
self.instance.assigned_object = self.cleaned_data['fhrpgroup']
# Primary IP assignment is only available if an interface has been assigned.
interface = self.cleaned_data.get('interface') or self.cleaned_data.get('vminterface')
@ -471,7 +481,7 @@ class IPAddressForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
# Assign/clear this IPAddress as the primary for the associated Device/VirtualMachine.
interface = self.instance.assigned_object
if interface:
if type(interface) in (Interface, VMInterface):
parent = interface.parent_object
if self.cleaned_data['primary_for_parent']:
if ipaddress.address.version == 4:

View File

@ -47,7 +47,7 @@ class FHRPGroup(PrimaryModel):
to='ipam.IPAddress',
content_type_field='assigned_object_type',
object_id_field='assigned_object_id',
related_query_name='fhrp_group'
related_query_name='fhrpgroup'
)
objects = RestrictedQuerySet.as_manager()

View File

@ -739,6 +739,12 @@ class IPAddressEditView(generic.ObjectEditView):
except (ValueError, VMInterface.DoesNotExist):
pass
elif 'fhrpgroup' in request.GET:
try:
obj.assigned_object = FHRPGroup.objects.get(pk=request.GET['fhrpgroup'])
except (ValueError, FHRPGroup.DoesNotExist):
pass
return obj

View File

@ -68,6 +68,13 @@
<div class="text-muted">None</div>
{% endif %}
</div>
{% if perms.ipam.add_ipaddress %}
<div class="card-footer text-end">
<a href="{% url 'ipam:ipaddress_add' %}?fhrpgroup={{ object.pk }}&return_url={{ object.get_absolute_url }}" class="btn btn-sm btn-primary">
<i class="mdi mdi-plus-thick" aria-hidden="true"></i> Add IP Address
</a>
</div>
{% endif %}
</div>
<div class="card">
<h5 class="card-header">Members</h5>

View File

@ -33,51 +33,41 @@
<div class="row mb-2">
<h5 class="offset-sm-3">Interface Assignment</h5>
</div>
{% with vm_tab_active=form.initial.vminterface %}
<div class="row mb-2">
<div class="offset-sm-3">
<ul class="nav nav-pills" role="tablist">
<li role="presentation" class="nav-item">
<button
role="tab"
type="button"
id="device_tab"
data-bs-toggle="tab"
aria-controls="device"
data-bs-target="#device"
class="nav-link {% if not vm_tab_active %}active{% endif %}"
>
Device
</button>
</li>
<li role="presentation" class="nav-item">
<button
role="tab"
type="button"
id="vm_tab"
data-bs-toggle="tab"
aria-controls="vm"
data-bs-target="#vm"
class="nav-link {% if vm_tab_active %}active{% endif %}"
>
Virtual Machine
</button>
</li>
</ul>
</div>
<div class="row mb-2">
<div class="offset-sm-3">
<ul class="nav nav-pills" role="tablist">
<li role="presentation" class="nav-item">
<button role="tab" type="button" id="device_tab" data-bs-toggle="tab" aria-controls="device" data-bs-target="#device" class="nav-link {% if not form.initial.vminterface and not form.initial.fhrpgroup %}active{% endif %}">
Device
</button>
</li>
<li role="presentation" class="nav-item">
<button role="tab" type="button" id="vm_tab" data-bs-toggle="tab" aria-controls="vm" data-bs-target="#vm" class="nav-link {% if form.initial.vminterface %}active{% endif %}">
Virtual Machine
</button>
</li>
<li role="presentation" class="nav-item">
<button role="tab" type="button" id="fhrpgroup_tab" data-bs-toggle="tab" aria-controls="fhrpgroup" data-bs-target="#fhrpgroup" class="nav-link {% if form.initial.fhrpgroup %}active{% endif %}">
FHRP Group
</button>
</li>
</ul>
</div>
<div class="tab-content p-0 border-0">
<div class="tab-pane {% if not vm_tab_active %}active{% endif %}" id="device" role="tabpanel" aria-labeled-by="device_tab">
{% render_field form.device %}
{% render_field form.interface %}
</div>
<div class="tab-pane {% if vm_tab_active %}active{% endif %}" id="vm" role="tabpanel" aria-labeled-by="vm_tab">
{% render_field form.virtual_machine %}
{% render_field form.vminterface %}
</div>
{% render_field form.primary_for_parent %}
</div>
<div class="tab-content p-0 border-0">
<div class="tab-pane {% if not form.initial.vminterface and not form.initial.fhrpgroup %}active{% endif %}" id="device" role="tabpanel" aria-labeled-by="device_tab">
{% render_field form.device %}
{% render_field form.interface %}
</div>
{% endwith %}
<div class="tab-pane {% if form.initial.vminterface %}active{% endif %}" id="vm" role="tabpanel" aria-labeled-by="vm_tab">
{% render_field form.virtual_machine %}
{% render_field form.vminterface %}
</div>
<div class="tab-pane {% if form.initial.fhrpgroup %}active{% endif %}" id="fhrpgroup" role="tabpanel" aria-labeled-by="fhrpgroup_tab">
{% render_field form.fhrpgroup %}
</div>
{% render_field form.primary_for_parent %}
</div>
</div>
<div class="field-group my-5">