diff --git a/CHANGELOG.md b/CHANGELOG.md index 17d588f02..9c91d21b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ 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 +* [#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) diff --git a/netbox/circuits/forms.py b/netbox/circuits/forms.py index a15960857..d481deb54 100644 --- a/netbox/circuits/forms.py +++ b/netbox/circuits/forms.py @@ -289,8 +289,6 @@ class CircuitFilterForm(BootstrapMixin, CustomFieldFilterForm): ) status = forms.MultipleChoiceField( choices=CIRCUIT_STATUS_CHOICES, - annotate=Circuit.objects.all(), - annotate_field='status', required=False, widget=StaticSelect2Multiple() ) diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index 397eaea45..e570c331d 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -1600,7 +1600,7 @@ class DeviceBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditF required=False, label='Type', widget=APISelect( - api_url="/api/dcim/device-types" + api_url="/api/dcim/device-types/" ) ) device_role = forms.ModelChoiceField( @@ -1608,21 +1608,21 @@ class DeviceBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditF required=False, label='Role', widget=APISelect( - api_url="/api/dcim/device-roles" + api_url="/api/dcim/device-roles/" ) ) tenant = forms.ModelChoiceField( queryset=Tenant.objects.all(), required=False, widget=APISelect( - api_url="/api/tenancy/tenants" + api_url="/api/tenancy/tenants/" ) ) platform = forms.ModelChoiceField( queryset=Platform.objects.all(), required=False, widget=APISelect( - api_url="/api/dcim/platforms" + api_url="/api/dcim/platforms/" ) ) status = forms.ChoiceField( diff --git a/netbox/ipam/forms.py b/netbox/ipam/forms.py index b6209f5df..b097ca048 100644 --- a/netbox/ipam/forms.py +++ b/netbox/ipam/forms.py @@ -1,7 +1,6 @@ from django import forms from django.core.exceptions import MultipleObjectsReturned from django.core.validators import MaxValueValidator, MinValueValidator -from django.db.models import Count from taggit.forms import TagField 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.models import Tenant from utilities.forms import ( - AnnotatedMultipleChoiceField, APISelect, BootstrapMixin, BulkEditNullBooleanSelect, ChainedModelChoiceField, - CSVChoiceField, ExpandableIPAddressField, FilterChoiceField, FlexibleModelChoiceField, Livesearch, ReturnURLForm, - SlugField, add_blank_choice, + add_blank_choice, APISelect, APISelectMultiple, BootstrapMixin, BulkEditNullBooleanSelect, ChainedModelChoiceField, + CSVChoiceField, ExpandableIPAddressField, FilterChoiceField, FlexibleModelChoiceField, ReturnURLForm, SlugField, + StaticSelect2, StaticSelect2Multiple, BOOLEAN_WITH_BLANK_CHOICES ) from virtualization.models import VirtualMachine from .constants import ( @@ -77,7 +76,10 @@ class VRFBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditForm ) tenant = forms.ModelChoiceField( queryset=Tenant.objects.all(), - required=False + required=False, + widget=APISelect( + api_url="/api/tenancy/tenants/" + ) ) enforce_unique = forms.NullBooleanField( required=False, @@ -102,11 +104,14 @@ class VRFFilterForm(BootstrapMixin, CustomFieldFilterForm): label='Search' ) tenant = FilterChoiceField( - queryset=Tenant.objects.annotate( - filter_count=Count('vrfs') - ), + queryset=Tenant.objects.all(), 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( required=False, label='Private', - widget=forms.Select( - choices=[ - ('', '---------'), - ('True', 'Yes'), - ('False', 'No'), - ] + widget=StaticSelect2( + choices=BOOLEAN_WITH_BLANK_CHOICES ) ) @@ -168,6 +169,11 @@ class AggregateForm(BootstrapMixin, CustomFieldForm): 'rir': "Regional Internet Registry responsible for this prefix", 'date_added': "Format: YYYY-MM-DD", } + widgets = { + 'rir': APISelect( + api_url="/api/ipam/rirs/" + ) + } class AggregateCSVForm(forms.ModelForm): @@ -193,7 +199,10 @@ class AggregateBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEd rir = forms.ModelChoiceField( queryset=RIR.objects.all(), required=False, - label='RIR' + label='RIR', + widget=APISelect( + api_url="/api/ipam/rirs/" + ) ) date_added = forms.DateField( required=False @@ -218,12 +227,17 @@ class AggregateFilterForm(BootstrapMixin, CustomFieldFilterForm): family = forms.ChoiceField( required=False, choices=IP_FAMILY_CHOICES, - label='Address family' + label='Address family', + widget=StaticSelect2() ) rir = FilterChoiceField( - queryset=RIR.objects.annotate(filter_count=Count('aggregates')), + queryset=RIR.objects.all(), 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(), required=False, label='Site', - widget=forms.Select( + widget=APISelect( + api_url="/api/dcim/sites/", + filter_for={ + 'vlan_group': 'site_id', + 'vlan': 'site_id', + }, attrs={ - 'filter-for': 'vlan_group', 'nullable': 'true', } ) @@ -276,9 +294,11 @@ class PrefixForm(BootstrapMixin, TenancyForm, CustomFieldForm): required=False, label='VLAN group', widget=APISelect( - api_url='/api/ipam/vlan-groups/?site_id={{site}}', + api_url='/api/ipam/vlan-groups/', + filter_for={ + 'vlan': 'group_id' + }, attrs={ - 'filter-for': 'vlan', 'nullable': 'true', } ) @@ -292,7 +312,7 @@ class PrefixForm(BootstrapMixin, TenancyForm, CustomFieldForm): required=False, label='VLAN', widget=APISelect( - api_url='/api/ipam/vlans/?site_id={{site}}&group_id={{vlan_group}}', + api_url='/api/ipam/vlans/', 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', 'tags', ] + widgets = { + 'vrf': APISelect( + api_url="/api/ipam/vrfs/" + ), + 'status': StaticSelect2(), + 'role': APISelect( + api_url="/api/ipam/roles/" + ) + } def __init__(self, *args, **kwargs): @@ -415,12 +444,18 @@ class PrefixBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditF ) site = forms.ModelChoiceField( queryset=Site.objects.all(), - required=False + required=False, + widget=APISelect( + api_url="/api/dcim/sites/" + ) ) vrf = forms.ModelChoiceField( queryset=VRF.objects.all(), required=False, - label='VRF' + label='VRF', + widget=APISelect( + api_url="/api/ipam/vrfs/" + ) ) prefix_length = forms.IntegerField( min_value=1, @@ -429,15 +464,22 @@ class PrefixBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditF ) tenant = forms.ModelChoiceField( queryset=Tenant.objects.all(), - required=False + required=False, + widget=APISelect( + api_url="/api/tenancy/tenants/" + ) ) status = forms.ChoiceField( choices=add_blank_choice(PREFIX_STATUS_CHOICES), - required=False + required=False, + widget=StaticSelect2() ) role = forms.ModelChoiceField( queryset=Role.objects.all(), - required=False + required=False, + widget=APISelect( + api_url="/api/ipam/roles/" + ) ) is_pool = forms.NullBooleanField( required=False, @@ -473,47 +515,60 @@ class PrefixFilterForm(BootstrapMixin, CustomFieldFilterForm): family = forms.ChoiceField( required=False, choices=IP_FAMILY_CHOICES, - label='Address family' + label='Address family', + widget=StaticSelect2() ) mask_length = forms.ChoiceField( required=False, choices=PREFIX_MASK_LENGTH_CHOICES, - label='Mask length' + label='Mask length', + widget=StaticSelect2() ) vrf = FilterChoiceField( - queryset=VRF.objects.annotate( - filter_count=Count('prefixes') - ), + queryset=VRF.objects.all(), to_field_name='rd', label='VRF', - null_label='-- Global --' + null_label='-- Global --', + widget=APISelectMultiple( + api_url="/api/ipam/vrfs/", + value_field="slug", + null_option=True, + ) ) tenant = FilterChoiceField( - queryset=Tenant.objects.annotate( - filter_count=Count('prefixes') - ), + queryset=Tenant.objects.all(), 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, - annotate=Prefix.objects.all(), - annotate_field='status', - required=False + required=False, + widget=StaticSelect2Multiple() ) site = FilterChoiceField( - queryset=Site.objects.annotate( - filter_count=Count('prefixes') - ), + queryset=Site.objects.all(), 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( - queryset=Role.objects.annotate( - filter_count=Count('prefixes') - ), + queryset=Role.objects.all(), 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( required=False, @@ -534,9 +589,11 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldForm) queryset=Site.objects.all(), required=False, label='Site', - widget=forms.Select( - attrs={ - 'filter-for': 'nat_rack' + widget=APISelect( + api_url="/api/dcim/sites/", + filter_for={ + 'nat_rack': 'site_id', + 'nat_device': 'site_id' } ) ) @@ -548,10 +605,12 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldForm) required=False, label='Rack', widget=APISelect( - api_url='/api/dcim/racks/?site_id={{nat_site}}', + api_url='/api/dcim/racks/', display_field='display_name', + filter_for={ + 'nat_device': 'rack_id' + }, attrs={ - 'filter-for': 'nat_device', 'nullable': 'true' } ) @@ -565,9 +624,11 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldForm) required=False, label='Device', widget=APISelect( - api_url='/api/dcim/devices/?site_id={{nat_site}}&rack_id={{nat_rack}}', + api_url='/api/dcim/devices/', display_field='display_name', - attrs={'filter-for': 'nat_inside'} + filter_for={ + 'nat_inside': 'device_id' + } ) ) nat_inside = ChainedModelChoiceField( @@ -578,20 +639,10 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldForm) required=False, label='IP Address', widget=APISelect( - api_url='/api/ipam/ip-addresses/?device_id={{nat_device}}', + api_url='/api/ipam/ip-addresses/', 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( required=False, 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', '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): @@ -685,6 +743,13 @@ class IPAddressBulkAddForm(BootstrapMixin, TenancyForm, CustomFieldForm): fields = [ '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): super().__init__(*args, **kwargs) @@ -822,7 +887,10 @@ class IPAddressBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEd vrf = forms.ModelChoiceField( queryset=VRF.objects.all(), required=False, - label='VRF' + label='VRF', + widget=APISelect( + api_url="/api/ipam/vrfs/" + ) ) mask_length = forms.IntegerField( min_value=1, @@ -831,15 +899,20 @@ class IPAddressBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEd ) tenant = forms.ModelChoiceField( queryset=Tenant.objects.all(), - required=False + required=False, + widget=APISelect( + api_url="/api/tenancy/tenants/" + ) ) status = forms.ChoiceField( choices=add_blank_choice(IPADDRESS_STATUS_CHOICES), - required=False + required=False, + widget=StaticSelect2() ) role = forms.ChoiceField( choices=add_blank_choice(IPADDRESS_ROLE_CHOICES), - required=False + required=False, + widget=StaticSelect2() ) description = forms.CharField( max_length=100, required=False @@ -856,7 +929,10 @@ class IPAddressAssignForm(BootstrapMixin, forms.Form): queryset=VRF.objects.all(), required=False, label='VRF', - empty_label='Global' + empty_label='Global', + widget=APISelect( + api_url="/api/ipam/vrfs/" + ) ) address = forms.CharField( label='IP Address' @@ -881,39 +957,45 @@ class IPAddressFilterForm(BootstrapMixin, CustomFieldFilterForm): family = forms.ChoiceField( required=False, choices=IP_FAMILY_CHOICES, - label='Address family' + label='Address family', + widget=StaticSelect2() ) mask_length = forms.ChoiceField( required=False, choices=IPADDRESS_MASK_LENGTH_CHOICES, - label='Mask length' + label='Mask length', + widget=StaticSelect2() ) vrf = FilterChoiceField( - queryset=VRF.objects.annotate( - filter_count=Count('ip_addresses') - ), + queryset=VRF.objects.all(), to_field_name='rd', label='VRF', - null_label='-- Global --' + null_label='-- Global --', + widget=APISelectMultiple( + api_url="/api/ipam/vrfs/", + value_field="slug", + null_option=True, + ) ) tenant = FilterChoiceField( - queryset=Tenant.objects.annotate( - filter_count=Count('ip_addresses') - ), + queryset=Tenant.objects.all(), 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, - annotate=IPAddress.objects.all(), - annotate_field='status', - required=False + required=False, + widget=StaticSelect2Multiple() ) - role = AnnotatedMultipleChoiceField( + role = forms.MultipleChoiceField( choices=IPADDRESS_ROLE_CHOICES, - annotate=IPAddress.objects.all(), - annotate_field='role', - required=False + required=False, + widget=StaticSelect2Multiple() ) @@ -929,6 +1011,11 @@ class VLANGroupForm(BootstrapMixin, forms.ModelForm): fields = [ 'site', 'name', 'slug', ] + widgets = { + 'site': APISelect( + api_url="/api/dcim/sites/" + ) + } class VLANGroupCSVForm(forms.ModelForm): @@ -953,11 +1040,14 @@ class VLANGroupCSVForm(forms.ModelForm): class VLANGroupFilterForm(BootstrapMixin, forms.Form): site = FilterChoiceField( - queryset=Site.objects.annotate( - filter_count=Count('vlan_groups') - ), + queryset=Site.objects.all(), 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( queryset=Site.objects.all(), required=False, - widget=forms.Select( + widget=APISelect( + api_url="/api/dcim/sites/", + filter_for={ + 'group': 'site_id' + }, attrs={ - 'filter-for': 'group', 'nullable': 'true', } ) @@ -984,7 +1077,7 @@ class VLANForm(BootstrapMixin, TenancyForm, CustomFieldForm): required=False, label='Group', widget=APISelect( - api_url='/api/ipam/vlan-groups/?site_id={{site}}', + api_url='/api/ipam/vlan-groups/', ) ) tags = TagField(required=False) @@ -1002,6 +1095,12 @@ class VLANForm(BootstrapMixin, TenancyForm, CustomFieldForm): 'status': "Operational status of this VLAN", 'role': "The primary function of this VLAN", } + widgets = { + 'status': StaticSelect2(), + 'role': APISelect( + api_url="/api/ipam/roles/" + ) + } class VLANCSVForm(forms.ModelForm): @@ -1077,23 +1176,36 @@ class VLANBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditFor ) site = forms.ModelChoiceField( queryset=Site.objects.all(), - required=False + required=False, + widget=APISelect( + api_url="/api/dcim/sites/" + ) ) group = forms.ModelChoiceField( queryset=VLANGroup.objects.all(), - required=False + required=False, + widget=APISelect( + api_url="/api/ipam/vlan-groups/" + ) ) tenant = forms.ModelChoiceField( queryset=Tenant.objects.all(), - required=False + required=False, + widget=APISelect( + api_url="/api/tenancy/tenants/" + ) ) status = forms.ChoiceField( choices=add_blank_choice(VLAN_STATUS_CHOICES), - required=False + required=False, + widget=StaticSelect2() ) role = forms.ModelChoiceField( queryset=Role.objects.all(), - required=False + required=False, + widget=APISelect( + api_url="/api/ipam/roles/" + ) ) description = forms.CharField( max_length=100, @@ -1113,38 +1225,48 @@ class VLANFilterForm(BootstrapMixin, CustomFieldFilterForm): label='Search' ) site = FilterChoiceField( - queryset=Site.objects.annotate( - filter_count=Count('vlans') - ), + queryset=Site.objects.all(), 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( - queryset=VLANGroup.objects.annotate( - filter_count=Count('vlans') - ), + queryset=VLANGroup.objects.all(), label='VLAN group', - null_label='-- None --' + null_label='-- None --', + widget=APISelectMultiple( + api_url="/api/ipam/vlan-groups/", + null_option=True, + ) ) tenant = FilterChoiceField( - queryset=Tenant.objects.annotate( - filter_count=Count('vlans') - ), + queryset=Tenant.objects.all(), 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, - annotate=VLAN.objects.all(), - annotate_field='status', - required=False + required=False, + widget=StaticSelect2Multiple() ) role = FilterChoiceField( - queryset=Role.objects.annotate( - filter_count=Count('vlans') - ), + queryset=Role.objects.all(), 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 " "reachable via all IPs assigned to the device.", } + widgets = { + 'protocol': StaticSelect2(), + 'ipaddresses': StaticSelect2Multiple(), + } def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -1192,10 +1318,11 @@ class ServiceFilterForm(BootstrapMixin, CustomFieldFilterForm): ) protocol = forms.ChoiceField( choices=add_blank_choice(IP_PROTOCOL_CHOICES), - required=False + required=False, + widget=StaticSelect2Multiple() ) port = forms.IntegerField( - required=False + required=False, ) @@ -1206,7 +1333,8 @@ class ServiceBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm): ) protocol = forms.ChoiceField( choices=add_blank_choice(IP_PROTOCOL_CHOICES), - required=False + required=False, + widget=StaticSelect2() ) port = forms.IntegerField( validators=[ @@ -1222,5 +1350,5 @@ class ServiceBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm): class Meta: nullable_fields = [ - 'site', 'group', 'tenant', 'role', 'description', + 'site', 'tenant', 'role', 'description', ] diff --git a/netbox/templates/ipam/ipaddress_edit.html b/netbox/templates/ipam/ipaddress_edit.html index ebd2c8484..771999bb4 100644 --- a/netbox/templates/ipam/ipaddress_edit.html +++ b/netbox/templates/ipam/ipaddress_edit.html @@ -60,7 +60,7 @@ {% render_field form.nat_device %} {% render_field form.nat_inside %} diff --git a/netbox/utilities/forms.py b/netbox/utilities/forms.py index 4679d3c96..45785ea47 100644 --- a/netbox/utilities/forms.py +++ b/netbox/utilities/forms.py @@ -717,17 +717,15 @@ class ChainedFieldsMixin(forms.BaseForm): filters_dict = {} for (db_field, parent_field) in field.chains: - if self.fields[parent_field].widget.attrs.get('nullable'): - filters_dict[db_field] = None - elif self.is_bound and parent_field in self.data and self.data[parent_field]: + if self.is_bound and parent_field in self.data and self.data[parent_field]: filters_dict[db_field] = self.data[parent_field] or None elif self.initial.get(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: break - print(filters_dict) - if 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):