Skip to content

Commit f3dfa81

Browse files
Merge pull request #6470 from netbox-community/5121-filter-tags-content-type
Closes #5121: Add object type filters for Tags
2 parents a6eeed4 + 5b4793a commit f3dfa81

File tree

18 files changed

+91
-36
lines changed

18 files changed

+91
-36
lines changed

netbox/circuits/models.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
)
2121

2222

23-
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
23+
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
2424
class Provider(PrimaryModel):
2525
"""
2626
Each Circuit belongs to a Provider. This is usually a telecommunications company or similar organization. This model
@@ -96,7 +96,7 @@ def to_csv(self):
9696
# Provider networks
9797
#
9898

99-
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
99+
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
100100
class ProviderNetwork(PrimaryModel):
101101
"""
102102
This represents a provider network which exists outside of NetBox, the details of which are unknown or
@@ -189,7 +189,7 @@ def to_csv(self):
189189
)
190190

191191

192-
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
192+
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
193193
class Circuit(PrimaryModel):
194194
"""
195195
A communications circuit connects two points. Each Circuit belongs to a Provider; Providers may have multiple

netbox/dcim/models/cables.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
# Cables
3131
#
3232

33-
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
33+
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
3434
class Cable(PrimaryModel):
3535
"""
3636
A physical connection between two endpoints.

netbox/dcim/models/device_components.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@ def connected_endpoint(self):
211211
# Console ports
212212
#
213213

214-
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
214+
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
215215
class ConsolePort(ComponentModel, CableTermination, PathEndpoint):
216216
"""
217217
A physical console port within a Device. ConsolePorts connect to ConsoleServerPorts.
@@ -254,7 +254,7 @@ def to_csv(self):
254254
# Console server ports
255255
#
256256

257-
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
257+
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
258258
class ConsoleServerPort(ComponentModel, CableTermination, PathEndpoint):
259259
"""
260260
A physical port within a Device (typically a designated console server) which provides access to ConsolePorts.
@@ -297,7 +297,7 @@ def to_csv(self):
297297
# Power ports
298298
#
299299

300-
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
300+
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
301301
class PowerPort(ComponentModel, CableTermination, PathEndpoint):
302302
"""
303303
A physical power supply (intake) port within a Device. PowerPorts connect to PowerOutlets.
@@ -408,7 +408,7 @@ def get_power_draw(self):
408408
# Power outlets
409409
#
410410

411-
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
411+
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
412412
class PowerOutlet(ComponentModel, CableTermination, PathEndpoint):
413413
"""
414414
A physical power outlet (output) within a Device which provides power to a PowerPort.
@@ -512,7 +512,7 @@ def count_ipaddresses(self):
512512
return self.ip_addresses.count()
513513

514514

515-
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
515+
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
516516
class Interface(ComponentModel, BaseInterface, CableTermination, PathEndpoint):
517517
"""
518518
A network interface within a Device. A physical Interface can connect to exactly one other Interface.
@@ -683,7 +683,7 @@ def is_lag(self):
683683
# Pass-through ports
684684
#
685685

686-
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
686+
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
687687
class FrontPort(ComponentModel, CableTermination):
688688
"""
689689
A pass-through port on the front of a Device.
@@ -748,7 +748,7 @@ def clean(self):
748748
})
749749

750750

751-
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
751+
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
752752
class RearPort(ComponentModel, CableTermination):
753753
"""
754754
A pass-through port on the rear of a Device.
@@ -801,7 +801,7 @@ def to_csv(self):
801801
# Device bays
802802
#
803803

804-
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
804+
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
805805
class DeviceBay(ComponentModel):
806806
"""
807807
An empty space within a Device which can house a child device
@@ -860,7 +860,7 @@ def clean(self):
860860
# Inventory items
861861
#
862862

863-
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
863+
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
864864
class InventoryItem(MPTTModel, ComponentModel):
865865
"""
866866
An InventoryItem represents a serialized piece of hardware within a Device, such as a line card or power supply.

netbox/dcim/models/devices.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ def to_csv(self):
7575
)
7676

7777

78-
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
78+
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
7979
class DeviceType(PrimaryModel):
8080
"""
8181
A DeviceType represents a particular make (Manufacturer) and model of device. It specifies rack height and depth, as
@@ -468,7 +468,7 @@ def to_csv(self):
468468
)
469469

470470

471-
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
471+
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
472472
class Device(PrimaryModel, ConfigContextModel):
473473
"""
474474
A Device represents a piece of physical hardware mounted within a Rack. Each Device is assigned a DeviceType,
@@ -922,7 +922,7 @@ def get_status_class(self):
922922
# Virtual chassis
923923
#
924924

925-
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
925+
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
926926
class VirtualChassis(PrimaryModel):
927927
"""
928928
A collection of Devices which operate with a shared control plane (e.g. a switch stack).

netbox/dcim/models/power.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
# Power
2222
#
2323

24-
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
24+
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
2525
class PowerPanel(PrimaryModel):
2626
"""
2727
A distribution point for electrical power; e.g. a data center RPP.
@@ -71,7 +71,7 @@ def clean(self):
7171
)
7272

7373

74-
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
74+
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
7575
class PowerFeed(PrimaryModel, PathEndpoint, CableTermination):
7676
"""
7777
An electrical circuit delivered from a PowerPanel.

netbox/dcim/models/racks.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ def to_csv(self):
7878
)
7979

8080

81-
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
81+
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
8282
class Rack(PrimaryModel):
8383
"""
8484
Devices are housed within Racks. Each rack has a defined height measured in rack units, and a front and rear face.
@@ -467,7 +467,7 @@ def get_power_utilization(self):
467467
return int(allocated_draw_total / available_power_total * 100)
468468

469469

470-
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
470+
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
471471
class RackReservation(PrimaryModel):
472472
"""
473473
One or more reserved units within a Rack.

netbox/dcim/models/sites.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ def get_site_count(self):
130130
# Sites
131131
#
132132

133-
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
133+
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
134134
class Site(PrimaryModel):
135135
"""
136136
A Site represents a geographic location within a network; typically a building or campus. The optional facility

netbox/extras/constants.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,6 @@
77
'custom_links',
88
'export_templates',
99
'job_results',
10+
'tags',
1011
'webhooks'
1112
]

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):

0 commit comments

Comments
 (0)