mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
* WIP * WIP * WIP * Make object selector functional * Replace extraneous form fields with selector widgets * Avoid overlap with filterset field names * Show checkmarks next to visibile filters * Update results automatically when searching * Include selector for device/VM component parent fields * Use selector for filtering VLAN group/site * Limit selector to 100 results
This commit is contained in:
@ -1,7 +1,7 @@
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from circuits.models import *
|
||||
from dcim.models import Region, Site, SiteGroup
|
||||
from dcim.models import Site
|
||||
from ipam.models import ASN
|
||||
from netbox.forms import NetBoxModelForm
|
||||
from tenancy.forms import TenancyForm
|
||||
@ -114,50 +114,22 @@ class CircuitTerminationForm(NetBoxModelForm):
|
||||
'provider_id': '$provider',
|
||||
},
|
||||
)
|
||||
region = DynamicModelChoiceField(
|
||||
queryset=Region.objects.all(),
|
||||
required=False,
|
||||
initial_params={
|
||||
'sites': '$site'
|
||||
}
|
||||
)
|
||||
site_group = DynamicModelChoiceField(
|
||||
queryset=SiteGroup.objects.all(),
|
||||
required=False,
|
||||
initial_params={
|
||||
'sites': '$site'
|
||||
}
|
||||
)
|
||||
site = DynamicModelChoiceField(
|
||||
queryset=Site.objects.all(),
|
||||
query_params={
|
||||
'region_id': '$region',
|
||||
'group_id': '$site_group',
|
||||
},
|
||||
required=False
|
||||
)
|
||||
provider_network_provider = DynamicModelChoiceField(
|
||||
queryset=Provider.objects.all(),
|
||||
required=False,
|
||||
label='Provider',
|
||||
initial_params={
|
||||
'networks': 'provider_network'
|
||||
}
|
||||
selector=True
|
||||
)
|
||||
provider_network = DynamicModelChoiceField(
|
||||
queryset=ProviderNetwork.objects.all(),
|
||||
query_params={
|
||||
'provider_id': '$provider_network_provider',
|
||||
},
|
||||
required=False
|
||||
required=False,
|
||||
selector=True
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = CircuitTermination
|
||||
fields = [
|
||||
'provider', 'circuit', 'term_side', 'region', 'site_group', 'site', 'provider_network_provider',
|
||||
'provider_network', 'mark_connected', 'port_speed', 'upstream_speed', 'xconnect_id', 'pp_info',
|
||||
'description', 'tags',
|
||||
'provider', 'circuit', 'term_side', 'site', 'provider_network', 'mark_connected', 'port_speed',
|
||||
'upstream_speed', 'xconnect_id', 'pp_info', 'description', 'tags',
|
||||
]
|
||||
widgets = {
|
||||
'port_speed': SelectSpeedWidget(),
|
||||
|
@ -14,9 +14,9 @@ from tenancy.forms import TenancyForm
|
||||
from utilities.forms import (
|
||||
APISelect, add_blank_choice, BootstrapMixin, ClearableFileInput, CommentField, ContentTypeChoiceField,
|
||||
DynamicModelChoiceField, DynamicModelMultipleChoiceField, JSONField, NumericArrayField, SelectWithPK,
|
||||
SlugField, SelectSpeedWidget,
|
||||
SlugField, SelectSpeedWidget
|
||||
)
|
||||
from virtualization.models import Cluster, ClusterGroup
|
||||
from virtualization.models import Cluster
|
||||
from wireless.models import WirelessLAN, WirelessLANGroup
|
||||
from .common import InterfaceCommonForm, ModuleCommonForm
|
||||
|
||||
@ -157,26 +157,9 @@ class SiteForm(TenancyForm, NetBoxModelForm):
|
||||
|
||||
|
||||
class LocationForm(TenancyForm, NetBoxModelForm):
|
||||
region = DynamicModelChoiceField(
|
||||
queryset=Region.objects.all(),
|
||||
required=False,
|
||||
initial_params={
|
||||
'sites': '$site'
|
||||
}
|
||||
)
|
||||
site_group = DynamicModelChoiceField(
|
||||
queryset=SiteGroup.objects.all(),
|
||||
required=False,
|
||||
initial_params={
|
||||
'sites': '$site'
|
||||
}
|
||||
)
|
||||
site = DynamicModelChoiceField(
|
||||
queryset=Site.objects.all(),
|
||||
query_params={
|
||||
'region_id': '$region',
|
||||
'group_id': '$site_group',
|
||||
}
|
||||
selector=True
|
||||
)
|
||||
parent = DynamicModelChoiceField(
|
||||
queryset=Location.objects.all(),
|
||||
@ -188,17 +171,14 @@ class LocationForm(TenancyForm, NetBoxModelForm):
|
||||
slug = SlugField()
|
||||
|
||||
fieldsets = (
|
||||
('Location', (
|
||||
'region', 'site_group', 'site', 'parent', 'name', 'slug', 'status', 'description', 'tags',
|
||||
)),
|
||||
('Location', ('site', 'parent', 'name', 'slug', 'status', 'description', 'tags')),
|
||||
('Tenancy', ('tenant_group', 'tenant')),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Location
|
||||
fields = (
|
||||
'region', 'site_group', 'site', 'parent', 'name', 'slug', 'status', 'description', 'tenant_group', 'tenant',
|
||||
'tags',
|
||||
'site', 'parent', 'name', 'slug', 'status', 'description', 'tenant_group', 'tenant', 'tags',
|
||||
)
|
||||
|
||||
|
||||
@ -219,26 +199,9 @@ class RackRoleForm(NetBoxModelForm):
|
||||
|
||||
|
||||
class RackForm(TenancyForm, NetBoxModelForm):
|
||||
region = DynamicModelChoiceField(
|
||||
queryset=Region.objects.all(),
|
||||
required=False,
|
||||
initial_params={
|
||||
'sites': '$site'
|
||||
}
|
||||
)
|
||||
site_group = DynamicModelChoiceField(
|
||||
queryset=SiteGroup.objects.all(),
|
||||
required=False,
|
||||
initial_params={
|
||||
'sites': '$site'
|
||||
}
|
||||
)
|
||||
site = DynamicModelChoiceField(
|
||||
queryset=Site.objects.all(),
|
||||
query_params={
|
||||
'region_id': '$region',
|
||||
'group_id': '$site_group',
|
||||
}
|
||||
selector=True
|
||||
)
|
||||
location = DynamicModelChoiceField(
|
||||
queryset=Location.objects.all(),
|
||||
@ -256,48 +219,16 @@ class RackForm(TenancyForm, NetBoxModelForm):
|
||||
class Meta:
|
||||
model = Rack
|
||||
fields = [
|
||||
'region', 'site_group', 'site', 'location', 'name', 'facility_id', 'tenant_group', 'tenant', 'status',
|
||||
'role', 'serial', 'asset_tag', 'type', 'width', 'u_height', 'desc_units', 'outer_width', 'outer_depth',
|
||||
'outer_unit', 'mounting_depth', 'weight', 'max_weight', 'weight_unit', 'description', 'comments', 'tags',
|
||||
'site', 'location', 'name', 'facility_id', 'tenant_group', 'tenant', 'status', 'role', 'serial',
|
||||
'asset_tag', 'type', 'width', 'u_height', 'desc_units', 'outer_width', 'outer_depth', 'outer_unit',
|
||||
'mounting_depth', 'weight', 'max_weight', 'weight_unit', 'description', 'comments', 'tags',
|
||||
]
|
||||
|
||||
|
||||
class RackReservationForm(TenancyForm, NetBoxModelForm):
|
||||
region = DynamicModelChoiceField(
|
||||
queryset=Region.objects.all(),
|
||||
required=False,
|
||||
initial_params={
|
||||
'sites': '$site'
|
||||
}
|
||||
)
|
||||
site_group = DynamicModelChoiceField(
|
||||
queryset=SiteGroup.objects.all(),
|
||||
required=False,
|
||||
initial_params={
|
||||
'sites': '$site'
|
||||
}
|
||||
)
|
||||
site = DynamicModelChoiceField(
|
||||
queryset=Site.objects.all(),
|
||||
required=False,
|
||||
query_params={
|
||||
'region_id': '$region',
|
||||
'group_id': '$site_group',
|
||||
}
|
||||
)
|
||||
location = DynamicModelChoiceField(
|
||||
queryset=Location.objects.all(),
|
||||
required=False,
|
||||
query_params={
|
||||
'site_id': '$site'
|
||||
}
|
||||
)
|
||||
rack = DynamicModelChoiceField(
|
||||
queryset=Rack.objects.all(),
|
||||
query_params={
|
||||
'site_id': '$site',
|
||||
'location_id': '$location',
|
||||
}
|
||||
selector=True
|
||||
)
|
||||
units = NumericArrayField(
|
||||
base_field=forms.IntegerField(),
|
||||
@ -311,15 +242,14 @@ class RackReservationForm(TenancyForm, NetBoxModelForm):
|
||||
comments = CommentField()
|
||||
|
||||
fieldsets = (
|
||||
('Reservation', ('region', 'site_group', 'site', 'location', 'rack', 'units', 'user', 'description', 'tags')),
|
||||
('Reservation', ('rack', 'units', 'user', 'description', 'tags')),
|
||||
('Tenancy', ('tenant_group', 'tenant')),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = RackReservation
|
||||
fields = [
|
||||
'region', 'site_group', 'site', 'location', 'rack', 'units', 'user', 'tenant_group', 'tenant',
|
||||
'description', 'comments', 'tags',
|
||||
'rack', 'units', 'user', 'tenant_group', 'tenant', 'description', 'comments', 'tags',
|
||||
]
|
||||
|
||||
|
||||
@ -441,26 +371,9 @@ class PlatformForm(NetBoxModelForm):
|
||||
|
||||
|
||||
class DeviceForm(TenancyForm, NetBoxModelForm):
|
||||
region = DynamicModelChoiceField(
|
||||
queryset=Region.objects.all(),
|
||||
required=False,
|
||||
initial_params={
|
||||
'sites': '$site'
|
||||
}
|
||||
)
|
||||
site_group = DynamicModelChoiceField(
|
||||
queryset=SiteGroup.objects.all(),
|
||||
required=False,
|
||||
initial_params={
|
||||
'sites': '$site'
|
||||
}
|
||||
)
|
||||
site = DynamicModelChoiceField(
|
||||
queryset=Site.objects.all(),
|
||||
query_params={
|
||||
'region_id': '$region',
|
||||
'group_id': '$site_group',
|
||||
}
|
||||
selector=True
|
||||
)
|
||||
location = DynamicModelChoiceField(
|
||||
queryset=Location.objects.all(),
|
||||
@ -491,43 +404,21 @@ class DeviceForm(TenancyForm, NetBoxModelForm):
|
||||
}
|
||||
)
|
||||
)
|
||||
manufacturer = DynamicModelChoiceField(
|
||||
queryset=Manufacturer.objects.all(),
|
||||
required=False,
|
||||
initial_params={
|
||||
'device_types': '$device_type'
|
||||
}
|
||||
)
|
||||
device_type = DynamicModelChoiceField(
|
||||
queryset=DeviceType.objects.all(),
|
||||
query_params={
|
||||
'manufacturer_id': '$manufacturer'
|
||||
}
|
||||
selector=True
|
||||
)
|
||||
device_role = DynamicModelChoiceField(
|
||||
queryset=DeviceRole.objects.all()
|
||||
)
|
||||
platform = DynamicModelChoiceField(
|
||||
queryset=Platform.objects.all(),
|
||||
required=False,
|
||||
query_params={
|
||||
'manufacturer_id': ['$manufacturer', 'null']
|
||||
}
|
||||
)
|
||||
cluster_group = DynamicModelChoiceField(
|
||||
queryset=ClusterGroup.objects.all(),
|
||||
required=False,
|
||||
null_option='None',
|
||||
initial_params={
|
||||
'clusters': '$cluster'
|
||||
}
|
||||
required=False
|
||||
)
|
||||
cluster = DynamicModelChoiceField(
|
||||
queryset=Cluster.objects.all(),
|
||||
required=False,
|
||||
query_params={
|
||||
'group_id': '$cluster_group'
|
||||
}
|
||||
selector=True
|
||||
)
|
||||
comments = CommentField()
|
||||
local_context_data = JSONField(
|
||||
@ -536,7 +427,8 @@ class DeviceForm(TenancyForm, NetBoxModelForm):
|
||||
)
|
||||
virtual_chassis = DynamicModelChoiceField(
|
||||
queryset=VirtualChassis.objects.all(),
|
||||
required=False
|
||||
required=False,
|
||||
selector=True
|
||||
)
|
||||
vc_position = forms.IntegerField(
|
||||
required=False,
|
||||
@ -556,10 +448,10 @@ class DeviceForm(TenancyForm, NetBoxModelForm):
|
||||
class Meta:
|
||||
model = Device
|
||||
fields = [
|
||||
'name', 'device_role', 'device_type', 'serial', 'asset_tag', 'region', 'site_group', 'site', 'rack',
|
||||
'location', 'position', 'face', 'status', 'airflow', 'platform', 'primary_ip4', 'primary_ip6',
|
||||
'cluster_group', 'cluster', 'tenant_group', 'tenant', 'virtual_chassis', 'vc_position', 'vc_priority',
|
||||
'description', 'config_template', 'comments', 'tags', 'local_context_data'
|
||||
'name', 'device_role', 'device_type', 'serial', 'asset_tag', 'site', 'rack', 'location', 'position', 'face',
|
||||
'status', 'airflow', 'platform', 'primary_ip4', 'primary_ip6', 'cluster', 'tenant_group', 'tenant',
|
||||
'virtual_chassis', 'vc_position', 'vc_priority', 'description', 'config_template', 'comments', 'tags',
|
||||
'local_context_data'
|
||||
]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
@ -632,18 +524,9 @@ class ModuleForm(ModuleCommonForm, NetBoxModelForm):
|
||||
'device_id': '$device'
|
||||
}
|
||||
)
|
||||
manufacturer = DynamicModelChoiceField(
|
||||
queryset=Manufacturer.objects.all(),
|
||||
required=False,
|
||||
initial_params={
|
||||
'module_types': '$module_type'
|
||||
}
|
||||
)
|
||||
module_type = DynamicModelChoiceField(
|
||||
queryset=ModuleType.objects.all(),
|
||||
query_params={
|
||||
'manufacturer_id': '$manufacturer'
|
||||
}
|
||||
selector=True
|
||||
)
|
||||
comments = CommentField()
|
||||
replicate_components = forms.BooleanField(
|
||||
@ -651,7 +534,6 @@ class ModuleForm(ModuleCommonForm, NetBoxModelForm):
|
||||
initial=True,
|
||||
help_text=_("Automatically populate components associated with this module type")
|
||||
)
|
||||
|
||||
adopt_components = forms.BooleanField(
|
||||
required=False,
|
||||
initial=False,
|
||||
@ -659,9 +541,7 @@ class ModuleForm(ModuleCommonForm, NetBoxModelForm):
|
||||
)
|
||||
|
||||
fieldsets = (
|
||||
('Module', (
|
||||
'device', 'module_bay', 'manufacturer', 'module_type', 'status', 'description', 'tags',
|
||||
)),
|
||||
('Module', ('device', 'module_bay', 'module_type', 'status', 'description', 'tags')),
|
||||
('Hardware', (
|
||||
'serial', 'asset_tag', 'replicate_components', 'adopt_components',
|
||||
)),
|
||||
@ -670,8 +550,8 @@ class ModuleForm(ModuleCommonForm, NetBoxModelForm):
|
||||
class Meta:
|
||||
model = Module
|
||||
fields = [
|
||||
'device', 'module_bay', 'manufacturer', 'module_type', 'status', 'serial', 'asset_tag', 'tags',
|
||||
'replicate_components', 'adopt_components', 'description', 'comments',
|
||||
'device', 'module_bay', 'module_type', 'status', 'serial', 'asset_tag', 'tags', 'replicate_components',
|
||||
'adopt_components', 'description', 'comments',
|
||||
]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
@ -702,26 +582,9 @@ class CableForm(TenancyForm, NetBoxModelForm):
|
||||
|
||||
|
||||
class PowerPanelForm(NetBoxModelForm):
|
||||
region = DynamicModelChoiceField(
|
||||
queryset=Region.objects.all(),
|
||||
required=False,
|
||||
initial_params={
|
||||
'sites': '$site'
|
||||
}
|
||||
)
|
||||
site_group = DynamicModelChoiceField(
|
||||
queryset=SiteGroup.objects.all(),
|
||||
required=False,
|
||||
initial_params={
|
||||
'sites': '$site'
|
||||
}
|
||||
)
|
||||
site = DynamicModelChoiceField(
|
||||
queryset=Site.objects.all(),
|
||||
query_params={
|
||||
'region_id': '$region',
|
||||
'group_id': '$site_group',
|
||||
}
|
||||
selector=True
|
||||
)
|
||||
location = DynamicModelChoiceField(
|
||||
queryset=Location.objects.all(),
|
||||
@ -733,80 +596,38 @@ class PowerPanelForm(NetBoxModelForm):
|
||||
comments = CommentField()
|
||||
|
||||
fieldsets = (
|
||||
('Power Panel', ('region', 'site_group', 'site', 'location', 'name', 'description', 'tags')),
|
||||
('Power Panel', ('site', 'location', 'name', 'description', 'tags')),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = PowerPanel
|
||||
fields = [
|
||||
'region', 'site_group', 'site', 'location', 'name', 'description', 'comments', 'tags',
|
||||
'site', 'location', 'name', 'description', 'comments', 'tags',
|
||||
]
|
||||
|
||||
|
||||
class PowerFeedForm(NetBoxModelForm):
|
||||
region = DynamicModelChoiceField(
|
||||
queryset=Region.objects.all(),
|
||||
required=False,
|
||||
initial_params={
|
||||
'sites__powerpanel': '$power_panel'
|
||||
}
|
||||
)
|
||||
site_group = DynamicModelChoiceField(
|
||||
queryset=SiteGroup.objects.all(),
|
||||
required=False,
|
||||
initial_params={
|
||||
'sites': '$site'
|
||||
}
|
||||
)
|
||||
site = DynamicModelChoiceField(
|
||||
queryset=Site.objects.all(),
|
||||
required=False,
|
||||
initial_params={
|
||||
'powerpanel': '$power_panel'
|
||||
},
|
||||
query_params={
|
||||
'region_id': '$region',
|
||||
'group_id': '$site_group',
|
||||
}
|
||||
)
|
||||
power_panel = DynamicModelChoiceField(
|
||||
queryset=PowerPanel.objects.all(),
|
||||
query_params={
|
||||
'site_id': '$site'
|
||||
}
|
||||
)
|
||||
location = DynamicModelChoiceField(
|
||||
queryset=Location.objects.all(),
|
||||
required=False,
|
||||
query_params={
|
||||
'site_id': '$site'
|
||||
},
|
||||
initial_params={
|
||||
'racks': '$rack'
|
||||
}
|
||||
selector=True
|
||||
)
|
||||
rack = DynamicModelChoiceField(
|
||||
queryset=Rack.objects.all(),
|
||||
required=False,
|
||||
query_params={
|
||||
'location_id': '$location',
|
||||
'site_id': '$site'
|
||||
}
|
||||
selector=True
|
||||
)
|
||||
comments = CommentField()
|
||||
|
||||
fieldsets = (
|
||||
('Power Panel', ('region', 'site', 'power_panel')),
|
||||
('Power Feed', ('location', 'rack', 'name', 'status', 'type', 'description', 'mark_connected', 'tags')),
|
||||
('Power Feed', ('power_panel', 'rack', 'name', 'status', 'type', 'description', 'mark_connected', 'tags')),
|
||||
('Characteristics', ('supply', 'voltage', 'amperage', 'phase', 'max_utilization')),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = PowerFeed
|
||||
fields = [
|
||||
'region', 'site_group', 'site', 'power_panel', 'location', 'rack', 'name', 'status', 'type',
|
||||
'mark_connected', 'supply', 'phase', 'voltage', 'amperage', 'max_utilization', 'description', 'comments',
|
||||
'tags',
|
||||
'power_panel', 'rack', 'name', 'status', 'type', 'mark_connected', 'supply', 'phase', 'voltage', 'amperage',
|
||||
'max_utilization', 'description', 'comments', 'tags',
|
||||
]
|
||||
|
||||
|
||||
@ -878,43 +699,12 @@ class DeviceVCMembershipForm(forms.ModelForm):
|
||||
|
||||
|
||||
class VCMemberSelectForm(BootstrapMixin, forms.Form):
|
||||
region = DynamicModelChoiceField(
|
||||
queryset=Region.objects.all(),
|
||||
required=False,
|
||||
initial_params={
|
||||
'sites': '$site'
|
||||
}
|
||||
)
|
||||
site_group = DynamicModelChoiceField(
|
||||
queryset=SiteGroup.objects.all(),
|
||||
required=False,
|
||||
initial_params={
|
||||
'sites': '$site'
|
||||
}
|
||||
)
|
||||
site = DynamicModelChoiceField(
|
||||
queryset=Site.objects.all(),
|
||||
required=False,
|
||||
query_params={
|
||||
'region_id': '$region',
|
||||
'group_id': '$site_group',
|
||||
}
|
||||
)
|
||||
rack = DynamicModelChoiceField(
|
||||
queryset=Rack.objects.all(),
|
||||
required=False,
|
||||
null_option='None',
|
||||
query_params={
|
||||
'site_id': '$site'
|
||||
}
|
||||
)
|
||||
device = DynamicModelChoiceField(
|
||||
queryset=Device.objects.all(),
|
||||
query_params={
|
||||
'site_id': '$site',
|
||||
'rack_id': '$rack',
|
||||
'virtual_chassis_id': 'null',
|
||||
}
|
||||
},
|
||||
selector=True
|
||||
)
|
||||
|
||||
def clean_device(self):
|
||||
@ -1150,7 +940,8 @@ class InventoryItemTemplateForm(ComponentTemplateForm):
|
||||
|
||||
class DeviceComponentForm(NetBoxModelForm):
|
||||
device = DynamicModelChoiceField(
|
||||
queryset=Device.objects.all()
|
||||
queryset=Device.objects.all(),
|
||||
selector=True
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
@ -1592,53 +1383,9 @@ class InventoryItemRoleForm(NetBoxModelForm):
|
||||
|
||||
|
||||
class VirtualDeviceContextForm(TenancyForm, NetBoxModelForm):
|
||||
region = DynamicModelChoiceField(
|
||||
queryset=Region.objects.all(),
|
||||
required=False,
|
||||
initial_params={
|
||||
'sites': '$site'
|
||||
}
|
||||
)
|
||||
site_group = DynamicModelChoiceField(
|
||||
queryset=SiteGroup.objects.all(),
|
||||
required=False,
|
||||
initial_params={
|
||||
'sites': '$site'
|
||||
}
|
||||
)
|
||||
site = DynamicModelChoiceField(
|
||||
queryset=Site.objects.all(),
|
||||
required=False,
|
||||
query_params={
|
||||
'region_id': '$region',
|
||||
'group_id': '$site_group',
|
||||
}
|
||||
)
|
||||
location = DynamicModelChoiceField(
|
||||
queryset=Location.objects.all(),
|
||||
required=False,
|
||||
query_params={
|
||||
'site_id': '$site'
|
||||
},
|
||||
initial_params={
|
||||
'racks': '$rack'
|
||||
}
|
||||
)
|
||||
rack = DynamicModelChoiceField(
|
||||
queryset=Rack.objects.all(),
|
||||
required=False,
|
||||
query_params={
|
||||
'site_id': '$site',
|
||||
'location_id': '$location',
|
||||
}
|
||||
)
|
||||
device = DynamicModelChoiceField(
|
||||
queryset=Device.objects.all(),
|
||||
query_params={
|
||||
'site_id': '$site',
|
||||
'location_id': '$location',
|
||||
'rack_id': '$rack',
|
||||
}
|
||||
selector=True
|
||||
)
|
||||
primary_ip4 = DynamicModelChoiceField(
|
||||
queryset=IPAddress.objects.all(),
|
||||
@ -1660,14 +1407,13 @@ class VirtualDeviceContextForm(TenancyForm, NetBoxModelForm):
|
||||
)
|
||||
|
||||
fieldsets = (
|
||||
('Assigned Device', ('region', 'site_group', 'site', 'location', 'rack', 'device')),
|
||||
('Virtual Device Context', ('name', 'status', 'identifier', 'primary_ip4', 'primary_ip6', 'tags')),
|
||||
('Virtual Device Context', ('device', 'name', 'status', 'identifier', 'primary_ip4', 'primary_ip6', 'tags')),
|
||||
('Tenancy', ('tenant_group', 'tenant'))
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = VirtualDeviceContext
|
||||
fields = [
|
||||
'region', 'site_group', 'site', 'location', 'rack', 'device', 'name', 'status', 'identifier',
|
||||
'primary_ip4', 'primary_ip6', 'tenant_group', 'tenant', 'comments', 'tags'
|
||||
'device', 'name', 'status', 'identifier', 'primary_ip4', 'primary_ip6', 'tenant_group', 'tenant',
|
||||
'comments', 'tags'
|
||||
]
|
||||
|
@ -200,40 +200,11 @@ class PrefixForm(TenancyForm, NetBoxModelForm):
|
||||
required=False,
|
||||
label=_('VRF')
|
||||
)
|
||||
region = DynamicModelChoiceField(
|
||||
queryset=Region.objects.all(),
|
||||
required=False,
|
||||
initial_params={
|
||||
'sites': '$site'
|
||||
}
|
||||
)
|
||||
site_group = DynamicModelChoiceField(
|
||||
queryset=SiteGroup.objects.all(),
|
||||
required=False,
|
||||
initial_params={
|
||||
'sites': '$site'
|
||||
}
|
||||
)
|
||||
site = DynamicModelChoiceField(
|
||||
queryset=Site.objects.all(),
|
||||
required=False,
|
||||
null_option='None',
|
||||
query_params={
|
||||
'region_id': '$region',
|
||||
'group_id': '$site_group',
|
||||
}
|
||||
)
|
||||
vlan_group = DynamicModelChoiceField(
|
||||
queryset=VLANGroup.objects.all(),
|
||||
required=False,
|
||||
label=_('VLAN group'),
|
||||
null_option='None',
|
||||
query_params={
|
||||
'site': '$site'
|
||||
},
|
||||
initial_params={
|
||||
'vlans': '$vlan'
|
||||
}
|
||||
selector=True,
|
||||
null_option='None'
|
||||
)
|
||||
vlan = DynamicModelChoiceField(
|
||||
queryset=VLAN.objects.all(),
|
||||
@ -241,7 +212,6 @@ class PrefixForm(TenancyForm, NetBoxModelForm):
|
||||
label=_('VLAN'),
|
||||
query_params={
|
||||
'site_id': '$site',
|
||||
'group_id': '$vlan_group',
|
||||
}
|
||||
)
|
||||
role = DynamicModelChoiceField(
|
||||
@ -252,7 +222,7 @@ class PrefixForm(TenancyForm, NetBoxModelForm):
|
||||
|
||||
fieldsets = (
|
||||
('Prefix', ('prefix', 'status', 'vrf', 'role', 'is_pool', 'mark_utilized', 'description', 'tags')),
|
||||
('Site/VLAN Assignment', ('region', 'site_group', 'site', 'vlan_group', 'vlan')),
|
||||
('Site/VLAN Assignment', ('site', 'vlan')),
|
||||
('Tenancy', ('tenant_group', 'tenant')),
|
||||
)
|
||||
|
||||
@ -329,65 +299,22 @@ class IPAddressForm(TenancyForm, NetBoxModelForm):
|
||||
required=False,
|
||||
label=_('VRF')
|
||||
)
|
||||
nat_region = DynamicModelChoiceField(
|
||||
queryset=Region.objects.all(),
|
||||
required=False,
|
||||
label=_('Region'),
|
||||
initial_params={
|
||||
'sites': '$nat_site'
|
||||
}
|
||||
)
|
||||
nat_site_group = DynamicModelChoiceField(
|
||||
queryset=SiteGroup.objects.all(),
|
||||
required=False,
|
||||
label=_('Site group'),
|
||||
initial_params={
|
||||
'sites': '$nat_site'
|
||||
}
|
||||
)
|
||||
nat_site = DynamicModelChoiceField(
|
||||
queryset=Site.objects.all(),
|
||||
required=False,
|
||||
label=_('Site'),
|
||||
query_params={
|
||||
'region_id': '$nat_region',
|
||||
'group_id': '$nat_site_group',
|
||||
}
|
||||
)
|
||||
nat_rack = DynamicModelChoiceField(
|
||||
queryset=Rack.objects.all(),
|
||||
required=False,
|
||||
label=_('Rack'),
|
||||
null_option='None',
|
||||
query_params={
|
||||
'site_id': '$site'
|
||||
}
|
||||
)
|
||||
nat_device = DynamicModelChoiceField(
|
||||
queryset=Device.objects.all(),
|
||||
required=False,
|
||||
label=_('Device'),
|
||||
query_params={
|
||||
'site_id': '$site',
|
||||
'rack_id': '$nat_rack',
|
||||
}
|
||||
)
|
||||
nat_cluster = DynamicModelChoiceField(
|
||||
queryset=Cluster.objects.all(),
|
||||
required=False,
|
||||
label=_('Cluster')
|
||||
selector=True,
|
||||
label=_('Device')
|
||||
)
|
||||
nat_virtual_machine = DynamicModelChoiceField(
|
||||
queryset=VirtualMachine.objects.all(),
|
||||
required=False,
|
||||
label=_('Virtual Machine'),
|
||||
query_params={
|
||||
'cluster_id': '$nat_cluster',
|
||||
}
|
||||
selector=True,
|
||||
label=_('Virtual Machine')
|
||||
)
|
||||
nat_vrf = DynamicModelChoiceField(
|
||||
queryset=VRF.objects.all(),
|
||||
required=False,
|
||||
selector=True,
|
||||
label=_('VRF')
|
||||
)
|
||||
nat_inside = DynamicModelChoiceField(
|
||||
@ -409,9 +336,8 @@ class IPAddressForm(TenancyForm, NetBoxModelForm):
|
||||
class Meta:
|
||||
model = IPAddress
|
||||
fields = [
|
||||
'address', 'vrf', 'status', 'role', 'dns_name', 'primary_for_parent', 'nat_site', 'nat_rack', 'nat_device',
|
||||
'nat_cluster', 'nat_virtual_machine', 'nat_vrf', 'nat_inside', 'tenant_group', 'tenant', 'description',
|
||||
'comments', 'tags',
|
||||
'address', 'vrf', 'status', 'role', 'dns_name', 'primary_for_parent', 'nat_device', 'nat_virtual_machine',
|
||||
'nat_vrf', 'nat_inside', 'tenant_group', 'tenant', 'description', 'comments', 'tags',
|
||||
]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
@ -714,58 +640,18 @@ class VLANGroupForm(NetBoxModelForm):
|
||||
|
||||
|
||||
class VLANForm(TenancyForm, NetBoxModelForm):
|
||||
# VLANGroup assignment fields
|
||||
scope_type = forms.ChoiceField(
|
||||
choices=(
|
||||
('', ''),
|
||||
('dcim.region', 'Region'),
|
||||
('dcim.sitegroup', 'Site group'),
|
||||
('dcim.site', 'Site'),
|
||||
('dcim.location', 'Location'),
|
||||
('dcim.rack', 'Rack'),
|
||||
('virtualization.clustergroup', 'Cluster group'),
|
||||
('virtualization.cluster', 'Cluster'),
|
||||
),
|
||||
required=False,
|
||||
label=_('Group scope')
|
||||
)
|
||||
group = DynamicModelChoiceField(
|
||||
queryset=VLANGroup.objects.all(),
|
||||
required=False,
|
||||
query_params={
|
||||
'scope_type': '$scope_type',
|
||||
},
|
||||
selector=True,
|
||||
label=_('VLAN Group')
|
||||
)
|
||||
|
||||
# Site assignment fields
|
||||
region = DynamicModelChoiceField(
|
||||
queryset=Region.objects.all(),
|
||||
required=False,
|
||||
initial_params={
|
||||
'sites': '$site'
|
||||
},
|
||||
label=_('Region')
|
||||
)
|
||||
sitegroup = DynamicModelChoiceField(
|
||||
queryset=SiteGroup.objects.all(),
|
||||
required=False,
|
||||
initial_params={
|
||||
'sites': '$site'
|
||||
},
|
||||
label=_('Site group')
|
||||
)
|
||||
site = DynamicModelChoiceField(
|
||||
queryset=Site.objects.all(),
|
||||
required=False,
|
||||
null_option='None',
|
||||
query_params={
|
||||
'region_id': '$region',
|
||||
'group_id': '$sitegroup',
|
||||
}
|
||||
selector=True
|
||||
)
|
||||
|
||||
# Other fields
|
||||
role = DynamicModelChoiceField(
|
||||
queryset=Role.objects.all(),
|
||||
required=False
|
||||
@ -804,11 +690,13 @@ class ServiceTemplateForm(NetBoxModelForm):
|
||||
class ServiceForm(NetBoxModelForm):
|
||||
device = DynamicModelChoiceField(
|
||||
queryset=Device.objects.all(),
|
||||
required=False
|
||||
required=False,
|
||||
selector=True
|
||||
)
|
||||
virtual_machine = DynamicModelChoiceField(
|
||||
queryset=VirtualMachine.objects.all(),
|
||||
required=False
|
||||
required=False,
|
||||
selector=True
|
||||
)
|
||||
ports = NumericArrayField(
|
||||
base_field=forms.IntegerField(
|
||||
@ -908,43 +796,21 @@ class L2VPNTerminationForm(NetBoxModelForm):
|
||||
label=_('L2VPN'),
|
||||
fetch_trigger='open'
|
||||
)
|
||||
device_vlan = DynamicModelChoiceField(
|
||||
queryset=Device.objects.all(),
|
||||
label=_("Available on Device"),
|
||||
required=False,
|
||||
query_params={}
|
||||
)
|
||||
vlan = DynamicModelChoiceField(
|
||||
queryset=VLAN.objects.all(),
|
||||
required=False,
|
||||
query_params={
|
||||
'available_on_device': '$device_vlan'
|
||||
},
|
||||
selector=True,
|
||||
label=_('VLAN')
|
||||
)
|
||||
device = DynamicModelChoiceField(
|
||||
queryset=Device.objects.all(),
|
||||
required=False,
|
||||
query_params={}
|
||||
)
|
||||
interface = DynamicModelChoiceField(
|
||||
queryset=Interface.objects.all(),
|
||||
required=False,
|
||||
query_params={
|
||||
'device_id': '$device'
|
||||
}
|
||||
)
|
||||
virtual_machine = DynamicModelChoiceField(
|
||||
queryset=VirtualMachine.objects.all(),
|
||||
required=False,
|
||||
query_params={}
|
||||
selector=True
|
||||
)
|
||||
vminterface = DynamicModelChoiceField(
|
||||
queryset=VMInterface.objects.all(),
|
||||
required=False,
|
||||
query_params={
|
||||
'virtual_machine_id': '$virtual_machine'
|
||||
},
|
||||
selector=True,
|
||||
label=_('Interface')
|
||||
)
|
||||
|
||||
@ -958,7 +824,6 @@ class L2VPNTerminationForm(NetBoxModelForm):
|
||||
|
||||
if instance:
|
||||
if type(instance.assigned_object) is Interface:
|
||||
initial['device'] = instance.assigned_object.parent
|
||||
initial['interface'] = instance.assigned_object
|
||||
elif type(instance.assigned_object) is VLAN:
|
||||
initial['vlan'] = instance.assigned_object
|
||||
|
@ -10,7 +10,7 @@ from extras.plugins.urls import plugin_admin_patterns, plugin_patterns, plugin_a
|
||||
from netbox.api.views import APIRootView, StatusView
|
||||
from netbox.graphql.schema import schema
|
||||
from netbox.graphql.views import GraphQLView
|
||||
from netbox.views import HomeView, StaticMediaFailureView, SearchView
|
||||
from netbox.views import HomeView, StaticMediaFailureView, SearchView, htmx
|
||||
from users.views import LoginView, LogoutView
|
||||
from .admin import admin_site
|
||||
|
||||
@ -51,6 +51,9 @@ _patterns = [
|
||||
path('virtualization/', include('virtualization.urls')),
|
||||
path('wireless/', include('wireless.urls')),
|
||||
|
||||
# HTMX views
|
||||
path('htmx/object-selector/', htmx.ObjectSelectorView.as_view(), name='htmx_object_selector'),
|
||||
|
||||
# API
|
||||
path('api/', APIRootView.as_view(), name='api-root'),
|
||||
path('api/circuits/', include('circuits.api.urls')),
|
||||
|
56
netbox/netbox/views/htmx.py
Normal file
56
netbox/netbox/views/htmx.py
Normal file
@ -0,0 +1,56 @@
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.http import Http404
|
||||
from django.shortcuts import render
|
||||
from django.utils.module_loading import import_string
|
||||
from django.views.generic import View
|
||||
|
||||
|
||||
class ObjectSelectorView(View):
|
||||
template_name = 'htmx/object_selector.html'
|
||||
|
||||
def get(self, request):
|
||||
model = self._get_model(request.GET.get('_model', ''))
|
||||
|
||||
form_class = self._get_form_class(model)
|
||||
form = form_class(request.GET)
|
||||
|
||||
if '_search' in request.GET:
|
||||
# Return only search results
|
||||
filterset = self._get_filterset_class(model)
|
||||
|
||||
queryset = model.objects.restrict(request.user)
|
||||
if filterset:
|
||||
queryset = filterset(request.GET, queryset, request=request).qs
|
||||
|
||||
return render(request, 'htmx/object_selector_results.html', {
|
||||
'results': queryset[:100],
|
||||
})
|
||||
|
||||
return render(request, self.template_name, {
|
||||
'form': form,
|
||||
'model': model,
|
||||
'target_id': request.GET.get('target'),
|
||||
})
|
||||
|
||||
def _get_model(self, label):
|
||||
try:
|
||||
app_label, model_name = label.split('.')
|
||||
content_type = ContentType.objects.get_by_natural_key(app_label, model_name)
|
||||
except (ValueError, ObjectDoesNotExist):
|
||||
raise Http404
|
||||
return content_type.model_class()
|
||||
|
||||
def _get_form_class(self, model):
|
||||
if hasattr(self, 'form_class'):
|
||||
return self.form_class
|
||||
app_label = model._meta.app_label
|
||||
class_name = f'{model.__name__}FilterForm'
|
||||
return import_string(f'{app_label}.forms.{class_name}')
|
||||
|
||||
def _get_filterset_class(self, model):
|
||||
if hasattr(self, 'filterset_class'):
|
||||
return self.filterset_class
|
||||
app_label = model._meta.app_label
|
||||
class_name = f'{model.__name__}FilterSet'
|
||||
return import_string(f'{app_label}.filtersets.{class_name}')
|
18
netbox/project-static/dist/netbox.js
vendored
18
netbox/project-static/dist/netbox.js
vendored
File diff suppressed because one or more lines are too long
4
netbox/project-static/dist/netbox.js.map
vendored
4
netbox/project-static/dist/netbox.js.map
vendored
File diff suppressed because one or more lines are too long
@ -1,9 +1,10 @@
|
||||
import { getElements, isTruthy } from './util';
|
||||
import { initButtons } from './buttons';
|
||||
import { initSelect } from './select';
|
||||
import { initObjectSelector } from './objectSelector';
|
||||
|
||||
function initDepedencies(): void {
|
||||
for (const init of [initButtons, initSelect]) {
|
||||
for (const init of [initButtons, initSelect, initObjectSelector]) {
|
||||
init();
|
||||
}
|
||||
}
|
||||
|
32
netbox/project-static/src/objectSelector.ts
Normal file
32
netbox/project-static/src/objectSelector.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import { getElements } from './util';
|
||||
|
||||
function handleSelection(link: HTMLAnchorElement): void {
|
||||
const selector_results = document.getElementById('selector_results');
|
||||
if (selector_results == null) {
|
||||
return
|
||||
}
|
||||
const target_id = selector_results.getAttribute('data-selector-target');
|
||||
if (target_id == null) {
|
||||
return
|
||||
}
|
||||
const target = document.getElementById(target_id);
|
||||
if (target == null) {
|
||||
return
|
||||
}
|
||||
|
||||
const label = link.getAttribute('data-label');
|
||||
const value = link.getAttribute('data-value');
|
||||
|
||||
//@ts-ignore
|
||||
target.slim.setData([
|
||||
{text: label, value: value}
|
||||
]);
|
||||
|
||||
}
|
||||
|
||||
|
||||
export function initObjectSelector(): void {
|
||||
for (const element of getElements<HTMLAnchorElement>('#selector_results a')) {
|
||||
element.addEventListener('click', () => handleSelection(element));
|
||||
}
|
||||
}
|
@ -27,12 +27,9 @@
|
||||
</div>
|
||||
<div class="tab-content p-0 border-0">
|
||||
<div class="tab-pane{% if not providernetwork_tab_active %} active{% endif %}" id="site">
|
||||
{% render_field form.region %}
|
||||
{% render_field form.site_group %}
|
||||
{% render_field form.site %}
|
||||
</div>
|
||||
<div class="tab-pane{% if providernetwork_tab_active %} active{% endif %}" id="providernetwork">
|
||||
{% render_field form.provider_network_provider %}
|
||||
{% render_field form.provider_network %}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -18,7 +18,6 @@
|
||||
<div class="row mb-2">
|
||||
<h5 class="offset-sm-3">Hardware</h5>
|
||||
</div>
|
||||
{% render_field form.manufacturer %}
|
||||
{% render_field form.device_type %}
|
||||
{% render_field form.airflow %}
|
||||
{% render_field form.serial %}
|
||||
@ -29,8 +28,6 @@
|
||||
<div class="row mb-2">
|
||||
<h5 class="offset-sm-3">Location</h5>
|
||||
</div>
|
||||
{% render_field form.region %}
|
||||
{% render_field form.site_group %}
|
||||
{% render_field form.site %}
|
||||
{% render_field form.location %}
|
||||
{% render_field form.rack %}
|
||||
@ -76,7 +73,6 @@
|
||||
<div class="row mb-2">
|
||||
<h5 class="offset-sm-3">Virtualization</h5>
|
||||
</div>
|
||||
{% render_field form.cluster_group %}
|
||||
{% render_field form.cluster %}
|
||||
</div>
|
||||
|
||||
|
@ -6,8 +6,6 @@
|
||||
<div class="row mb-2">
|
||||
<h5 class="offset-sm-3">Rack</h5>
|
||||
</div>
|
||||
{% render_field form.region %}
|
||||
{% render_field form.site_group %}
|
||||
{% render_field form.site %}
|
||||
{% render_field form.location %}
|
||||
{% render_field form.name %}
|
||||
|
@ -74,3 +74,7 @@ Context:
|
||||
</div>
|
||||
|
||||
{% endblock content-wrapper %}
|
||||
|
||||
{% block modals %}
|
||||
{% include 'inc/htmx_modal.html' with size='lg' %}
|
||||
{% endblock %}
|
||||
|
32
netbox/templates/htmx/object_selector.html
Normal file
32
netbox/templates/htmx/object_selector.html
Normal file
@ -0,0 +1,32 @@
|
||||
{% load form_helpers %}
|
||||
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Select {{ model|meta:"verbose_name"|bettertitle }}</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body row">
|
||||
<div class="col-3">
|
||||
<div class="list-group list-group-flush">
|
||||
{% for field in form.visible_fields %}
|
||||
<a href="#" class="list-group-item list-group-item-action px-0 py-1" data-bs-toggle="collapse" data-bs-target="#checkmark{{ forloop.counter }}, #selector{{ forloop.counter }}">
|
||||
<span id="checkmark{{ forloop.counter }}" class="collapse{% if forloop.counter < 3 %} show{% endif %}"><i class="mdi mdi-check-bold"></i></span>
|
||||
{{ field.label }}
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-9">
|
||||
<form hx-get="{% url 'htmx_object_selector' %}?_model={{ model|meta:"label_lower" }}" hx-target="#selector_results" hx-trigger="load, submit, keyup from:#id_q delay:500ms">
|
||||
<input type="hidden" name="_search" value="true" />
|
||||
<div class="tab-content p-1">
|
||||
{% for field in form.visible_fields %}
|
||||
<div class="collapse{% if forloop.counter < 3 %} show{% endif %}" id="selector{{ forloop.counter }}">{% render_field field %}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="text-end">
|
||||
<button type="submit" class="btn btn-sm btn-primary">Search</button>
|
||||
</div>
|
||||
</form>
|
||||
<div id="selector_results" class="mt-3" data-selector-target="{{ target_id }}"></div>
|
||||
</div>
|
||||
</div>
|
13
netbox/templates/htmx/object_selector_results.html
Normal file
13
netbox/templates/htmx/object_selector_results.html
Normal file
@ -0,0 +1,13 @@
|
||||
<div class="list-group">
|
||||
{% for object in results %}
|
||||
<a href="#" class="list-group-item list-group-item-action" data-label="{{ object }}" data-value="{{ object.pk }}" data-bs-dismiss="modal">
|
||||
<h6 class="mb-1">
|
||||
{{ object }}
|
||||
{% if object.status %}{% badge object.get_status_display bg_color=object.get_status_color %}{% endif %}
|
||||
</h6>
|
||||
{% if object.description %}
|
||||
<small>{{ object.description }}</small>
|
||||
{% endif %}
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
@ -1,5 +1,5 @@
|
||||
<div class="modal fade" id="htmx-modal" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-dialog{% if size %} modal-{{ size }}{% endif %}">
|
||||
<div class="modal-content" id="htmx-modal-content">
|
||||
{# Dynamic content goes here #}
|
||||
</div>
|
||||
|
@ -121,14 +121,9 @@
|
||||
</div>
|
||||
<div class="tab-content p-0 border-0">
|
||||
<div class="tab-pane active" id="by_device" aria-labelledby="device_tab" role="tabpanel">
|
||||
{% render_field form.nat_region %}
|
||||
{% render_field form.nat_site_group %}
|
||||
{% render_field form.nat_site %}
|
||||
{% render_field form.nat_rack %}
|
||||
{% render_field form.nat_device %}
|
||||
</div>
|
||||
<div class="tab-pane" id="by_vm" aria-labelledby="vm_tab" role="tabpanel">
|
||||
{% render_field form.nat_cluster %}
|
||||
{% render_field form.nat_virtual_machine %}
|
||||
</div>
|
||||
<div class="tab-pane" id="by_vrf" aria-labelledby="vrf_tab" role="tabpanel">
|
||||
|
@ -32,15 +32,12 @@
|
||||
<div class="row mb-3">
|
||||
<div class="tab-content p-0 border-0">
|
||||
<div class="tab-pane {% if not form.initial.interface or form.initial.vminterface %}active{% endif %}" id="vlan" role="tabpanel" aria-labeled-by="vlan_tab">
|
||||
{% render_field form.device_vlan %}
|
||||
{% render_field form.vlan %}
|
||||
</div>
|
||||
<div class="tab-pane {% if form.initial.interface %}active{% endif %}" id="interface" role="tabpanel" aria-labeled-by="interface_tab">
|
||||
{% render_field form.device %}
|
||||
{% render_field form.interface %}
|
||||
</div>
|
||||
<div class="tab-pane {% if form.initial.vminterface %}active{% endif %}" id="vminterface" role="tabpanel" aria-labeled-by="vminterface_tab">
|
||||
{% render_field form.virtual_machine %}
|
||||
{% render_field form.vminterface %}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -43,12 +43,9 @@
|
||||
</div>
|
||||
<div class="tab-content p-0 border-0">
|
||||
<div class="tab-pane{% if not site_tab_active %} active{% endif %}" id="group">
|
||||
{% render_field form.scope_type %}
|
||||
{% render_field form.group %}
|
||||
</div>
|
||||
<div class="tab-pane{% if site_tab_active %} active{% endif %}" id="site">
|
||||
{% render_field form.region %}
|
||||
{% render_field form.sitegroup %}
|
||||
{% render_field form.site %}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -26,24 +26,38 @@ class DynamicModelChoiceMixin:
|
||||
choice (optional)
|
||||
fetch_trigger: The event type which will cause the select element to
|
||||
fetch data from the API. Must be 'load', 'open', or 'collapse'. (optional)
|
||||
selector: Include an advanced object selection widget to assist the user in identifying the desired object
|
||||
"""
|
||||
filter = django_filters.ModelChoiceFilter
|
||||
widget = widgets.APISelect
|
||||
|
||||
def __init__(self, query_params=None, initial_params=None, null_option=None, disabled_indicator=None,
|
||||
fetch_trigger=None, empty_label=None, *args, **kwargs):
|
||||
def __init__(
|
||||
self,
|
||||
queryset,
|
||||
*,
|
||||
query_params=None,
|
||||
initial_params=None,
|
||||
null_option=None,
|
||||
disabled_indicator=None,
|
||||
fetch_trigger=None,
|
||||
empty_label=None,
|
||||
selector=False,
|
||||
**kwargs
|
||||
):
|
||||
self.model = queryset.model
|
||||
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
|
||||
self.selector = selector
|
||||
|
||||
# to_field_name is set by ModelChoiceField.__init__(), but we need to set it early for reference
|
||||
# by widget_attrs()
|
||||
self.to_field_name = kwargs.get('to_field_name')
|
||||
self.empty_option = empty_label or ""
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
super().__init__(queryset, **kwargs)
|
||||
|
||||
def widget_attrs(self, widget):
|
||||
attrs = {
|
||||
@ -70,6 +84,10 @@ class DynamicModelChoiceMixin:
|
||||
if (len(self.query_params) > 0):
|
||||
widget.add_query_params(self.query_params)
|
||||
|
||||
# Include object selector?
|
||||
if self.selector:
|
||||
attrs['selector'] = self.model._meta.label_lower
|
||||
|
||||
return attrs
|
||||
|
||||
def get_bound_field(self, form, field_name):
|
||||
|
@ -121,6 +121,7 @@ class APISelect(forms.Select):
|
||||
|
||||
:param api_url: API endpoint URL. Required if not set automatically by the parent field.
|
||||
"""
|
||||
template_name = 'widgets/apiselect.html'
|
||||
option_template_name = 'widgets/select_option.html'
|
||||
dynamic_params: Dict[str, str]
|
||||
static_params: Dict[str, List[str]]
|
||||
|
18
netbox/utilities/templates/widgets/apiselect.html
Normal file
18
netbox/utilities/templates/widgets/apiselect.html
Normal file
@ -0,0 +1,18 @@
|
||||
{% if widget.attrs.selector %}
|
||||
<div class="d-flex">
|
||||
{% include 'django/forms/widgets/select.html' %}
|
||||
<button
|
||||
type="button"
|
||||
title="Open selector"
|
||||
class="btn btn-sm btn-outline-dark border-input ms-1"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#htmx-modal"
|
||||
hx-get="{% url 'htmx_object_selector' %}?_model={{ widget.attrs.selector }}&target={{ widget.attrs.id }}"
|
||||
hx-target="#htmx-modal-content"
|
||||
>
|
||||
<i class="mdi mdi-database-search-outline"></i>
|
||||
</button>
|
||||
</div>
|
||||
{% else %}
|
||||
{% include 'django/forms/widgets/select.html' %}
|
||||
{% endif %}
|
@ -65,41 +65,22 @@ class ClusterForm(TenancyForm, NetBoxModelForm):
|
||||
queryset=ClusterGroup.objects.all(),
|
||||
required=False
|
||||
)
|
||||
region = DynamicModelChoiceField(
|
||||
queryset=Region.objects.all(),
|
||||
required=False,
|
||||
initial_params={
|
||||
'sites': '$site'
|
||||
}
|
||||
)
|
||||
site_group = DynamicModelChoiceField(
|
||||
queryset=SiteGroup.objects.all(),
|
||||
required=False,
|
||||
initial_params={
|
||||
'sites': '$site'
|
||||
}
|
||||
)
|
||||
site = DynamicModelChoiceField(
|
||||
queryset=Site.objects.all(),
|
||||
required=False,
|
||||
query_params={
|
||||
'region_id': '$region',
|
||||
'group_id': '$site_group',
|
||||
}
|
||||
selector=True
|
||||
)
|
||||
comments = CommentField()
|
||||
|
||||
fieldsets = (
|
||||
('Cluster', ('name', 'type', 'group', 'status', 'description', 'tags')),
|
||||
('Site', ('region', 'site_group', 'site')),
|
||||
('Cluster', ('name', 'type', 'group', 'site', 'status', 'description', 'tags')),
|
||||
('Tenancy', ('tenant_group', 'tenant')),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Cluster
|
||||
fields = (
|
||||
'name', 'type', 'group', 'status', 'tenant', 'region', 'site_group', 'site', 'description', 'comments',
|
||||
'tags',
|
||||
'name', 'type', 'group', 'status', 'tenant', 'site', 'description', 'comments', 'tags',
|
||||
)
|
||||
|
||||
|
||||
@ -178,20 +159,12 @@ class VirtualMachineForm(TenancyForm, NetBoxModelForm):
|
||||
queryset=Site.objects.all(),
|
||||
required=False
|
||||
)
|
||||
cluster_group = DynamicModelChoiceField(
|
||||
queryset=ClusterGroup.objects.all(),
|
||||
required=False,
|
||||
null_option='None',
|
||||
initial_params={
|
||||
'clusters': '$cluster'
|
||||
}
|
||||
)
|
||||
cluster = DynamicModelChoiceField(
|
||||
queryset=Cluster.objects.all(),
|
||||
required=False,
|
||||
selector=True,
|
||||
query_params={
|
||||
'site_id': '$site',
|
||||
'group_id': '$cluster_group',
|
||||
}
|
||||
)
|
||||
device = DynamicModelChoiceField(
|
||||
@ -222,7 +195,7 @@ class VirtualMachineForm(TenancyForm, NetBoxModelForm):
|
||||
|
||||
fieldsets = (
|
||||
('Virtual Machine', ('name', 'role', 'status', 'description', 'tags')),
|
||||
('Site/Cluster', ('site', 'cluster_group', 'cluster', 'device')),
|
||||
('Site/Cluster', ('site', 'cluster', 'device')),
|
||||
('Tenancy', ('tenant_group', 'tenant')),
|
||||
('Management', ('platform', 'primary_ip4', 'primary_ip6')),
|
||||
('Resources', ('vcpus', 'memory', 'disk')),
|
||||
@ -232,9 +205,8 @@ class VirtualMachineForm(TenancyForm, NetBoxModelForm):
|
||||
class Meta:
|
||||
model = VirtualMachine
|
||||
fields = [
|
||||
'name', 'status', 'site', 'cluster_group', 'cluster', 'device', 'role', 'tenant_group', 'tenant',
|
||||
'platform', 'primary_ip4', 'primary_ip6', 'vcpus', 'memory', 'disk', 'description', 'comments', 'tags',
|
||||
'local_context_data',
|
||||
'name', 'status', 'site', 'cluster', 'device', 'role', 'tenant_group', 'tenant', 'platform', 'primary_ip4',
|
||||
'primary_ip6', 'vcpus', 'memory', 'disk', 'description', 'comments', 'tags', 'local_context_data',
|
||||
]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
@ -280,7 +252,8 @@ class VirtualMachineForm(TenancyForm, NetBoxModelForm):
|
||||
|
||||
class VMInterfaceForm(InterfaceCommonForm, NetBoxModelForm):
|
||||
virtual_machine = DynamicModelChoiceField(
|
||||
queryset=VirtualMachine.objects.all()
|
||||
queryset=VirtualMachine.objects.all(),
|
||||
selector=True
|
||||
)
|
||||
parent = DynamicModelChoiceField(
|
||||
queryset=VMInterface.objects.all(),
|
||||
|
@ -38,55 +38,16 @@ class WirelessLANForm(TenancyForm, NetBoxModelForm):
|
||||
queryset=WirelessLANGroup.objects.all(),
|
||||
required=False
|
||||
)
|
||||
region = DynamicModelChoiceField(
|
||||
queryset=Region.objects.all(),
|
||||
required=False,
|
||||
initial_params={
|
||||
'sites': '$site'
|
||||
}
|
||||
)
|
||||
site_group = DynamicModelChoiceField(
|
||||
queryset=SiteGroup.objects.all(),
|
||||
required=False,
|
||||
initial_params={
|
||||
'sites': '$site'
|
||||
}
|
||||
)
|
||||
site = DynamicModelChoiceField(
|
||||
queryset=Site.objects.all(),
|
||||
required=False,
|
||||
null_option='None',
|
||||
query_params={
|
||||
'region_id': '$region',
|
||||
'group_id': '$site_group',
|
||||
}
|
||||
)
|
||||
vlan_group = DynamicModelChoiceField(
|
||||
queryset=VLANGroup.objects.all(),
|
||||
required=False,
|
||||
label=_('VLAN group'),
|
||||
null_option='None',
|
||||
query_params={
|
||||
'site': '$site'
|
||||
},
|
||||
initial_params={
|
||||
'vlans': '$vlan'
|
||||
}
|
||||
)
|
||||
vlan = DynamicModelChoiceField(
|
||||
queryset=VLAN.objects.all(),
|
||||
required=False,
|
||||
label=_('VLAN'),
|
||||
query_params={
|
||||
'site_id': '$site',
|
||||
'group_id': '$vlan_group',
|
||||
}
|
||||
selector=True,
|
||||
label=_('VLAN')
|
||||
)
|
||||
comments = CommentField()
|
||||
|
||||
fieldsets = (
|
||||
('Wireless LAN', ('ssid', 'group', 'status', 'description', 'tags')),
|
||||
('VLAN', ('region', 'site_group', 'site', 'vlan_group', 'vlan',)),
|
||||
('Wireless LAN', ('ssid', 'group', 'vlan', 'status', 'description', 'tags')),
|
||||
('Tenancy', ('tenant_group', 'tenant')),
|
||||
('Authentication', ('auth_type', 'auth_cipher', 'auth_psk')),
|
||||
)
|
||||
@ -94,8 +55,8 @@ class WirelessLANForm(TenancyForm, NetBoxModelForm):
|
||||
class Meta:
|
||||
model = WirelessLAN
|
||||
fields = [
|
||||
'ssid', 'group', 'region', 'site_group', 'site', 'status', 'vlan_group', 'vlan', 'tenant_group', 'tenant',
|
||||
'auth_type', 'auth_cipher', 'auth_psk', 'description', 'comments', 'tags',
|
||||
'ssid', 'group', 'status', 'vlan', 'tenant_group', 'tenant', 'auth_type', 'auth_cipher', 'auth_psk',
|
||||
'description', 'comments', 'tags',
|
||||
]
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user