Skip to content

Commit 5b4793a

Browse files
committed
Closes #5121: Add content_type filters for tags
1 parent b6660c7 commit 5b4793a

File tree

3 files changed

+57
-3
lines changed

3 files changed

+57
-3
lines changed

netbox/extras/filtersets.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from dcim.models import DeviceRole, DeviceType, Platform, Region, Site, SiteGroup
77
from netbox.filtersets import BaseFilterSet, ChangeLoggedModelFilterSet
88
from tenancy.models import Tenant, TenantGroup
9-
from utilities.filters import ContentTypeFilter
9+
from utilities.filters import ContentTypeFilter, MultiValueCharFilter, MultiValueNumberFilter
1010
from virtualization.models import Cluster, ClusterGroup
1111
from .choices import *
1212
from .models import *
@@ -114,6 +114,12 @@ class TagFilterSet(ChangeLoggedModelFilterSet):
114114
method='search',
115115
label='Search',
116116
)
117+
content_type = MultiValueCharFilter(
118+
method='_content_type'
119+
)
120+
content_type_id = MultiValueNumberFilter(
121+
method='_content_type_id'
122+
)
117123

118124
class Meta:
119125
model = Tag
@@ -127,6 +133,32 @@ def search(self, queryset, name, value):
127133
Q(slug__icontains=value)
128134
)
129135

136+
def _content_type(self, queryset, name, values):
137+
ct_filter = Q()
138+
139+
# Compile list of app_label & model pairings
140+
for value in values:
141+
try:
142+
app_label, model = value.lower().split('.')
143+
ct_filter |= Q(
144+
app_label=app_label,
145+
model=model
146+
)
147+
except ValueError:
148+
pass
149+
150+
# Get ContentType instances
151+
content_types = ContentType.objects.filter(ct_filter)
152+
153+
return queryset.filter(extras_taggeditem_items__content_type__in=content_types).distinct()
154+
155+
def _content_type_id(self, queryset, name, values):
156+
157+
# Get ContentType instances
158+
content_types = ContentType.objects.filter(pk__in=values)
159+
160+
return queryset.filter(extras_taggeditem_items__content_type__in=content_types).distinct()
161+
130162

131163
class ConfigContextFilterSet(ChangeLoggedModelFilterSet):
132164
q = django_filters.CharFilter(

netbox/extras/forms.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,13 @@
88
from tenancy.models import Tenant, TenantGroup
99
from utilities.forms import (
1010
add_blank_choice, APISelectMultiple, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect, ColorSelect,
11-
CommentField, CSVModelForm, DateTimePicker, DynamicModelMultipleChoiceField, JSONField, SlugField, StaticSelect2,
12-
BOOLEAN_WITH_BLANK_CHOICES,
11+
CommentField, ContentTypeMultipleChoiceField, CSVModelForm, DateTimePicker, DynamicModelMultipleChoiceField,
12+
JSONField, SlugField, StaticSelect2, BOOLEAN_WITH_BLANK_CHOICES,
1313
)
1414
from virtualization.models import Cluster, ClusterGroup
1515
from .choices import *
1616
from .models import ConfigContext, CustomField, ImageAttachment, JournalEntry, ObjectChange, Tag
17+
from .utils import FeatureQuery
1718

1819

1920
#
@@ -180,6 +181,11 @@ class TagFilterForm(BootstrapMixin, forms.Form):
180181
required=False,
181182
label=_('Search')
182183
)
184+
content_type_id = ContentTypeMultipleChoiceField(
185+
queryset=ContentType.objects.filter(FeatureQuery('tags').get_query()),
186+
required=False,
187+
label=_('Tagged object type')
188+
)
183189

184190

185191
class TagBulkEditForm(BootstrapMixin, BulkEditForm):

netbox/extras/tests/test_filtersets.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from django.contrib.contenttypes.models import ContentType
66
from django.test import TestCase
77

8+
from circuits.models import Provider
89
from dcim.models import DeviceRole, DeviceType, Manufacturer, Platform, Rack, Region, Site, SiteGroup
910
from extras.choices import JournalEntryKindChoices, ObjectChangeActionChoices
1011
from extras.filtersets import *
@@ -537,6 +538,13 @@ def setUpTestData(cls):
537538
)
538539
Tag.objects.bulk_create(tags)
539540

541+
# Apply some tags so we can filter by content type
542+
site = Site.objects.create(name='Site 1', slug='site-1')
543+
provider = Provider.objects.create(name='Provider 1', slug='provider-1')
544+
545+
site.tags.set(tags[0])
546+
provider.tags.set(tags[1])
547+
540548
def test_name(self):
541549
params = {'name': ['Tag 1', 'Tag 2']}
542550
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
@@ -549,6 +557,14 @@ def test_color(self):
549557
params = {'color': ['ff0000', '00ff00']}
550558
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
551559

560+
def test_content_type(self):
561+
params = {'content_type': ['dcim.site', 'circuits.provider']}
562+
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
563+
site_ct = ContentType.objects.get_for_model(Site).pk
564+
provider_ct = ContentType.objects.get_for_model(Provider).pk
565+
params = {'content_type_id': [site_ct, provider_ct]}
566+
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
567+
552568

553569
class ObjectChangeTestCase(TestCase, BaseFilterSetTests):
554570
queryset = ObjectChange.objects.all()

0 commit comments

Comments
 (0)