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
This commit is contained in:
Jeremy Stretch 2024-12-16 10:57:09 -05:00 committed by GitHub
parent 39ca3ce571
commit aa56b99566
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 46 additions and 17 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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