Skip to content

Commit a40ab9f

Browse files
committed
Fixes #9657: Fix filtering for custom fields and webhooks in the UI
1 parent 55b3e4e commit a40ab9f

File tree

4 files changed

+85
-10
lines changed

4 files changed

+85
-10
lines changed

docs/release-notes/version-3.2.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
* [#8854](https://github.com/netbox-community/netbox/issues/8854) - Fix `REMOTE_AUTH_DEFAULT_GROUPS` for social-auth backends
1515
* [#9575](https://github.com/netbox-community/netbox/issues/9575) - Fix AttributeError exception for FHRP group with an IP address assigned
1616
* [#9597](https://github.com/netbox-community/netbox/issues/9597) - Include `installed_module` in module bay REST API serializer
17+
* [#9657](https://github.com/netbox-community/netbox/issues/9657) - Fix filtering for custom fields and webhooks in the UI
1718
* [#9682](https://github.com/netbox-community/netbox/issues/9682) - Fix bulk assignment of ASNs to sites
1819

1920
---

netbox/extras/filtersets.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ class WebhookFilterSet(BaseFilterSet):
3232
method='search',
3333
label='Search',
3434
)
35+
content_type_id = MultiValueNumberFilter(
36+
field_name='content_types__id'
37+
)
3538
content_types = ContentTypeFilter()
3639
http_method = django_filters.MultipleChoiceFilter(
3740
choices=WebhookHttpMethodChoices
@@ -40,8 +43,8 @@ class WebhookFilterSet(BaseFilterSet):
4043
class Meta:
4144
model = Webhook
4245
fields = [
43-
'id', 'content_types', 'name', 'type_create', 'type_update', 'type_delete', 'payload_url', 'enabled',
44-
'http_method', 'http_content_type', 'secret', 'ssl_verification', 'ca_file_path',
46+
'id', 'name', 'type_create', 'type_update', 'type_delete', 'payload_url', 'enabled', 'http_method',
47+
'http_content_type', 'secret', 'ssl_verification', 'ca_file_path',
4548
]
4649

4750
def search(self, queryset, name, value):
@@ -58,11 +61,17 @@ class CustomFieldFilterSet(BaseFilterSet):
5861
method='search',
5962
label='Search',
6063
)
64+
type = django_filters.MultipleChoiceFilter(
65+
choices=CustomFieldTypeChoices
66+
)
67+
content_type_id = MultiValueNumberFilter(
68+
field_name='content_types__id'
69+
)
6170
content_types = ContentTypeFilter()
6271

6372
class Meta:
6473
model = CustomField
65-
fields = ['id', 'content_types', 'name', 'required', 'filter_logic', 'weight', 'description']
74+
fields = ['id', 'name', 'required', 'filter_logic', 'weight', 'description']
6675

6776
def search(self, queryset, name, value):
6877
if not value.strip():

netbox/extras/forms/filtersets.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,13 @@
3232
class CustomFieldFilterForm(FilterForm):
3333
fieldsets = (
3434
(None, ('q',)),
35-
('Attributes', ('type', 'content_types', 'weight', 'required')),
35+
('Attributes', ('type', 'content_type_id', 'weight', 'required')),
3636
)
37-
content_types = ContentTypeMultipleChoiceField(
37+
content_type_id = ContentTypeMultipleChoiceField(
3838
queryset=ContentType.objects.all(),
3939
limit_choices_to=FeatureQuery('custom_fields'),
40-
required=False
40+
required=False,
41+
label='Object type'
4142
)
4243
type = MultipleChoiceField(
4344
choices=CustomFieldTypeChoices,
@@ -110,13 +111,14 @@ class ExportTemplateFilterForm(FilterForm):
110111
class WebhookFilterForm(FilterForm):
111112
fieldsets = (
112113
(None, ('q',)),
113-
('Attributes', ('content_types', 'http_method', 'enabled')),
114+
('Attributes', ('content_type_id', 'http_method', 'enabled')),
114115
('Events', ('type_create', 'type_update', 'type_delete')),
115116
)
116-
content_types = ContentTypeMultipleChoiceField(
117+
content_type_id = ContentTypeMultipleChoiceField(
117118
queryset=ContentType.objects.all(),
118119
limit_choices_to=FeatureQuery('webhooks'),
119-
required=False
120+
required=False,
121+
label='Object type'
120122
)
121123
http_method = MultipleChoiceField(
122124
choices=WebhookHttpMethodChoices,

netbox/extras/tests/test_filtersets.py

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77

88
from circuits.models import Provider
99
from dcim.models import DeviceRole, DeviceType, Manufacturer, Platform, Rack, Region, Site, SiteGroup
10-
from extras.choices import JournalEntryKindChoices, ObjectChangeActionChoices
10+
from extras.choices import (
11+
CustomFieldTypeChoices, CustomFieldFilterLogicChoices, JournalEntryKindChoices, ObjectChangeActionChoices,
12+
)
1113
from extras.filtersets import *
1214
from extras.models import *
1315
from ipam.models import IPAddress
@@ -16,6 +18,65 @@
1618
from virtualization.models import Cluster, ClusterGroup, ClusterType
1719

1820

21+
class CustomFieldTestCase(TestCase, BaseFilterSetTests):
22+
queryset = CustomField.objects.all()
23+
filterset = CustomFieldFilterSet
24+
25+
@classmethod
26+
def setUpTestData(cls):
27+
content_types = ContentType.objects.filter(model__in=['site', 'rack', 'device'])
28+
29+
custom_fields = (
30+
CustomField(
31+
name='Custom Field 1',
32+
type=CustomFieldTypeChoices.TYPE_TEXT,
33+
required=True,
34+
weight=100,
35+
filter_logic=CustomFieldFilterLogicChoices.FILTER_LOOSE
36+
),
37+
CustomField(
38+
name='Custom Field 2',
39+
type=CustomFieldTypeChoices.TYPE_INTEGER,
40+
required=False,
41+
weight=200,
42+
filter_logic=CustomFieldFilterLogicChoices.FILTER_EXACT
43+
),
44+
CustomField(
45+
name='Custom Field 3',
46+
type=CustomFieldTypeChoices.TYPE_BOOLEAN,
47+
required=False,
48+
weight=300,
49+
filter_logic=CustomFieldFilterLogicChoices.FILTER_DISABLED
50+
),
51+
)
52+
CustomField.objects.bulk_create(custom_fields)
53+
custom_fields[0].content_types.add(content_types[0])
54+
custom_fields[1].content_types.add(content_types[1])
55+
custom_fields[2].content_types.add(content_types[2])
56+
57+
def test_name(self):
58+
params = {'name': ['Custom Field 1', 'Custom Field 2']}
59+
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
60+
61+
def test_content_types(self):
62+
params = {'content_types': 'dcim.site'}
63+
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
64+
params = {'content_type_id': [ContentType.objects.get_for_model(Site).pk]}
65+
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
66+
67+
def test_required(self):
68+
params = {'required': True}
69+
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
70+
71+
def test_weight(self):
72+
params = {'weight': [100, 200]}
73+
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
74+
75+
def test_filter_logic(self):
76+
params = {'filter_logic': CustomFieldFilterLogicChoices.FILTER_LOOSE}
77+
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
78+
79+
1980
class WebhookTestCase(TestCase, BaseFilterSetTests):
2081
queryset = Webhook.objects.all()
2182
filterset = WebhookFilterSet
@@ -62,6 +123,8 @@ def test_name(self):
62123
def test_content_types(self):
63124
params = {'content_types': 'dcim.site'}
64125
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
126+
params = {'content_type_id': [ContentType.objects.get_for_model(Site).pk]}
127+
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
65128

66129
def test_type_create(self):
67130
params = {'type_create': True}

0 commit comments

Comments
 (0)