Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
wants to merge 2 commits into
base: feature
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 0 additions & 8 deletions netbox/dcim/forms/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@

from dcim.choices import *
from dcim.constants import *
from dcim.models import MACAddress
from utilities.forms import get_field_value
from utilities.forms.fields import DynamicModelChoiceField

__all__ = (
'InterfaceCommonForm',
Expand All @@ -20,12 +18,6 @@ class InterfaceCommonForm(forms.Form):
max_value=INTERFACE_MTU_MAX,
label=_('MTU')
)
primary_mac_address = DynamicModelChoiceField(
queryset=MACAddress.objects.all(),
label=_('Primary MAC address'),
required=False,
quick_add=True
)

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
Expand Down
7 changes: 7 additions & 0 deletions netbox/dcim/forms/model_forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -1410,6 +1410,13 @@ class InterfaceForm(InterfaceCommonForm, ModularDeviceComponentForm):
required=False,
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(
empty_value=None,
required=False,
Expand Down
26 changes: 20 additions & 6 deletions netbox/utilities/forms/fields/dynamic.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ class DynamicModelChoiceMixin:
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 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:
value: The name of the attribute which contains the option's value (default: 'id')
Expand All @@ -93,6 +95,7 @@ def __init__(
context=None,
selector=False,
quick_add=False,
quick_add_params=None,
**kwargs
):
self.model = queryset.model
Expand All @@ -103,6 +106,7 @@ def __init__(
self.context = context or {}
self.selector = selector
self.quick_add = quick_add
self.quick_add_params = quick_add_params or {}

super().__init__(queryset, **kwargs)

Expand All @@ -125,12 +129,6 @@ def widget_attrs(self, widget):
if self.selector:
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

def get_bound_field(self, form, field_name):
Expand Down Expand Up @@ -171,6 +169,22 @@ def get_bound_field(self, form, field_name):
viewname = get_viewname(self.queryset.model, action='list', rest_api=True)
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


Expand Down
9 changes: 9 additions & 0 deletions netbox/utilities/forms/widgets/apiselect.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,15 @@ class APISelect(forms.Select):
dynamic_params: Dict[str, 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):
super().__init__(*args, **kwargs)

Expand Down
4 changes: 2 additions & 2 deletions netbox/utilities/templates/widgets/apiselect.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,15 @@
<i class="mdi mdi-database-search-outline"></i>
</button>
{% endif %}
{% if widget.attrs.quick_add and not widget.attrs.disabled %}
{% if quick_add and not widget.attrs.disabled %}
{# Opens the quick add modal #}
<button
type="button"
title="{% trans "Quick add" %}"
class="btn btn-outline-secondary ms-1"
data-bs-toggle="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"
>
<i class="mdi mdi-plus-circle"></i>
Expand Down
9 changes: 8 additions & 1 deletion netbox/virtualization/forms/model_forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

from dcim.forms.common import InterfaceCommonForm
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 ipam.choices import VLANQinQRoleChoices
from ipam.models import IPAddress, VLAN, VLANGroup, VLANTranslationPolicy, VRF
Expand Down Expand Up @@ -298,6 +298,13 @@ def __init__(self, *args, **kwargs):


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(
queryset=VMInterface.objects.all(),
required=False,
Expand Down
Loading