1
0
mirror of https://github.com/netbox-community/netbox.git synced 2024-05-10 07:54:54 +00:00

DCIM filter forms select2

This commit is contained in:
John Anderson
2019-01-08 15:35:34 -08:00
parent 72d5c6fd1b
commit bf8d57c7d1
6 changed files with 296 additions and 209 deletions

View File

@ -15,11 +15,12 @@ from ipam.models import IPAddress, VLAN, VLANGroup
from tenancy.forms import TenancyForm from tenancy.forms import TenancyForm
from tenancy.models import Tenant from tenancy.models import Tenant
from utilities.forms import ( from utilities.forms import (
AnnotatedMultipleChoiceField, APISelect, add_blank_choice, ArrayFieldSelectMultiple, BootstrapMixin, BulkEditForm, AnnotatedMultipleChoiceField, APISelect, APISelectMultiple, add_blank_choice, ArrayFieldSelectMultiple,
BulkEditNullBooleanSelect, ChainedFieldsMixin, ChainedModelChoiceField, ColorSelect, CommentField, ComponentForm, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect, ChainedFieldsMixin, ChainedModelChoiceField,
ConfirmationForm, ContentTypeSelect, CSVChoiceField, ExpandableNameField, FilterChoiceField, ColorSelect, CommentField, ComponentForm, ConfirmationForm, ContentTypeSelect, CSVChoiceField,
FilterTreeNodeMultipleChoiceField, FlexibleModelChoiceField, JSONField, Livesearch, SelectWithPK, SmallTextarea, ExpandableNameField, FilterChoiceField, FilterTreeNodeMultipleChoiceField, FlexibleModelChoiceField,
SlugField, StaticSelect2, BOOLEAN_WITH_BLANK_CHOICES, COLOR_CHOICES, JSONField, Livesearch, SelectWithPK, SmallTextarea, SlugField, StaticSelect2, StaticSelect2Multiple,
BOOLEAN_WITH_BLANK_CHOICES, COLOR_CHOICES,
) )
from virtualization.models import Cluster, ClusterGroup from virtualization.models import Cluster, ClusterGroup
@ -218,15 +219,22 @@ class SiteBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditFor
status = forms.ChoiceField( status = forms.ChoiceField(
choices=add_blank_choice(SITE_STATUS_CHOICES), choices=add_blank_choice(SITE_STATUS_CHOICES),
required=False, required=False,
initial='' initial='',
widget=StaticSelect2()
) )
region = TreeNodeChoiceField( region = TreeNodeChoiceField(
queryset=Region.objects.all(), queryset=Region.objects.all(),
required=False required=False,
widget=APISelect(
api_url="/api/dcim/regions/"
)
) )
tenant = forms.ModelChoiceField( tenant = forms.ModelChoiceField(
queryset=Tenant.objects.all(), queryset=Tenant.objects.all(),
required=False required=False,
widget=APISelect(
api_url="/api/tenancy/tenants",
)
) )
asn = forms.IntegerField( asn = forms.IntegerField(
min_value=1, min_value=1,
@ -240,7 +248,8 @@ class SiteBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditFor
) )
time_zone = TimeZoneFormField( time_zone = TimeZoneFormField(
choices=add_blank_choice(TimeZoneFormField().choices), choices=add_blank_choice(TimeZoneFormField().choices),
required=False required=False,
widget=StaticSelect2()
) )
class Meta: class Meta:
@ -259,18 +268,27 @@ class SiteFilterForm(BootstrapMixin, CustomFieldFilterForm):
choices=SITE_STATUS_CHOICES, choices=SITE_STATUS_CHOICES,
annotate=Site.objects.all(), annotate=Site.objects.all(),
annotate_field='status', annotate_field='status',
required=False required=False,
widget=StaticSelect2Multiple()
) )
region = FilterTreeNodeMultipleChoiceField( region = forms.ModelMultipleChoiceField(
queryset=Region.objects.all(), queryset=Region.objects.all(),
to_field_name='slug', to_field_name='slug',
required=False, required=False,
count_attr='site_count' widget=APISelectMultiple(
api_url="/api/dcim/regions/",
value_field="slug",
)
) )
tenant = FilterChoiceField( tenant = FilterChoiceField(
queryset=Tenant.objects.annotate(filter_count=Count('sites')), queryset=Tenant.objects.annotate(filter_count=Count('sites')),
to_field_name='slug', to_field_name='slug',
null_label='-- None --' null_label='-- None --',
widget=APISelectMultiple(
api_url="/api/tenancy/tenants/",
value_field="slug",
null_option=True,
)
) )
@ -317,7 +335,11 @@ class RackGroupFilterForm(BootstrapMixin, forms.Form):
queryset=Site.objects.annotate( queryset=Site.objects.annotate(
filter_count=Count('rack_groups') filter_count=Count('rack_groups')
), ),
to_field_name='slug' to_field_name='slug',
widget=APISelectMultiple(
api_url="/api/dcim/sites/",
value_field="slug",
)
) )
@ -494,24 +516,40 @@ class RackBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditFor
) )
site = forms.ModelChoiceField( site = forms.ModelChoiceField(
queryset=Site.objects.all(), queryset=Site.objects.all(),
required=False required=False,
widget=APISelect(
api_url="/api/dcim/sites",
filter_for={
'group': 'site_id',
}
)
) )
group = forms.ModelChoiceField( group = forms.ModelChoiceField(
queryset=RackGroup.objects.all(), queryset=RackGroup.objects.all(),
required=False required=False,
widget=APISelect(
api_url="/api/dcim/rack-groups",
)
) )
tenant = forms.ModelChoiceField( tenant = forms.ModelChoiceField(
queryset=Tenant.objects.all(), queryset=Tenant.objects.all(),
required=False required=False,
widget=APISelect(
api_url="/api/tenancy/tenants",
)
) )
status = forms.ChoiceField( status = forms.ChoiceField(
choices=add_blank_choice(RACK_STATUS_CHOICES), choices=add_blank_choice(RACK_STATUS_CHOICES),
required=False, required=False,
initial='' initial='',
widget=StaticSelect2()
) )
role = forms.ModelChoiceField( role = forms.ModelChoiceField(
queryset=RackRole.objects.all(), queryset=RackRole.objects.all(),
required=False required=False,
widget=APISelect(
api_url="/api/dcim/rack-roles",
)
) )
serial = forms.CharField( serial = forms.CharField(
max_length=50, max_length=50,
@ -524,11 +562,13 @@ class RackBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditFor
) )
type = forms.ChoiceField( type = forms.ChoiceField(
choices=add_blank_choice(RACK_TYPE_CHOICES), choices=add_blank_choice(RACK_TYPE_CHOICES),
required=False required=False,
widget=StaticSelect2()
) )
width = forms.ChoiceField( width = forms.ChoiceField(
choices=add_blank_choice(RACK_WIDTH_CHOICES), choices=add_blank_choice(RACK_WIDTH_CHOICES),
required=False required=False,
widget=StaticSelect2()
) )
u_height = forms.IntegerField( u_height = forms.IntegerField(
required=False, required=False,
@ -549,7 +589,8 @@ class RackBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditFor
) )
outer_unit = forms.ChoiceField( outer_unit = forms.ChoiceField(
choices=add_blank_choice(RACK_DIMENSION_UNIT_CHOICES), choices=add_blank_choice(RACK_DIMENSION_UNIT_CHOICES),
required=False required=False,
widget=StaticSelect2()
) )
comments = CommentField( comments = CommentField(
widget=SmallTextarea widget=SmallTextarea
@ -571,7 +612,11 @@ class RackFilterForm(BootstrapMixin, CustomFieldFilterForm):
queryset=Site.objects.annotate( queryset=Site.objects.annotate(
filter_count=Count('racks') filter_count=Count('racks')
), ),
to_field_name='slug' to_field_name='slug',
widget=APISelectMultiple(
api_url="/api/dcim/sites/",
value_field="slug",
)
) )
group_id = FilterChoiceField( group_id = FilterChoiceField(
queryset=RackGroup.objects.select_related( queryset=RackGroup.objects.select_related(
@ -580,27 +625,42 @@ class RackFilterForm(BootstrapMixin, CustomFieldFilterForm):
filter_count=Count('racks') filter_count=Count('racks')
), ),
label='Rack group', label='Rack group',
null_label='-- None --' null_label='-- None --',
widget=APISelectMultiple(
api_url="/api/dcim/rack-groups/",
null_option=True,
)
) )
tenant = FilterChoiceField( tenant = FilterChoiceField(
queryset=Tenant.objects.annotate( queryset=Tenant.objects.annotate(
filter_count=Count('racks') filter_count=Count('racks')
), ),
to_field_name='slug', to_field_name='slug',
null_label='-- None --' null_label='-- None --',
widget=APISelectMultiple(
api_url="/api/tenancy/tenants/",
value_field="slug",
null_option=True,
)
) )
status = AnnotatedMultipleChoiceField( status = AnnotatedMultipleChoiceField(
choices=RACK_STATUS_CHOICES, choices=RACK_STATUS_CHOICES,
annotate=Rack.objects.all(), annotate=Rack.objects.all(),
annotate_field='status', annotate_field='status',
required=False required=False,
widget=StaticSelect2Multiple()
) )
role = FilterChoiceField( role = FilterChoiceField(
queryset=RackRole.objects.annotate( queryset=RackRole.objects.annotate(
filter_count=Count('racks') filter_count=Count('racks')
), ),
to_field_name='slug', to_field_name='slug',
null_label='-- None --' null_label='-- None --',
widget=APISelectMultiple(
api_url="/api/dcim/rack-roles/",
value_field="slug",
null_option=True,
)
) )
@ -620,7 +680,8 @@ class RackReservationForm(BootstrapMixin, TenancyForm, forms.ModelForm):
user = forms.ModelChoiceField( user = forms.ModelChoiceField(
queryset=User.objects.order_by( queryset=User.objects.order_by(
'username' 'username'
) ),
widget=StaticSelect2()
) )
class Meta: class Meta:
@ -655,7 +716,11 @@ class RackReservationFilterForm(BootstrapMixin, forms.Form):
queryset=Site.objects.annotate( queryset=Site.objects.annotate(
filter_count=Count('racks__reservations') filter_count=Count('racks__reservations')
), ),
to_field_name='slug' to_field_name='slug',
widget=APISelectMultiple(
api_url="/api/dcim/sites/",
value_field="slug",
)
) )
group_id = FilterChoiceField( group_id = FilterChoiceField(
queryset=RackGroup.objects.select_related( queryset=RackGroup.objects.select_related(
@ -664,14 +729,23 @@ class RackReservationFilterForm(BootstrapMixin, forms.Form):
filter_count=Count('racks__reservations') filter_count=Count('racks__reservations')
), ),
label='Rack group', label='Rack group',
null_label='-- None --' null_label='-- None --',
widget=APISelectMultiple(
api_url="/api/dcim/rack-groups/",
null_option=True,
)
) )
tenant = FilterChoiceField( tenant = FilterChoiceField(
queryset=Tenant.objects.annotate( queryset=Tenant.objects.annotate(
filter_count=Count('rackreservations') filter_count=Count('rackreservations')
), ),
to_field_name='slug', to_field_name='slug',
null_label='-- None --' null_label='-- None --',
widget=APISelectMultiple(
api_url="/api/tenancy/tenants/",
value_field="slug",
null_option=True,
)
) )
@ -684,11 +758,15 @@ class RackReservationBulkEditForm(BootstrapMixin, BulkEditForm):
queryset=User.objects.order_by( queryset=User.objects.order_by(
'username' 'username'
), ),
required=False required=False,
widget=StaticSelect2()
) )
tenant = forms.ModelChoiceField( tenant = forms.ModelChoiceField(
queryset=Tenant.objects.all(), queryset=Tenant.objects.all(),
required=False required=False,
widget=APISelect(
api_url="/api/tenancy/tenant",
)
) )
description = forms.CharField( description = forms.CharField(
max_length=100, max_length=100,
@ -782,7 +860,10 @@ class DeviceTypeBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkE
) )
manufacturer = forms.ModelChoiceField( manufacturer = forms.ModelChoiceField(
queryset=Manufacturer.objects.all(), queryset=Manufacturer.objects.all(),
required=False required=False,
widget=APISelect(
api_url="/api/dcim/manufactureres"
)
) )
u_height = forms.IntegerField( u_height = forms.IntegerField(
min_value=1, min_value=1,
@ -808,54 +889,58 @@ class DeviceTypeFilterForm(BootstrapMixin, CustomFieldFilterForm):
queryset=Manufacturer.objects.annotate( queryset=Manufacturer.objects.annotate(
filter_count=Count('device_types') filter_count=Count('device_types')
), ),
to_field_name='slug' to_field_name='slug',
widget=APISelectMultiple(
api_url="/api/dcim/manufacturers/",
value_field="slug",
)
) )
subdevice_role = forms.NullBooleanField( subdevice_role = forms.NullBooleanField(
required=False, required=False,
label='Subdevice role', label='Subdevice role',
widget=forms.Select( widget=StaticSelect2(
choices=add_blank_choice(SUBDEVICE_ROLE_CHOICES) choices=add_blank_choice(SUBDEVICE_ROLE_CHOICES)
) )
) )
console_ports = forms.NullBooleanField( console_ports = forms.NullBooleanField(
required=False, required=False,
label='Has console ports', label='Has console ports',
widget=forms.Select( widget=StaticSelect2(
choices=BOOLEAN_WITH_BLANK_CHOICES choices=BOOLEAN_WITH_BLANK_CHOICES
) )
) )
console_server_ports = forms.NullBooleanField( console_server_ports = forms.NullBooleanField(
required=False, required=False,
label='Has console server ports', label='Has console server ports',
widget=forms.Select( widget=StaticSelect2(
choices=BOOLEAN_WITH_BLANK_CHOICES choices=BOOLEAN_WITH_BLANK_CHOICES
) )
) )
power_ports = forms.NullBooleanField( power_ports = forms.NullBooleanField(
required=False, required=False,
label='Has power ports', label='Has power ports',
widget=forms.Select( widget=StaticSelect2(
choices=BOOLEAN_WITH_BLANK_CHOICES choices=BOOLEAN_WITH_BLANK_CHOICES
) )
) )
power_outlets = forms.NullBooleanField( power_outlets = forms.NullBooleanField(
required=False, required=False,
label='Has power outlets', label='Has power outlets',
widget=forms.Select( widget=StaticSelect2(
choices=BOOLEAN_WITH_BLANK_CHOICES choices=BOOLEAN_WITH_BLANK_CHOICES
) )
) )
interfaces = forms.NullBooleanField( interfaces = forms.NullBooleanField(
required=False, required=False,
label='Has interfaces', label='Has interfaces',
widget=forms.Select( widget=StaticSelect2(
choices=BOOLEAN_WITH_BLANK_CHOICES choices=BOOLEAN_WITH_BLANK_CHOICES
) )
) )
pass_through_ports = forms.NullBooleanField( pass_through_ports = forms.NullBooleanField(
required=False, required=False,
label='Has pass-through ports', label='Has pass-through ports',
widget=forms.Select( widget=StaticSelect2(
choices=BOOLEAN_WITH_BLANK_CHOICES choices=BOOLEAN_WITH_BLANK_CHOICES
) )
) )
@ -971,7 +1056,8 @@ class InterfaceTemplateBulkEditForm(BootstrapMixin, BulkEditForm):
) )
form_factor = forms.ChoiceField( form_factor = forms.ChoiceField(
choices=add_blank_choice(IFACE_FF_CHOICES), choices=add_blank_choice(IFACE_FF_CHOICES),
required=False required=False,
widget=StaticSelect2()
) )
mgmt_only = forms.NullBooleanField( mgmt_only = forms.NullBooleanField(
required=False, required=False,
@ -1001,7 +1087,8 @@ class FrontPortTemplateCreateForm(ComponentForm):
label='Name' label='Name'
) )
type = forms.ChoiceField( type = forms.ChoiceField(
choices=PORT_TYPE_CHOICES choices=PORT_TYPE_CHOICES,
widget=StaticSelect2()
) )
rear_port_set = forms.MultipleChoiceField( rear_port_set = forms.MultipleChoiceField(
choices=[], choices=[],
@ -1539,25 +1626,38 @@ class DeviceBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditF
device_type = forms.ModelChoiceField( device_type = forms.ModelChoiceField(
queryset=DeviceType.objects.all(), queryset=DeviceType.objects.all(),
required=False, required=False,
label='Type' label='Type',
widget=APISelect(
api_url="/api/dcim/device-types"
)
) )
device_role = forms.ModelChoiceField( device_role = forms.ModelChoiceField(
queryset=DeviceRole.objects.all(), queryset=DeviceRole.objects.all(),
required=False, required=False,
label='Role' label='Role',
widget=APISelect(
api_url="/api/dcim/device-roles"
)
) )
tenant = forms.ModelChoiceField( tenant = forms.ModelChoiceField(
queryset=Tenant.objects.all(), queryset=Tenant.objects.all(),
required=False required=False,
widget=APISelect(
api_url="/api/tenancy/tenants"
)
) )
platform = forms.ModelChoiceField( platform = forms.ModelChoiceField(
queryset=Platform.objects.all(), queryset=Platform.objects.all(),
required=False required=False,
widget=APISelect(
api_url="/api/dcim/platforms"
)
) )
status = forms.ChoiceField( status = forms.ChoiceField(
choices=add_blank_choice(DEVICE_STATUS_CHOICES), choices=add_blank_choice(DEVICE_STATUS_CHOICES),
required=False, required=False,
initial='' initial='',
widget=StaticSelect2()
) )
serial = forms.CharField( serial = forms.CharField(
max_length=50, max_length=50,
@ -1577,16 +1677,31 @@ class DeviceFilterForm(BootstrapMixin, CustomFieldFilterForm):
required=False, required=False,
label='Search' label='Search'
) )
region = FilterTreeNodeMultipleChoiceField( region = FilterChoiceField(
queryset=Region.objects.all(), queryset=Region.objects.all(),
to_field_name='slug', to_field_name='slug',
required=False, required=False,
widget=APISelectMultiple(
api_url="/api/dcim/regions/",
value_field="slug",
filter_for={
'site': 'region'
}
)
) )
site = FilterChoiceField( site = FilterChoiceField(
queryset=Site.objects.annotate( queryset=Site.objects.annotate(
filter_count=Count('devices') filter_count=Count('devices')
), ),
to_field_name='slug', to_field_name='slug',
widget=APISelectMultiple(
api_url="/api/dcim/sites/",
value_field="slug",
filter_for={
'rack_group_id': 'site',
'rack_id': 'site',
}
)
) )
rack_group_id = FilterChoiceField( rack_group_id = FilterChoiceField(
queryset=RackGroup.objects.select_related( queryset=RackGroup.objects.select_related(
@ -1595,6 +1710,12 @@ class DeviceFilterForm(BootstrapMixin, CustomFieldFilterForm):
filter_count=Count('racks__devices') filter_count=Count('racks__devices')
), ),
label='Rack group', label='Rack group',
widget=APISelectMultiple(
api_url="/api/dcim/rack-groups/",
filter_for={
'rack_id': 'rack_group_id',
}
)
) )
rack_id = FilterChoiceField( rack_id = FilterChoiceField(
queryset=Rack.objects.annotate( queryset=Rack.objects.annotate(
@ -1602,12 +1723,21 @@ class DeviceFilterForm(BootstrapMixin, CustomFieldFilterForm):
), ),
label='Rack', label='Rack',
null_label='-- None --', null_label='-- None --',
widget=APISelectMultiple(
api_url="/api/dcim/racks/",
null_option=True,
)
) )
role = FilterChoiceField( role = FilterChoiceField(
queryset=DeviceRole.objects.annotate( queryset=DeviceRole.objects.annotate(
filter_count=Count('devices') filter_count=Count('devices')
), ),
to_field_name='slug', to_field_name='slug',
widget=APISelectMultiple(
api_url="/api/dcim/device-roles/",
value_field="slug",
null_option=True,
)
) )
tenant = FilterChoiceField( tenant = FilterChoiceField(
queryset=Tenant.objects.annotate( queryset=Tenant.objects.annotate(
@ -1615,10 +1745,21 @@ class DeviceFilterForm(BootstrapMixin, CustomFieldFilterForm):
), ),
to_field_name='slug', to_field_name='slug',
null_label='-- None --', null_label='-- None --',
widget=APISelectMultiple(
api_url="/api/tenancy/tenants/",
value_field="slug",
null_option=True,
)
) )
manufacturer_id = FilterChoiceField( manufacturer_id = FilterChoiceField(
queryset=Manufacturer.objects.all(), queryset=Manufacturer.objects.all(),
label='Manufacturer' label='Manufacturer',
widget=APISelectMultiple(
api_url="/api/dcim/manufacturers/",
filter_for={
'device_type_id': 'manufacturer_id',
}
)
) )
device_type_id = FilterChoiceField( device_type_id = FilterChoiceField(
queryset=DeviceType.objects.select_related( queryset=DeviceType.objects.select_related(
@ -1629,6 +1770,10 @@ class DeviceFilterForm(BootstrapMixin, CustomFieldFilterForm):
filter_count=Count('instances'), filter_count=Count('instances'),
), ),
label='Model', label='Model',
widget=APISelectMultiple(
api_url="/api/dcim/device-types/",
display_field="model",
)
) )
platform = FilterChoiceField( platform = FilterChoiceField(
queryset=Platform.objects.annotate( queryset=Platform.objects.annotate(
@ -1636,12 +1781,18 @@ class DeviceFilterForm(BootstrapMixin, CustomFieldFilterForm):
), ),
to_field_name='slug', to_field_name='slug',
null_label='-- None --', null_label='-- None --',
widget=APISelectMultiple(
api_url="/api/dcim/platforms/",
value_field="slug",
null_option=True,
)
) )
status = AnnotatedMultipleChoiceField( status = AnnotatedMultipleChoiceField(
choices=DEVICE_STATUS_CHOICES, choices=DEVICE_STATUS_CHOICES,
annotate=Device.objects.all(), annotate=Device.objects.all(),
annotate_field='status', annotate_field='status',
required=False required=False,
widget=StaticSelect2Multiple()
) )
mac_address = forms.CharField( mac_address = forms.CharField(
required=False, required=False,
@ -1650,49 +1801,49 @@ class DeviceFilterForm(BootstrapMixin, CustomFieldFilterForm):
has_primary_ip = forms.NullBooleanField( has_primary_ip = forms.NullBooleanField(
required=False, required=False,
label='Has a primary IP', label='Has a primary IP',
widget=forms.Select( widget=StaticSelect2(
choices=BOOLEAN_WITH_BLANK_CHOICES choices=BOOLEAN_WITH_BLANK_CHOICES
) )
) )
console_ports = forms.NullBooleanField( console_ports = forms.NullBooleanField(
required=False, required=False,
label='Has console ports', label='Has console ports',
widget=forms.Select( widget=StaticSelect2(
choices=BOOLEAN_WITH_BLANK_CHOICES choices=BOOLEAN_WITH_BLANK_CHOICES
) )
) )
console_server_ports = forms.NullBooleanField( console_server_ports = forms.NullBooleanField(
required=False, required=False,
label='Has console server ports', label='Has console server ports',
widget=forms.Select( widget=StaticSelect2(
choices=BOOLEAN_WITH_BLANK_CHOICES choices=BOOLEAN_WITH_BLANK_CHOICES
) )
) )
power_ports = forms.NullBooleanField( power_ports = forms.NullBooleanField(
required=False, required=False,
label='Has power ports', label='Has power ports',
widget=forms.Select( widget=StaticSelect2(
choices=BOOLEAN_WITH_BLANK_CHOICES choices=BOOLEAN_WITH_BLANK_CHOICES
) )
) )
power_outlets = forms.NullBooleanField( power_outlets = forms.NullBooleanField(
required=False, required=False,
label='Has power outlets', label='Has power outlets',
widget=forms.Select( widget=StaticSelect2(
choices=BOOLEAN_WITH_BLANK_CHOICES choices=BOOLEAN_WITH_BLANK_CHOICES
) )
) )
interfaces = forms.NullBooleanField( interfaces = forms.NullBooleanField(
required=False, required=False,
label='Has interfaces', label='Has interfaces',
widget=forms.Select( widget=StaticSelect2(
choices=BOOLEAN_WITH_BLANK_CHOICES choices=BOOLEAN_WITH_BLANK_CHOICES
) )
) )
pass_through_ports = forms.NullBooleanField( pass_through_ports = forms.NullBooleanField(
required=False, required=False,
label='Has pass-through ports', label='Has pass-through ports',
widget=forms.Select( widget=StaticSelect2(
choices=BOOLEAN_WITH_BLANK_CHOICES choices=BOOLEAN_WITH_BLANK_CHOICES
) )
) )
@ -1714,7 +1865,8 @@ class DeviceBulkAddComponentForm(BootstrapMixin, forms.Form):
class DeviceBulkAddInterfaceForm(DeviceBulkAddComponentForm): class DeviceBulkAddInterfaceForm(DeviceBulkAddComponentForm):
form_factor = forms.ChoiceField( form_factor = forms.ChoiceField(
choices=IFACE_FF_CHOICES choices=IFACE_FF_CHOICES,
widget=StaticSelect2()
) )
enabled = forms.BooleanField( enabled = forms.BooleanField(
required=False, required=False,
@ -1941,7 +2093,7 @@ class InterfaceAssignVLANsForm(BootstrapMixin, forms.ModelForm):
vlans = forms.MultipleChoiceField( vlans = forms.MultipleChoiceField(
choices=[], choices=[],
label='VLANs', label='VLANs',
widget=forms.SelectMultiple( widget=StaticSelect2Multiple(
attrs={ attrs={
'size': 20, 'size': 20,
} }
@ -2093,7 +2245,8 @@ class InterfaceBulkEditForm(BootstrapMixin, AddRemoveTagsForm, BulkEditForm):
) )
form_factor = forms.ChoiceField( form_factor = forms.ChoiceField(
choices=add_blank_choice(IFACE_FF_CHOICES), choices=add_blank_choice(IFACE_FF_CHOICES),
required=False required=False,
widget=StaticSelect2()
) )
enabled = forms.NullBooleanField( enabled = forms.NullBooleanField(
required=False, required=False,
@ -2102,7 +2255,8 @@ class InterfaceBulkEditForm(BootstrapMixin, AddRemoveTagsForm, BulkEditForm):
lag = forms.ModelChoiceField( lag = forms.ModelChoiceField(
queryset=Interface.objects.all(), queryset=Interface.objects.all(),
required=False, required=False,
label='Parent LAG' label='Parent LAG',
widget=StaticSelect2()
) )
mtu = forms.IntegerField( mtu = forms.IntegerField(
required=False, required=False,
@ -2121,7 +2275,8 @@ class InterfaceBulkEditForm(BootstrapMixin, AddRemoveTagsForm, BulkEditForm):
) )
mode = forms.ChoiceField( mode = forms.ChoiceField(
choices=add_blank_choice(IFACE_MODE_CHOICES), choices=add_blank_choice(IFACE_MODE_CHOICES),
required=False required=False,
widget=StaticSelect2()
) )
class Meta: class Meta:
@ -2199,7 +2354,7 @@ class FrontPortCreateForm(ComponentForm):
rear_port_set = forms.MultipleChoiceField( rear_port_set = forms.MultipleChoiceField(
choices=[], choices=[],
label='Rear ports', label='Rear ports',
help_text='Select one rear port assignment for each front port being created.' help_text='Select one rear port assignment for each front port being created.',
) )
description = forms.CharField( description = forms.CharField(
required=False required=False
@ -2546,7 +2701,8 @@ class CableBulkEditForm(BootstrapMixin, BulkEditForm):
type = forms.ChoiceField( type = forms.ChoiceField(
choices=add_blank_choice(CABLE_TYPE_CHOICES), choices=add_blank_choice(CABLE_TYPE_CHOICES),
required=False, required=False,
initial='' initial='',
widget=StaticSelect2()
) )
status = forms.ChoiceField( status = forms.ChoiceField(
choices=add_blank_choice(CONNECTION_STATUS_CHOICES), choices=add_blank_choice(CONNECTION_STATUS_CHOICES),
@ -2555,7 +2711,8 @@ class CableBulkEditForm(BootstrapMixin, BulkEditForm):
) )
label = forms.CharField( label = forms.CharField(
max_length=100, max_length=100,
required=False required=False,
widget=StaticSelect2()
) )
color = forms.CharField( color = forms.CharField(
max_length=6, max_length=6,
@ -2569,7 +2726,8 @@ class CableBulkEditForm(BootstrapMixin, BulkEditForm):
length_unit = forms.ChoiceField( length_unit = forms.ChoiceField(
choices=add_blank_choice(CABLE_LENGTH_UNIT_CHOICES), choices=add_blank_choice(CABLE_LENGTH_UNIT_CHOICES),
required=False, required=False,
initial='' initial='',
widget=StaticSelect2()
) )
class Meta: class Meta:
@ -2594,17 +2752,15 @@ class CableFilterForm(BootstrapMixin, forms.Form):
required=False, required=False,
label='Search' label='Search'
) )
type = AnnotatedMultipleChoiceField( type = forms.MultipleChoiceField(
choices=CABLE_TYPE_CHOICES, choices=CABLE_TYPE_CHOICES,
annotate=Cable.objects.all(), required=False,
annotate_field='type', widget=StaticSelect2()
required=False
) )
color = AnnotatedMultipleChoiceField( color = forms.CharField(
choices=COLOR_CHOICES, max_length=6,
annotate=Cable.objects.all(), required=False,
annotate_field='color', widget=ColorSelect()
required=False
) )

View File

@ -67,6 +67,7 @@ $(document).ready(function() {
form.submit(); form.submit();
}); });
// Parse URLs which may contain variable refrences to other field values
function parseURL(url) { function parseURL(url) {
var filter_regex = /\{\{([a-z_]+)\}\}/g; var filter_regex = /\{\{([a-z_]+)\}\}/g;
var match; var match;
@ -86,8 +87,8 @@ $(document).ready(function() {
return rendered_url return rendered_url
} }
// Assign color picker selection classes
function colorPickerClassCopy(data, container) { function colorPickerClassCopy(data, container) {
console.log("hello");
if (data.element) { if (data.element) {
$(container).addClass($(data.element).attr("class")); $(container).addClass($(data.element).attr("class"));
} }
@ -108,23 +109,27 @@ $(document).ready(function() {
placeholder: "---------", placeholder: "---------",
}) })
// API backed single selection // API backed selection
// Includes live search and chained fields // Includes live search and chained fields
// The `multiple` setting may be controled via a data-* attribute
$('.netbox-select2-api').select2({ $('.netbox-select2-api').select2({
allowClear: true, allowClear: true,
placeholder: "---------", placeholder: "---------",
ajax: { ajax: {
delay: 500, delay: 500,
url: function(params) { url: function(params) {
var element = this[0]; var element = this[0];
var url = element.getAttribute("data-url"); var url = parseURL(element.getAttribute("data-url"));
url = parseURL(url);
if (url.includes("{{")) { if (url.includes("{{")) {
// URL is not furry rendered yet, abort the request // URL is not fully rendered yet, abort the request
return null; return false;
} }
return url; return url;
}, },
data: function(params) { data: function(params) {
var element = this[0]; var element = this[0];
// Paging // Paging
@ -136,29 +141,35 @@ $(document).ready(function() {
limit: 50, limit: 50,
offset: offset, offset: offset,
}; };
// filter-for fields from a chain // filter-for fields from a chain
var attr_name = "data-filter-for-" + $(element).attr("name"); var attr_name = "data-filter-for-" + $(element).attr("name");
var form = $(element).closest('form'); var form = $(element).closest('form');
var filter_for_elements = form.find("select[" + attr_name + "]"); var filter_for_elements = form.find("select[" + attr_name + "]");
filter_for_elements.each(function(index, filter_for_element) { filter_for_elements.each(function(index, filter_for_element) {
var param_name = $(filter_for_element).attr(attr_name); var param_name = $(filter_for_element).attr(attr_name);
var value = $(filter_for_element).val(); var value = $(filter_for_element).val();
if (param_name && value) { if (param_name && value) {
parameters[param_name] = $(filter_for_element).val(); parameters[param_name] = value;
} }
}); });
// Conditional query params // Conditional query params
$.each(element.attributes, function(index, attr){ $.each(element.attributes, function(index, attr){
if (attr.name.includes("data-conditional-query-param-")){ if (attr.name.includes("data-conditional-query-param-")){
var conditional = attr.name.split("data-conditional-query-param-")[1].split("__"); var conditional = attr.name.split("data-conditional-query-param-")[1].split("__");
var field = $("#id_" + conditional[0]); var field = $("#id_" + conditional[0]);
var field_value = conditional[1]; var field_value = conditional[1];
if ($('option:selected', field).attr('api-value') === field_value){ if ($('option:selected', field).attr('api-value') === field_value){
var _val = attr.value.split("="); var _val = attr.value.split("=");
parameters[_val[0]] = _val[1]; parameters[_val[0]] = _val[1];
} }
} }
}) })
// Additional query params // Additional query params
$.each(element.attributes, function(index, attr){ $.each(element.attributes, function(index, attr){
if (attr.name.includes("data-additional-query-param-")){ if (attr.name.includes("data-additional-query-param-")){
@ -166,14 +177,28 @@ $(document).ready(function() {
parameters[param_name] = attr.value; parameters[param_name] = attr.value;
} }
}) })
return parameters;
// This will handle params with multiple values (i.e. for list filter forms)
return $.param(parameters, true);
}, },
processResults: function (data) { processResults: function (data) {
var element = this.$element[0]; var element = this.$element[0];
var results = $.map(data.results, function (obj) { var results = $.map(data.results, function (obj) {
obj.text = obj.name || obj[element.getAttribute('display-field')]; obj.text = obj[element.getAttribute('display-field')] || obj.name;
obj.id = obj[element.getAttribute('value-field')] || obj.id;
return obj; return obj;
}); });
// Handle the null option
if (element.getAttribute('data-null-option')) {
var null_option = $(element).children()[0]
results.unshift({
id: null_option.value,
text: null_option.text
});
}
// Check if there are more results to page // Check if there are more results to page
var page = data.next !== null; var page = data.next !== null;
return { return {
@ -208,9 +233,11 @@ $(document).ready(function() {
multiple: true, multiple: true,
allowClear: true, allowClear: true,
placeholder: "Tags", placeholder: "Tags",
ajax: { ajax: {
delay: 250, delay: 250,
url: "/api/extras/tags/", url: "/api/extras/tags/",
data: function(params) { data: function(params) {
// paging // paging
var offset = params.page * 50 || 0; var offset = params.page * 50 || 0;
@ -222,6 +249,7 @@ $(document).ready(function() {
}; };
return parameters; return parameters;
}, },
processResults: function (data) { processResults: function (data) {
var results = $.map(data.results, function (obj) { var results = $.map(data.results, function (obj) {
return { return {
@ -229,6 +257,7 @@ $(document).ready(function() {
text: obj.name text: obj.name
} }
}); });
// Check if there are more results to page // Check if there are more results to page
var page = data.next !== null; var page = data.next !== null;
return { return {

View File

@ -20,88 +20,3 @@
</div> </div>
</div> </div>
{% endblock %} {% endblock %}
{% block javascript %}
<script type="text/javascript">
$(document).ready(function() {
var site_list = $('#id_site');
var rack_group_list = $('#id_rack_group_id');
var rack_list = $('#id_rack_id');
var manufacturer_list = $('#id_manufacturer_id');
var model_list = $('#id_device_type_id');
// Update device type options based on selected manufacturer
manufacturer_list.change(function() {
var selected_manufacturers = $(this).val();
if (selected_manufacturers) {
model_list.empty();
$.ajax({
url: netbox_api_path + 'dcim/device-types/?limit=500&manufacturer_id=' + selected_manufacturers.join('&manufacturer_id='),
dataType: 'json',
success: function (response, status) {
$.each(response["results"], function (index, device_type) {
var option = $("<option></option>").attr("value", device_type.id).text(device_type.model + " (" + device_type.instance_count + ")");
model_list.append(option);
});
}
});
}
});
// Update rack group and rack options based on selected site
site_list.change(function() {
var selected_sites = $(this).val();
if (selected_sites) {
// Update rack group options
rack_group_list.empty();
$.ajax({
url: netbox_api_path + 'dcim/rack-groups/?limit=500&site=' + selected_sites.join('&site='),
dataType: 'json',
success: function (response, status) {
$.each(response["results"], function (index, group) {
var option = $("<option></option>").attr("value", group.id).text(group.name);
rack_group_list.append(option);
});
}
});
// Update rack options
rack_list.empty();
rack_list.append($("<option></option>").attr("value", "0").text("None"));
$.ajax({
url: netbox_api_path + 'dcim/racks/?limit=500&site=' + selected_sites.join('&site='),
dataType: 'json',
success: function (response, status) {
$.each(response["results"], function (index, rack) {
var option = $("<option></option>").attr("value", rack.id).text(rack.display_name);
rack_list.append(option);
});
}
});
}
});
// Update rack options based on selected rack group
rack_group_list.change(function() {
var selected_rack_groups = $(this).val();
if (selected_rack_groups) {
rack_list.empty();
$.ajax({
url: netbox_api_path + 'dcim/racks/?limit=500&group_id=' + selected_rack_groups.join('&group_id='),
dataType: 'json',
success: function (response, status) {
$.each(response["results"], function (index, rack) {
var option = $("<option></option>").attr("value", rack.id).text(rack.display_name);
rack_list.append(option);
});
}
});
}
});
});
</script>
{% endblock %}

View File

@ -1,29 +0,0 @@
<script type="text/javascript">
$(document).ready(function() {
var site_list = $('#id_site');
var rack_group_list = $('#id_group_id');
// Update rack group and rack options based on selected site
site_list.change(function() {
var selected_sites = $(this).val();
if (selected_sites) {
// Update rack group options
rack_group_list.empty();
$.ajax({
url: netbox_api_path + 'dcim/rack-groups/?limit=500&site=' + selected_sites.join('&site='),
dataType: 'json',
success: function (response, status) {
$.each(response["results"], function (index, group) {
var option = $("<option></option>").attr("value", group.id).text(group.name);
rack_group_list.append(option);
});
}
});
}
});
});
</script>

View File

@ -20,8 +20,3 @@
</div> </div>
</div> </div>
{% endblock %} {% endblock %}
{% block javascript %}
{% include 'dcim/inc/filter_rack_group.html' %}
{% endblock %}

View File

@ -186,6 +186,7 @@ class BulkEditNullBooleanSelect(forms.NullBooleanSelect):
('2', 'Yes'), ('2', 'Yes'),
('3', 'No'), ('3', 'No'),
) )
self.attrs['class'] = 'netbox-select2-static'
class SelectWithDisabled(forms.Select): class SelectWithDisabled(forms.Select):
@ -223,6 +224,14 @@ class StaticSelect2(SelectWithDisabled):
self.attrs['data-filter-for-{}'.format(name)] = value self.attrs['data-filter-for-{}'.format(name)] = value
class StaticSelect2Multiple(StaticSelect2, forms.SelectMultiple):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.attrs['data-multiple'] = 1
class SelectWithPK(StaticSelect2): class SelectWithPK(StaticSelect2):
""" """
Include the primary key of each option in the option label (e.g. "Router7 (4721)"). Include the primary key of each option in the option label (e.g. "Router7 (4721)").
@ -265,6 +274,7 @@ class APISelect(SelectWithDisabled):
:param api_url: API URL :param api_url: API URL
:param display_field: (Optional) Field to display for child in selection list. Defaults to `name`. :param display_field: (Optional) Field to display for child in selection list. Defaults to `name`.
:param value_field: (Optional) Field to use for the option value in selection list. Defaults to `id`.
:param disabled_indicator: (Optional) Mark option as disabled if this field equates true. :param disabled_indicator: (Optional) Mark option as disabled if this field equates true.
:param filter_for: (Optional) A dict of chained form fields for which this field is a filter. The key is the :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. name of the filter-for field (child field) and the value is the name of the query param filter.
@ -273,18 +283,21 @@ class APISelect(SelectWithDisabled):
If the provided field value is selected for the given field, the URL query param will be appended to If the provided field value is selected for the given field, the URL query param will be appended to
the rendered URL. The value is the in the from `<param_name>=<param_value>`. This is useful in cases where the rendered URL. The value is the in the from `<param_name>=<param_value>`. This is useful in cases where
a particular field value dictates an additional API filter. a particular field value dictates an additional API filter.
:param additional_query_params: A dict of query params to append to the API request. The key is the name :param additional_query_params: Optional) A dict of query params to append to the API request. The key is the
of the query param and the value if the query param's value. name of the query param and the value if the query param's value.
:param null_option: If true, include the static null option in the selection list.
""" """
def __init__( def __init__(
self, self,
api_url, api_url,
display_field=None, display_field=None,
value_field=None,
disabled_indicator=None, disabled_indicator=None,
filter_for=None, filter_for=None,
conditional_query_params=None, conditional_query_params=None,
additional_query_params=None, additional_query_params=None,
null_option=False,
*args, *args,
**kwargs **kwargs
): ):
@ -295,6 +308,8 @@ class APISelect(SelectWithDisabled):
self.attrs['data-url'] = '/{}{}'.format(settings.BASE_PATH, api_url.lstrip('/')) # Inject BASE_PATH self.attrs['data-url'] = '/{}{}'.format(settings.BASE_PATH, api_url.lstrip('/')) # Inject BASE_PATH
if display_field: if display_field:
self.attrs['display-field'] = display_field self.attrs['display-field'] = display_field
if value_field:
self.attrs['value-field'] = value_field
if disabled_indicator: if disabled_indicator:
self.attrs['disabled-indicator'] = disabled_indicator self.attrs['disabled-indicator'] = disabled_indicator
if filter_for: if filter_for:
@ -306,6 +321,8 @@ class APISelect(SelectWithDisabled):
if additional_query_params: if additional_query_params:
for key, value in additional_query_params.items(): for key, value in additional_query_params.items():
self.add_additional_query_param(key, value) self.add_additional_query_param(key, value)
if null_option:
self.attrs['data-null-option'] = 1
def add_filter_for(self, name, value): def add_filter_for(self, name, value):
""" """
@ -336,8 +353,12 @@ class APISelect(SelectWithDisabled):
self.attrs['data-conditional-query-param-{}'.format(condition)] = value self.attrs['data-conditional-query-param-{}'.format(condition)] = value
class APISelectMultiple(APISelect): class APISelectMultiple(APISelect, forms.SelectMultiple):
allow_multiple_selected = True
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.attrs['data-multiple'] = 1
class Livesearch(forms.TextInput): class Livesearch(forms.TextInput):