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 ### 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 * [#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` * [#7775](https://github.com/netbox-community/netbox/issues/7775) - Enable dynamic config for `CHANGELOG_RETENTION`, `CUSTOM_VALIDATORS`, and `GRAPHQL_ENABLED`
### Bug Fixes ### Bug Fixes

View File

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

View File

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

View File

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

View File

@ -68,6 +68,13 @@
<div class="text-muted">None</div> <div class="text-muted">None</div>
{% endif %} {% endif %}
</div> </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>
<div class="card"> <div class="card">
<h5 class="card-header">Members</h5> <h5 class="card-header">Members</h5>

View File

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