Просмотр исходного кода

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

jeremystretch 4 лет назад
Родитель
Сommit
83b2102705

+ 1 - 0
docs/release-notes/version-3.1.md

@@ -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

+ 15 - 5
netbox/ipam/forms/models.py

@@ -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:

+ 1 - 1
netbox/ipam/models/fhrp.py

@@ -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()

+ 6 - 0
netbox/ipam/views.py

@@ -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
 
 

+ 7 - 0
netbox/templates/ipam/fhrpgroup.html

@@ -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>

+ 33 - 43
netbox/templates/ipam/ipaddress_edit.html

@@ -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>
+        <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>
-      {% endwith %}
+        <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">