Merge branch 'feature-object-filter' into feature

This commit is contained in:
thatmattlove 2021-08-03 06:57:54 -07:00
commit 8bdfa34c7d
20 changed files with 530 additions and 257 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)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

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

@ -268,6 +268,7 @@ div.title-container {
nav.search {
// Don't overtake dropdowns
z-index: 999;
justify-content: center;
background-color: var(--nbx-body-bg);
form button.dropdown-toggle {

View File

@ -72,8 +72,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 {
color: var(--nbx-select-value-color);
border-radius: $badge-border-radius;
// 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-right-radius: $form-select-border-radius;
border-bottom-left-radius: $form-select-border-radius;
.ss-search {
padding-right: $spacer * 0.5;
button {
margin-left: $spacer * 0.75;
}
input[type='search'] {
color: $input-color;
background-color: $form-select-bg;

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

@ -65,6 +65,7 @@ class SlugField(forms.SlugField):
"""
Extend the built-in SlugField to automatically populate from a field called `name` unless otherwise specified.
"""
def __init__(self, slug_source='name', *args, **kwargs):
label = kwargs.pop('label', "Slug")
help_text = kwargs.pop('help_text', "URL-friendly unique shorthand")
@ -113,6 +114,7 @@ class JSONField(_JSONField):
"""
Custom wrapper around Django's built-in JSONField to avoid presenting "null" as the default text.
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if not self.help_text:
@ -312,6 +314,7 @@ class ExpandableNameField(forms.CharField):
A field which allows for numeric range expansion
Example: 'Gi0/[1-3]' => ['Gi0/1', 'Gi0/2', 'Gi0/3']
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if not self.help_text:
@ -337,6 +340,7 @@ class ExpandableIPAddressField(forms.CharField):
A field which allows for expansion of IP address ranges
Example: '192.0.2.[1-254]/24' => ['192.0.2.1/24', '192.0.2.2/24', '192.0.2.3/24' ... '192.0.2.254/24']
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if not self.help_text:
@ -363,16 +367,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()
@ -395,6 +402,10 @@ class DynamicModelChoiceMixin:
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():
widget.add_query_param(key, value)

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,