Deprecate collapsible advanced search and re-implement field-based filtering on object views

This commit is contained in:
checktheroads 2021-08-01 21:24:22 -07:00
parent 0b09365d0d
commit 863048cda2
20 changed files with 523 additions and 249 deletions

View File

@ -113,7 +113,8 @@ class ProviderFilterForm(BootstrapMixin, CustomFieldModelFilterForm):
region_id = DynamicModelMultipleChoiceField(
queryset=Region.objects.all(),
required=False,
label=_('Region')
label=_('Region'),
fetch_trigger='open'
)
site_id = DynamicModelMultipleChoiceField(
queryset=Site.objects.all(),
@ -121,7 +122,8 @@ class ProviderFilterForm(BootstrapMixin, CustomFieldModelFilterForm):
query_params={
'region_id': '$region_id'
},
label=_('Site')
label=_('Site'),
fetch_trigger='open'
)
asn = forms.IntegerField(
required=False,
@ -198,7 +200,8 @@ class ProviderNetworkFilterForm(BootstrapMixin, CustomFieldModelFilterForm):
provider_id = DynamicModelMultipleChoiceField(
queryset=Provider.objects.all(),
required=False,
label=_('Provider')
label=_('Provider'),
fetch_trigger='open'
)
tag = TagFilterField(model)
@ -368,12 +371,14 @@ class CircuitFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldModelFilte
type_id = DynamicModelMultipleChoiceField(
queryset=CircuitType.objects.all(),
required=False,
label=_('Type')
label=_('Type'),
fetch_trigger='open'
)
provider_id = DynamicModelMultipleChoiceField(
queryset=Provider.objects.all(),
required=False,
label=_('Provider')
label=_('Provider'),
fetch_trigger='open'
)
provider_network_id = DynamicModelMultipleChoiceField(
queryset=ProviderNetwork.objects.all(),
@ -381,7 +386,8 @@ class CircuitFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldModelFilte
query_params={
'provider_id': '$provider_id'
},
label=_('Provider network')
label=_('Provider network'),
fetch_trigger='open'
)
status = forms.MultipleChoiceField(
choices=CircuitStatusChoices,
@ -391,7 +397,8 @@ class CircuitFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldModelFilte
region_id = DynamicModelMultipleChoiceField(
queryset=Region.objects.all(),
required=False,
label=_('Region')
label=_('Region'),
fetch_trigger='open'
)
site_id = DynamicModelMultipleChoiceField(
queryset=Site.objects.all(),
@ -399,7 +406,8 @@ class CircuitFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldModelFilte
query_params={
'region_id': '$region_id'
},
label=_('Site')
label=_('Site'),
fetch_trigger='open'
)
commit_rate = forms.IntegerField(
required=False,

View File

@ -71,12 +71,14 @@ class DeviceComponentFilterForm(BootstrapMixin, CustomFieldModelFilterForm):
region_id = DynamicModelMultipleChoiceField(
queryset=Region.objects.all(),
required=False,
label=_('Region')
label=_('Region'),
fetch_trigger='open'
)
site_group_id = DynamicModelMultipleChoiceField(
queryset=SiteGroup.objects.all(),
required=False,
label=_('Site group')
label=_('Site group'),
fetch_trigger='open'
)
site_id = DynamicModelMultipleChoiceField(
queryset=Site.objects.all(),
@ -84,7 +86,8 @@ class DeviceComponentFilterForm(BootstrapMixin, CustomFieldModelFilterForm):
query_params={
'region_id': '$region_id'
},
label=_('Site')
label=_('Site'),
fetch_trigger='open'
)
device_id = DynamicModelMultipleChoiceField(
queryset=Device.objects.all(),
@ -92,7 +95,8 @@ class DeviceComponentFilterForm(BootstrapMixin, CustomFieldModelFilterForm):
query_params={
'site_id': '$site_id'
},
label=_('Device')
label=_('Device'),
fetch_trigger='open'
)
@ -457,17 +461,19 @@ class SiteFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldModelFilterFo
status = forms.MultipleChoiceField(
choices=SiteStatusChoices,
required=False,
widget=StaticSelectMultiple()
widget=StaticSelectMultiple(),
)
region_id = DynamicModelMultipleChoiceField(
queryset=Region.objects.all(),
required=False,
label=_('Region')
label=_('Region'),
fetch_trigger='open'
)
group_id = DynamicModelMultipleChoiceField(
queryset=SiteGroup.objects.all(),
required=False,
label=_('Group')
label=_('Group'),
fetch_trigger='open'
)
tag = TagFilterField(model)
@ -565,7 +571,8 @@ class LocationFilterForm(BootstrapMixin, CustomFieldModelFilterForm):
region_id = DynamicModelMultipleChoiceField(
queryset=Region.objects.all(),
required=False,
label=_('Region')
label=_('Region'),
fetch_trigger='open'
)
site_id = DynamicModelMultipleChoiceField(
queryset=Site.objects.all(),
@ -573,7 +580,8 @@ class LocationFilterForm(BootstrapMixin, CustomFieldModelFilterForm):
query_params={
'region_id': '$region_id'
},
label=_('Site')
label=_('Site'),
fetch_trigger='open'
)
parent_id = DynamicModelMultipleChoiceField(
queryset=Location.objects.all(),
@ -582,7 +590,8 @@ class LocationFilterForm(BootstrapMixin, CustomFieldModelFilterForm):
'region_id': '$region_id',
'site_id': '$site_id',
},
label=_('Parent')
label=_('Parent'),
fetch_trigger='open'
)
@ -862,7 +871,8 @@ class RackFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldModelFilterFo
region_id = DynamicModelMultipleChoiceField(
queryset=Region.objects.all(),
required=False,
label=_('Region')
label=_('Region'),
fetch_trigger='open'
)
site_id = DynamicModelMultipleChoiceField(
queryset=Site.objects.all(),
@ -870,7 +880,8 @@ class RackFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldModelFilterFo
query_params={
'region_id': '$region_id'
},
label=_('Site')
label=_('Site'),
fetch_trigger='open'
)
location_id = DynamicModelMultipleChoiceField(
queryset=Location.objects.all(),
@ -879,7 +890,8 @@ class RackFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldModelFilterFo
query_params={
'site_id': '$site_id'
},
label=_('Location')
label=_('Location'),
fetch_trigger='open'
)
status = forms.MultipleChoiceField(
choices=RackStatusChoices,
@ -900,7 +912,8 @@ class RackFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldModelFilterFo
queryset=RackRole.objects.all(),
required=False,
null_option='None',
label=_('Role')
label=_('Role'),
fetch_trigger='open'
)
asset_tag = forms.CharField(
required=False
@ -923,7 +936,8 @@ class RackElevationFilterForm(RackFilterForm):
query_params={
'site_id': '$site_id',
'location_id': '$location_id',
}
},
fetch_trigger='open'
)
@ -937,14 +951,16 @@ class RackReservationForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
required=False,
initial_params={
'sites': '$site'
}
},
fetch_trigger='open'
)
site_group = DynamicModelChoiceField(
queryset=SiteGroup.objects.all(),
required=False,
initial_params={
'sites': '$site'
}
},
fetch_trigger='open'
)
site = DynamicModelChoiceField(
queryset=Site.objects.all(),
@ -952,21 +968,24 @@ class RackReservationForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
query_params={
'region_id': '$region',
'group_id': '$site_group',
}
},
fetch_trigger='open'
)
location = DynamicModelChoiceField(
queryset=Location.objects.all(),
required=False,
query_params={
'site_id': '$site'
}
},
fetch_trigger='open'
)
rack = DynamicModelChoiceField(
queryset=Rack.objects.all(),
query_params={
'site_id': '$site',
'location_id': '$location',
}
},
fetch_trigger='open'
)
units = NumericArrayField(
base_field=forms.IntegerField(),
@ -980,7 +999,8 @@ class RackReservationForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
)
tags = DynamicModelMultipleChoiceField(
queryset=Tag.objects.all(),
required=False
required=False,
fetch_trigger='open'
)
class Meta:
@ -1080,7 +1100,8 @@ class RackReservationFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldMo
region_id = DynamicModelMultipleChoiceField(
queryset=Region.objects.all(),
required=False,
label=_('Region')
label=_('Region'),
fetch_trigger='open'
)
site_id = DynamicModelMultipleChoiceField(
queryset=Site.objects.all(),
@ -1088,13 +1109,15 @@ class RackReservationFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldMo
query_params={
'region_id': '$region_id'
},
label=_('Region')
label=_('Region'),
fetch_trigger='open'
)
location_id = DynamicModelMultipleChoiceField(
queryset=Location.objects.prefetch_related('site'),
required=False,
label=_('Location'),
null_option='None'
null_option='None',
fetch_trigger='open'
)
user_id = DynamicModelMultipleChoiceField(
queryset=User.objects.all(),
@ -1102,7 +1125,8 @@ class RackReservationFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldMo
label=_('User'),
widget=APISelectMultiple(
api_url='/api/users/users/',
)
),
fetch_trigger='open'
)
tag = TagFilterField(model)
@ -1231,7 +1255,8 @@ class DeviceTypeFilterForm(BootstrapMixin, CustomFieldModelFilterForm):
manufacturer_id = DynamicModelMultipleChoiceField(
queryset=Manufacturer.objects.all(),
required=False,
label=_('Manufacturer')
label=_('Manufacturer'),
fetch_trigger='open'
)
subdevice_role = forms.MultipleChoiceField(
choices=add_blank_choice(SubdeviceRoleChoices),
@ -2036,7 +2061,8 @@ class PlatformFilterForm(BootstrapMixin, CustomFieldModelFilterForm):
manufacturer_id = DynamicModelMultipleChoiceField(
queryset=Manufacturer.objects.all(),
required=False,
label=_('Manufacturer')
label=_('Manufacturer'),
fetch_trigger='open'
)
@ -2452,7 +2478,8 @@ class DeviceFilterForm(BootstrapMixin, LocalConfigContextFilterForm, TenancyFilt
region_id = DynamicModelMultipleChoiceField(
queryset=Region.objects.all(),
required=False,
label=_('Region')
label=_('Region'),
fetch_trigger='open'
)
site_id = DynamicModelMultipleChoiceField(
queryset=Site.objects.all(),
@ -2460,7 +2487,8 @@ class DeviceFilterForm(BootstrapMixin, LocalConfigContextFilterForm, TenancyFilt
query_params={
'region_id': '$region_id'
},
label=_('Site')
label=_('Site'),
fetch_trigger='open'
)
location_id = DynamicModelMultipleChoiceField(
queryset=Location.objects.all(),
@ -2469,7 +2497,8 @@ class DeviceFilterForm(BootstrapMixin, LocalConfigContextFilterForm, TenancyFilt
query_params={
'site_id': '$site_id'
},
label=_('Location')
label=_('Location'),
fetch_trigger='open'
)
rack_id = DynamicModelMultipleChoiceField(
queryset=Rack.objects.all(),
@ -2479,17 +2508,20 @@ class DeviceFilterForm(BootstrapMixin, LocalConfigContextFilterForm, TenancyFilt
'site_id': '$site_id',
'location_id': '$location_id',
},
label=_('Rack')
label=_('Rack'),
fetch_trigger='open'
)
role_id = DynamicModelMultipleChoiceField(
queryset=DeviceRole.objects.all(),
required=False,
label=_('Role')
label=_('Role'),
fetch_trigger='open'
)
manufacturer_id = DynamicModelMultipleChoiceField(
queryset=Manufacturer.objects.all(),
required=False,
label=_('Manufacturer')
label=_('Manufacturer'),
fetch_trigger='open'
)
device_type_id = DynamicModelMultipleChoiceField(
queryset=DeviceType.objects.all(),
@ -2497,13 +2529,15 @@ class DeviceFilterForm(BootstrapMixin, LocalConfigContextFilterForm, TenancyFilt
query_params={
'manufacturer_id': '$manufacturer_id'
},
label=_('Model')
label=_('Model'),
fetch_trigger='open'
)
platform_id = DynamicModelMultipleChoiceField(
queryset=Platform.objects.all(),
required=False,
null_option='None',
label=_('Platform')
label=_('Platform'),
fetch_trigger='open'
)
status = forms.MultipleChoiceField(
choices=DeviceStatusChoices,
@ -3987,7 +4021,8 @@ class InventoryItemFilterForm(DeviceComponentFilterForm):
manufacturer_id = DynamicModelMultipleChoiceField(
queryset=Manufacturer.objects.all(),
required=False,
label=_('Manufacturer')
label=_('Manufacturer'),
fetch_trigger='open'
)
serial = forms.CharField(
required=False
@ -4461,7 +4496,8 @@ class CableFilterForm(BootstrapMixin, CustomFieldModelFilterForm):
region_id = DynamicModelMultipleChoiceField(
queryset=Region.objects.all(),
required=False,
label=_('Region')
label=_('Region'),
fetch_trigger='open'
)
site_id = DynamicModelMultipleChoiceField(
queryset=Site.objects.all(),
@ -4469,12 +4505,14 @@ class CableFilterForm(BootstrapMixin, CustomFieldModelFilterForm):
query_params={
'region_id': '$region_id'
},
label=_('Site')
label=_('Site'),
fetch_trigger='open'
)
tenant_id = DynamicModelMultipleChoiceField(
queryset=Tenant.objects.all(),
required=False,
label=_('Tenant')
label=_('Tenant'),
fetch_trigger='open'
)
rack_id = DynamicModelMultipleChoiceField(
queryset=Rack.objects.all(),
@ -4483,7 +4521,8 @@ class CableFilterForm(BootstrapMixin, CustomFieldModelFilterForm):
null_option='None',
query_params={
'site_id': '$site_id'
}
},
fetch_trigger='open'
)
type = forms.MultipleChoiceField(
choices=add_blank_choice(CableTypeChoices),
@ -4506,7 +4545,8 @@ class CableFilterForm(BootstrapMixin, CustomFieldModelFilterForm):
'tenant_id': '$tenant_id',
'rack_id': '$rack_id',
},
label=_('Device')
label=_('Device'),
fetch_trigger='open'
)
tag = TagFilterField(model)
@ -4519,7 +4559,8 @@ class ConsoleConnectionFilterForm(BootstrapMixin, forms.Form):
region_id = DynamicModelMultipleChoiceField(
queryset=Region.objects.all(),
required=False,
label=_('Region')
label=_('Region'),
fetch_trigger='open'
)
site_id = DynamicModelMultipleChoiceField(
queryset=Site.objects.all(),
@ -4527,7 +4568,8 @@ class ConsoleConnectionFilterForm(BootstrapMixin, forms.Form):
query_params={
'region_id': '$region_id'
},
label=_('Site')
label=_('Site'),
fetch_trigger='open'
)
device_id = DynamicModelMultipleChoiceField(
queryset=Device.objects.all(),
@ -4535,7 +4577,8 @@ class ConsoleConnectionFilterForm(BootstrapMixin, forms.Form):
query_params={
'site_id': '$site_id'
},
label=_('Device')
label=_('Device'),
fetch_trigger='open'
)
@ -4543,7 +4586,8 @@ class PowerConnectionFilterForm(BootstrapMixin, forms.Form):
region_id = DynamicModelMultipleChoiceField(
queryset=Region.objects.all(),
required=False,
label=_('Region')
label=_('Region'),
fetch_trigger='open'
)
site_id = DynamicModelMultipleChoiceField(
queryset=Site.objects.all(),
@ -4551,7 +4595,8 @@ class PowerConnectionFilterForm(BootstrapMixin, forms.Form):
query_params={
'region_id': '$region_id'
},
label=_('Site')
label=_('Site'),
fetch_trigger='open'
)
device_id = DynamicModelMultipleChoiceField(
queryset=Device.objects.all(),
@ -4559,7 +4604,8 @@ class PowerConnectionFilterForm(BootstrapMixin, forms.Form):
query_params={
'site_id': '$site_id'
},
label=_('Device')
label=_('Device'),
fetch_trigger='open'
)
@ -4567,7 +4613,8 @@ class InterfaceConnectionFilterForm(BootstrapMixin, forms.Form):
region_id = DynamicModelMultipleChoiceField(
queryset=Region.objects.all(),
required=False,
label=_('Region')
label=_('Region'),
fetch_trigger='open'
)
site_id = DynamicModelMultipleChoiceField(
queryset=Site.objects.all(),
@ -4575,7 +4622,8 @@ class InterfaceConnectionFilterForm(BootstrapMixin, forms.Form):
query_params={
'region_id': '$region_id'
},
label=_('Site')
label=_('Site'),
fetch_trigger='open'
)
device_id = DynamicModelMultipleChoiceField(
queryset=Device.objects.all(),
@ -4583,7 +4631,8 @@ class InterfaceConnectionFilterForm(BootstrapMixin, forms.Form):
query_params={
'site_id': '$site_id'
},
label=_('Device')
label=_('Device'),
fetch_trigger='open'
)
@ -4837,12 +4886,14 @@ class VirtualChassisFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldMod
region_id = DynamicModelMultipleChoiceField(
queryset=Region.objects.all(),
required=False,
label=_('Region')
label=_('Region'),
fetch_trigger='open'
)
site_group_id = DynamicModelMultipleChoiceField(
queryset=SiteGroup.objects.all(),
required=False,
label=_('Site group')
label=_('Site group'),
fetch_trigger='open'
)
site_id = DynamicModelMultipleChoiceField(
queryset=Site.objects.all(),
@ -4850,7 +4901,8 @@ class VirtualChassisFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldMod
query_params={
'region_id': '$region_id'
},
label=_('Site')
label=_('Site'),
fetch_trigger='open'
)
tag = TagFilterField(model)
@ -4973,12 +5025,14 @@ class PowerPanelFilterForm(BootstrapMixin, CustomFieldModelFilterForm):
region_id = DynamicModelMultipleChoiceField(
queryset=Region.objects.all(),
required=False,
label=_('Region')
label=_('Region'),
fetch_trigger='open'
)
site_group_id = DynamicModelMultipleChoiceField(
queryset=SiteGroup.objects.all(),
required=False,
label=_('Site group')
label=_('Site group'),
fetch_trigger='open'
)
site_id = DynamicModelMultipleChoiceField(
queryset=Site.objects.all(),
@ -4986,7 +5040,8 @@ class PowerPanelFilterForm(BootstrapMixin, CustomFieldModelFilterForm):
query_params={
'region_id': '$region_id'
},
label=_('Site')
label=_('Site'),
fetch_trigger='open'
)
location_id = DynamicModelMultipleChoiceField(
queryset=Location.objects.all(),
@ -4995,7 +5050,8 @@ class PowerPanelFilterForm(BootstrapMixin, CustomFieldModelFilterForm):
query_params={
'site_id': '$site_id'
},
label=_('Location')
label=_('Location'),
fetch_trigger='open'
)
tag = TagFilterField(model)
@ -5213,12 +5269,14 @@ class PowerFeedFilterForm(BootstrapMixin, CustomFieldModelFilterForm):
region_id = DynamicModelMultipleChoiceField(
queryset=Region.objects.all(),
required=False,
label=_('Region')
label=_('Region'),
fetch_trigger='open'
)
site_group_id = DynamicModelMultipleChoiceField(
queryset=SiteGroup.objects.all(),
required=False,
label=_('Site group')
label=_('Site group'),
fetch_trigger='open'
)
site_id = DynamicModelMultipleChoiceField(
queryset=Site.objects.all(),
@ -5226,7 +5284,8 @@ class PowerFeedFilterForm(BootstrapMixin, CustomFieldModelFilterForm):
query_params={
'region_id': '$region_id'
},
label=_('Site')
label=_('Site'),
fetch_trigger='open'
)
power_panel_id = DynamicModelMultipleChoiceField(
queryset=PowerPanel.objects.all(),
@ -5235,7 +5294,8 @@ class PowerFeedFilterForm(BootstrapMixin, CustomFieldModelFilterForm):
query_params={
'site_id': '$site_id'
},
label=_('Power panel')
label=_('Power panel'),
fetch_trigger='open'
)
rack_id = DynamicModelMultipleChoiceField(
queryset=Rack.objects.all(),
@ -5244,7 +5304,8 @@ class PowerFeedFilterForm(BootstrapMixin, CustomFieldModelFilterForm):
query_params={
'site_id': '$site_id'
},
label=_('Rack')
label=_('Rack'),
fetch_trigger='open'
)
status = forms.MultipleChoiceField(
choices=PowerFeedStatusChoices,

View File

@ -676,58 +676,69 @@ class ConfigContextFilterForm(BootstrapMixin, forms.Form):
region_id = DynamicModelMultipleChoiceField(
queryset=Region.objects.all(),
required=False,
label=_('Regions')
label=_('Regions'),
fetch_trigger='open'
)
site_group_id = DynamicModelMultipleChoiceField(
queryset=SiteGroup.objects.all(),
required=False,
label=_('Site groups')
label=_('Site groups'),
fetch_trigger='open'
)
site_id = DynamicModelMultipleChoiceField(
queryset=Site.objects.all(),
required=False,
label=_('Sites')
label=_('Sites'),
fetch_trigger='open'
)
device_type_id = DynamicModelMultipleChoiceField(
queryset=DeviceType.objects.all(),
required=False,
label=_('Device types')
label=_('Device types'),
fetch_trigger='open'
)
role_id = DynamicModelMultipleChoiceField(
queryset=DeviceRole.objects.all(),
required=False,
label=_('Roles')
label=_('Roles'),
fetch_trigger='open'
)
platform_id = DynamicModelMultipleChoiceField(
queryset=Platform.objects.all(),
required=False,
label=_('Platforms')
label=_('Platforms'),
fetch_trigger='open'
)
cluster_group_id = DynamicModelMultipleChoiceField(
queryset=ClusterGroup.objects.all(),
required=False,
label=_('Cluster groups')
label=_('Cluster groups'),
fetch_trigger='open'
)
cluster_id = DynamicModelMultipleChoiceField(
queryset=Cluster.objects.all(),
required=False,
label=_('Clusters')
label=_('Clusters'),
fetch_trigger='open'
)
tenant_group_id = DynamicModelMultipleChoiceField(
queryset=TenantGroup.objects.all(),
required=False,
label=_('Tenant groups')
label=_('Tenant groups'),
fetch_trigger='open'
)
tenant_id = DynamicModelMultipleChoiceField(
queryset=Tenant.objects.all(),
required=False,
label=_('Tenant')
label=_('Tenant'),
fetch_trigger='open'
)
tag = DynamicModelMultipleChoiceField(
queryset=Tag.objects.all(),
to_field_name='slug',
required=False,
label=_('Tags')
label=_('Tags'),
fetch_trigger='open'
)
@ -820,7 +831,8 @@ class JournalEntryFilterForm(BootstrapMixin, forms.Form):
label=_('User'),
widget=APISelectMultiple(
api_url='/api/users/users/',
)
),
fetch_trigger='open'
)
assigned_object_type_id = DynamicModelMultipleChoiceField(
queryset=ContentType.objects.all(),
@ -828,7 +840,8 @@ class JournalEntryFilterForm(BootstrapMixin, forms.Form):
label=_('Object Type'),
widget=APISelectMultiple(
api_url='/api/extras/content-types/',
)
),
fetch_trigger='open'
)
kind = forms.ChoiceField(
choices=add_blank_choice(JournalEntryKindChoices),
@ -868,7 +881,8 @@ class ObjectChangeFilterForm(BootstrapMixin, forms.Form):
label=_('User'),
widget=APISelectMultiple(
api_url='/api/users/users/',
)
),
fetch_trigger='open'
)
changed_object_type_id = DynamicModelMultipleChoiceField(
queryset=ContentType.objects.all(),
@ -876,7 +890,8 @@ class ObjectChangeFilterForm(BootstrapMixin, forms.Form):
label=_('Object Type'),
widget=APISelectMultiple(
api_url='/api/extras/content-types/',
)
),
fetch_trigger='open'
)

View File

@ -115,12 +115,14 @@ class VRFFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldModelFilterFor
import_target_id = DynamicModelMultipleChoiceField(
queryset=RouteTarget.objects.all(),
required=False,
label=_('Import targets')
label=_('Import targets'),
fetch_trigger='open'
)
export_target_id = DynamicModelMultipleChoiceField(
queryset=RouteTarget.objects.all(),
required=False,
label=_('Export targets')
label=_('Export targets'),
fetch_trigger='open'
)
tag = TagFilterField(model)
@ -185,12 +187,14 @@ class RouteTargetFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldModelF
importing_vrf_id = DynamicModelMultipleChoiceField(
queryset=VRF.objects.all(),
required=False,
label=_('Imported by VRF')
label=_('Imported by VRF'),
fetch_trigger='open'
)
exporting_vrf_id = DynamicModelMultipleChoiceField(
queryset=VRF.objects.all(),
required=False,
label=_('Exported by VRF')
label=_('Exported by VRF'),
fetch_trigger='open'
)
tag = TagFilterField(model)
@ -345,7 +349,8 @@ class AggregateFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldModelFil
rir_id = DynamicModelMultipleChoiceField(
queryset=RIR.objects.all(),
required=False,
label=_('RIR')
label=_('RIR'),
fetch_trigger='open'
)
tag = TagFilterField(model)
@ -642,12 +647,14 @@ class PrefixFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldModelFilter
queryset=VRF.objects.all(),
required=False,
label=_('Assigned VRF'),
null_option='Global'
null_option='Global',
fetch_trigger='open'
)
present_in_vrf_id = DynamicModelChoiceField(
queryset=VRF.objects.all(),
required=False,
label=_('Present in VRF')
label=_('Present in VRF'),
fetch_trigger='open'
)
status = forms.MultipleChoiceField(
choices=PrefixStatusChoices,
@ -657,12 +664,14 @@ class PrefixFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldModelFilter
region_id = DynamicModelMultipleChoiceField(
queryset=Region.objects.all(),
required=False,
label=_('Region')
label=_('Region'),
fetch_trigger='open'
)
site_group_id = DynamicModelMultipleChoiceField(
queryset=SiteGroup.objects.all(),
required=False,
label=_('Site group')
label=_('Site group'),
fetch_trigger='open'
)
site_id = DynamicModelMultipleChoiceField(
queryset=Site.objects.all(),
@ -671,13 +680,15 @@ class PrefixFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldModelFilter
query_params={
'region_id': '$region_id'
},
label=_('Site')
label=_('Site'),
fetch_trigger='open'
)
role_id = DynamicModelMultipleChoiceField(
queryset=Role.objects.all(),
required=False,
null_option='None',
label=_('Role')
label=_('Role'),
fetch_trigger='open'
)
is_pool = forms.NullBooleanField(
required=False,
@ -818,7 +829,8 @@ class IPRangeFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldModelFilte
queryset=VRF.objects.all(),
required=False,
label=_('Assigned VRF'),
null_option='Global'
null_option='Global',
fetch_trigger='open'
)
status = forms.MultipleChoiceField(
choices=PrefixStatusChoices,
@ -829,7 +841,8 @@ class IPRangeFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldModelFilte
queryset=Role.objects.all(),
required=False,
null_option='None',
label=_('Role')
label=_('Role'),
fetch_trigger='open'
)
tag = TagFilterField(model)
@ -1265,12 +1278,14 @@ class IPAddressFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldModelFil
queryset=VRF.objects.all(),
required=False,
label=_('Assigned VRF'),
null_option='Global'
null_option='Global',
fetch_trigger='open'
)
present_in_vrf_id = DynamicModelChoiceField(
queryset=VRF.objects.all(),
required=False,
label=_('Present in VRF')
label=_('Present in VRF'),
fetch_trigger='open'
)
status = forms.MultipleChoiceField(
choices=IPAddressStatusChoices,
@ -1439,27 +1454,32 @@ class VLANGroupFilterForm(BootstrapMixin, forms.Form):
region = DynamicModelMultipleChoiceField(
queryset=Region.objects.all(),
required=False,
label=_('Region')
label=_('Region'),
fetch_trigger='open'
)
sitegroup = DynamicModelMultipleChoiceField(
queryset=SiteGroup.objects.all(),
required=False,
label=_('Site group')
label=_('Site group'),
fetch_trigger='open'
)
site = DynamicModelMultipleChoiceField(
queryset=Site.objects.all(),
required=False,
label=_('Site')
label=_('Site'),
fetch_trigger='open'
)
location = DynamicModelMultipleChoiceField(
queryset=Location.objects.all(),
required=False,
label=_('Location')
label=_('Location'),
fetch_trigger='open'
)
rack = DynamicModelMultipleChoiceField(
queryset=Rack.objects.all(),
required=False,
label=_('Rack')
label=_('Rack'),
fetch_trigger='open'
)
@ -1652,12 +1672,14 @@ class VLANFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldModelFilterFo
region_id = DynamicModelMultipleChoiceField(
queryset=Region.objects.all(),
required=False,
label=_('Region')
label=_('Region'),
fetch_trigger='open'
)
site_group_id = DynamicModelMultipleChoiceField(
queryset=SiteGroup.objects.all(),
required=False,
label=_('Site group')
label=_('Site group'),
fetch_trigger='open'
)
site_id = DynamicModelMultipleChoiceField(
queryset=Site.objects.all(),
@ -1666,7 +1688,8 @@ class VLANFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldModelFilterFo
query_params={
'region': '$region'
},
label=_('Site')
label=_('Site'),
fetch_trigger='open'
)
group_id = DynamicModelMultipleChoiceField(
queryset=VLANGroup.objects.all(),
@ -1675,7 +1698,8 @@ class VLANFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldModelFilterFo
query_params={
'region': '$region'
},
label=_('VLAN group')
label=_('VLAN group'),
fetch_trigger='open'
)
status = forms.MultipleChoiceField(
choices=VLANStatusChoices,
@ -1686,7 +1710,8 @@ class VLANFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldModelFilterFo
queryset=Role.objects.all(),
required=False,
null_option='None',
label=_('Role')
label=_('Role'),
fetch_trigger='open'
)
tag = TagFilterField(model)

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -2,7 +2,7 @@ import queryString from 'query-string';
import { readableColor } from 'color2k';
import SlimSelect from 'slim-select';
import { createToast } from '../bs';
import { hasUrl, hasExclusions } from './util';
import { hasUrl, hasExclusions, isTrigger } from './util';
import {
isTruthy,
hasError,
@ -10,6 +10,7 @@ import {
getApiData,
isApiError,
getElements,
createElement,
findFirstAdjacent,
} from '../util';
@ -17,6 +18,20 @@ import type { Option } from 'slim-select/dist/data';
type QueryFilter = Map<string, string | number | boolean>;
export type Trigger =
/**
* Load data when the select element is opened.
*/
| 'open'
/**
* Load data when the element is loaded.
*/
| 'load'
/**
* Load data when a parent element is uncollapsed.
*/
| 'collapse';
// Various one-off patterns to replace in query param keys.
const REPLACE_PATTERNS = [
// Don't query `termination_a_device=1`, but rather `device=1`.
@ -57,6 +72,17 @@ class APISelect {
*/
public readonly placeholder: string;
/**
* Event that will initiate the API call to NetBox to load option data. By default, the trigger
* is `'load'`, so data will be fetched when the element renders on the page.
*/
private readonly trigger: Trigger;
/**
* If `true`, a refresh button will be added next to the search/filter `<input/>` element.
*/
private readonly allowRefresh: boolean = true;
/**
* Event to be dispatched when dependent fields' values change.
*/
@ -153,6 +179,7 @@ class APISelect {
allowDeselect: true,
deselectLabel: `<i class="mdi mdi-close-circle" style="color:currentColor;"></i>`,
placeholder: this.placeholder,
searchPlaceholder: 'Filter',
onChange: () => this.handleSlimChange(),
});
@ -186,20 +213,44 @@ class APISelect {
// Initialize controlling elements.
this.initResetButton();
// Add the refresh button to the search element.
this.initRefreshButton();
// Add dependency event listeners.
this.addEventListeners();
// Determine if the fetch trigger has been set.
const triggerAttr = this.base.getAttribute('data-fetch-trigger');
// Determine if this element is part of collapsible element.
const collapse = this.base.closest('.content-container .collapse');
if (collapse !== null) {
// If this element is part of a collapsible element, only load the data when the
// collapsible element is shown.
// See: https://getbootstrap.com/docs/5.0/components/collapse/#events
collapse.addEventListener('show.bs.collapse', () => this.loadData());
collapse.addEventListener('hide.bs.collapse', () => this.resetOptions());
if (isTrigger(triggerAttr)) {
this.trigger = triggerAttr;
} else if (collapse !== null) {
this.trigger = 'collapse';
} else {
// Otherwise, load the data on render.
Promise.all([this.loadData()]);
this.trigger = 'load';
}
switch (this.trigger) {
case 'collapse':
if (collapse !== null) {
// If this element is part of a collapsible element, only load the data when the
// collapsible element is shown.
// See: https://getbootstrap.com/docs/5.0/components/collapse/#events
collapse.addEventListener('show.bs.collapse', () => this.loadData());
collapse.addEventListener('hide.bs.collapse', () => this.resetOptions());
}
break;
case 'open':
// If the trigger is 'open', only load API data when the select element is opened.
this.slim.beforeOpen = () => this.loadData();
break;
case 'load':
// Otherwise, load the data immediately.
Promise.all([this.loadData()]);
break;
}
}
@ -713,21 +764,37 @@ class APISelect {
}
/**
* Initialize any adjacent reset buttons so that when clicked, the instance's selected value is cleared.
* Initialize any adjacent reset buttons so that when clicked, the page is reloaded without
* query parameters.
*/
private initResetButton(): void {
const resetButton = findFirstAdjacent<HTMLButtonElement>(this.base, 'button[data-reset-select');
const resetButton = findFirstAdjacent<HTMLButtonElement>(
this.base,
'button[data-reset-select]',
);
if (resetButton !== null) {
resetButton.addEventListener('click', () => {
this.base.value = '';
if (this.base.multiple) {
this.slim.setSelected([]);
} else {
this.slim.setSelected('');
}
window.location.assign(window.location.origin + window.location.pathname);
});
}
}
/**
* Add a refresh button to the search container element. When clicked, the API data will be
* reloaded.
*/
private initRefreshButton(): void {
if (this.allowRefresh) {
const refreshButton = createElement(
'button',
{ type: 'button' },
['btn', 'btn-sm', 'btn-ghost-dark'],
[createElement('i', {}, ['mdi', 'mdi-reload'])],
);
refreshButton.addEventListener('click', () => this.loadData());
this.slim.slim.search.container.appendChild(refreshButton);
}
}
}
export function initApiSelect() {

View File

@ -1,3 +1,5 @@
import type { Trigger } from './api';
/**
* Determine if an element has the `data-url` attribute set.
*/
@ -15,3 +17,10 @@ export function hasExclusions(
const exclude = el.getAttribute('data-query-param-exclude');
return typeof exclude === 'string' && exclude !== '';
}
/**
* Determine if a trigger value is valid.
*/
export function isTrigger(value: unknown): value is Trigger {
return typeof value === 'string' && ['load', 'open', 'collapse'].includes(value);
}

View File

@ -52,7 +52,7 @@
}
* {
transition: $transition-100ms-ease-in-out;
transition: background-color, color 0.1s ease-in-out;
}
.mw-25 {
@ -302,8 +302,13 @@ span.profile-button .dropdown-menu {
}
}
div#advanced-search-content div.card div.card-body div.col:not(:last-child) {
margin-right: 1rem;
div#advanced-search-content {
&.collapsing {
transition: height 0.1s ease-in-out;
}
div.card div.card-body div.col:not(:last-child) {
margin-right: 1rem;
}
}
body {
@ -430,6 +435,7 @@ nav.search {
background-color: var(--nbx-body-bg);
// Don't overtake dropdowns
z-index: 999;
justify-content: center;
form button.dropdown-toggle {
border-color: $input-border-color;
font-weight: $input-group-addon-font-weight;

View File

@ -71,8 +71,8 @@ $spacing-s: $input-padding-x;
border-color: currentColor;
}
}
// Don't show the depth indicator outside of the menu.
.placeholder .depth {
// Don't show the depth indicator outside of the menu.
display: none;
}
span.placeholder > *,
@ -94,6 +94,11 @@ $spacing-s: $input-padding-x;
.ss-value {
border-radius: $badge-border-radius;
color: var(--nbx-select-value-color);
// Don't show the depth indicator outside of the menu.
.depth {
display: none;
}
}
}
.ss-add {
@ -133,10 +138,34 @@ $spacing-s: $input-padding-x;
opacity: 0.3;
}
}
&::-webkit-scrollbar {
right: 0;
width: 4px;
&:hover {
opacity: 0.8;
}
}
&::-webkit-scrollbar-track {
background: transparent;
}
&::-webkit-scrollbar-thumb {
right: 0;
width: 2px;
background-color: var(--nbx-sidebar-scroll);
}
}
border-bottom-left-radius: $form-select-border-radius;
border-bottom-right-radius: $form-select-border-radius;
.ss-search {
padding-right: $spacer * 0.5;
button {
margin-left: $spacer * 0.75;
}
input[type='search'] {
background-color: $form-select-bg;
color: $input-color;

View File

@ -1,42 +1,24 @@
{% extends 'base/layout.html' %}
{% load buttons %}
{% load render_table from django_tables2 %}
{% block title %}{{ title }}{% endblock %}
{% block extra_controls %}{% export_button content_type %}{% endblock %}
{% block content %}
{% if filter_form %}
<div class="col col-md-12 noprint">
{% include 'inc/advanced_search.html' %}
</div>
{% endif %}
<div class="row mb-3">
<div class="col col-md-12">
<div class="card">
<div class="card-header">
<div class="row">
<div class="col col-md-4 offset-md-8 d-flex noprint table-controls">
<div class="input-group input-group-sm">
<input type="text" class="form-control object-filter" placeholder="Filter" title="Filter text (regular expressions supported)" />
{% if filter_form %}
<button
type="button"
class="btn btn-sm btn-outline-dark"
data-bs-toggle="collapse"
data-bs-target="#advanced-search-content">
Advanced Search
</button>
{% endif %}
</div>
</div>
</div>
</div>
<div class="card-body">
{% include 'inc/responsive_table.html' %}
<div class="row mb-3">
<div class="col col-md-7 col-lg-8 col-xl-9 col-xxl-10">
{% include 'inc/table_controls.html' %}
<div class="table-responsive">
{% render_table table 'inc/table.html' %}
</div>
{% include 'inc/paginator.html' with paginator=table.paginator page=table.page %}
</div>
{% include 'inc/paginator.html' with paginator=table.paginator page=table.page %}
{% if filter_form %}
{% include 'inc/filter_list.html' %}
{% endif %}
</div>
</div>
{% endblock %}

View File

@ -5,63 +5,58 @@
{% block title %}Rack Elevations{% endblock %}
{% block controls %}
<div class="container mb-2 mx-0">
<div class="d-flex flex-wrap justify-content-end">
<button type="button" class="btn btn-sm btn-outline-dark m-1" data-bs-toggle="collapse" data-bs-target="#advanced-search-content">
Advanced Search
</button>
<button class="btn btn-sm btn-outline-dark toggle-images m-1" selected="selected">
<span class="mdi mdi mdi-checkbox-marked-circle-outline" aria-hidden="true"></span> Show Images
</button>
<div class="btn-group btn-group-sm m-1" role="group">
<a href="{% url 'dcim:rack_elevation_list' %}{% querystring request face='front' %}" class="btn btn-outline-secondary{% if rack_face == 'front' %} active{% endif %}">Front</a>
<a href="{% url 'dcim:rack_elevation_list' %}{% querystring request face='rear' %}" class="btn btn-outline-secondary{% if rack_face == 'rear' %} active{% endif %}">Rear</a>
</div>
<div class="btn-group btn-group-sm m-1" role="group">
<a href="{% url 'dcim:rack_elevation_list' %}{% querystring request reverse=None %}" class="btn btn-outline-secondary{% if not reverse %} active{% endif %}">Normal</a>
<a href="{% url 'dcim:rack_elevation_list' %}{% querystring request reverse='true' %}" class="btn btn-outline-secondary{% if reverse %} active{% endif %}">Reversed</a>
<div class="container mb-2 mx-0">
<div class="d-flex flex-wrap justify-content-end">
<button class="btn btn-sm btn-outline-dark toggle-images m-1" selected="selected">
<span class="mdi mdi mdi-checkbox-marked-circle-outline" aria-hidden="true"></span> Show Images
</button>
<div class="btn-group btn-group-sm m-1" role="group">
<a href="{% url 'dcim:rack_elevation_list' %}{% querystring request face='front' %}" class="btn btn-outline-secondary{% if rack_face == 'front' %} active{% endif %}">Front</a>
<a href="{% url 'dcim:rack_elevation_list' %}{% querystring request face='rear' %}" class="btn btn-outline-secondary{% if rack_face == 'rear' %} active{% endif %}">Rear</a>
</div>
<div class="btn-group btn-group-sm m-1" role="group">
<a href="{% url 'dcim:rack_elevation_list' %}{% querystring request reverse=None %}" class="btn btn-outline-secondary{% if not reverse %} active{% endif %}">Normal</a>
<a href="{% url 'dcim:rack_elevation_list' %}{% querystring request reverse='true' %}" class="btn btn-outline-secondary{% if reverse %} active{% endif %}">Reversed</a>
</div>
</div>
</div>
</div>
{% endblock %}
{% block content %}
<div class="col col-md-12 noprint">
{% include 'inc/advanced_search.html' %}
</div>
<div class="row">
<div class="col col-md-12">
{% if page %}
<div style="white-space: nowrap; overflow-x: scroll;">
{% for rack in page %}
<div style="display: inline-block; margin-right: 12px; width: 254px">
<div style="margin-left: 30px">
<div class="text-center">
<strong><a href="{% url 'dcim:rack' pk=rack.pk %}">{{ rack.name }}</a></strong>
{% if rack.role %}
<br /><span class="badge my-3" style="color: {{ rack.role.color|fgcolor }}; background-color: #{{ rack.role.color }}">{{ rack.role }}</span>
{% endif %}
{% if rack.facility_id %}
<br /><small class="text-muted">{{ rack.facility_id }}</small>
{% endif %}
<div class="row">
<div class="col col-md-7 col-lg-8 col-xl-9 col-xxl-10">
{% if page %}
<div style="white-space: nowrap; overflow-x: scroll;">
{% for rack in page %}
<div style="display: inline-block; margin-right: 12px; width: 254px">
<div style="margin-left: 30px">
<div class="text-center">
<strong><a href="{% url 'dcim:rack' pk=rack.pk %}">{{ rack.name }}</a></strong>
{% if rack.role %}
<br /><span class="badge my-3" style="color: {{ rack.role.color|fgcolor }}; background-color: #{{ rack.role.color }}">{{ rack.role }}</span>
{% endif %}
{% if rack.facility_id %}
<br /><small class="text-muted">{{ rack.facility_id }}</small>
{% endif %}
</div>
{% include 'dcim/inc/rack_elevation.html' with object=rack face=rack_face %}
<div class="clearfix"></div>
<div class="text-center">
<strong><a href="{% url 'dcim:rack' pk=rack.pk %}">{{ rack.name }}</a></strong>
{% if rack.facility_id %}
<small class="text-muted">({{ rack.facility_id }})</small>
{% endif %}
</div>
</div>
{% include 'dcim/inc/rack_elevation.html' with object=rack face=rack_face %}
<div class="clearfix"></div>
<div class="text-center">
<strong><a href="{% url 'dcim:rack' pk=rack.pk %}">{{ rack.name }}</a></strong>
{% if rack.facility_id %}
<small class="text-muted">({{ rack.facility_id }})</small>
{% endif %}
</div>
</div>
</div>
{% endfor %}
</div>
<br />
{% include 'inc/paginator.html' %}
{% else %}
<p>No Racks Found</p>
{% endif %}
{% endfor %}
</div>
<br />
{% include 'inc/paginator.html' %}
{% else %}
<p>No Racks Found</p>
{% endif %}
</div>
{% include 'inc/filter_list.html' %}
</div>
</div>
{% endblock %}

View File

@ -24,9 +24,6 @@
{% endblock controls %}
{% block content %}
{% if filter_form %}
{% include 'inc/advanced_search.html' %}
{% endif %}
{% if table.paginator.num_pages > 1 %}
{% with bulk_edit_url=content_type.model_class|validated_viewname:"bulk_edit" bulk_delete_url=content_type.model_class|validated_viewname:"bulk_delete" %}
<div id="select-all-box" class="d-none card noprint">
@ -57,12 +54,12 @@
{% endwith %}
{% endif %}
{# Object list filter, table config #}
{% include 'inc/table_controls.html' with table_modal="ObjectTable_config" %}
{# Object table #}
<div class="row">
<div class="col col-md-12">
<div class="col col-md-7 col-lg-8 col-xl-9 col-xxl-10">
{# Object list filter, table config #}
{% include 'inc/table_controls.html' with table_modal="ObjectTable_config" %}
{% with bulk_edit_url=content_type.model_class|validated_viewname:"bulk_edit" bulk_delete_url=content_type.model_class|validated_viewname:"bulk_delete" %}
{% if permissions.change or permissions.delete %}
<form method="post" class="form form-horizontal">
@ -95,6 +92,9 @@
{% endwith %}
{% include 'inc/paginator.html' with paginator=table.paginator page=table.page %}
</div>
{% if filter_form %}
{% include 'inc/filter_list.html' %}
{% endif %}
</div>
{% table_config_form table table_name="ObjectTable" %}
{% endblock content %}

View File

@ -0,0 +1,62 @@
{% load form_helpers %}
{% load helpers %}
<div class="col col-md-5 col-lg-4 col-xl-3 col-xxl-2 noprint">
<form action="." method="get">
<div class="card small">
<h5 class="card-header">
Field Filters
</h5>
<div class="card-body overflow-visible d-flex flex-wrap justify-content-between py-3">
{% for field in filter_form.hidden_fields %}
{{ field }}
{% endfor %}
{% if filter_form.field_groups %}
{% for group in filter_form.field_groups %}
<div class="col col-12">
{% for name in group %}
{% with field=filter_form|get_item:name %}
{% if field|widget_type == 'checkboxinput' %}
<div class="form-check mb-3">
<label class="form-check-label" for="{{ field.id_for_label }}">{{ field.label }}</label>
{{ field }}
</div>
{% else %}
<div class="mb-3 mx-3">
<label class="form-label" for="{{ field.id_for_label }}">{{ field.label }}</label>
{{ field }}
</div>
{% endif %}
{% endwith %}
{% endfor %}
</div>
{% endfor %}
{% else %}
{% for field in filter_form.visible_fields %}
<div class="col">
{% if field|widget_type == 'checkboxinput' %}
<div class="form-check mb-3">
<label class="form-check-label" for="{{ field.id_for_label }}">{{ field.label }}</label>
{{ field }}
</div>
{% else %}
<div class="mb-3">
<label class="form-label" for="{{ field.id_for_label }}">{{ field.label }}</label>
{{ field }}
</div>
{% endif %}
</div>
{% endfor %}
{% endif %}
</div>
<div class="card-footer text-end noprint border-0">
<button type="button" class="btn btn-sm btn-outline-danger m-1" data-reset-select>
<i class="mdi mdi-backspace"></i> Reset
</button>
<button type="submit" class="btn btn-sm btn-primary m-1">
<i class="mdi mdi-filter-variant"></i> Filter
</button>
</div>
</div>
</form>
</div>

View File

@ -1,6 +1,6 @@
<div class="row mb-3 justify-content-between">
<div class="col col-md-2 mb-0 d-flex noprint table-controls">
{% if request.user.is_authenticated %}
{% if request.user.is_authenticated and table_modal %}
<div class="input-group input-group-sm">
<button
type="button"
@ -22,16 +22,6 @@
placeholder="Filter"
title="Filter text (regular expressions supported)"
/>
{% if filter_form %}
<button
type="button"
class="btn btn-sm btn-outline-dark"
data-bs-toggle="collapse"
data-bs-target="#advanced-search-content"
>
Advanced Search
</button>
{% endif %}
</div>
</div>
</div>

View File

@ -67,7 +67,8 @@ class TenantGroupFilterForm(BootstrapMixin, CustomFieldModelFilterForm):
parent_id = DynamicModelMultipleChoiceField(
queryset=TenantGroup.objects.all(),
required=False,
label=_('Parent group')
label=_('Parent group'),
fetch_trigger='open'
)
@ -137,7 +138,8 @@ class TenantFilterForm(BootstrapMixin, CustomFieldModelFilterForm):
queryset=TenantGroup.objects.all(),
required=False,
null_option='None',
label=_('Group')
label=_('Group'),
fetch_trigger='open'
)
tag = TagFilterField(model)
@ -169,7 +171,8 @@ class TenancyFilterForm(forms.Form):
queryset=TenantGroup.objects.all(),
required=False,
null_option='None',
label=_('Tenant group')
label=_('Tenant group'),
fetch_trigger='open'
)
tenant_id = DynamicModelMultipleChoiceField(
queryset=Tenant.objects.all(),
@ -178,5 +181,6 @@ class TenancyFilterForm(forms.Form):
query_params={
'group_id': '$tenant_group_id'
},
label=_('Tenant')
label=_('Tenant'),
fetch_trigger='open'
)

View File

@ -363,16 +363,19 @@ class DynamicModelChoiceMixin:
: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 str fetch_trigger: The event type which will cause the select element to
fetch data from the API. Must be 'load', 'open', or 'collapse'. (optional)
"""
filter = django_filters.ModelChoiceFilter
widget = widgets.APISelect
def __init__(self, query_params=None, initial_params=None, null_option=None, disabled_indicator=None, *args,
def __init__(self, query_params=None, initial_params=None, null_option=None, disabled_indicator=None, fetch_trigger=None, *args,
**kwargs):
self.query_params = query_params or {}
self.initial_params = initial_params or {}
self.null_option = null_option
self.disabled_indicator = disabled_indicator
self.fetch_trigger = fetch_trigger
# to_field_name is set by ModelChoiceField.__init__(), but we need to set it early for reference
# by widget_attrs()
@ -394,6 +397,10 @@ class DynamicModelChoiceMixin:
# Set the disabled indicator, if any
if self.disabled_indicator is not None:
attrs['disabled-indicator'] = self.disabled_indicator
# Set the fetch trigger, if any.
if self.fetch_trigger is not None:
attrs['data-fetch-trigger'] = self.fetch_trigger
# Attach any static query parameters
for key, value in self.query_params.items():

View File

@ -236,12 +236,14 @@ class ClusterFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldModelFilte
type_id = DynamicModelMultipleChoiceField(
queryset=ClusterType.objects.all(),
required=False,
label=_('Type')
label=_('Type'),
fetch_trigger='open'
)
region_id = DynamicModelMultipleChoiceField(
queryset=Region.objects.all(),
required=False,
label=_('Region')
label=_('Region'),
fetch_trigger='open'
)
site_id = DynamicModelMultipleChoiceField(
queryset=Site.objects.all(),
@ -250,13 +252,15 @@ class ClusterFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldModelFilte
query_params={
'region_id': '$region_id'
},
label=_('Site')
label=_('Site'),
fetch_trigger='open'
)
group_id = DynamicModelMultipleChoiceField(
queryset=ClusterGroup.objects.all(),
required=False,
null_option='None',
label=_('Group')
label=_('Group'),
fetch_trigger='open'
)
tag = TagFilterField(model)
@ -547,28 +551,33 @@ class VirtualMachineFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldMod
queryset=ClusterGroup.objects.all(),
required=False,
null_option='None',
label=_('Cluster group')
label=_('Cluster group'),
fetch_trigger='open'
)
cluster_type_id = DynamicModelMultipleChoiceField(
queryset=ClusterType.objects.all(),
required=False,
null_option='None',
label=_('Cluster type')
label=_('Cluster type'),
fetch_trigger='open'
)
cluster_id = DynamicModelMultipleChoiceField(
queryset=Cluster.objects.all(),
required=False,
label=_('Cluster')
label=_('Cluster'),
fetch_trigger='open'
)
region_id = DynamicModelMultipleChoiceField(
queryset=Region.objects.all(),
required=False,
label=_('Region')
label=_('Region'),
fetch_trigger='open'
)
site_group_id = DynamicModelMultipleChoiceField(
queryset=SiteGroup.objects.all(),
required=False,
label=_('Site group')
label=_('Site group'),
fetch_trigger='open'
)
site_id = DynamicModelMultipleChoiceField(
queryset=Site.objects.all(),
@ -578,7 +587,8 @@ class VirtualMachineFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldMod
'region_id': '$region_id',
'group_id': '$site_group_id',
},
label=_('Site')
label=_('Site'),
fetch_trigger='open'
)
role_id = DynamicModelMultipleChoiceField(
queryset=DeviceRole.objects.all(),
@ -587,7 +597,8 @@ class VirtualMachineFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldMod
query_params={
'vm_role': "True"
},
label=_('Role')
label=_('Role'),
fetch_trigger='open'
)
status = forms.MultipleChoiceField(
choices=VirtualMachineStatusChoices,
@ -598,7 +609,8 @@ class VirtualMachineFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldMod
queryset=Platform.objects.all(),
required=False,
null_option='None',
label=_('Platform')
label=_('Platform'),
fetch_trigger='open'
)
mac_address = forms.CharField(
required=False,
@ -850,7 +862,8 @@ class VMInterfaceFilterForm(BootstrapMixin, forms.Form):
cluster_id = DynamicModelMultipleChoiceField(
queryset=Cluster.objects.all(),
required=False,
label=_('Cluster')
label=_('Cluster'),
fetch_trigger='open'
)
virtual_machine_id = DynamicModelMultipleChoiceField(
queryset=VirtualMachine.objects.all(),
@ -858,7 +871,8 @@ class VMInterfaceFilterForm(BootstrapMixin, forms.Form):
query_params={
'cluster_id': '$cluster_id'
},
label=_('Virtual machine')
label=_('Virtual machine'),
fetch_trigger='open'
)
enabled = forms.NullBooleanField(
required=False,