mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-14 09:51:22 -06:00
IPAM Select2 forms and changelog
This commit is contained in:
parent
5f1f8ee73b
commit
ad4fb3ce8b
@ -1,5 +1,9 @@
|
|||||||
Select2 issues
|
Select2 issues
|
||||||
|
* [#2516](https://github.com/digitalocean/netbox/issues/2516) - Implemented Select2 for all Model backed selection fields
|
||||||
* [#2590](https://github.com/digitalocean/netbox/issues/2590) - Implemented the color picker with Select2 to show colors in the background
|
* [#2590](https://github.com/digitalocean/netbox/issues/2590) - Implemented the color picker with Select2 to show colors in the background
|
||||||
|
* [#2735](https://github.com/digitalocean/netbox/issues/2735) - Implemented Select2 for all list filter form select elements
|
||||||
|
* [#2753](https://github.com/digitalocean/netbox/issues/2753) - Implemented Select2 to replace most all instances of select fields in forms
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
v2.5.3 (FUTURE)
|
v2.5.3 (FUTURE)
|
||||||
|
@ -289,8 +289,6 @@ class CircuitFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
|||||||
)
|
)
|
||||||
status = forms.MultipleChoiceField(
|
status = forms.MultipleChoiceField(
|
||||||
choices=CIRCUIT_STATUS_CHOICES,
|
choices=CIRCUIT_STATUS_CHOICES,
|
||||||
annotate=Circuit.objects.all(),
|
|
||||||
annotate_field='status',
|
|
||||||
required=False,
|
required=False,
|
||||||
widget=StaticSelect2Multiple()
|
widget=StaticSelect2Multiple()
|
||||||
)
|
)
|
||||||
|
@ -1600,7 +1600,7 @@ class DeviceBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditF
|
|||||||
required=False,
|
required=False,
|
||||||
label='Type',
|
label='Type',
|
||||||
widget=APISelect(
|
widget=APISelect(
|
||||||
api_url="/api/dcim/device-types"
|
api_url="/api/dcim/device-types/"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
device_role = forms.ModelChoiceField(
|
device_role = forms.ModelChoiceField(
|
||||||
@ -1608,21 +1608,21 @@ class DeviceBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditF
|
|||||||
required=False,
|
required=False,
|
||||||
label='Role',
|
label='Role',
|
||||||
widget=APISelect(
|
widget=APISelect(
|
||||||
api_url="/api/dcim/device-roles"
|
api_url="/api/dcim/device-roles/"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
tenant = forms.ModelChoiceField(
|
tenant = forms.ModelChoiceField(
|
||||||
queryset=Tenant.objects.all(),
|
queryset=Tenant.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
widget=APISelect(
|
widget=APISelect(
|
||||||
api_url="/api/tenancy/tenants"
|
api_url="/api/tenancy/tenants/"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
platform = forms.ModelChoiceField(
|
platform = forms.ModelChoiceField(
|
||||||
queryset=Platform.objects.all(),
|
queryset=Platform.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
widget=APISelect(
|
widget=APISelect(
|
||||||
api_url="/api/dcim/platforms"
|
api_url="/api/dcim/platforms/"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
status = forms.ChoiceField(
|
status = forms.ChoiceField(
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
from django import forms
|
from django import forms
|
||||||
from django.core.exceptions import MultipleObjectsReturned
|
from django.core.exceptions import MultipleObjectsReturned
|
||||||
from django.core.validators import MaxValueValidator, MinValueValidator
|
from django.core.validators import MaxValueValidator, MinValueValidator
|
||||||
from django.db.models import Count
|
|
||||||
from taggit.forms import TagField
|
from taggit.forms import TagField
|
||||||
|
|
||||||
from dcim.models import Site, Rack, Device, Interface
|
from dcim.models import Site, Rack, Device, Interface
|
||||||
@ -9,9 +8,9 @@ from extras.forms import AddRemoveTagsForm, CustomFieldForm, CustomFieldBulkEdit
|
|||||||
from tenancy.forms import TenancyForm
|
from tenancy.forms import TenancyForm
|
||||||
from tenancy.models import Tenant
|
from tenancy.models import Tenant
|
||||||
from utilities.forms import (
|
from utilities.forms import (
|
||||||
AnnotatedMultipleChoiceField, APISelect, BootstrapMixin, BulkEditNullBooleanSelect, ChainedModelChoiceField,
|
add_blank_choice, APISelect, APISelectMultiple, BootstrapMixin, BulkEditNullBooleanSelect, ChainedModelChoiceField,
|
||||||
CSVChoiceField, ExpandableIPAddressField, FilterChoiceField, FlexibleModelChoiceField, Livesearch, ReturnURLForm,
|
CSVChoiceField, ExpandableIPAddressField, FilterChoiceField, FlexibleModelChoiceField, ReturnURLForm, SlugField,
|
||||||
SlugField, add_blank_choice,
|
StaticSelect2, StaticSelect2Multiple, BOOLEAN_WITH_BLANK_CHOICES
|
||||||
)
|
)
|
||||||
from virtualization.models import VirtualMachine
|
from virtualization.models import VirtualMachine
|
||||||
from .constants import (
|
from .constants import (
|
||||||
@ -77,7 +76,10 @@ class VRFBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditForm
|
|||||||
)
|
)
|
||||||
tenant = forms.ModelChoiceField(
|
tenant = forms.ModelChoiceField(
|
||||||
queryset=Tenant.objects.all(),
|
queryset=Tenant.objects.all(),
|
||||||
required=False
|
required=False,
|
||||||
|
widget=APISelect(
|
||||||
|
api_url="/api/tenancy/tenants/"
|
||||||
|
)
|
||||||
)
|
)
|
||||||
enforce_unique = forms.NullBooleanField(
|
enforce_unique = forms.NullBooleanField(
|
||||||
required=False,
|
required=False,
|
||||||
@ -102,11 +104,14 @@ class VRFFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
|||||||
label='Search'
|
label='Search'
|
||||||
)
|
)
|
||||||
tenant = FilterChoiceField(
|
tenant = FilterChoiceField(
|
||||||
queryset=Tenant.objects.annotate(
|
queryset=Tenant.objects.all(),
|
||||||
filter_count=Count('vrfs')
|
|
||||||
),
|
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
null_label='-- None --'
|
null_label='-- None --',
|
||||||
|
widget=APISelectMultiple(
|
||||||
|
api_url="/api/tenancy/tenants/",
|
||||||
|
value_field="slug",
|
||||||
|
null_option=True,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -139,12 +144,8 @@ class RIRFilterForm(BootstrapMixin, forms.Form):
|
|||||||
is_private = forms.NullBooleanField(
|
is_private = forms.NullBooleanField(
|
||||||
required=False,
|
required=False,
|
||||||
label='Private',
|
label='Private',
|
||||||
widget=forms.Select(
|
widget=StaticSelect2(
|
||||||
choices=[
|
choices=BOOLEAN_WITH_BLANK_CHOICES
|
||||||
('', '---------'),
|
|
||||||
('True', 'Yes'),
|
|
||||||
('False', 'No'),
|
|
||||||
]
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -168,6 +169,11 @@ class AggregateForm(BootstrapMixin, CustomFieldForm):
|
|||||||
'rir': "Regional Internet Registry responsible for this prefix",
|
'rir': "Regional Internet Registry responsible for this prefix",
|
||||||
'date_added': "Format: YYYY-MM-DD",
|
'date_added': "Format: YYYY-MM-DD",
|
||||||
}
|
}
|
||||||
|
widgets = {
|
||||||
|
'rir': APISelect(
|
||||||
|
api_url="/api/ipam/rirs/"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class AggregateCSVForm(forms.ModelForm):
|
class AggregateCSVForm(forms.ModelForm):
|
||||||
@ -193,7 +199,10 @@ class AggregateBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEd
|
|||||||
rir = forms.ModelChoiceField(
|
rir = forms.ModelChoiceField(
|
||||||
queryset=RIR.objects.all(),
|
queryset=RIR.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
label='RIR'
|
label='RIR',
|
||||||
|
widget=APISelect(
|
||||||
|
api_url="/api/ipam/rirs/"
|
||||||
|
)
|
||||||
)
|
)
|
||||||
date_added = forms.DateField(
|
date_added = forms.DateField(
|
||||||
required=False
|
required=False
|
||||||
@ -218,12 +227,17 @@ class AggregateFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
|||||||
family = forms.ChoiceField(
|
family = forms.ChoiceField(
|
||||||
required=False,
|
required=False,
|
||||||
choices=IP_FAMILY_CHOICES,
|
choices=IP_FAMILY_CHOICES,
|
||||||
label='Address family'
|
label='Address family',
|
||||||
|
widget=StaticSelect2()
|
||||||
)
|
)
|
||||||
rir = FilterChoiceField(
|
rir = FilterChoiceField(
|
||||||
queryset=RIR.objects.annotate(filter_count=Count('aggregates')),
|
queryset=RIR.objects.all(),
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
label='RIR'
|
label='RIR',
|
||||||
|
widget=APISelectMultiple(
|
||||||
|
api_url="/api/ipam/rirs/",
|
||||||
|
value_field="slug",
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -261,9 +275,13 @@ class PrefixForm(BootstrapMixin, TenancyForm, CustomFieldForm):
|
|||||||
queryset=Site.objects.all(),
|
queryset=Site.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
label='Site',
|
label='Site',
|
||||||
widget=forms.Select(
|
widget=APISelect(
|
||||||
|
api_url="/api/dcim/sites/",
|
||||||
|
filter_for={
|
||||||
|
'vlan_group': 'site_id',
|
||||||
|
'vlan': 'site_id',
|
||||||
|
},
|
||||||
attrs={
|
attrs={
|
||||||
'filter-for': 'vlan_group',
|
|
||||||
'nullable': 'true',
|
'nullable': 'true',
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -276,9 +294,11 @@ class PrefixForm(BootstrapMixin, TenancyForm, CustomFieldForm):
|
|||||||
required=False,
|
required=False,
|
||||||
label='VLAN group',
|
label='VLAN group',
|
||||||
widget=APISelect(
|
widget=APISelect(
|
||||||
api_url='/api/ipam/vlan-groups/?site_id={{site}}',
|
api_url='/api/ipam/vlan-groups/',
|
||||||
|
filter_for={
|
||||||
|
'vlan': 'group_id'
|
||||||
|
},
|
||||||
attrs={
|
attrs={
|
||||||
'filter-for': 'vlan',
|
|
||||||
'nullable': 'true',
|
'nullable': 'true',
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -292,7 +312,7 @@ class PrefixForm(BootstrapMixin, TenancyForm, CustomFieldForm):
|
|||||||
required=False,
|
required=False,
|
||||||
label='VLAN',
|
label='VLAN',
|
||||||
widget=APISelect(
|
widget=APISelect(
|
||||||
api_url='/api/ipam/vlans/?site_id={{site}}&group_id={{vlan_group}}',
|
api_url='/api/ipam/vlans/',
|
||||||
display_field='display_name'
|
display_field='display_name'
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -304,6 +324,15 @@ class PrefixForm(BootstrapMixin, TenancyForm, CustomFieldForm):
|
|||||||
'prefix', 'vrf', 'site', 'vlan', 'status', 'role', 'is_pool', 'description', 'tenant_group', 'tenant',
|
'prefix', 'vrf', 'site', 'vlan', 'status', 'role', 'is_pool', 'description', 'tenant_group', 'tenant',
|
||||||
'tags',
|
'tags',
|
||||||
]
|
]
|
||||||
|
widgets = {
|
||||||
|
'vrf': APISelect(
|
||||||
|
api_url="/api/ipam/vrfs/"
|
||||||
|
),
|
||||||
|
'status': StaticSelect2(),
|
||||||
|
'role': APISelect(
|
||||||
|
api_url="/api/ipam/roles/"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
|
|
||||||
@ -415,12 +444,18 @@ class PrefixBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditF
|
|||||||
)
|
)
|
||||||
site = forms.ModelChoiceField(
|
site = forms.ModelChoiceField(
|
||||||
queryset=Site.objects.all(),
|
queryset=Site.objects.all(),
|
||||||
required=False
|
required=False,
|
||||||
|
widget=APISelect(
|
||||||
|
api_url="/api/dcim/sites/"
|
||||||
|
)
|
||||||
)
|
)
|
||||||
vrf = forms.ModelChoiceField(
|
vrf = forms.ModelChoiceField(
|
||||||
queryset=VRF.objects.all(),
|
queryset=VRF.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
label='VRF'
|
label='VRF',
|
||||||
|
widget=APISelect(
|
||||||
|
api_url="/api/ipam/vrfs/"
|
||||||
|
)
|
||||||
)
|
)
|
||||||
prefix_length = forms.IntegerField(
|
prefix_length = forms.IntegerField(
|
||||||
min_value=1,
|
min_value=1,
|
||||||
@ -429,15 +464,22 @@ class PrefixBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditF
|
|||||||
)
|
)
|
||||||
tenant = forms.ModelChoiceField(
|
tenant = forms.ModelChoiceField(
|
||||||
queryset=Tenant.objects.all(),
|
queryset=Tenant.objects.all(),
|
||||||
required=False
|
required=False,
|
||||||
|
widget=APISelect(
|
||||||
|
api_url="/api/tenancy/tenants/"
|
||||||
|
)
|
||||||
)
|
)
|
||||||
status = forms.ChoiceField(
|
status = forms.ChoiceField(
|
||||||
choices=add_blank_choice(PREFIX_STATUS_CHOICES),
|
choices=add_blank_choice(PREFIX_STATUS_CHOICES),
|
||||||
required=False
|
required=False,
|
||||||
|
widget=StaticSelect2()
|
||||||
)
|
)
|
||||||
role = forms.ModelChoiceField(
|
role = forms.ModelChoiceField(
|
||||||
queryset=Role.objects.all(),
|
queryset=Role.objects.all(),
|
||||||
required=False
|
required=False,
|
||||||
|
widget=APISelect(
|
||||||
|
api_url="/api/ipam/roles/"
|
||||||
|
)
|
||||||
)
|
)
|
||||||
is_pool = forms.NullBooleanField(
|
is_pool = forms.NullBooleanField(
|
||||||
required=False,
|
required=False,
|
||||||
@ -473,47 +515,60 @@ class PrefixFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
|||||||
family = forms.ChoiceField(
|
family = forms.ChoiceField(
|
||||||
required=False,
|
required=False,
|
||||||
choices=IP_FAMILY_CHOICES,
|
choices=IP_FAMILY_CHOICES,
|
||||||
label='Address family'
|
label='Address family',
|
||||||
|
widget=StaticSelect2()
|
||||||
)
|
)
|
||||||
mask_length = forms.ChoiceField(
|
mask_length = forms.ChoiceField(
|
||||||
required=False,
|
required=False,
|
||||||
choices=PREFIX_MASK_LENGTH_CHOICES,
|
choices=PREFIX_MASK_LENGTH_CHOICES,
|
||||||
label='Mask length'
|
label='Mask length',
|
||||||
|
widget=StaticSelect2()
|
||||||
)
|
)
|
||||||
vrf = FilterChoiceField(
|
vrf = FilterChoiceField(
|
||||||
queryset=VRF.objects.annotate(
|
queryset=VRF.objects.all(),
|
||||||
filter_count=Count('prefixes')
|
|
||||||
),
|
|
||||||
to_field_name='rd',
|
to_field_name='rd',
|
||||||
label='VRF',
|
label='VRF',
|
||||||
null_label='-- Global --'
|
null_label='-- Global --',
|
||||||
|
widget=APISelectMultiple(
|
||||||
|
api_url="/api/ipam/vrfs/",
|
||||||
|
value_field="slug",
|
||||||
|
null_option=True,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
tenant = FilterChoiceField(
|
tenant = FilterChoiceField(
|
||||||
queryset=Tenant.objects.annotate(
|
queryset=Tenant.objects.all(),
|
||||||
filter_count=Count('prefixes')
|
|
||||||
),
|
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
null_label='-- None --'
|
null_label='-- None --',
|
||||||
|
widget=APISelectMultiple(
|
||||||
|
api_url="/api/tenancy/tenants/",
|
||||||
|
value_field="slug",
|
||||||
|
null_option=True,
|
||||||
)
|
)
|
||||||
status = AnnotatedMultipleChoiceField(
|
)
|
||||||
|
status = forms.MultipleChoiceField(
|
||||||
choices=PREFIX_STATUS_CHOICES,
|
choices=PREFIX_STATUS_CHOICES,
|
||||||
annotate=Prefix.objects.all(),
|
required=False,
|
||||||
annotate_field='status',
|
widget=StaticSelect2Multiple()
|
||||||
required=False
|
|
||||||
)
|
)
|
||||||
site = FilterChoiceField(
|
site = FilterChoiceField(
|
||||||
queryset=Site.objects.annotate(
|
queryset=Site.objects.all(),
|
||||||
filter_count=Count('prefixes')
|
|
||||||
),
|
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
null_label='-- None --'
|
null_label='-- None --',
|
||||||
|
widget=APISelectMultiple(
|
||||||
|
api_url="/api/dcim/sites/",
|
||||||
|
value_field="slug",
|
||||||
|
null_option=True,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
role = FilterChoiceField(
|
role = FilterChoiceField(
|
||||||
queryset=Role.objects.annotate(
|
queryset=Role.objects.all(),
|
||||||
filter_count=Count('prefixes')
|
|
||||||
),
|
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
null_label='-- None --'
|
null_label='-- None --',
|
||||||
|
widget=APISelectMultiple(
|
||||||
|
api_url="/api/ipam/roles/",
|
||||||
|
value_field="slug",
|
||||||
|
null_option=True,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
expand = forms.BooleanField(
|
expand = forms.BooleanField(
|
||||||
required=False,
|
required=False,
|
||||||
@ -534,9 +589,11 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldForm)
|
|||||||
queryset=Site.objects.all(),
|
queryset=Site.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
label='Site',
|
label='Site',
|
||||||
widget=forms.Select(
|
widget=APISelect(
|
||||||
attrs={
|
api_url="/api/dcim/sites/",
|
||||||
'filter-for': 'nat_rack'
|
filter_for={
|
||||||
|
'nat_rack': 'site_id',
|
||||||
|
'nat_device': 'site_id'
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -548,10 +605,12 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldForm)
|
|||||||
required=False,
|
required=False,
|
||||||
label='Rack',
|
label='Rack',
|
||||||
widget=APISelect(
|
widget=APISelect(
|
||||||
api_url='/api/dcim/racks/?site_id={{nat_site}}',
|
api_url='/api/dcim/racks/',
|
||||||
display_field='display_name',
|
display_field='display_name',
|
||||||
|
filter_for={
|
||||||
|
'nat_device': 'rack_id'
|
||||||
|
},
|
||||||
attrs={
|
attrs={
|
||||||
'filter-for': 'nat_device',
|
|
||||||
'nullable': 'true'
|
'nullable': 'true'
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -565,9 +624,11 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldForm)
|
|||||||
required=False,
|
required=False,
|
||||||
label='Device',
|
label='Device',
|
||||||
widget=APISelect(
|
widget=APISelect(
|
||||||
api_url='/api/dcim/devices/?site_id={{nat_site}}&rack_id={{nat_rack}}',
|
api_url='/api/dcim/devices/',
|
||||||
display_field='display_name',
|
display_field='display_name',
|
||||||
attrs={'filter-for': 'nat_inside'}
|
filter_for={
|
||||||
|
'nat_inside': 'device_id'
|
||||||
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
nat_inside = ChainedModelChoiceField(
|
nat_inside = ChainedModelChoiceField(
|
||||||
@ -578,20 +639,10 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldForm)
|
|||||||
required=False,
|
required=False,
|
||||||
label='IP Address',
|
label='IP Address',
|
||||||
widget=APISelect(
|
widget=APISelect(
|
||||||
api_url='/api/ipam/ip-addresses/?device_id={{nat_device}}',
|
api_url='/api/ipam/ip-addresses/',
|
||||||
display_field='address'
|
display_field='address'
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
livesearch = forms.CharField(
|
|
||||||
required=False,
|
|
||||||
label='Search',
|
|
||||||
widget=Livesearch(
|
|
||||||
query_key='q',
|
|
||||||
query_url='ipam-api:ipaddress-list',
|
|
||||||
field_to_update='nat_inside',
|
|
||||||
obj_label='address'
|
|
||||||
)
|
|
||||||
)
|
|
||||||
primary_for_parent = forms.BooleanField(
|
primary_for_parent = forms.BooleanField(
|
||||||
required=False,
|
required=False,
|
||||||
label='Make this the primary IP for the device/VM'
|
label='Make this the primary IP for the device/VM'
|
||||||
@ -606,6 +657,13 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldForm)
|
|||||||
'address', 'vrf', 'status', 'role', 'description', 'interface', 'primary_for_parent', 'nat_site',
|
'address', 'vrf', 'status', 'role', 'description', 'interface', 'primary_for_parent', 'nat_site',
|
||||||
'nat_rack', 'nat_inside', 'tenant_group', 'tenant', 'tags',
|
'nat_rack', 'nat_inside', 'tenant_group', 'tenant', 'tags',
|
||||||
]
|
]
|
||||||
|
widgets = {
|
||||||
|
'status': StaticSelect2(),
|
||||||
|
'role': StaticSelect2(),
|
||||||
|
'vrf': APISelect(
|
||||||
|
api_url="/api/ipam/vrfs/"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
|
|
||||||
@ -685,6 +743,13 @@ class IPAddressBulkAddForm(BootstrapMixin, TenancyForm, CustomFieldForm):
|
|||||||
fields = [
|
fields = [
|
||||||
'address', 'vrf', 'status', 'role', 'description', 'tenant_group', 'tenant',
|
'address', 'vrf', 'status', 'role', 'description', 'tenant_group', 'tenant',
|
||||||
]
|
]
|
||||||
|
widgets = {
|
||||||
|
'status': StaticSelect2(),
|
||||||
|
'role': StaticSelect2(),
|
||||||
|
'vrf': APISelect(
|
||||||
|
api_url="/api/ipam/vrfs/"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
@ -822,7 +887,10 @@ class IPAddressBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEd
|
|||||||
vrf = forms.ModelChoiceField(
|
vrf = forms.ModelChoiceField(
|
||||||
queryset=VRF.objects.all(),
|
queryset=VRF.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
label='VRF'
|
label='VRF',
|
||||||
|
widget=APISelect(
|
||||||
|
api_url="/api/ipam/vrfs/"
|
||||||
|
)
|
||||||
)
|
)
|
||||||
mask_length = forms.IntegerField(
|
mask_length = forms.IntegerField(
|
||||||
min_value=1,
|
min_value=1,
|
||||||
@ -831,15 +899,20 @@ class IPAddressBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEd
|
|||||||
)
|
)
|
||||||
tenant = forms.ModelChoiceField(
|
tenant = forms.ModelChoiceField(
|
||||||
queryset=Tenant.objects.all(),
|
queryset=Tenant.objects.all(),
|
||||||
required=False
|
required=False,
|
||||||
|
widget=APISelect(
|
||||||
|
api_url="/api/tenancy/tenants/"
|
||||||
|
)
|
||||||
)
|
)
|
||||||
status = forms.ChoiceField(
|
status = forms.ChoiceField(
|
||||||
choices=add_blank_choice(IPADDRESS_STATUS_CHOICES),
|
choices=add_blank_choice(IPADDRESS_STATUS_CHOICES),
|
||||||
required=False
|
required=False,
|
||||||
|
widget=StaticSelect2()
|
||||||
)
|
)
|
||||||
role = forms.ChoiceField(
|
role = forms.ChoiceField(
|
||||||
choices=add_blank_choice(IPADDRESS_ROLE_CHOICES),
|
choices=add_blank_choice(IPADDRESS_ROLE_CHOICES),
|
||||||
required=False
|
required=False,
|
||||||
|
widget=StaticSelect2()
|
||||||
)
|
)
|
||||||
description = forms.CharField(
|
description = forms.CharField(
|
||||||
max_length=100, required=False
|
max_length=100, required=False
|
||||||
@ -856,7 +929,10 @@ class IPAddressAssignForm(BootstrapMixin, forms.Form):
|
|||||||
queryset=VRF.objects.all(),
|
queryset=VRF.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
label='VRF',
|
label='VRF',
|
||||||
empty_label='Global'
|
empty_label='Global',
|
||||||
|
widget=APISelect(
|
||||||
|
api_url="/api/ipam/vrfs/"
|
||||||
|
)
|
||||||
)
|
)
|
||||||
address = forms.CharField(
|
address = forms.CharField(
|
||||||
label='IP Address'
|
label='IP Address'
|
||||||
@ -881,39 +957,45 @@ class IPAddressFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
|||||||
family = forms.ChoiceField(
|
family = forms.ChoiceField(
|
||||||
required=False,
|
required=False,
|
||||||
choices=IP_FAMILY_CHOICES,
|
choices=IP_FAMILY_CHOICES,
|
||||||
label='Address family'
|
label='Address family',
|
||||||
|
widget=StaticSelect2()
|
||||||
)
|
)
|
||||||
mask_length = forms.ChoiceField(
|
mask_length = forms.ChoiceField(
|
||||||
required=False,
|
required=False,
|
||||||
choices=IPADDRESS_MASK_LENGTH_CHOICES,
|
choices=IPADDRESS_MASK_LENGTH_CHOICES,
|
||||||
label='Mask length'
|
label='Mask length',
|
||||||
|
widget=StaticSelect2()
|
||||||
)
|
)
|
||||||
vrf = FilterChoiceField(
|
vrf = FilterChoiceField(
|
||||||
queryset=VRF.objects.annotate(
|
queryset=VRF.objects.all(),
|
||||||
filter_count=Count('ip_addresses')
|
|
||||||
),
|
|
||||||
to_field_name='rd',
|
to_field_name='rd',
|
||||||
label='VRF',
|
label='VRF',
|
||||||
null_label='-- Global --'
|
null_label='-- Global --',
|
||||||
|
widget=APISelectMultiple(
|
||||||
|
api_url="/api/ipam/vrfs/",
|
||||||
|
value_field="slug",
|
||||||
|
null_option=True,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
tenant = FilterChoiceField(
|
tenant = FilterChoiceField(
|
||||||
queryset=Tenant.objects.annotate(
|
queryset=Tenant.objects.all(),
|
||||||
filter_count=Count('ip_addresses')
|
|
||||||
),
|
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
null_label='-- None --'
|
null_label='-- None --',
|
||||||
|
widget=APISelectMultiple(
|
||||||
|
api_url="/api/tenancy/tenants/",
|
||||||
|
value_field="slug",
|
||||||
|
null_option=True,
|
||||||
)
|
)
|
||||||
status = AnnotatedMultipleChoiceField(
|
)
|
||||||
|
status = forms.MultipleChoiceField(
|
||||||
choices=IPADDRESS_STATUS_CHOICES,
|
choices=IPADDRESS_STATUS_CHOICES,
|
||||||
annotate=IPAddress.objects.all(),
|
required=False,
|
||||||
annotate_field='status',
|
widget=StaticSelect2Multiple()
|
||||||
required=False
|
|
||||||
)
|
)
|
||||||
role = AnnotatedMultipleChoiceField(
|
role = forms.MultipleChoiceField(
|
||||||
choices=IPADDRESS_ROLE_CHOICES,
|
choices=IPADDRESS_ROLE_CHOICES,
|
||||||
annotate=IPAddress.objects.all(),
|
required=False,
|
||||||
annotate_field='role',
|
widget=StaticSelect2Multiple()
|
||||||
required=False
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -929,6 +1011,11 @@ class VLANGroupForm(BootstrapMixin, forms.ModelForm):
|
|||||||
fields = [
|
fields = [
|
||||||
'site', 'name', 'slug',
|
'site', 'name', 'slug',
|
||||||
]
|
]
|
||||||
|
widgets = {
|
||||||
|
'site': APISelect(
|
||||||
|
api_url="/api/dcim/sites/"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class VLANGroupCSVForm(forms.ModelForm):
|
class VLANGroupCSVForm(forms.ModelForm):
|
||||||
@ -953,11 +1040,14 @@ class VLANGroupCSVForm(forms.ModelForm):
|
|||||||
|
|
||||||
class VLANGroupFilterForm(BootstrapMixin, forms.Form):
|
class VLANGroupFilterForm(BootstrapMixin, forms.Form):
|
||||||
site = FilterChoiceField(
|
site = FilterChoiceField(
|
||||||
queryset=Site.objects.annotate(
|
queryset=Site.objects.all(),
|
||||||
filter_count=Count('vlan_groups')
|
|
||||||
),
|
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
null_label='-- Global --'
|
null_label='-- Global --',
|
||||||
|
widget=APISelectMultiple(
|
||||||
|
api_url="/api/dcim/sites/",
|
||||||
|
value_field="slug",
|
||||||
|
null_option=True,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -969,9 +1059,12 @@ class VLANForm(BootstrapMixin, TenancyForm, CustomFieldForm):
|
|||||||
site = forms.ModelChoiceField(
|
site = forms.ModelChoiceField(
|
||||||
queryset=Site.objects.all(),
|
queryset=Site.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
widget=forms.Select(
|
widget=APISelect(
|
||||||
|
api_url="/api/dcim/sites/",
|
||||||
|
filter_for={
|
||||||
|
'group': 'site_id'
|
||||||
|
},
|
||||||
attrs={
|
attrs={
|
||||||
'filter-for': 'group',
|
|
||||||
'nullable': 'true',
|
'nullable': 'true',
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -984,7 +1077,7 @@ class VLANForm(BootstrapMixin, TenancyForm, CustomFieldForm):
|
|||||||
required=False,
|
required=False,
|
||||||
label='Group',
|
label='Group',
|
||||||
widget=APISelect(
|
widget=APISelect(
|
||||||
api_url='/api/ipam/vlan-groups/?site_id={{site}}',
|
api_url='/api/ipam/vlan-groups/',
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
tags = TagField(required=False)
|
tags = TagField(required=False)
|
||||||
@ -1002,6 +1095,12 @@ class VLANForm(BootstrapMixin, TenancyForm, CustomFieldForm):
|
|||||||
'status': "Operational status of this VLAN",
|
'status': "Operational status of this VLAN",
|
||||||
'role': "The primary function of this VLAN",
|
'role': "The primary function of this VLAN",
|
||||||
}
|
}
|
||||||
|
widgets = {
|
||||||
|
'status': StaticSelect2(),
|
||||||
|
'role': APISelect(
|
||||||
|
api_url="/api/ipam/roles/"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class VLANCSVForm(forms.ModelForm):
|
class VLANCSVForm(forms.ModelForm):
|
||||||
@ -1077,23 +1176,36 @@ class VLANBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditFor
|
|||||||
)
|
)
|
||||||
site = forms.ModelChoiceField(
|
site = forms.ModelChoiceField(
|
||||||
queryset=Site.objects.all(),
|
queryset=Site.objects.all(),
|
||||||
required=False
|
required=False,
|
||||||
|
widget=APISelect(
|
||||||
|
api_url="/api/dcim/sites/"
|
||||||
|
)
|
||||||
)
|
)
|
||||||
group = forms.ModelChoiceField(
|
group = forms.ModelChoiceField(
|
||||||
queryset=VLANGroup.objects.all(),
|
queryset=VLANGroup.objects.all(),
|
||||||
required=False
|
required=False,
|
||||||
|
widget=APISelect(
|
||||||
|
api_url="/api/ipam/vlan-groups/"
|
||||||
|
)
|
||||||
)
|
)
|
||||||
tenant = forms.ModelChoiceField(
|
tenant = forms.ModelChoiceField(
|
||||||
queryset=Tenant.objects.all(),
|
queryset=Tenant.objects.all(),
|
||||||
required=False
|
required=False,
|
||||||
|
widget=APISelect(
|
||||||
|
api_url="/api/tenancy/tenants/"
|
||||||
|
)
|
||||||
)
|
)
|
||||||
status = forms.ChoiceField(
|
status = forms.ChoiceField(
|
||||||
choices=add_blank_choice(VLAN_STATUS_CHOICES),
|
choices=add_blank_choice(VLAN_STATUS_CHOICES),
|
||||||
required=False
|
required=False,
|
||||||
|
widget=StaticSelect2()
|
||||||
)
|
)
|
||||||
role = forms.ModelChoiceField(
|
role = forms.ModelChoiceField(
|
||||||
queryset=Role.objects.all(),
|
queryset=Role.objects.all(),
|
||||||
required=False
|
required=False,
|
||||||
|
widget=APISelect(
|
||||||
|
api_url="/api/ipam/roles/"
|
||||||
|
)
|
||||||
)
|
)
|
||||||
description = forms.CharField(
|
description = forms.CharField(
|
||||||
max_length=100,
|
max_length=100,
|
||||||
@ -1113,38 +1225,48 @@ class VLANFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
|||||||
label='Search'
|
label='Search'
|
||||||
)
|
)
|
||||||
site = FilterChoiceField(
|
site = FilterChoiceField(
|
||||||
queryset=Site.objects.annotate(
|
queryset=Site.objects.all(),
|
||||||
filter_count=Count('vlans')
|
|
||||||
),
|
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
null_label='-- Global --'
|
null_label='-- Global --',
|
||||||
|
widget=APISelectMultiple(
|
||||||
|
api_url="/api/dcim/sites/",
|
||||||
|
value_field="slug",
|
||||||
|
null_option=True,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
group_id = FilterChoiceField(
|
group_id = FilterChoiceField(
|
||||||
queryset=VLANGroup.objects.annotate(
|
queryset=VLANGroup.objects.all(),
|
||||||
filter_count=Count('vlans')
|
|
||||||
),
|
|
||||||
label='VLAN group',
|
label='VLAN group',
|
||||||
null_label='-- None --'
|
null_label='-- None --',
|
||||||
|
widget=APISelectMultiple(
|
||||||
|
api_url="/api/ipam/vlan-groups/",
|
||||||
|
null_option=True,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
tenant = FilterChoiceField(
|
tenant = FilterChoiceField(
|
||||||
queryset=Tenant.objects.annotate(
|
queryset=Tenant.objects.all(),
|
||||||
filter_count=Count('vlans')
|
|
||||||
),
|
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
null_label='-- None --'
|
null_label='-- None --',
|
||||||
|
widget=APISelectMultiple(
|
||||||
|
api_url="/api/tenancy/tenants/",
|
||||||
|
value_field="slug",
|
||||||
|
null_option=True,
|
||||||
)
|
)
|
||||||
status = AnnotatedMultipleChoiceField(
|
)
|
||||||
|
status = forms.MultipleChoiceField(
|
||||||
choices=VLAN_STATUS_CHOICES,
|
choices=VLAN_STATUS_CHOICES,
|
||||||
annotate=VLAN.objects.all(),
|
required=False,
|
||||||
annotate_field='status',
|
widget=StaticSelect2Multiple()
|
||||||
required=False
|
|
||||||
)
|
)
|
||||||
role = FilterChoiceField(
|
role = FilterChoiceField(
|
||||||
queryset=Role.objects.annotate(
|
queryset=Role.objects.all(),
|
||||||
filter_count=Count('vlans')
|
|
||||||
),
|
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
null_label='-- None --'
|
null_label='-- None --',
|
||||||
|
widget=APISelectMultiple(
|
||||||
|
api_url="/api/ipam/roles/",
|
||||||
|
value_field="slug",
|
||||||
|
null_option=True,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -1166,6 +1288,10 @@ class ServiceForm(BootstrapMixin, CustomFieldForm):
|
|||||||
'ipaddresses': "IP address assignment is optional. If no IPs are selected, the service is assumed to be "
|
'ipaddresses': "IP address assignment is optional. If no IPs are selected, the service is assumed to be "
|
||||||
"reachable via all IPs assigned to the device.",
|
"reachable via all IPs assigned to the device.",
|
||||||
}
|
}
|
||||||
|
widgets = {
|
||||||
|
'protocol': StaticSelect2(),
|
||||||
|
'ipaddresses': StaticSelect2Multiple(),
|
||||||
|
}
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
@ -1192,10 +1318,11 @@ class ServiceFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
|||||||
)
|
)
|
||||||
protocol = forms.ChoiceField(
|
protocol = forms.ChoiceField(
|
||||||
choices=add_blank_choice(IP_PROTOCOL_CHOICES),
|
choices=add_blank_choice(IP_PROTOCOL_CHOICES),
|
||||||
required=False
|
required=False,
|
||||||
|
widget=StaticSelect2Multiple()
|
||||||
)
|
)
|
||||||
port = forms.IntegerField(
|
port = forms.IntegerField(
|
||||||
required=False
|
required=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -1206,7 +1333,8 @@ class ServiceBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
|
|||||||
)
|
)
|
||||||
protocol = forms.ChoiceField(
|
protocol = forms.ChoiceField(
|
||||||
choices=add_blank_choice(IP_PROTOCOL_CHOICES),
|
choices=add_blank_choice(IP_PROTOCOL_CHOICES),
|
||||||
required=False
|
required=False,
|
||||||
|
widget=StaticSelect2()
|
||||||
)
|
)
|
||||||
port = forms.IntegerField(
|
port = forms.IntegerField(
|
||||||
validators=[
|
validators=[
|
||||||
@ -1222,5 +1350,5 @@ class ServiceBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
nullable_fields = [
|
nullable_fields = [
|
||||||
'site', 'group', 'tenant', 'role', 'description',
|
'site', 'tenant', 'role', 'description',
|
||||||
]
|
]
|
||||||
|
@ -60,7 +60,7 @@
|
|||||||
{% render_field form.nat_device %}
|
{% render_field form.nat_device %}
|
||||||
</div>
|
</div>
|
||||||
<div class="tab-pane" id="search">
|
<div class="tab-pane" id="search">
|
||||||
{% render_field form.livesearch %}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% render_field form.nat_inside %}
|
{% render_field form.nat_inside %}
|
||||||
|
@ -717,17 +717,15 @@ class ChainedFieldsMixin(forms.BaseForm):
|
|||||||
|
|
||||||
filters_dict = {}
|
filters_dict = {}
|
||||||
for (db_field, parent_field) in field.chains:
|
for (db_field, parent_field) in field.chains:
|
||||||
if self.fields[parent_field].widget.attrs.get('nullable'):
|
if self.is_bound and parent_field in self.data and self.data[parent_field]:
|
||||||
filters_dict[db_field] = None
|
|
||||||
elif self.is_bound and parent_field in self.data and self.data[parent_field]:
|
|
||||||
filters_dict[db_field] = self.data[parent_field] or None
|
filters_dict[db_field] = self.data[parent_field] or None
|
||||||
elif self.initial.get(parent_field):
|
elif self.initial.get(parent_field):
|
||||||
filters_dict[db_field] = self.initial[parent_field]
|
filters_dict[db_field] = self.initial[parent_field]
|
||||||
|
elif self.fields[parent_field].widget.attrs.get('nullable'):
|
||||||
|
filters_dict[db_field] = None
|
||||||
else:
|
else:
|
||||||
break
|
break
|
||||||
|
|
||||||
print(filters_dict)
|
|
||||||
|
|
||||||
if filters_dict:
|
if filters_dict:
|
||||||
field.queryset = field.queryset.filter(**filters_dict)
|
field.queryset = field.queryset.filter(**filters_dict)
|
||||||
elif not self.is_bound and getattr(self, 'instance', None) and hasattr(self.instance, field_name):
|
elif not self.is_bound and getattr(self, 'instance', None) and hasattr(self.instance, field_name):
|
||||||
|
Loading…
Reference in New Issue
Block a user