diff --git a/netbox/circuits/forms/model_forms.py b/netbox/circuits/forms/model_forms.py index d06f0bd9d7..537db50dfb 100644 --- a/netbox/circuits/forms/model_forms.py +++ b/netbox/circuits/forms/model_forms.py @@ -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(), diff --git a/netbox/dcim/forms/model_forms.py b/netbox/dcim/forms/model_forms.py index f1f392c99a..13c58d02f4 100644 --- a/netbox/dcim/forms/model_forms.py +++ b/netbox/dcim/forms/model_forms.py @@ -14,9 +14,9 @@ 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 Meta: 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 Meta: 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 Meta: 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 Meta: 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 @@ def clean_vc_position(self): 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 Meta: 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 Meta: 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' ] diff --git a/netbox/ipam/forms/model_forms.py b/netbox/ipam/forms/model_forms.py index a29aabebd7..3904281a83 100644 --- a/netbox/ipam/forms/model_forms.py +++ b/netbox/ipam/forms/model_forms.py @@ -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 @@ def clean(self): 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 Meta: 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 @@ def __init__(self, *args, **kwargs): 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 diff --git a/netbox/netbox/urls.py b/netbox/netbox/urls.py index 22c47f7bbd..8c859528c7 100644 --- a/netbox/netbox/urls.py +++ b/netbox/netbox/urls.py @@ -10,7 +10,7 @@ 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 @@ 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')), diff --git a/netbox/netbox/views/htmx.py b/netbox/netbox/views/htmx.py new file mode 100644 index 0000000000..04ddcb06ba --- /dev/null +++ b/netbox/netbox/views/htmx.py @@ -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}') diff --git a/netbox/project-static/dist/netbox.js b/netbox/project-static/dist/netbox.js index a64ac5df76..cbb0d10e38 100644 --- a/netbox/project-static/dist/netbox.js +++ b/netbox/project-static/dist/netbox.js @@ -1,10 +1,10 @@ -(()=>{var pw=Object.create;var Zo=Object.defineProperty,mw=Object.defineProperties,gw=Object.getOwnPropertyDescriptor,vw=Object.getOwnPropertyDescriptors,bw=Object.getOwnPropertyNames,eh=Object.getOwnPropertySymbols,yw=Object.getPrototypeOf,th=Object.prototype.hasOwnProperty,Ew=Object.prototype.propertyIsEnumerable;var iu=(ii,ti,ei)=>ti in ii?Zo(ii,ti,{enumerable:!0,configurable:!0,writable:!0,value:ei}):ii[ti]=ei,Vi=(ii,ti)=>{for(var ei in ti||(ti={}))th.call(ti,ei)&&iu(ii,ei,ti[ei]);if(eh)for(var ei of eh(ti))Ew.call(ti,ei)&&iu(ii,ei,ti[ei]);return ii},rl=(ii,ti)=>mw(ii,vw(ti)),ih=ii=>Zo(ii,"__esModule",{value:!0});var _i=(ii,ti)=>()=>(ti||ii((ti={exports:{}}).exports,ti),ti.exports),_w=(ii,ti)=>{ih(ii);for(var ei in ti)Zo(ii,ei,{get:ti[ei],enumerable:!0})},ww=(ii,ti,ei)=>{if(ti&&typeof ti=="object"||typeof ti=="function")for(let ri of bw(ti))!th.call(ii,ri)&&ri!=="default"&&Zo(ii,ri,{get:()=>ti[ri],enumerable:!(ei=gw(ti,ri))||ei.enumerable});return ii},Ln=ii=>ww(ih(Zo(ii!=null?pw(yw(ii)):{},"default",ii&&ii.__esModule&&"default"in ii?{get:()=>ii.default,enumerable:!0}:{value:ii,enumerable:!0})),ii);var Yi=(ii,ti,ei)=>(iu(ii,typeof ti!="symbol"?ti+"":ti,ei),ei);var Fr=(ii,ti,ei)=>new Promise((ri,ni)=>{var si=li=>{try{ai(ei.next(li))}catch(ci){ni(ci)}},oi=li=>{try{ai(ei.throw(li))}catch(ci){ni(ci)}},ai=li=>li.done?ri(li.value):Promise.resolve(li.value).then(si,oi);ai((ei=ei.apply(ii,ti)).next())});var Hp=_i((exports,module)=>{(function(ii,ti){typeof define=="function"&&define.amd?define([],ti):ii.htmx=ii.htmx||ti()})(typeof self!="undefined"?self:exports,function(){return function(){"use strict";var U={onLoad:t,process:vt,on:X,off:F,trigger:$,ajax:nr,find:R,findAll:O,closest:N,values:function(ii,ti){var ei=Pt(ii,ti||"post");return ei.values},remove:q,addClass:L,removeClass:T,toggleClass:A,takeClass:H,defineExtension:fr,removeExtension:cr,logAll:C,logger:null,config:{historyEnabled:!0,historyCacheSize:10,refreshOnHistoryMiss:!1,defaultSwapStyle:"innerHTML",defaultSwapDelay:0,defaultSettleDelay:20,includeIndicatorStyles:!0,indicatorClass:"htmx-indicator",requestClass:"htmx-request",addedClass:"htmx-added",settlingClass:"htmx-settling",swappingClass:"htmx-swapping",allowEval:!0,inlineScriptNonce:"",attributesToSettle:["class","style","width","height"],withCredentials:!1,timeout:0,wsReconnectDelay:"full-jitter",disableSelector:"[hx-disable], [data-hx-disable]",useTemplateFragments:!1,scrollBehavior:"smooth",defaultFocusScroll:!1},parseInterval:v,_:e,createEventSource:function(ii){return new EventSource(ii,{withCredentials:!0})},createWebSocket:function(ii){return new WebSocket(ii,[])},version:"1.8.0"},r={addTriggerHandler:st,bodyContains:K,canAccessLocalStorage:E,filterValues:Ut,hasAttribute:o,getAttributeValue:V,getClosestMatch:h,getExpressionVars:Qt,getHeaders:Bt,getInputValues:Pt,getInternalData:W,getSwapSpecification:_t,getTriggerSpecs:Me,getTarget:re,makeFragment:g,mergeObjects:Y,makeSettleInfo:Gt,oobSwap:ae,selectAndSwap:Ee,settleImmediately:Lt,shouldCancel:je,triggerEvent:$,triggerErrorEvent:J,withExtensions:xt},n=["get","post","put","delete","patch"],i=n.map(function(ii){return"[hx-"+ii+"], [data-hx-"+ii+"]"}).join(", ");function v(ii){if(ii!=null)return ii.slice(-2)=="ms"?parseFloat(ii.slice(0,-2))||void 0:ii.slice(-1)=="s"?parseFloat(ii.slice(0,-1))*1e3||void 0:ii.slice(-1)=="m"?parseFloat(ii.slice(0,-1))*1e3*60||void 0:parseFloat(ii)||void 0}function f(ii,ti){return ii.getAttribute&&ii.getAttribute(ti)}function o(ii,ti){return ii.hasAttribute&&(ii.hasAttribute(ti)||ii.hasAttribute("data-"+ti))}function V(ii,ti){return f(ii,ti)||f(ii,"data-"+ti)}function u(ii){return ii.parentElement}function _(){return document}function h(ii,ti){for(;ii&&!ti(ii);)ii=u(ii);return ii||null}function a(ii,ti,ei){var ri=V(ti,ei),ni=V(ti,"hx-disinherit");return ii!==ti&&ni&&(ni==="*"||ni.split(" ").indexOf(ei)>=0)?"unset":ri}function z(ii,ti){var ei=null;if(h(ii,function(ri){return ei=a(ii,ri,ti)}),ei!=="unset")return ei}function d(ii,ti){var ei=ii.matches||ii.matchesSelector||ii.msMatchesSelector||ii.mozMatchesSelector||ii.webkitMatchesSelector||ii.oMatchesSelector;return ei&&ei.call(ii,ti)}function s(ii){var ti=/<([a-z][^\/\0>\x20\t\r\n\f]*)/i,ei=ti.exec(ii);return ei?ei[1].toLowerCase():""}function l(ii,ti){for(var ei=new DOMParser,ri=ei.parseFromString(ii,"text/html"),ni=ri.body;ti>0;)ti--,ni=ni.firstChild;return ni==null&&(ni=_().createDocumentFragment()),ni}function g(ii){if(U.config.useTemplateFragments){var ti=l("
"+ii+"",0);return ti.querySelector("template").content}else{var ei=s(ii);switch(ei){case"thead":case"tbody":case"tfoot":case"colgroup":case"caption":return l("