diff --git a/netbox/circuits/forms.py b/netbox/circuits/forms.py index 26d38d56a..8acad4bb9 100644 --- a/netbox/circuits/forms.py +++ b/netbox/circuits/forms.py @@ -174,7 +174,7 @@ class CircuitFilterForm(BootstrapMixin, CustomFieldFilterForm): tenant = FilterChoiceField( queryset=Tenant.objects.annotate(filter_count=Count('circuits')), to_field_name='slug', - null_option=(0, 'None') + null_label='-- None --' ) site = FilterChoiceField( queryset=Site.objects.annotate(filter_count=Count('circuit_terminations')), diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index 1f5d50c4d..e051e33e5 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -163,7 +163,7 @@ class SiteFilterForm(BootstrapMixin, CustomFieldFilterForm): tenant = FilterChoiceField( queryset=Tenant.objects.annotate(filter_count=Count('sites')), to_field_name='slug', - null_option=(0, 'None') + null_label='-- None --' ) @@ -359,17 +359,17 @@ class RackFilterForm(BootstrapMixin, CustomFieldFilterForm): group_id = FilterChoiceField( queryset=RackGroup.objects.select_related('site').annotate(filter_count=Count('racks')), label='Rack group', - null_option=(0, 'None') + null_label='-- None --' ) tenant = FilterChoiceField( queryset=Tenant.objects.annotate(filter_count=Count('racks')), to_field_name='slug', - null_option=(0, 'None') + null_label='-- None --' ) role = FilterChoiceField( queryset=RackRole.objects.annotate(filter_count=Count('racks')), to_field_name='slug', - null_option=(0, 'None') + null_label='-- None --' ) @@ -411,7 +411,7 @@ class RackReservationFilterForm(BootstrapMixin, forms.Form): group_id = FilterChoiceField( queryset=RackGroup.objects.select_related('site').annotate(filter_count=Count('racks__reservations')), label='Rack group', - null_option=(0, 'None') + null_label='-- None --' ) @@ -1031,7 +1031,7 @@ class DeviceFilterForm(BootstrapMixin, CustomFieldFilterForm): rack_id = FilterChoiceField( queryset=Rack.objects.annotate(filter_count=Count('devices')), label='Rack', - null_option=(0, 'None'), + null_label='-- None --', ) role = FilterChoiceField( queryset=DeviceRole.objects.annotate(filter_count=Count('devices')), @@ -1040,7 +1040,7 @@ class DeviceFilterForm(BootstrapMixin, CustomFieldFilterForm): tenant = FilterChoiceField( queryset=Tenant.objects.annotate(filter_count=Count('devices')), to_field_name='slug', - null_option=(0, 'None'), + null_label='-- None --', ) manufacturer_id = FilterChoiceField(queryset=Manufacturer.objects.all(), label='Manufacturer') device_type_id = FilterChoiceField( @@ -1052,7 +1052,7 @@ class DeviceFilterForm(BootstrapMixin, CustomFieldFilterForm): platform = FilterChoiceField( queryset=Platform.objects.annotate(filter_count=Count('devices')), to_field_name='slug', - null_option=(0, 'None'), + null_label='-- None --', ) status = forms.MultipleChoiceField(choices=device_status_choices, required=False) mac_address = forms.CharField(required=False, label='MAC address') diff --git a/netbox/ipam/forms.py b/netbox/ipam/forms.py index a5b0a7e3c..c67921e3e 100644 --- a/netbox/ipam/forms.py +++ b/netbox/ipam/forms.py @@ -78,8 +78,11 @@ class VRFBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm): class VRFFilterForm(BootstrapMixin, CustomFieldFilterForm): model = VRF q = forms.CharField(required=False, label='Search') - tenant = FilterChoiceField(queryset=Tenant.objects.annotate(filter_count=Count('vrfs')), to_field_name='slug', - null_option=(0, None)) + tenant = FilterChoiceField( + queryset=Tenant.objects.annotate(filter_count=Count('vrfs')), + to_field_name='slug', + null_label='-- None --' + ) # @@ -368,23 +371,23 @@ class PrefixFilterForm(BootstrapMixin, CustomFieldFilterForm): queryset=VRF.objects.annotate(filter_count=Count('prefixes')), to_field_name='rd', label='VRF', - null_option=(0, 'Global') + null_label='-- Global --' ) tenant = FilterChoiceField( queryset=Tenant.objects.annotate(filter_count=Count('prefixes')), to_field_name='slug', - null_option=(0, 'None') + null_label='-- None --' ) status = forms.MultipleChoiceField(choices=prefix_status_choices, required=False) site = FilterChoiceField( queryset=Site.objects.annotate(filter_count=Count('prefixes')), to_field_name='slug', - null_option=(0, 'None') + null_label='-- None --' ) role = FilterChoiceField( queryset=Role.objects.annotate(filter_count=Count('prefixes')), to_field_name='slug', - null_option=(0, 'None') + null_label='-- None --' ) expand = forms.BooleanField(required=False, label='Expand prefix hierarchy') @@ -719,12 +722,12 @@ class IPAddressFilterForm(BootstrapMixin, CustomFieldFilterForm): queryset=VRF.objects.annotate(filter_count=Count('ip_addresses')), to_field_name='rd', label='VRF', - null_option=(0, 'Global') + null_label='-- Global --' ) tenant = FilterChoiceField( queryset=Tenant.objects.annotate(filter_count=Count('ip_addresses')), to_field_name='slug', - null_option=(0, 'None') + null_label='-- None --' ) status = forms.MultipleChoiceField(choices=ipaddress_status_choices, required=False) role = forms.MultipleChoiceField(choices=ipaddress_role_choices, required=False) @@ -766,7 +769,7 @@ class VLANGroupFilterForm(BootstrapMixin, forms.Form): site = FilterChoiceField( queryset=Site.objects.annotate(filter_count=Count('vlan_groups')), to_field_name='slug', - null_option=(0, 'Global') + null_label='-- Global --' ) @@ -896,23 +899,23 @@ class VLANFilterForm(BootstrapMixin, CustomFieldFilterForm): site = FilterChoiceField( queryset=Site.objects.annotate(filter_count=Count('vlans')), to_field_name='slug', - null_option=(0, 'Global') + null_label='-- Global --' ) group_id = FilterChoiceField( queryset=VLANGroup.objects.annotate(filter_count=Count('vlans')), label='VLAN group', - null_option=(0, 'None') + null_label='-- None --' ) tenant = FilterChoiceField( queryset=Tenant.objects.annotate(filter_count=Count('vlans')), to_field_name='slug', - null_option=(0, 'None') + null_label='-- None --' ) status = forms.MultipleChoiceField(choices=vlan_status_choices, required=False) role = FilterChoiceField( queryset=Role.objects.annotate(filter_count=Count('vlans')), to_field_name='slug', - null_option=(0, 'None') + null_label='-- None --' ) diff --git a/netbox/tenancy/forms.py b/netbox/tenancy/forms.py index 5eb3bda61..00194d4e8 100644 --- a/netbox/tenancy/forms.py +++ b/netbox/tenancy/forms.py @@ -81,7 +81,7 @@ class TenantFilterForm(BootstrapMixin, CustomFieldFilterForm): group = FilterChoiceField( queryset=TenantGroup.objects.annotate(filter_count=Count('tenants')), to_field_name='slug', - null_option=(0, 'None') + null_label='-- None --' ) diff --git a/netbox/utilities/filters.py b/netbox/utilities/filters.py index 647ecb723..3e403e676 100644 --- a/netbox/utilities/filters.py +++ b/netbox/utilities/filters.py @@ -42,7 +42,7 @@ class NullableModelMultipleChoiceField(forms.ModelMultipleChoiceField): """ iterator = forms.models.ModelChoiceIterator - def __init__(self, null_value=0, null_label='None', *args, **kwargs): + def __init__(self, null_value=0, null_label='-- None --', *args, **kwargs): self.null_value = null_value self.null_label = null_label super(NullableModelMultipleChoiceField, self).__init__(*args, **kwargs) diff --git a/netbox/utilities/forms.py b/netbox/utilities/forms.py index 6ba49d02c..1817cd9a9 100644 --- a/netbox/utilities/forms.py +++ b/netbox/utilities/forms.py @@ -407,11 +407,25 @@ class SlugField(forms.SlugField): self.widget.attrs['slug-source'] = slug_source -class FilterChoiceFieldMixin(object): - iterator = forms.models.ModelChoiceIterator +class FilterChoiceIterator(forms.models.ModelChoiceIterator): - def __init__(self, null_option=None, *args, **kwargs): - self.null_option = null_option + def __iter__(self): + # Filter on "empty" choice using FILTERS_NULL_CHOICE_VALUE (instead of an empty string) + if self.field.null_label is not None: + yield (settings.FILTERS_NULL_CHOICE_VALUE, self.field.null_label) + queryset = self.queryset.all() + # Can't use iterator() when queryset uses prefetch_related() + if not queryset._prefetch_related_lookups: + queryset = queryset.iterator() + for obj in queryset: + yield self.choice(obj) + + +class FilterChoiceFieldMixin(object): + iterator = FilterChoiceIterator + + def __init__(self, null_label=None, *args, **kwargs): + self.null_label = null_label if 'required' not in kwargs: kwargs['required'] = False if 'widget' not in kwargs: @@ -424,15 +438,6 @@ class FilterChoiceFieldMixin(object): return '{} ({})'.format(label, obj.filter_count) return label - def _get_choices(self): - if hasattr(self, '_choices'): - return self._choices - if self.null_option is not None: - return itertools.chain([self.null_option], self.iterator(self)) - return self.iterator(self) - - choices = property(_get_choices, forms.ChoiceField._set_choices) - class FilterChoiceField(FilterChoiceFieldMixin, forms.ModelMultipleChoiceField): pass diff --git a/netbox/virtualization/forms.py b/netbox/virtualization/forms.py index 16b33962c..d697de755 100644 --- a/netbox/virtualization/forms.py +++ b/netbox/virtualization/forms.py @@ -137,13 +137,13 @@ class ClusterFilterForm(BootstrapMixin, CustomFieldFilterForm): group = FilterChoiceField( queryset=ClusterGroup.objects.annotate(filter_count=Count('clusters')), to_field_name='slug', - null_option=(0, 'None'), + null_label='-- None --', required=False, ) site = FilterChoiceField( queryset=Site.objects.annotate(filter_count=Count('clusters')), to_field_name='slug', - null_option=(0, 'None'), + null_label='-- None --', required=False, ) @@ -338,12 +338,12 @@ class VirtualMachineFilterForm(BootstrapMixin, CustomFieldFilterForm): cluster_group = FilterChoiceField( queryset=ClusterGroup.objects.all(), to_field_name='slug', - null_option=(0, 'None') + null_label='-- None --' ) cluster_type = FilterChoiceField( queryset=ClusterType.objects.all(), to_field_name='slug', - null_option=(0, 'None') + null_label='-- None --' ) cluster_id = FilterChoiceField( queryset=Cluster.objects.annotate(filter_count=Count('virtual_machines')), @@ -352,23 +352,23 @@ class VirtualMachineFilterForm(BootstrapMixin, CustomFieldFilterForm): site = FilterChoiceField( queryset=Site.objects.annotate(filter_count=Count('clusters__virtual_machines')), to_field_name='slug', - null_option=(0, 'None') + null_label='-- None --' ) role = FilterChoiceField( queryset=DeviceRole.objects.filter(vm_role=True).annotate(filter_count=Count('virtual_machines')), to_field_name='slug', - null_option=(0, 'None') + null_label='-- None --' ) status = forms.MultipleChoiceField(choices=vm_status_choices, required=False) tenant = FilterChoiceField( queryset=Tenant.objects.annotate(filter_count=Count('virtual_machines')), to_field_name='slug', - null_option=(0, 'None') + null_label='-- None --' ) platform = FilterChoiceField( queryset=Platform.objects.annotate(filter_count=Count('virtual_machines')), to_field_name='slug', - null_option=(0, 'None') + null_label='-- None --' )