소스 검색

Closes #18045: Enable adding a new MAC to an interface via quick add (#18200)

* Closes #18045: Enable adding a new MAC to an interface via quick add

* Misc cleanup
Jeremy Stretch 1 년 전
부모
커밋
aa56b99566

+ 0 - 8
netbox/dcim/forms/common.py

@@ -3,9 +3,7 @@ from django.utils.translation import gettext_lazy as _
 
 
 from dcim.choices import *
 from dcim.choices import *
 from dcim.constants import *
 from dcim.constants import *
-from dcim.models import MACAddress
 from utilities.forms import get_field_value
 from utilities.forms import get_field_value
-from utilities.forms.fields import DynamicModelChoiceField
 
 
 __all__ = (
 __all__ = (
     'InterfaceCommonForm',
     'InterfaceCommonForm',
@@ -20,12 +18,6 @@ class InterfaceCommonForm(forms.Form):
         max_value=INTERFACE_MTU_MAX,
         max_value=INTERFACE_MTU_MAX,
         label=_('MTU')
         label=_('MTU')
     )
     )
-    primary_mac_address = DynamicModelChoiceField(
-        queryset=MACAddress.objects.all(),
-        label=_('Primary MAC address'),
-        required=False,
-        quick_add=True
-    )
 
 
     def __init__(self, *args, **kwargs):
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
         super().__init__(*args, **kwargs)

+ 7 - 0
netbox/dcim/forms/model_forms.py

@@ -1410,6 +1410,13 @@ class InterfaceForm(InterfaceCommonForm, ModularDeviceComponentForm):
         required=False,
         required=False,
         label=_('VRF')
         label=_('VRF')
     )
     )
+    primary_mac_address = DynamicModelChoiceField(
+        queryset=MACAddress.objects.all(),
+        label=_('Primary MAC address'),
+        required=False,
+        quick_add=True,
+        quick_add_params={'interface': '$pk'}
+    )
     wwn = forms.CharField(
     wwn = forms.CharField(
         empty_value=None,
         empty_value=None,
         required=False,
         required=False,

+ 20 - 6
netbox/utilities/forms/fields/dynamic.py

@@ -68,6 +68,8 @@ class DynamicModelChoiceMixin:
         selector: Include an advanced object selection widget to assist the user in identifying the desired object
         selector: Include an advanced object selection widget to assist the user in identifying the desired object
         quick_add: Include a widget to quickly create a new related object for assignment. NOTE: Nested usage of
         quick_add: Include a widget to quickly create a new related object for assignment. NOTE: Nested usage of
             quick-add fields is not currently supported.
             quick-add fields is not currently supported.
+        quick_add_params: A dictionary of initial data to include when launching the quick-add form (optional). The
+            token string "$pk" will be replaced with the primary key of the form's instance, if any.
 
 
     Context keys:
     Context keys:
         value: The name of the attribute which contains the option's value (default: 'id')
         value: The name of the attribute which contains the option's value (default: 'id')
@@ -93,6 +95,7 @@ class DynamicModelChoiceMixin:
             context=None,
             context=None,
             selector=False,
             selector=False,
             quick_add=False,
             quick_add=False,
+            quick_add_params=None,
             **kwargs
             **kwargs
     ):
     ):
         self.model = queryset.model
         self.model = queryset.model
@@ -103,6 +106,7 @@ class DynamicModelChoiceMixin:
         self.context = context or {}
         self.context = context or {}
         self.selector = selector
         self.selector = selector
         self.quick_add = quick_add
         self.quick_add = quick_add
+        self.quick_add_params = quick_add_params or {}
 
 
         super().__init__(queryset, **kwargs)
         super().__init__(queryset, **kwargs)
 
 
@@ -125,12 +129,6 @@ class DynamicModelChoiceMixin:
         if self.selector:
         if self.selector:
             attrs['selector'] = self.model._meta.label_lower
             attrs['selector'] = self.model._meta.label_lower
 
 
-        # Include quick add?
-        if self.quick_add:
-            app_label = self.model._meta.app_label
-            model_name = self.model._meta.model_name
-            attrs['quick_add'] = reverse_lazy(f'{app_label}:{model_name}_add')
-
         return attrs
         return attrs
 
 
     def get_bound_field(self, form, field_name):
     def get_bound_field(self, form, field_name):
@@ -171,6 +169,22 @@ class DynamicModelChoiceMixin:
             viewname = get_viewname(self.queryset.model, action='list', rest_api=True)
             viewname = get_viewname(self.queryset.model, action='list', rest_api=True)
             widget.attrs['data-url'] = reverse(viewname)
             widget.attrs['data-url'] = reverse(viewname)
 
 
+        # Include quick add?
+        if self.quick_add:
+            app_label = self.model._meta.app_label
+            model_name = self.model._meta.model_name
+            widget.quick_add_context = {
+                'url': reverse_lazy(f'{app_label}:{model_name}_add'),
+                'params': {},
+            }
+            for k, v in self.quick_add_params.items():
+                if v == '$pk':
+                    # Replace "$pk" token with the primary key of the form's instance (if any)
+                    if getattr(form.instance, 'pk', None):
+                        widget.quick_add_context['params'][k] = form.instance.pk
+                else:
+                    widget.quick_add_context['params'][k] = v
+
         return bound_field
         return bound_field
 
 
 
 

+ 9 - 0
netbox/utilities/forms/widgets/apiselect.py

@@ -22,6 +22,15 @@ class APISelect(forms.Select):
     dynamic_params: Dict[str, str]
     dynamic_params: Dict[str, str]
     static_params: Dict[str, List[str]]
     static_params: Dict[str, List[str]]
 
 
+    def get_context(self, name, value, attrs):
+        context = super().get_context(name, value, attrs)
+
+        # Add quick-add context data, if enabled for the widget
+        if hasattr(self, 'quick_add_context'):
+            context['quick_add'] = self.quick_add_context
+
+        return context
+
     def __init__(self, api_url=None, full=False, *args, **kwargs):
     def __init__(self, api_url=None, full=False, *args, **kwargs):
         super().__init__(*args, **kwargs)
         super().__init__(*args, **kwargs)
 
 

+ 2 - 2
netbox/utilities/templates/widgets/apiselect.html

@@ -15,7 +15,7 @@
       <i class="mdi mdi-database-search-outline"></i>
       <i class="mdi mdi-database-search-outline"></i>
     </button>
     </button>
   {% endif %}
   {% endif %}
-  {% if widget.attrs.quick_add and not widget.attrs.disabled %}
+  {% if quick_add and not widget.attrs.disabled %}
     {# Opens the quick add modal #}
     {# Opens the quick add modal #}
     <button
     <button
       type="button"
       type="button"
@@ -23,7 +23,7 @@
       class="btn btn-outline-secondary ms-1"
       class="btn btn-outline-secondary ms-1"
       data-bs-toggle="modal"
       data-bs-toggle="modal"
       data-bs-target="#htmx-modal"
       data-bs-target="#htmx-modal"
-      hx-get="{{ widget.attrs.quick_add }}?_quickadd=True&target={{ widget.attrs.id }}"
+      hx-get="{{ quick_add.url }}?_quickadd=True&target={{ widget.attrs.id }}{% for k, v in quick_add.params.items %}&{{ k }}={{ v }}{% endfor %}"
       hx-target="#htmx-modal-content"
       hx-target="#htmx-modal-content"
     >
     >
       <i class="mdi mdi-plus-circle"></i>
       <i class="mdi mdi-plus-circle"></i>

+ 8 - 1
netbox/virtualization/forms/model_forms.py

@@ -5,7 +5,7 @@ from django.utils.translation import gettext_lazy as _
 
 
 from dcim.forms.common import InterfaceCommonForm
 from dcim.forms.common import InterfaceCommonForm
 from dcim.forms.mixins import ScopedForm
 from dcim.forms.mixins import ScopedForm
-from dcim.models import Device, DeviceRole, Platform, Rack, Region, Site, SiteGroup
+from dcim.models import Device, DeviceRole, MACAddress, Platform, Rack, Region, Site, SiteGroup
 from extras.models import ConfigTemplate
 from extras.models import ConfigTemplate
 from ipam.choices import VLANQinQRoleChoices
 from ipam.choices import VLANQinQRoleChoices
 from ipam.models import IPAddress, VLAN, VLANGroup, VLANTranslationPolicy, VRF
 from ipam.models import IPAddress, VLAN, VLANGroup, VLANTranslationPolicy, VRF
@@ -298,6 +298,13 @@ class VMComponentForm(NetBoxModelForm):
 
 
 
 
 class VMInterfaceForm(InterfaceCommonForm, VMComponentForm):
 class VMInterfaceForm(InterfaceCommonForm, VMComponentForm):
+    primary_mac_address = DynamicModelChoiceField(
+        queryset=MACAddress.objects.all(),
+        label=_('Primary MAC address'),
+        required=False,
+        quick_add=True,
+        quick_add_params={'vminterface': '$pk'}
+    )
     parent = DynamicModelChoiceField(
     parent = DynamicModelChoiceField(
         queryset=VMInterface.objects.all(),
         queryset=VMInterface.objects.all(),
         required=False,
         required=False,