diff --git a/docs/additional-features/custom-scripts.md b/docs/additional-features/custom-scripts.md
index f06856502..2416370e3 100644
--- a/docs/additional-features/custom-scripts.md
+++ b/docs/additional-features/custom-scripts.md
@@ -163,12 +163,46 @@ In the example above, selecting the choice labeled "North" will submit the value
### ObjectVar
-A NetBox object of a particular type, identified by the associated queryset.
+A particular object within NetBox. Each ObjectVar must specify a particular model, and allows the user to select one of the available instances. ObjectVar accepts several arguments, listed below.
-* `queryset` - The base [Django queryset](https://docs.djangoproject.com/en/stable/topics/db/queries/) for the model
+* `model` - The model class
+* `display_field` - The name of the REST API object field to display in the selection list (default: `'name'`)
+* `query_params` - A dictionary of query parameters to use when retrieving available options (optional)
+* `null_option` - A label representing a "null" or empty choice (optional)
-!!! warning
- Because the available options for this field are populated using the REST API, any filtering or exclusions performed on the specified queryset will not have any effect. While it is possible to influence the manner in which field options are populated using NetBox's `APISelect` widget, please note that this component is not officially supported and is planned to be replaced in a future release.
+The `display_field` argument is useful when referencing a model which does not have a `name` field. For example, when displaying a list of device types, you would likely use the `model` field:
+
+```python
+device_type = ObjectVar(
+ model=DeviceType,
+ display_field='model'
+)
+```
+
+To limit the selections available within the list, additional query parameters can be passed as the `query_params` dictionary. For example, to show only devices with an "active" status:
+
+```python
+device = ObjectVar(
+ model=Device,
+ query_params={
+ 'status': 'active'
+ }
+)
+```
+
+Multiple values can be specified by assigning a list to the dictionary key. It is also possible to reference the value of other fields in the form by prepending a dollar sign (`$`) to the variable's name.
+
+```python
+region = ObjectVar(
+ model=Region
+)
+site = ObjectVar(
+ model=Site,
+ query_params={
+ 'region_id': '$region'
+ }
+)
+```
### MultiObjectVar
@@ -207,9 +241,8 @@ These variables are presented as a web form to be completed by the user. Once su
from django.utils.text import slugify
from dcim.choices import DeviceStatusChoices, SiteStatusChoices
-from dcim.models import Device, DeviceRole, DeviceType, Site
+from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Site
from extras.scripts import *
-from utilities.forms import APISelect
class NewBranchScript(Script):
@@ -225,12 +258,17 @@ class NewBranchScript(Script):
switch_count = IntegerVar(
description="Number of access switches to create"
)
+ manufacturer = ObjectVar(
+ model=Manufacturer,
+ required=False
+ )
switch_model = ObjectVar(
description="Access switch model",
- queryset=DeviceType.objects.all(),
- widget=APISelect(
- display_field='model'
- )
+ model=DeviceType,
+ display_field='model',
+ query_params={
+ 'manufacturer_id': '$manufacturer'
+ }
)
def run(self, data, commit):
diff --git a/netbox/circuits/forms.py b/netbox/circuits/forms.py
index 341a7a9b7..5e5a88080 100644
--- a/netbox/circuits/forms.py
+++ b/netbox/circuits/forms.py
@@ -8,9 +8,9 @@ from extras.models import Tag
from tenancy.forms import TenancyFilterForm, TenancyForm
from tenancy.models import Tenant
from utilities.forms import (
- APISelectMultiple, add_blank_choice, BootstrapMixin, CommentField, CSVChoiceField, CSVModelChoiceField,
- CSVModelForm, DatePicker, DynamicModelChoiceField, DynamicModelMultipleChoiceField, SmallTextarea, SlugField,
- StaticSelect2, StaticSelect2Multiple, TagFilterField,
+ add_blank_choice, BootstrapMixin, CommentField, CSVChoiceField, CSVModelChoiceField, CSVModelForm, DatePicker,
+ DynamicModelChoiceField, DynamicModelMultipleChoiceField, SmallTextarea, SlugField, StaticSelect2,
+ StaticSelect2Multiple, TagFilterField,
)
from .choices import CircuitStatusChoices
from .models import Circuit, CircuitTermination, CircuitType, Provider
@@ -106,21 +106,15 @@ class ProviderFilterForm(BootstrapMixin, CustomFieldFilterForm):
region = DynamicModelMultipleChoiceField(
queryset=Region.objects.all(),
to_field_name='slug',
- required=False,
- widget=APISelectMultiple(
- value_field="slug",
- filter_for={
- 'site': 'region'
- }
- )
+ required=False
)
site = DynamicModelMultipleChoiceField(
queryset=Site.objects.all(),
to_field_name='slug',
required=False,
- widget=APISelectMultiple(
- value_field="slug",
- )
+ query_params={
+ 'region': '$region'
+ }
)
asn = forms.IntegerField(
required=False,
@@ -271,18 +265,12 @@ class CircuitFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm
type = DynamicModelMultipleChoiceField(
queryset=CircuitType.objects.all(),
to_field_name='slug',
- required=False,
- widget=APISelectMultiple(
- value_field="slug",
- )
+ required=False
)
provider = DynamicModelMultipleChoiceField(
queryset=Provider.objects.all(),
to_field_name='slug',
- required=False,
- widget=APISelectMultiple(
- value_field="slug",
- )
+ required=False
)
status = forms.MultipleChoiceField(
choices=CircuitStatusChoices,
@@ -292,21 +280,15 @@ class CircuitFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm
region = DynamicModelMultipleChoiceField(
queryset=Region.objects.all(),
to_field_name='slug',
- required=False,
- widget=APISelectMultiple(
- value_field="slug",
- filter_for={
- 'site': 'region'
- }
- )
+ required=False
)
site = DynamicModelMultipleChoiceField(
queryset=Site.objects.all(),
to_field_name='slug',
required=False,
- widget=APISelectMultiple(
- value_field="slug",
- )
+ query_params={
+ 'region': '$region'
+ }
)
commit_rate = forms.IntegerField(
required=False,
diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py
index febeeaa12..8af8df783 100644
--- a/netbox/dcim/forms.py
+++ b/netbox/dcim/forms.py
@@ -10,7 +10,7 @@ from netaddr import EUI
from netaddr.core import AddrFormatError
from timezone_field import TimeZoneFormField
-from circuits.models import Circuit, Provider
+from circuits.models import Circuit, CircuitTermination, Provider
from extras.forms import (
AddRemoveTagsForm, CustomFieldBulkEditForm, CustomFieldModelCSVForm, CustomFieldFilterForm, CustomFieldModelForm,
LocalConfigContextFilterForm,
@@ -21,7 +21,7 @@ from ipam.models import IPAddress, VLAN
from tenancy.forms import TenancyFilterForm, TenancyForm
from tenancy.models import Tenant, TenantGroup
from utilities.forms import (
- APISelect, APISelectMultiple, add_blank_choice, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect,
+ APISelect, add_blank_choice, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect,
ColorSelect, CommentField, CSVChoiceField, CSVModelChoiceField, CSVModelForm, DynamicModelChoiceField,
DynamicModelMultipleChoiceField, ExpandableNameField, form_from_model, JSONField, NumericArrayField, SelectWithPK,
SmallTextarea, SlugField, StaticSelect2, StaticSelect2Multiple, TagFilterField, BOOLEAN_WITH_BLANK_CHOICES,
@@ -68,29 +68,23 @@ class DeviceComponentFilterForm(BootstrapMixin, forms.Form):
region = DynamicModelMultipleChoiceField(
queryset=Region.objects.all(),
to_field_name='slug',
- required=False,
- widget=APISelectMultiple(
- value_field='slug',
- filter_for={
- 'site': 'region'
- }
- )
+ required=False
)
site = DynamicModelMultipleChoiceField(
queryset=Site.objects.all(),
to_field_name='slug',
required=False,
- widget=APISelectMultiple(
- value_field="slug",
- filter_for={
- 'device_id': 'site',
- }
- )
+ query_params={
+ 'region': '$region'
+ }
)
device_id = DynamicModelMultipleChoiceField(
queryset=Device.objects.all(),
required=False,
- label='Device'
+ label='Device',
+ query_params={
+ 'site': '$site'
+ }
)
@@ -348,10 +342,7 @@ class SiteFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
region = DynamicModelMultipleChoiceField(
queryset=Region.objects.all(),
to_field_name='slug',
- required=False,
- widget=APISelectMultiple(
- value_field="slug",
- )
+ required=False
)
tag = TagFilterField(model)
@@ -362,16 +353,14 @@ class SiteFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
class RackGroupForm(BootstrapMixin, forms.ModelForm):
site = DynamicModelChoiceField(
- queryset=Site.objects.all(),
- widget=APISelect(
- filter_for={
- 'parent': 'site_id',
- }
- )
+ queryset=Site.objects.all()
)
parent = DynamicModelChoiceField(
queryset=RackGroup.objects.all(),
- required=False
+ required=False,
+ query_params={
+ 'site_id': '$site'
+ }
)
slug = SlugField()
@@ -407,34 +396,24 @@ class RackGroupFilterForm(BootstrapMixin, forms.Form):
region = DynamicModelMultipleChoiceField(
queryset=Region.objects.all(),
to_field_name='slug',
- required=False,
- widget=APISelectMultiple(
- value_field="slug",
- filter_for={
- 'site': 'region',
- 'parent': 'region',
- }
- )
+ required=False
)
site = DynamicModelMultipleChoiceField(
queryset=Site.objects.all(),
to_field_name='slug',
required=False,
- widget=APISelectMultiple(
- value_field="slug",
- filter_for={
- 'parent': 'site',
- }
- )
+ query_params={
+ 'region': '$region'
+ }
)
parent = DynamicModelMultipleChoiceField(
queryset=RackGroup.objects.all(),
to_field_name='slug',
required=False,
- widget=APISelectMultiple(
- api_url="/api/dcim/rack-groups/",
- value_field="slug",
- )
+ query_params={
+ 'region': '$region',
+ 'site': '$site',
+ }
)
@@ -469,16 +448,14 @@ class RackRoleCSVForm(CSVModelForm):
class RackForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
site = DynamicModelChoiceField(
- queryset=Site.objects.all(),
- widget=APISelect(
- filter_for={
- 'group': 'site_id',
- }
- )
+ queryset=Site.objects.all()
)
group = DynamicModelChoiceField(
queryset=RackGroup.objects.all(),
- required=False
+ required=False,
+ query_params={
+ 'site_id': '$site'
+ }
)
role = DynamicModelChoiceField(
queryset=RackRole.objects.all(),
@@ -573,16 +550,14 @@ class RackBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditFor
)
site = DynamicModelChoiceField(
queryset=Site.objects.all(),
- required=False,
- widget=APISelect(
- filter_for={
- 'group': 'site_id',
- }
- )
+ required=False
)
group = DynamicModelChoiceField(
queryset=RackGroup.objects.all(),
- required=False
+ required=False,
+ query_params={
+ 'site_id': '$site'
+ }
)
tenant = DynamicModelChoiceField(
queryset=Tenant.objects.all(),
@@ -660,34 +635,24 @@ class RackFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
region = DynamicModelMultipleChoiceField(
queryset=Region.objects.all(),
to_field_name='slug',
- required=False,
- widget=APISelectMultiple(
- value_field="slug",
- filter_for={
- 'site': 'region'
- }
- )
+ required=False
)
site = DynamicModelMultipleChoiceField(
queryset=Site.objects.all(),
to_field_name='slug',
required=False,
- widget=APISelectMultiple(
- value_field="slug",
- filter_for={
- 'group_id': 'site'
- }
- )
+ query_params={
+ 'region': '$region'
+ }
)
group_id = DynamicModelMultipleChoiceField(
- queryset=RackGroup.objects.prefetch_related(
- 'site'
- ),
+ queryset=RackGroup.objects.all(),
required=False,
label='Rack group',
- widget=APISelectMultiple(
- null_option=True
- )
+ null_option='None',
+ query_params={
+ 'site': '$site'
+ }
)
status = forms.MultipleChoiceField(
choices=RackStatusChoices,
@@ -698,10 +663,7 @@ class RackFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
queryset=RackRole.objects.all(),
to_field_name='slug',
required=False,
- widget=APISelectMultiple(
- value_field="slug",
- null_option=True,
- )
+ null_option='None'
)
tag = TagFilterField(model)
@@ -716,18 +678,13 @@ class RackElevationFilterForm(RackFilterForm):
queryset=Rack.objects.all(),
label='Rack',
required=False,
- widget=APISelectMultiple(
- display_field='display_name',
- )
+ display_field='display_name',
+ query_params={
+ 'site': '$site',
+ 'group_id': '$group_id',
+ }
)
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
-
- # Filter the rack field based on the site and group
- self.fields['site'].widget.add_filter_for('id', 'site')
- self.fields['group_id'].widget.add_filter_for('id', 'group_id')
-
#
# Rack reservations
@@ -736,25 +693,22 @@ class RackElevationFilterForm(RackFilterForm):
class RackReservationForm(BootstrapMixin, TenancyForm, forms.ModelForm):
site = DynamicModelChoiceField(
queryset=Site.objects.all(),
- required=False,
- widget=APISelect(
- filter_for={
- 'rack_group': 'site_id',
- 'rack': 'site_id',
- }
- )
+ required=False
)
rack_group = DynamicModelChoiceField(
queryset=RackGroup.objects.all(),
required=False,
- widget=APISelect(
- filter_for={
- 'rack': 'group_id'
- }
- )
+ query_params={
+ 'site_id': '$site'
+ }
)
rack = DynamicModelChoiceField(
- queryset=Rack.objects.all()
+ queryset=Rack.objects.all(),
+ display_field='display_name',
+ query_params={
+ 'site_id': '$site',
+ 'group_id': 'rack',
+ }
)
units = NumericArrayField(
base_field=forms.IntegerField(),
@@ -863,18 +817,13 @@ class RackReservationFilterForm(BootstrapMixin, TenancyFilterForm):
site = DynamicModelMultipleChoiceField(
queryset=Site.objects.all(),
to_field_name='slug',
- required=False,
- widget=APISelectMultiple(
- value_field="slug",
- )
+ required=False
)
group_id = DynamicModelMultipleChoiceField(
queryset=RackGroup.objects.prefetch_related('site'),
required=False,
label='Rack group',
- widget=APISelectMultiple(
- null_option=True,
- )
+ null_option='None'
)
tag = TagFilterField(model)
@@ -974,10 +923,7 @@ class DeviceTypeFilterForm(BootstrapMixin, CustomFieldFilterForm):
manufacturer = DynamicModelMultipleChoiceField(
queryset=Manufacturer.objects.all(),
to_field_name='slug',
- required=False,
- widget=APISelectMultiple(
- value_field="slug",
- )
+ required=False
)
subdevice_role = forms.MultipleChoiceField(
choices=add_blank_choice(SubdeviceRoleChoices),
@@ -1039,18 +985,14 @@ class ComponentTemplateCreateForm(ComponentForm):
"""
manufacturer = DynamicModelChoiceField(
queryset=Manufacturer.objects.all(),
- required=False,
- widget=APISelect(
- filter_for={
- 'device_type': 'manufacturer_id'
- }
- )
+ required=False
)
device_type = DynamicModelChoiceField(
queryset=DeviceType.objects.all(),
- widget=APISelect(
- display_field='model'
- )
+ display_field='model',
+ query_params={
+ 'manufacturer_id': '$manufacturer'
+ }
)
description = forms.CharField(
required=False
@@ -1729,19 +1671,15 @@ class PlatformCSVForm(CSVModelForm):
class DeviceForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
site = DynamicModelChoiceField(
- queryset=Site.objects.all(),
- widget=APISelect(
- filter_for={
- 'rack': 'site_id'
- }
- )
+ queryset=Site.objects.all()
)
rack = DynamicModelChoiceField(
queryset=Rack.objects.all(),
required=False,
- widget=APISelect(
- display_field='display_name'
- )
+ display_field='display_name',
+ query_params={
+ 'site_id': '$site'
+ }
)
position = forms.TypedChoiceField(
required=False,
@@ -1749,24 +1687,22 @@ class DeviceForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
help_text="The lowest-numbered unit occupied by the device",
widget=APISelect(
api_url='/api/dcim/racks/{{rack}}/elevation/',
- disabled_indicator='device'
+ attrs={
+ 'disabled-indicator': 'device',
+ 'data-query-param-face': "[\"$face\"]",
+ }
)
)
manufacturer = DynamicModelChoiceField(
queryset=Manufacturer.objects.all(),
- required=False,
- widget=APISelect(
- filter_for={
- 'device_type': 'manufacturer_id',
- 'platform': 'manufacturer_id'
- }
- )
+ required=False
)
device_type = DynamicModelChoiceField(
queryset=DeviceType.objects.all(),
- widget=APISelect(
- display_field='model'
- )
+ display_field='model',
+ query_params={
+ 'manufacturer_id': '$manufacturer'
+ }
)
device_role = DynamicModelChoiceField(
queryset=DeviceRole.objects.all()
@@ -1774,27 +1710,21 @@ class DeviceForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
platform = DynamicModelChoiceField(
queryset=Platform.objects.all(),
required=False,
- widget=APISelect(
- additional_query_params={
- "manufacturer_id": "null"
- }
- )
+ query_params={
+ 'manufacturer_id': ['$manufacturer', 'null']
+ }
)
cluster_group = DynamicModelChoiceField(
queryset=ClusterGroup.objects.all(),
required=False,
- widget=APISelect(
- filter_for={
- 'cluster': 'group_id'
- },
- attrs={
- 'nullable': 'true'
- }
- )
+ null_option='None'
)
cluster = DynamicModelChoiceField(
queryset=Cluster.objects.all(),
- required=False
+ required=False,
+ query_params={
+ 'group_id': '$cluster_group'
+ }
)
comments = CommentField()
local_context_data = JSONField(
@@ -1820,11 +1750,6 @@ class DeviceForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
"config context",
}
widgets = {
- 'face': StaticSelect2(
- filter_for={
- 'position': 'face'
- }
- ),
'status': StaticSelect2(),
'primary_ip4': StaticSelect2(),
'primary_ip6': StaticSelect2(),
@@ -1885,7 +1810,7 @@ class DeviceForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
# If editing an existing device, exclude it from the list of occupied rack units. This ensures that a device
# can be flipped from one face to another.
- self.fields['position'].widget.add_additional_query_param('exclude', self.instance.pk)
+ self.fields['position'].widget.add_query_param('exclude', self.instance.pk)
# Limit platform by manufacturer
self.fields['platform'].queryset = Platform.objects.filter(
@@ -2075,12 +2000,17 @@ class DeviceBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditF
queryset=Device.objects.all(),
widget=forms.MultipleHiddenInput()
)
+ manufacturer = DynamicModelChoiceField(
+ queryset=Manufacturer.objects.all(),
+ required=False
+ )
device_type = DynamicModelChoiceField(
queryset=DeviceType.objects.all(),
required=False,
- widget=APISelect(
- display_field="model",
- )
+ display_field='model',
+ query_params={
+ 'manufacturer_id': '$manufacturer'
+ }
)
device_role = DynamicModelChoiceField(
queryset=DeviceRole.objects.all(),
@@ -2124,78 +2054,59 @@ class DeviceFilterForm(BootstrapMixin, LocalConfigContextFilterForm, TenancyFilt
region = DynamicModelMultipleChoiceField(
queryset=Region.objects.all(),
to_field_name='slug',
- required=False,
- widget=APISelectMultiple(
- value_field="slug",
- filter_for={
- 'site': 'region'
- }
- )
+ required=False
)
site = DynamicModelMultipleChoiceField(
queryset=Site.objects.all(),
to_field_name='slug',
required=False,
- widget=APISelectMultiple(
- value_field="slug",
- filter_for={
- 'rack_group_id': 'site',
- 'rack_id': 'site',
- }
- )
+ query_params={
+ 'region': '$region'
+ }
)
rack_group_id = DynamicModelMultipleChoiceField(
queryset=RackGroup.objects.all(),
required=False,
label='Rack group',
- widget=APISelectMultiple(
- filter_for={
- 'rack_id': 'group_id',
- }
- )
+ query_params={
+ 'site': '$site'
+ }
)
rack_id = DynamicModelMultipleChoiceField(
queryset=Rack.objects.all(),
required=False,
label='Rack',
- widget=APISelectMultiple(
- null_option=True,
- )
+ null_option='None',
+ query_params={
+ 'site': '$site',
+ 'group_id': '$rack_group_id',
+ }
)
role = DynamicModelMultipleChoiceField(
queryset=DeviceRole.objects.all(),
to_field_name='slug',
- required=False,
- widget=APISelectMultiple(
- value_field="slug",
- )
+ required=False
)
- manufacturer_id = DynamicModelMultipleChoiceField(
+ manufacturer = DynamicModelMultipleChoiceField(
queryset=Manufacturer.objects.all(),
+ to_field_name='slug',
required=False,
- label='Manufacturer',
- widget=APISelectMultiple(
- filter_for={
- 'device_type_id': 'manufacturer_id',
- }
- )
+ label='Manufacturer'
)
device_type_id = DynamicModelMultipleChoiceField(
queryset=DeviceType.objects.all(),
required=False,
label='Model',
- widget=APISelectMultiple(
- display_field="model",
- )
+ display_field='model',
+ query_params={
+ 'manufacturer': '$manufacturer'
+ }
)
platform = DynamicModelMultipleChoiceField(
queryset=Platform.objects.all(),
to_field_name='slug',
required=False,
- widget=APISelectMultiple(
- value_field="slug",
- null_option=True,
- )
+ null_option='None'
)
status = forms.MultipleChoiceField(
choices=DeviceStatusChoices,
@@ -2274,7 +2185,8 @@ class ComponentCreateForm(ComponentForm):
Base form for the creation of device components (models subclassed from ComponentModel).
"""
device = DynamicModelChoiceField(
- queryset=Device.objects.all()
+ queryset=Device.objects.all(),
+ display_field='display_name'
)
description = forms.CharField(
max_length=100,
@@ -2715,25 +2627,21 @@ class InterfaceForm(InterfaceCommonForm, BootstrapMixin, forms.ModelForm):
queryset=VLAN.objects.all(),
required=False,
label='Untagged VLAN',
- widget=APISelect(
- display_field='display_name',
- full=True,
- additional_query_params={
- 'site_id': 'null',
- },
- )
+ display_field='display_name',
+ brief_mode=False,
+ query_params={
+ 'site_id': 'null',
+ }
)
tagged_vlans = DynamicModelMultipleChoiceField(
queryset=VLAN.objects.all(),
required=False,
label='Tagged VLANs',
- widget=APISelectMultiple(
- display_field='display_name',
- full=True,
- additional_query_params={
- 'site_id': 'null',
- },
- )
+ display_field='display_name',
+ brief_mode=False,
+ query_params={
+ 'site_id': 'null',
+ }
)
tags = DynamicModelMultipleChoiceField(
queryset=Tag.objects.all(),
@@ -2774,8 +2682,8 @@ class InterfaceForm(InterfaceCommonForm, BootstrapMixin, forms.ModelForm):
)
# Add current site to VLANs query params
- self.fields['untagged_vlan'].widget.add_additional_query_param('site_id', device.site.pk)
- self.fields['tagged_vlans'].widget.add_additional_query_param('site_id', device.site.pk)
+ self.fields['untagged_vlan'].widget.add_query_param('site_id', device.site.pk)
+ self.fields['tagged_vlans'].widget.add_query_param('site_id', device.site.pk)
class InterfaceCreateForm(ComponentCreateForm, InterfaceCommonForm):
@@ -2816,24 +2724,20 @@ class InterfaceCreateForm(ComponentCreateForm, InterfaceCommonForm):
untagged_vlan = DynamicModelChoiceField(
queryset=VLAN.objects.all(),
required=False,
- widget=APISelect(
- display_field='display_name',
- full=True,
- additional_query_params={
- 'site_id': 'null',
- },
- )
+ display_field='display_name',
+ brief_mode=False,
+ query_params={
+ 'site_id': 'null',
+ }
)
tagged_vlans = DynamicModelMultipleChoiceField(
queryset=VLAN.objects.all(),
required=False,
- widget=APISelectMultiple(
- display_field='display_name',
- full=True,
- additional_query_params={
- 'site_id': 'null',
- },
- )
+ display_field='display_name',
+ brief_mode=False,
+ query_params={
+ 'site_id': 'null',
+ }
)
field_order = (
'device', 'name_pattern', 'label_pattern', 'type', 'enabled', 'lag', 'mtu', 'mac_address', 'description',
@@ -2853,8 +2757,8 @@ class InterfaceCreateForm(ComponentCreateForm, InterfaceCommonForm):
)
# Add current site to VLANs query params
- self.fields['untagged_vlan'].widget.add_additional_query_param('site_id', device.site.pk)
- self.fields['tagged_vlans'].widget.add_additional_query_param('site_id', device.site.pk)
+ self.fields['untagged_vlan'].widget.add_query_param('site_id', device.site.pk)
+ self.fields['tagged_vlans'].widget.add_query_param('site_id', device.site.pk)
class InterfaceBulkCreateForm(
@@ -2885,24 +2789,20 @@ class InterfaceBulkEditForm(
untagged_vlan = DynamicModelChoiceField(
queryset=VLAN.objects.all(),
required=False,
- widget=APISelect(
- display_field='display_name',
- full=True,
- additional_query_params={
- 'site_id': 'null',
- },
- )
+ display_field='display_name',
+ brief_mode=False,
+ query_params={
+ 'site_id': 'null',
+ }
)
tagged_vlans = DynamicModelMultipleChoiceField(
queryset=VLAN.objects.all(),
required=False,
- widget=APISelectMultiple(
- display_field='display_name',
- full=True,
- additional_query_params={
- 'site_id': 'null',
- },
- )
+ display_field='display_name',
+ brief_mode=False,
+ query_params={
+ 'site_id': 'null',
+ }
)
class Meta:
@@ -2922,8 +2822,8 @@ class InterfaceBulkEditForm(
)
# Add current site to VLANs query params
- self.fields['untagged_vlan'].widget.add_additional_query_param('site_id', device.site.pk)
- self.fields['tagged_vlans'].widget.add_additional_query_param('site_id', device.site.pk)
+ self.fields['untagged_vlan'].widget.add_query_param('site_id', device.site.pk)
+ self.fields['tagged_vlans'].widget.add_query_param('site_id', device.site.pk)
else:
self.fields['lag'].choices = ()
self.fields['lag'].widget.attrs['disabled'] = True
@@ -3366,7 +3266,8 @@ class DeviceBayCSVForm(CSVModelForm):
class InventoryItemForm(BootstrapMixin, forms.ModelForm):
device = DynamicModelChoiceField(
- queryset=Device.objects.prefetch_related('device_type__manufacturer')
+ queryset=Device.objects.all(),
+ display_field='display_name'
)
manufacturer = DynamicModelChoiceField(
queryset=Manufacturer.objects.all(),
@@ -3458,10 +3359,7 @@ class InventoryItemFilterForm(DeviceComponentFilterForm):
manufacturer = DynamicModelMultipleChoiceField(
queryset=Manufacturer.objects.all(),
to_field_name='slug',
- required=False,
- widget=APISelect(
- value_field="slug",
- )
+ required=False
)
serial = forms.CharField(
required=False
@@ -3489,37 +3387,27 @@ class ConnectCableToDeviceForm(BootstrapMixin, forms.ModelForm):
termination_b_site = DynamicModelChoiceField(
queryset=Site.objects.all(),
label='Site',
- required=False,
- widget=APISelect(
- filter_for={
- 'termination_b_rack': 'site_id',
- 'termination_b_device': 'site_id',
- }
- )
+ required=False
)
termination_b_rack = DynamicModelChoiceField(
queryset=Rack.objects.all(),
label='Rack',
required=False,
- widget=APISelect(
- filter_for={
- 'termination_b_device': 'rack_id',
- },
- attrs={
- 'nullable': 'true',
- }
- )
+ display_field='display_name',
+ null_option='None',
+ query_params={
+ 'site_id': '$termination_b_site'
+ }
)
termination_b_device = DynamicModelChoiceField(
queryset=Device.objects.all(),
label='Device',
required=False,
- widget=APISelect(
- display_field='display_name',
- filter_for={
- 'termination_b_id': 'device_id',
- }
- )
+ display_field='display_name',
+ query_params={
+ 'site_id': '$termination_b_site',
+ 'rack_id': '$termination_b_rack',
+ }
)
class Meta:
@@ -3534,77 +3422,86 @@ class ConnectCableToDeviceForm(BootstrapMixin, forms.ModelForm):
'length_unit': StaticSelect2,
}
+ def clean_termination_b_id(self):
+ # Return the PK rather than the object
+ return getattr(self.cleaned_data['termination_b_id'], 'pk', None)
+
class ConnectCableToConsolePortForm(ConnectCableToDeviceForm):
- termination_b_id = forms.IntegerField(
+ termination_b_id = DynamicModelChoiceField(
+ queryset=ConsolePort.objects.all(),
label='Name',
- widget=APISelect(
- api_url='/api/dcim/console-ports/',
- disabled_indicator='cable',
- )
+ disabled_indicator='cable',
+ query_params={
+ 'device_id': '$termination_b_device'
+ }
)
class ConnectCableToConsoleServerPortForm(ConnectCableToDeviceForm):
- termination_b_id = forms.IntegerField(
+ termination_b_id = DynamicModelChoiceField(
+ queryset=ConsoleServerPort.objects.all(),
label='Name',
- widget=APISelect(
- api_url='/api/dcim/console-server-ports/',
- disabled_indicator='cable',
- )
+ disabled_indicator='cable',
+ query_params={
+ 'device_id': '$termination_b_device'
+ }
)
class ConnectCableToPowerPortForm(ConnectCableToDeviceForm):
- termination_b_id = forms.IntegerField(
+ termination_b_id = DynamicModelChoiceField(
+ queryset=PowerPort.objects.all(),
label='Name',
- widget=APISelect(
- api_url='/api/dcim/power-ports/',
- disabled_indicator='cable',
- )
+ disabled_indicator='cable',
+ query_params={
+ 'device_id': '$termination_b_device'
+ }
)
class ConnectCableToPowerOutletForm(ConnectCableToDeviceForm):
- termination_b_id = forms.IntegerField(
+ termination_b_id = DynamicModelChoiceField(
+ queryset=PowerOutlet.objects.all(),
label='Name',
- widget=APISelect(
- api_url='/api/dcim/power-outlets/',
- disabled_indicator='cable',
- )
+ disabled_indicator='cable',
+ query_params={
+ 'device_id': '$termination_b_device'
+ }
)
class ConnectCableToInterfaceForm(ConnectCableToDeviceForm):
- termination_b_id = forms.IntegerField(
+ termination_b_id = DynamicModelChoiceField(
+ queryset=Interface.objects.all(),
label='Name',
- widget=APISelect(
- api_url='/api/dcim/interfaces/',
- disabled_indicator='cable',
- additional_query_params={
- 'kind': 'physical',
- }
- )
+ disabled_indicator='cable',
+ query_params={
+ 'device_id': '$termination_b_device',
+ 'kind': 'physical',
+ }
)
class ConnectCableToFrontPortForm(ConnectCableToDeviceForm):
- termination_b_id = forms.IntegerField(
+ termination_b_id = DynamicModelChoiceField(
+ queryset=FrontPort.objects.all(),
label='Name',
- widget=APISelect(
- api_url='/api/dcim/front-ports/',
- disabled_indicator='cable',
- )
+ disabled_indicator='cable',
+ query_params={
+ 'device_id': '$termination_b_device'
+ }
)
class ConnectCableToRearPortForm(ConnectCableToDeviceForm):
- termination_b_id = forms.IntegerField(
+ termination_b_id = DynamicModelChoiceField(
+ queryset=RearPort.objects.all(),
label='Name',
- widget=APISelect(
- api_url='/api/dcim/rear-ports/',
- disabled_indicator='cable',
- )
+ disabled_indicator='cable',
+ query_params={
+ 'device_id': '$termination_b_device'
+ }
)
@@ -3612,41 +3509,30 @@ class ConnectCableToCircuitTerminationForm(BootstrapMixin, forms.ModelForm):
termination_b_provider = DynamicModelChoiceField(
queryset=Provider.objects.all(),
label='Provider',
- required=False,
- widget=APISelect(
- filter_for={
- 'termination_b_circuit': 'provider_id',
- }
- )
+ required=False
)
termination_b_site = DynamicModelChoiceField(
queryset=Site.objects.all(),
label='Site',
- required=False,
- widget=APISelect(
- filter_for={
- 'termination_b_circuit': 'site_id',
- }
- )
+ required=False
)
termination_b_circuit = DynamicModelChoiceField(
queryset=Circuit.objects.all(),
label='Circuit',
- widget=APISelect(
- display_field='cid',
- filter_for={
- 'termination_b_id': 'circuit_id',
- }
- )
+ display_field='cid',
+ query_params={
+ 'provider_id': '$termination_b_provider',
+ 'site_id': '$termination_b_site',
+ }
)
- termination_b_id = forms.IntegerField(
+ termination_b_id = DynamicModelChoiceField(
+ queryset=CircuitTermination.objects.all(),
label='Side',
- widget=APISelect(
- api_url='/api/circuits/circuit-terminations/',
- disabled_indicator='cable',
- display_field='term_side',
- full=True
- )
+ display_field='term_side',
+ disabled_indicator='cable',
+ query_params={
+ 'circuit_id': '$termination_b_circuit'
+ }
)
class Meta:
@@ -3656,46 +3542,43 @@ class ConnectCableToCircuitTerminationForm(BootstrapMixin, forms.ModelForm):
'status', 'label', 'color', 'length', 'length_unit',
]
+ def clean_termination_b_id(self):
+ # Return the PK rather than the object
+ return getattr(self.cleaned_data['termination_b_id'], 'pk', None)
+
class ConnectCableToPowerFeedForm(BootstrapMixin, forms.ModelForm):
termination_b_site = DynamicModelChoiceField(
queryset=Site.objects.all(),
label='Site',
required=False,
- widget=APISelect(
- display_field='cid',
- filter_for={
- 'termination_b_rackgroup': 'site_id',
- 'termination_b_powerpanel': 'site_id',
- }
- )
+ display_field='cid'
)
termination_b_rackgroup = DynamicModelChoiceField(
queryset=RackGroup.objects.all(),
label='Rack Group',
required=False,
- widget=APISelect(
- display_field='cid',
- filter_for={
- 'termination_b_powerpanel': 'rackgroup_id',
- }
- )
+ display_field='cid',
+ query_params={
+ 'site_id': '$termination_b_site'
+ }
)
termination_b_powerpanel = DynamicModelChoiceField(
queryset=PowerPanel.objects.all(),
label='Power Panel',
required=False,
- widget=APISelect(
- filter_for={
- 'termination_b_id': 'power_panel_id',
- }
- )
+ query_params={
+ 'site_id': '$termination_b_site',
+ 'rack_group_id': '$termination_b_rackgroup',
+ }
)
- termination_b_id = forms.IntegerField(
+ termination_b_id = DynamicModelChoiceField(
+ queryset=PowerFeed.objects.all(),
label='Name',
- widget=APISelect(
- api_url='/api/dcim/power-feeds/',
- )
+ disabled_indicator='cable',
+ query_params={
+ 'power_panel_id': '$termination_b_powerpanel'
+ }
)
class Meta:
@@ -3705,6 +3588,10 @@ class ConnectCableToPowerFeedForm(BootstrapMixin, forms.ModelForm):
'color', 'length', 'length_unit',
]
+ def clean_termination_b_id(self):
+ # Return the PK rather than the object
+ return getattr(self.cleaned_data['termination_b_id'], 'pk', None)
+
class CableForm(BootstrapMixin, forms.ModelForm):
tags = DynamicModelMultipleChoiceField(
@@ -3910,36 +3797,21 @@ class CableFilterForm(BootstrapMixin, forms.Form):
site = DynamicModelMultipleChoiceField(
queryset=Site.objects.all(),
to_field_name='slug',
- required=False,
- widget=APISelectMultiple(
- value_field="slug",
- filter_for={
- 'rack_id': 'site',
- 'device_id': 'site',
- }
- )
+ required=False
)
tenant = DynamicModelMultipleChoiceField(
queryset=Tenant.objects.all(),
to_field_name='slug',
- required=False,
- widget=APISelectMultiple(
- value_field='slug',
- filter_for={
- 'device_id': 'tenant',
- }
- )
+ required=False
)
rack_id = DynamicModelMultipleChoiceField(
queryset=Rack.objects.all(),
required=False,
label='Rack',
- widget=APISelectMultiple(
- null_option=True,
- filter_for={
- 'device_id': 'rack_id',
- }
- )
+ null_option='None',
+ query_params={
+ 'site': '$site'
+ }
)
type = forms.MultipleChoiceField(
choices=add_blank_choice(CableTypeChoices),
@@ -3959,7 +3831,12 @@ class CableFilterForm(BootstrapMixin, forms.Form):
device_id = DynamicModelMultipleChoiceField(
queryset=Device.objects.all(),
required=False,
- label='Device'
+ label='Device',
+ query_params={
+ 'site': '$site',
+ 'tenant': '$tenant',
+ 'rack_id': '$rack_id',
+ }
)
tag = TagFilterField(model)
@@ -3972,18 +3849,15 @@ class ConsoleConnectionFilterForm(BootstrapMixin, forms.Form):
site = DynamicModelMultipleChoiceField(
queryset=Site.objects.all(),
to_field_name='slug',
- required=False,
- widget=APISelectMultiple(
- value_field="slug",
- filter_for={
- 'device_id': 'site',
- }
- )
+ required=False
)
device_id = DynamicModelMultipleChoiceField(
queryset=Device.objects.all(),
required=False,
- label='Device'
+ label='Device',
+ query_params={
+ 'site': '$site'
+ }
)
@@ -3991,18 +3865,15 @@ class PowerConnectionFilterForm(BootstrapMixin, forms.Form):
site = DynamicModelMultipleChoiceField(
queryset=Site.objects.all(),
to_field_name='slug',
- required=False,
- widget=APISelectMultiple(
- value_field="slug",
- filter_for={
- 'device_id': 'site',
- }
- )
+ required=False
)
device_id = DynamicModelMultipleChoiceField(
queryset=Device.objects.all(),
required=False,
- label='Device'
+ label='Device',
+ query_params={
+ 'site': '$site'
+ }
)
@@ -4010,18 +3881,15 @@ class InterfaceConnectionFilterForm(BootstrapMixin, forms.Form):
site = DynamicModelMultipleChoiceField(
queryset=Site.objects.all(),
to_field_name='slug',
- required=False,
- widget=APISelectMultiple(
- value_field="slug",
- filter_for={
- 'device_id': 'site',
- }
- )
+ required=False
)
device_id = DynamicModelMultipleChoiceField(
queryset=Device.objects.all(),
required=False,
- label='Device'
+ label='Device',
+ query_params={
+ 'site': '$site'
+ }
)
@@ -4039,29 +3907,24 @@ class DeviceSelectionForm(forms.Form):
class VirtualChassisCreateForm(BootstrapMixin, forms.ModelForm):
site = DynamicModelChoiceField(
queryset=Site.objects.all(),
- required=False,
- widget=APISelect(
- filter_for={
- 'rack': 'site_id',
- 'members': 'site_id',
- }
- )
+ required=False
)
rack = DynamicModelChoiceField(
queryset=Rack.objects.all(),
required=False,
- widget=APISelect(
- filter_for={
- 'members': 'rack_id'
- },
- attrs={
- 'nullable': 'true',
- }
- )
+ null_option='None',
+ display_field='display_name',
+ query_params={
+ 'site_id': '$site'
+ }
)
members = DynamicModelMultipleChoiceField(
queryset=Device.objects.all(),
required=False,
+ query_params={
+ 'site_id': '$site',
+ 'rack_id': '$rack',
+ }
)
initial_position = forms.IntegerField(
initial=1,
@@ -4175,34 +4038,25 @@ class DeviceVCMembershipForm(forms.ModelForm):
class VCMemberSelectForm(BootstrapMixin, forms.Form):
site = DynamicModelChoiceField(
queryset=Site.objects.all(),
- required=False,
- widget=APISelect(
- filter_for={
- 'rack': 'site_id',
- 'device': 'site_id',
- }
- )
+ required=False
)
rack = DynamicModelChoiceField(
queryset=Rack.objects.all(),
required=False,
- widget=APISelect(
- filter_for={
- 'device': 'rack_id'
- },
- attrs={
- 'nullable': 'true',
- }
- )
+ null_option='None',
+ display_field='display_name',
+ query_params={
+ 'site_id': '$site'
+ }
)
device = DynamicModelChoiceField(
- queryset=Device.objects.filter(
- virtual_chassis__isnull=True
- ),
- widget=APISelect(
- display_field='display_name',
- disabled_indicator='virtual_chassis'
- )
+ queryset=Device.objects.all(),
+ display_field='display_name',
+ query_params={
+ 'site_id': '$site',
+ 'rack_id': '$rack',
+ 'virtual_chassis_id': 'null',
+ }
)
def clean_device(self):
@@ -4250,42 +4104,30 @@ class VirtualChassisFilterForm(BootstrapMixin, CustomFieldFilterForm):
region = DynamicModelMultipleChoiceField(
queryset=Region.objects.all(),
to_field_name='slug',
- required=False,
- widget=APISelectMultiple(
- value_field="slug",
- filter_for={
- 'site': 'region'
- }
- )
+ required=False
)
site = DynamicModelMultipleChoiceField(
queryset=Site.objects.all(),
to_field_name='slug',
required=False,
- widget=APISelectMultiple(
- value_field="slug",
- )
+ query_params={
+ 'region': '$region'
+ }
)
tenant_group = DynamicModelMultipleChoiceField(
queryset=TenantGroup.objects.all(),
to_field_name='slug',
required=False,
- widget=APISelectMultiple(
- value_field="slug",
- null_option=True,
- filter_for={
- 'tenant': 'group'
- }
- )
+ null_option='None'
)
tenant = DynamicModelMultipleChoiceField(
queryset=Tenant.objects.all(),
to_field_name='slug',
required=False,
- widget=APISelectMultiple(
- value_field="slug",
- null_option=True,
- )
+ null_option='None',
+ query_params={
+ 'group': '$tenant_group'
+ }
)
tag = TagFilterField(model)
@@ -4296,16 +4138,14 @@ class VirtualChassisFilterForm(BootstrapMixin, CustomFieldFilterForm):
class PowerPanelForm(BootstrapMixin, forms.ModelForm):
site = DynamicModelChoiceField(
- queryset=Site.objects.all(),
- widget=APISelect(
- filter_for={
- 'rack_group': 'site_id',
- }
- )
+ queryset=Site.objects.all()
)
rack_group = DynamicModelChoiceField(
queryset=RackGroup.objects.all(),
- required=False
+ required=False,
+ query_params={
+ 'site_id': '$site'
+ }
)
tags = DynamicModelMultipleChoiceField(
queryset=Tag.objects.all(),
@@ -4352,16 +4192,14 @@ class PowerPanelBulkEditForm(BootstrapMixin, AddRemoveTagsForm, BulkEditForm):
)
site = DynamicModelChoiceField(
queryset=Site.objects.all(),
- required=False,
- widget=APISelect(
- filter_for={
- 'rack_group': 'site_id',
- }
- )
+ required=False
)
rack_group = DynamicModelChoiceField(
queryset=RackGroup.objects.all(),
- required=False
+ required=False,
+ query_params={
+ 'site_id': '$site'
+ }
)
class Meta:
@@ -4379,32 +4217,24 @@ class PowerPanelFilterForm(BootstrapMixin, CustomFieldFilterForm):
region = DynamicModelMultipleChoiceField(
queryset=Region.objects.all(),
to_field_name='slug',
- required=False,
- widget=APISelectMultiple(
- value_field="slug",
- filter_for={
- 'site': 'region'
- }
- )
+ required=False
)
site = DynamicModelMultipleChoiceField(
queryset=Site.objects.all(),
to_field_name='slug',
required=False,
- widget=APISelectMultiple(
- value_field="slug",
- filter_for={
- 'rack_group_id': 'site',
- }
- )
+ query_params={
+ 'region': '$region'
+ }
)
rack_group_id = DynamicModelMultipleChoiceField(
queryset=RackGroup.objects.all(),
required=False,
label='Rack group (ID)',
- widget=APISelectMultiple(
- null_option=True,
- )
+ null_option='None',
+ query_params={
+ 'site': '$site'
+ }
)
tag = TagFilterField(model)
@@ -4416,20 +4246,21 @@ class PowerPanelFilterForm(BootstrapMixin, CustomFieldFilterForm):
class PowerFeedForm(BootstrapMixin, CustomFieldModelForm):
site = DynamicModelChoiceField(
queryset=Site.objects.all(),
- required=False,
- widget=APISelect(
- filter_for={
- 'power_panel': 'site_id',
- 'rack': 'site_id',
- }
- )
+ required=False
)
power_panel = DynamicModelChoiceField(
- queryset=PowerPanel.objects.all()
+ queryset=PowerPanel.objects.all(),
+ query_params={
+ 'site_id': '$site'
+ }
)
rack = DynamicModelChoiceField(
queryset=Rack.objects.all(),
- required=False
+ required=False,
+ display_field='display_name',
+ query_params={
+ 'site_id': '$site'
+ }
)
comments = CommentField()
tags = DynamicModelMultipleChoiceField(
@@ -4535,16 +4366,12 @@ class PowerFeedBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEd
)
power_panel = DynamicModelChoiceField(
queryset=PowerPanel.objects.all(),
- required=False,
- widget=APISelect(
- filter_for={
- 'rackgroup': 'site_id',
- }
- )
+ required=False
)
rack = DynamicModelChoiceField(
queryset=Rack.objects.all(),
- required=False
+ required=False,
+ display_field='display_name'
)
status = forms.ChoiceField(
choices=add_blank_choice(PowerFeedStatusChoices),
@@ -4599,41 +4426,33 @@ class PowerFeedFilterForm(BootstrapMixin, CustomFieldFilterForm):
region = DynamicModelMultipleChoiceField(
queryset=Region.objects.all(),
to_field_name='slug',
- required=False,
- widget=APISelectMultiple(
- value_field="slug",
- filter_for={
- 'site': 'region'
- }
- )
+ required=False
)
site = DynamicModelMultipleChoiceField(
queryset=Site.objects.all(),
to_field_name='slug',
required=False,
- widget=APISelectMultiple(
- value_field="slug",
- filter_for={
- 'power_panel_id': 'site',
- 'rack_id': 'site',
- }
- )
+ query_params={
+ 'region': '$region'
+ }
)
power_panel_id = DynamicModelMultipleChoiceField(
queryset=PowerPanel.objects.all(),
required=False,
label='Power panel',
- widget=APISelectMultiple(
- null_option=True,
- )
+ null_option='None',
+ query_params={
+ 'site': '$site'
+ }
)
rack_id = DynamicModelMultipleChoiceField(
queryset=Rack.objects.all(),
required=False,
label='Rack',
- widget=APISelectMultiple(
- null_option=True,
- )
+ null_option='None',
+ query_params={
+ 'site': '$site'
+ }
)
status = forms.MultipleChoiceField(
choices=PowerFeedStatusChoices,
diff --git a/netbox/extras/forms.py b/netbox/extras/forms.py
index a565375c5..cecbfae72 100644
--- a/netbox/extras/forms.py
+++ b/netbox/extras/forms.py
@@ -290,42 +290,27 @@ class ConfigContextFilterForm(BootstrapMixin, forms.Form):
region = DynamicModelMultipleChoiceField(
queryset=Region.objects.all(),
to_field_name='slug',
- required=False,
- widget=APISelectMultiple(
- value_field="slug",
- )
+ required=False
)
site = DynamicModelMultipleChoiceField(
queryset=Site.objects.all(),
to_field_name='slug',
- required=False,
- widget=APISelectMultiple(
- value_field="slug",
- )
+ required=False
)
role = DynamicModelMultipleChoiceField(
queryset=DeviceRole.objects.all(),
to_field_name='slug',
- required=False,
- widget=APISelectMultiple(
- value_field="slug",
- )
+ required=False
)
platform = DynamicModelMultipleChoiceField(
queryset=Platform.objects.all(),
to_field_name='slug',
- required=False,
- widget=APISelectMultiple(
- value_field="slug",
- )
+ required=False
)
cluster_group = DynamicModelMultipleChoiceField(
queryset=ClusterGroup.objects.all(),
to_field_name='slug',
- required=False,
- widget=APISelectMultiple(
- value_field="slug",
- )
+ required=False
)
cluster_id = DynamicModelMultipleChoiceField(
queryset=Cluster.objects.all(),
@@ -335,26 +320,17 @@ class ConfigContextFilterForm(BootstrapMixin, forms.Form):
tenant_group = DynamicModelMultipleChoiceField(
queryset=TenantGroup.objects.all(),
to_field_name='slug',
- required=False,
- widget=APISelectMultiple(
- value_field="slug",
- )
+ required=False
)
tenant = DynamicModelMultipleChoiceField(
queryset=Tenant.objects.all(),
to_field_name='slug',
- required=False,
- widget=APISelectMultiple(
- value_field="slug",
- )
+ required=False
)
tag = DynamicModelMultipleChoiceField(
queryset=Tag.objects.all(),
to_field_name='slug',
- required=False,
- widget=APISelectMultiple(
- value_field="slug",
- )
+ required=False
)
@@ -413,9 +389,9 @@ class ObjectChangeFilterForm(BootstrapMixin, forms.Form):
user = DynamicModelMultipleChoiceField(
queryset=User.objects.all(),
required=False,
+ display_field='username',
widget=APISelectMultiple(
api_url='/api/users/users/',
- display_field='username'
)
)
changed_object_type = forms.ModelChoiceField(
diff --git a/netbox/extras/scripts.py b/netbox/extras/scripts.py
index c3957d4be..b296e140c 100644
--- a/netbox/extras/scripts.py
+++ b/netbox/extras/scripts.py
@@ -170,28 +170,42 @@ class ChoiceVar(ScriptVariable):
class ObjectVar(ScriptVariable):
"""
A single object within NetBox.
+
+ :param model: The NetBox model being referenced
+ :param display_field: The attribute of the returned object to display in the selection list (default: 'name')
+ :param query_params: A dictionary of additional query parameters to attach when making REST API requests (optional)
+ :param null_option: The label to use as a "null" selection option (optional)
"""
form_field = DynamicModelChoiceField
- def __init__(self, queryset, *args, **kwargs):
+ def __init__(self, model=None, queryset=None, display_field='name', query_params=None, null_option=None, *args,
+ **kwargs):
super().__init__(*args, **kwargs)
- # Queryset for field choices
- self.field_attrs['queryset'] = queryset
+ # Set the form field's queryset. Support backward compatibility for the "queryset" argument for now.
+ if model is not None:
+ self.field_attrs['queryset'] = model.objects.all()
+ elif queryset is not None:
+ warnings.warn(
+ f'{self}: Specifying a queryset for ObjectVar is no longer supported. Please use "model" instead.'
+ )
+ self.field_attrs['queryset'] = queryset
+ else:
+ raise TypeError('ObjectVar must specify a model')
+
+ self.field_attrs.update({
+ 'display_field': display_field,
+ 'query_params': query_params,
+ 'null_option': null_option,
+ })
-class MultiObjectVar(ScriptVariable):
+class MultiObjectVar(ObjectVar):
"""
Like ObjectVar, but can represent one or more objects.
"""
form_field = DynamicModelMultipleChoiceField
- def __init__(self, queryset, *args, **kwargs):
- super().__init__(*args, **kwargs)
-
- # Queryset for field choices
- self.field_attrs['queryset'] = queryset
-
class FileVar(ScriptVariable):
"""
diff --git a/netbox/ipam/forms.py b/netbox/ipam/forms.py
index 15c69c504..d3c272606 100644
--- a/netbox/ipam/forms.py
+++ b/netbox/ipam/forms.py
@@ -1,5 +1,4 @@
from django import forms
-from django.contrib.contenttypes.models import ContentType
from django.core.validators import MaxValueValidator, MinValueValidator
from dcim.models import Device, Interface, Rack, Region, Site
@@ -10,10 +9,9 @@ from extras.models import Tag
from tenancy.forms import TenancyFilterForm, TenancyForm
from tenancy.models import Tenant
from utilities.forms import (
- add_blank_choice, APISelect, APISelectMultiple, BootstrapMixin, BulkEditNullBooleanSelect, CSVChoiceField,
- CSVModelChoiceField, CSVModelForm, DatePicker, DynamicModelChoiceField, DynamicModelMultipleChoiceField,
- ExpandableIPAddressField, ReturnURLForm, SlugField, StaticSelect2, StaticSelect2Multiple, TagFilterField,
- BOOLEAN_WITH_BLANK_CHOICES,
+ add_blank_choice, BootstrapMixin, BulkEditNullBooleanSelect, CSVChoiceField, CSVModelChoiceField, CSVModelForm,
+ DatePicker, DynamicModelChoiceField, DynamicModelMultipleChoiceField, ExpandableIPAddressField, ReturnURLForm,
+ SlugField, StaticSelect2, StaticSelect2Multiple, TagFilterField, BOOLEAN_WITH_BLANK_CHOICES,
)
from virtualization.models import VirtualMachine, VMInterface
from .choices import *
@@ -217,10 +215,7 @@ class AggregateFilterForm(BootstrapMixin, CustomFieldFilterForm):
queryset=RIR.objects.all(),
to_field_name='slug',
required=False,
- label='RIR',
- widget=APISelectMultiple(
- value_field="slug",
- )
+ label='RIR'
)
tag = TagFilterField(model)
@@ -255,41 +250,32 @@ class PrefixForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
vrf = DynamicModelChoiceField(
queryset=VRF.objects.all(),
required=False,
- label='VRF'
+ label='VRF',
+ display_field='display_name'
)
site = DynamicModelChoiceField(
queryset=Site.objects.all(),
required=False,
- widget=APISelect(
- filter_for={
- 'vlan_group': 'site_id',
- 'vlan': 'site_id',
- },
- attrs={
- 'nullable': 'true',
- }
- )
+ null_option='None'
)
vlan_group = DynamicModelChoiceField(
queryset=VLANGroup.objects.all(),
required=False,
label='VLAN group',
- widget=APISelect(
- filter_for={
- 'vlan': 'group_id'
- },
- attrs={
- 'nullable': 'true',
- }
- )
+ null_option='None',
+ query_params={
+ 'site_id': '$site'
+ }
)
vlan = DynamicModelChoiceField(
queryset=VLAN.objects.all(),
required=False,
label='VLAN',
- widget=APISelect(
- display_field='display_name'
- )
+ display_field='display_name',
+ query_params={
+ 'site_id': '$site',
+ 'group_id': '$vlan_group',
+ }
)
role = DynamicModelChoiceField(
queryset=Role.objects.all(),
@@ -469,9 +455,7 @@ class PrefixFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm)
queryset=VRF.objects.all(),
required=False,
label='VRF',
- widget=APISelectMultiple(
- null_option=True,
- )
+ null_option='Global'
)
status = forms.MultipleChoiceField(
choices=PrefixStatusChoices,
@@ -481,31 +465,22 @@ class PrefixFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm)
region = DynamicModelMultipleChoiceField(
queryset=Region.objects.all(),
to_field_name='slug',
- required=False,
- widget=APISelectMultiple(
- value_field="slug",
- filter_for={
- 'site': 'region'
- }
- )
+ required=False
)
site = DynamicModelMultipleChoiceField(
queryset=Site.objects.all(),
to_field_name='slug',
required=False,
- widget=APISelectMultiple(
- value_field="slug",
- null_option=True,
- )
+ null_option='None',
+ query_params={
+ 'region': '$region'
+ }
)
role = DynamicModelMultipleChoiceField(
queryset=Role.objects.all(),
to_field_name='slug',
required=False,
- widget=APISelectMultiple(
- value_field="slug",
- null_option=True,
- )
+ null_option='None'
)
is_pool = forms.NullBooleanField(
required=False,
@@ -525,29 +500,26 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldModel
device = DynamicModelChoiceField(
queryset=Device.objects.all(),
required=False,
- widget=APISelect(
- filter_for={
- 'interface': 'device_id'
- }
- )
+ display_field='display_name'
)
interface = DynamicModelChoiceField(
queryset=Interface.objects.all(),
- required=False
+ required=False,
+ query_params={
+ 'device_id': '$device'
+ }
)
virtual_machine = DynamicModelChoiceField(
queryset=VirtualMachine.objects.all(),
- required=False,
- widget=APISelect(
- filter_for={
- 'vminterface': 'virtual_machine_id'
- }
- )
+ required=False
)
vminterface = DynamicModelChoiceField(
queryset=VMInterface.objects.all(),
required=False,
- label='Interface'
+ label='Interface',
+ query_params={
+ 'virtual_machine_id': '$virtual_machine'
+ }
)
vrf = DynamicModelChoiceField(
queryset=VRF.objects.all(),
@@ -557,56 +529,42 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldModel
nat_site = DynamicModelChoiceField(
queryset=Site.objects.all(),
required=False,
- label='Site',
- widget=APISelect(
- filter_for={
- 'nat_rack': 'site_id',
- 'nat_device': 'site_id'
- }
- )
+ label='Site'
)
nat_rack = DynamicModelChoiceField(
queryset=Rack.objects.all(),
required=False,
label='Rack',
- widget=APISelect(
- display_field='display_name',
- filter_for={
- 'nat_device': 'rack_id'
- },
- attrs={
- 'nullable': 'true'
- }
- )
+ display_field='display_name',
+ null_option='None',
+ query_params={
+ 'site_id': '$site'
+ }
)
nat_device = DynamicModelChoiceField(
queryset=Device.objects.all(),
required=False,
label='Device',
- widget=APISelect(
- display_field='display_name',
- filter_for={
- 'nat_inside': 'device_id'
- }
- )
+ display_field='display_name',
+ query_params={
+ 'site_id': '$site',
+ 'rack_id': '$nat_rack',
+ }
)
nat_vrf = DynamicModelChoiceField(
queryset=VRF.objects.all(),
required=False,
- label='VRF',
- widget=APISelect(
- filter_for={
- 'nat_inside': 'vrf_id'
- }
- )
+ label='VRF'
)
nat_inside = DynamicModelChoiceField(
queryset=IPAddress.objects.all(),
required=False,
label='IP Address',
- widget=APISelect(
- display_field='address'
- )
+ display_field='address',
+ query_params={
+ 'device_id': '$nat_device',
+ 'vrf_id': '$nat_vrf',
+ }
)
primary_for_parent = forms.BooleanField(
required=False,
@@ -920,9 +878,7 @@ class IPAddressFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterFo
queryset=VRF.objects.all(),
required=False,
label='VRF',
- widget=APISelectMultiple(
- null_option=True,
- )
+ null_option='Global'
)
status = forms.MultipleChoiceField(
choices=IPAddressStatusChoices,
@@ -980,22 +936,16 @@ class VLANGroupFilterForm(BootstrapMixin, forms.Form):
region = DynamicModelMultipleChoiceField(
queryset=Region.objects.all(),
to_field_name='slug',
- required=False,
- widget=APISelectMultiple(
- value_field="slug",
- filter_for={
- 'site': 'region',
- }
- )
+ required=False
)
site = DynamicModelMultipleChoiceField(
queryset=Site.objects.all(),
to_field_name='slug',
required=False,
- widget=APISelectMultiple(
- value_field="slug",
- null_option=True,
- )
+ null_option='None',
+ query_params={
+ 'region': '$region'
+ }
)
@@ -1007,18 +957,14 @@ class VLANForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
site = DynamicModelChoiceField(
queryset=Site.objects.all(),
required=False,
- widget=APISelect(
- filter_for={
- 'group': 'site_id'
- },
- attrs={
- 'nullable': 'true',
- }
- )
+ null_option='None'
)
group = DynamicModelChoiceField(
queryset=VLANGroup.objects.all(),
- required=False
+ required=False,
+ query_params={
+ 'site_id': '$site'
+ }
)
role = DynamicModelChoiceField(
queryset=Role.objects.all(),
@@ -1102,16 +1048,14 @@ class VLANBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditFor
)
site = DynamicModelChoiceField(
queryset=Site.objects.all(),
- required=False,
- widget=APISelect(
- filter_for={
- 'group': 'site_id'
- }
- )
+ required=False
)
group = DynamicModelChoiceField(
queryset=VLANGroup.objects.all(),
- required=False
+ required=False,
+ query_params={
+ 'site_id': '$site'
+ }
)
tenant = DynamicModelChoiceField(
queryset=Tenant.objects.all(),
@@ -1147,31 +1091,25 @@ class VLANFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
region = DynamicModelMultipleChoiceField(
queryset=Region.objects.all(),
to_field_name='slug',
- required=False,
- widget=APISelectMultiple(
- value_field="slug",
- filter_for={
- 'site': 'region',
- 'group_id': 'region'
- }
- )
+ required=False
)
site = DynamicModelMultipleChoiceField(
queryset=Site.objects.all(),
to_field_name='slug',
required=False,
- widget=APISelectMultiple(
- value_field="slug",
- null_option=True,
- )
+ null_option='None',
+ query_params={
+ 'region': '$region'
+ }
)
group_id = DynamicModelMultipleChoiceField(
queryset=VLANGroup.objects.all(),
required=False,
label='VLAN group',
- widget=APISelectMultiple(
- null_option=True,
- )
+ null_option='None',
+ query_params={
+ 'region': '$region'
+ }
)
status = forms.MultipleChoiceField(
choices=VLANStatusChoices,
@@ -1182,10 +1120,7 @@ class VLANFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
queryset=Role.objects.all(),
to_field_name='slug',
required=False,
- widget=APISelectMultiple(
- value_field="slug",
- null_option=True,
- )
+ null_option='None'
)
tag = TagFilterField(model)
diff --git a/netbox/project-static/js/forms.js b/netbox/project-static/js/forms.js
index 26c8338c2..0feb7fc2f 100644
--- a/netbox/project-static/js/forms.js
+++ b/netbox/project-static/js/forms.js
@@ -74,7 +74,7 @@ $(document).ready(function() {
form.submit();
});
- // Parse URLs which may contain variable refrences to other field values
+ // Parse URLs which may contain variable references to other field values
function parseURL(url) {
var filter_regex = /\{\{([a-z_]+)\}\}/g;
var match;
@@ -87,7 +87,7 @@ $(document).ready(function() {
rendered_url = rendered_url.replace(match[0], custom_attr);
} else if (filter_field.val()) {
rendered_url = rendered_url.replace(match[0], filter_field.val());
- } else if (filter_field.attr('nullable') == 'true') {
+ } else if (filter_field.attr('data-null-option')) {
rendered_url = rendered_url.replace(match[0], 'null');
}
}
@@ -123,7 +123,7 @@ $(document).ready(function() {
// API backed selection
// Includes live search and chained fields
- // The `multiple` setting may be controled via a data-* attribute
+ // The `multiple` setting may be controlled via a data-* attribute
$('.netbox-select2-api').select2({
allowClear: true,
placeholder: "---------",
@@ -157,47 +157,23 @@ $(document).ready(function() {
// Allow for controlling the brief setting from within APISelect
parameters.brief = ( $(element).is('[data-full]') ? undefined : true );
- // filter-for fields from a chain
- var attr_name = "data-filter-for-" + $(element).attr("name");
- var form = $(element).closest('form');
- var filter_for_elements = form.find("select[" + attr_name + "]");
-
- filter_for_elements.each(function(index, filter_for_element) {
- var param_name = $(filter_for_element).attr(attr_name);
- var is_required = $(filter_for_element).attr("required");
- var is_nullable = $(filter_for_element).attr("nullable");
- var is_visible = $(filter_for_element).is(":visible");
- var value = $(filter_for_element).val();
-
- if (param_name && is_visible) {
- if (value) {
- parameters[param_name] = value;
- } else if (is_required && is_nullable) {
- parameters[param_name] = "null";
- }
- }
- });
-
- // Conditional query params
+ // Attach any extra query parameters
$.each(element.attributes, function(index, attr){
- if (attr.name.includes("data-conditional-query-param-")){
- var conditional = attr.name.split("data-conditional-query-param-")[1].split("__");
- var field = $("#id_" + conditional[0]);
- var field_value = conditional[1];
-
- if ($('option:selected', field).attr('api-value') === field_value){
- var _val = attr.value.split("=");
- parameters[_val[0]] = _val[1];
- }
- }
- });
-
- // Additional query params
- $.each(element.attributes, function(index, attr){
- if (attr.name.includes("data-additional-query-param-")){
- var param_name = attr.name.split("data-additional-query-param-")[1];
+ if (attr.name.includes("data-query-param-")){
+ var param_name = attr.name.split("data-query-param-")[1];
$.each($.parseJSON(attr.value), function(index, value) {
+ // Referencing the value of another form field
+ if (value.startsWith('$')) {
+ let ref_field = $('#id_' + value.slice(1));
+ if (ref_field.val() && ref_field.is(":visible")) {
+ value = ref_field.val();
+ } else if (ref_field.attr("required") && ref_field.attr("data-null-option")) {
+ value = "null";
+ } else {
+ return true; // Skip if ref_field has no value
+ }
+ }
if (param_name in parameters) {
if (Array.isArray(parameters[param_name])) {
parameters[param_name].push(value);
@@ -261,7 +237,7 @@ $(document).ready(function() {
if (element.getAttribute('data-null-option') && data.previous === null) {
results.unshift({
id: 'null',
- text: 'None'
+ text: element.getAttribute('data-null-option')
});
}
diff --git a/netbox/secrets/forms.py b/netbox/secrets/forms.py
index 54178f46b..8f04edc5b 100644
--- a/netbox/secrets/forms.py
+++ b/netbox/secrets/forms.py
@@ -8,8 +8,8 @@ from extras.forms import (
)
from extras.models import Tag
from utilities.forms import (
- APISelectMultiple, BootstrapMixin, CSVModelChoiceField, CSVModelForm, DynamicModelChoiceField,
- DynamicModelMultipleChoiceField, SlugField, StaticSelect2Multiple, TagFilterField,
+ BootstrapMixin, CSVModelChoiceField, CSVModelForm, DynamicModelChoiceField, DynamicModelMultipleChoiceField,
+ SlugField, TagFilterField,
)
from .constants import *
from .models import Secret, SecretRole, UserKey
@@ -63,7 +63,8 @@ class SecretRoleCSVForm(CSVModelForm):
class SecretForm(BootstrapMixin, CustomFieldModelForm):
device = DynamicModelChoiceField(
- queryset=Device.objects.all()
+ queryset=Device.objects.all(),
+ display_field='display_name'
)
plaintext = forms.CharField(
max_length=SECRET_PLAINTEXT_MAX_LENGTH,
@@ -178,10 +179,7 @@ class SecretFilterForm(BootstrapMixin, CustomFieldFilterForm):
role = DynamicModelMultipleChoiceField(
queryset=SecretRole.objects.all(),
to_field_name='slug',
- required=False,
- widget=APISelectMultiple(
- value_field="slug",
- )
+ required=False
)
tag = TagFilterField(model)
diff --git a/netbox/templates/virtualization/cluster_add_devices.html b/netbox/templates/virtualization/cluster_add_devices.html
index 397f53d01..d53fc3a03 100644
--- a/netbox/templates/virtualization/cluster_add_devices.html
+++ b/netbox/templates/virtualization/cluster_add_devices.html
@@ -35,33 +35,3 @@
{% endblock %}
-
-{% block javascript %}
-
-{% endblock %}
diff --git a/netbox/tenancy/forms.py b/netbox/tenancy/forms.py
index 04a3980a1..142333bff 100644
--- a/netbox/tenancy/forms.py
+++ b/netbox/tenancy/forms.py
@@ -5,8 +5,8 @@ from extras.forms import (
)
from extras.models import Tag
from utilities.forms import (
- APISelect, APISelectMultiple, BootstrapMixin, CommentField, CSVModelChoiceField, CSVModelForm,
- DynamicModelChoiceField, DynamicModelMultipleChoiceField, SlugField, TagFilterField,
+ BootstrapMixin, CommentField, CSVModelChoiceField, CSVModelForm, DynamicModelChoiceField,
+ DynamicModelMultipleChoiceField, SlugField, TagFilterField,
)
from .models import Tenant, TenantGroup
@@ -106,10 +106,7 @@ class TenantFilterForm(BootstrapMixin, CustomFieldFilterForm):
queryset=TenantGroup.objects.all(),
to_field_name='slug',
required=False,
- widget=APISelectMultiple(
- value_field="slug",
- null_option=True,
- )
+ null_option='None'
)
tag = TagFilterField(model)
@@ -122,18 +119,14 @@ class TenancyForm(forms.Form):
tenant_group = DynamicModelChoiceField(
queryset=TenantGroup.objects.all(),
required=False,
- widget=APISelect(
- filter_for={
- 'tenant': 'group_id',
- },
- attrs={
- 'nullable': 'true',
- }
- )
+ null_option='None'
)
tenant = DynamicModelChoiceField(
queryset=Tenant.objects.all(),
- required=False
+ required=False,
+ query_params={
+ 'group_id': '$tenant_group'
+ }
)
def __init__(self, *args, **kwargs):
@@ -153,20 +146,14 @@ class TenancyFilterForm(forms.Form):
queryset=TenantGroup.objects.all(),
to_field_name='slug',
required=False,
- widget=APISelectMultiple(
- value_field="slug",
- null_option=True,
- filter_for={
- 'tenant': 'group'
- }
- )
+ null_option='None'
)
tenant = DynamicModelMultipleChoiceField(
queryset=Tenant.objects.all(),
to_field_name='slug',
required=False,
- widget=APISelectMultiple(
- value_field="slug",
- null_option=True,
- )
+ null_option='None',
+ query_params={
+ 'group': '$tenant_group'
+ }
)
diff --git a/netbox/utilities/forms/fields.py b/netbox/utilities/forms/fields.py
index b4948a419..6146e00d3 100644
--- a/netbox/utilities/forms/fields.py
+++ b/netbox/utilities/forms/fields.py
@@ -11,6 +11,7 @@ from django.db.models import Count
from django.forms import BoundField
from django.urls import reverse
+from utilities.api import get_serializer_for_model
from utilities.choices import unpack_grouped_choices
from utilities.validators import EnhancedURLValidator
from . import widgets
@@ -244,9 +245,58 @@ class TagFilterField(forms.MultipleChoiceField):
class DynamicModelChoiceMixin:
+ """
+ :param display_field: The name of the attribute of an API response object to display in the selection list
+ :param query_params: A dictionary of additional key/value pairs to attach to the API request
+ :param null_option: The string used to represent a null selection (if any)
+ :param disabled_indicator: The name of the field which, if populated, will disable selection of the
+ choice (optional)
+ :param brief_mode: Use the "brief" format (?brief=true) when making API requests (default)
+ """
filter = django_filters.ModelChoiceFilter
widget = widgets.APISelect
+ def __init__(self, display_field='name', query_params=None, null_option=None, disabled_indicator=None,
+ brief_mode=True, *args, **kwargs):
+ self.display_field = display_field
+ self.query_params = query_params or {}
+ self.null_option = null_option
+ self.disabled_indicator = disabled_indicator
+ self.brief_mode = brief_mode
+
+ # to_field_name is set by ModelChoiceField.__init__(), but we need to set it early for reference
+ # by widget_attrs()
+ self.to_field_name = kwargs.get('to_field_name')
+
+ super().__init__(*args, **kwargs)
+
+ def widget_attrs(self, widget):
+ attrs = {
+ 'display-field': self.display_field,
+ }
+
+ # Set value-field attribute if the field specifies to_field_name
+ if self.to_field_name:
+ attrs['value-field'] = self.to_field_name
+
+ # Set the string used to represent a null option
+ if self.null_option is not None:
+ attrs['data-null-option'] = self.null_option
+
+ # Set the disabled indicator, if any
+ if self.disabled_indicator is not None:
+ attrs['disabled-indicator'] = self.disabled_indicator
+
+ # Toggle brief mode
+ if not self.brief_mode:
+ attrs['data-full'] = 'true'
+
+ # Attach any static query parameters
+ for key, value in self.query_params.items():
+ widget.add_query_param(key, value)
+
+ return attrs
+
def get_bound_field(self, form, field_name):
bound_field = BoundField(form, self, field_name)
diff --git a/netbox/utilities/forms/widgets.py b/netbox/utilities/forms/widgets.py
index 1c9d654f3..9bda413bd 100644
--- a/netbox/utilities/forms/widgets.py
+++ b/netbox/utilities/forms/widgets.py
@@ -79,29 +79,12 @@ class SelectWithDisabled(forms.Select):
class StaticSelect2(SelectWithDisabled):
"""
- A static content using the Select2 widget
-
- :param filter_for: (Optional) A dict of chained form fields for which this field is a filter. The key is the
- name of the filter-for field (child field) and the value is the name of the query param filter.
+ A static