diff --git a/CHANGELOG.md b/CHANGELOG.md index fd9ded8a5..4e02f1446 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ v2.4.8 (FUTURE) ## Bug Fixes * [#2473](https://github.com/digitalocean/netbox/issues/2473) - Fix encoding of long (>127 character) secrets +* [#2558](https://github.com/digitalocean/netbox/issues/2558) - Filter on all tags when multiple are passed * [#2575](https://github.com/digitalocean/netbox/issues/2575) - Correct model specified for rack roles table --- diff --git a/netbox/circuits/filters.py b/netbox/circuits/filters.py index 79efdc950..a159fad42 100644 --- a/netbox/circuits/filters.py +++ b/netbox/circuits/filters.py @@ -6,7 +6,7 @@ from django.db.models import Q from dcim.models import Site from extras.filters import CustomFieldFilterSet from tenancy.models import Tenant -from utilities.filters import NumericInFilter +from utilities.filters import NumericInFilter, TagFilter from .constants import CIRCUIT_STATUS_CHOICES from .models import Provider, Circuit, CircuitTermination, CircuitType @@ -28,9 +28,7 @@ class ProviderFilter(CustomFieldFilterSet, django_filters.FilterSet): to_field_name='slug', label='Site (slug)', ) - tag = django_filters.CharFilter( - name='tags__slug', - ) + tag = TagFilter() class Meta: model = Provider @@ -106,9 +104,7 @@ class CircuitFilter(CustomFieldFilterSet, django_filters.FilterSet): to_field_name='slug', label='Site (slug)', ) - tag = django_filters.CharFilter( - name='tags__slug', - ) + tag = TagFilter() class Meta: model = Circuit diff --git a/netbox/dcim/filters.py b/netbox/dcim/filters.py index 689e88a5d..8b40ca7b7 100644 --- a/netbox/dcim/filters.py +++ b/netbox/dcim/filters.py @@ -9,7 +9,7 @@ from netaddr.core import AddrFormatError from extras.filters import CustomFieldFilterSet from tenancy.models import Tenant -from utilities.filters import NullableCharFieldFilter, NumericInFilter +from utilities.filters import NullableCharFieldFilter, NumericInFilter, TagFilter from virtualization.models import Cluster from .constants import ( DEVICE_STATUS_CHOICES, IFACE_FF_LAG, NONCONNECTABLE_IFACE_TYPES, SITE_STATUS_CHOICES, VIRTUAL_IFACE_TYPES, @@ -83,9 +83,7 @@ class SiteFilter(CustomFieldFilterSet, django_filters.FilterSet): to_field_name='slug', label='Tenant (slug)', ) - tag = django_filters.CharFilter( - name='tags__slug', - ) + tag = TagFilter() class Meta: model = Site @@ -196,9 +194,7 @@ class RackFilter(CustomFieldFilterSet, django_filters.FilterSet): to_field_name='slug', label='Role (slug)', ) - tag = django_filters.CharFilter( - name='tags__slug', - ) + tag = TagFilter() class Meta: model = Rack @@ -306,9 +302,7 @@ class DeviceTypeFilter(CustomFieldFilterSet, django_filters.FilterSet): to_field_name='slug', label='Manufacturer (slug)', ) - tag = django_filters.CharFilter( - name='tags__slug', - ) + tag = TagFilter() class Meta: model = DeviceType @@ -530,9 +524,7 @@ class DeviceFilter(CustomFieldFilterSet, django_filters.FilterSet): queryset=VirtualChassis.objects.all(), label='Virtual chassis (ID)', ) - tag = django_filters.CharFilter( - name='tags__slug', - ) + tag = TagFilter() class Meta: model = Device @@ -592,9 +584,7 @@ class DeviceComponentFilterSet(django_filters.FilterSet): to_field_name='name', label='Device (name)', ) - tag = django_filters.CharFilter( - name='tags__slug', - ) + tag = TagFilter() class ConsolePortFilter(DeviceComponentFilterSet): @@ -653,9 +643,7 @@ class InterfaceFilter(django_filters.FilterSet): method='_mac_address', label='MAC address', ) - tag = django_filters.CharFilter( - name='tags__slug', - ) + tag = TagFilter() vlan_id = django_filters.CharFilter( method='filter_vlan_id', label='Assigned VLAN' @@ -797,9 +785,7 @@ class VirtualChassisFilter(django_filters.FilterSet): to_field_name='slug', label='Tenant (slug)', ) - tag = django_filters.CharFilter( - name='tags__slug', - ) + tag = TagFilter() class Meta: model = VirtualChassis diff --git a/netbox/ipam/filters.py b/netbox/ipam/filters.py index 0a8606e52..700a25ae9 100644 --- a/netbox/ipam/filters.py +++ b/netbox/ipam/filters.py @@ -9,7 +9,7 @@ from netaddr.core import AddrFormatError from dcim.models import Site, Device, Interface from extras.filters import CustomFieldFilterSet from tenancy.models import Tenant -from utilities.filters import NumericInFilter +from utilities.filters import NumericInFilter, TagFilter from virtualization.models import VirtualMachine from .constants import IPADDRESS_ROLE_CHOICES, IPADDRESS_STATUS_CHOICES, PREFIX_STATUS_CHOICES, VLAN_STATUS_CHOICES from .models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF @@ -31,9 +31,7 @@ class VRFFilter(CustomFieldFilterSet, django_filters.FilterSet): to_field_name='slug', label='Tenant (slug)', ) - tag = django_filters.CharFilter( - name='tags__slug', - ) + tag = TagFilter() def search(self, queryset, name, value): if not value.strip(): @@ -73,9 +71,7 @@ class AggregateFilter(CustomFieldFilterSet, django_filters.FilterSet): to_field_name='slug', label='RIR (slug)', ) - tag = django_filters.CharFilter( - name='tags__slug', - ) + tag = TagFilter() class Meta: model = Aggregate @@ -174,9 +170,7 @@ class PrefixFilter(CustomFieldFilterSet, django_filters.FilterSet): choices=PREFIX_STATUS_CHOICES, null_value=None ) - tag = django_filters.CharFilter( - name='tags__slug', - ) + tag = TagFilter() class Meta: model = Prefix @@ -303,9 +297,7 @@ class IPAddressFilter(CustomFieldFilterSet, django_filters.FilterSet): role = django_filters.MultipleChoiceFilter( choices=IPADDRESS_ROLE_CHOICES ) - tag = django_filters.CharFilter( - name='tags__slug', - ) + tag = TagFilter() class Meta: model = IPAddress @@ -422,9 +414,7 @@ class VLANFilter(CustomFieldFilterSet, django_filters.FilterSet): choices=VLAN_STATUS_CHOICES, null_value=None ) - tag = django_filters.CharFilter( - name='tags__slug', - ) + tag = TagFilter() class Meta: model = VLAN @@ -466,9 +456,7 @@ class ServiceFilter(django_filters.FilterSet): to_field_name='name', label='Virtual machine (name)', ) - tag = django_filters.CharFilter( - name='tags__slug', - ) + tag = TagFilter() class Meta: model = Service diff --git a/netbox/secrets/filters.py b/netbox/secrets/filters.py index f43a82b22..aa7e02e43 100644 --- a/netbox/secrets/filters.py +++ b/netbox/secrets/filters.py @@ -5,7 +5,7 @@ from django.db.models import Q from dcim.models import Device from extras.filters import CustomFieldFilterSet -from utilities.filters import NumericInFilter +from utilities.filters import NumericInFilter, TagFilter from .models import Secret, SecretRole @@ -42,9 +42,7 @@ class SecretFilter(CustomFieldFilterSet, django_filters.FilterSet): to_field_name='name', label='Device (name)', ) - tag = django_filters.CharFilter( - name='tags__slug', - ) + tag = TagFilter() class Meta: model = Secret diff --git a/netbox/tenancy/filters.py b/netbox/tenancy/filters.py index 7eccff5d3..4ff620d39 100644 --- a/netbox/tenancy/filters.py +++ b/netbox/tenancy/filters.py @@ -4,7 +4,7 @@ import django_filters from django.db.models import Q from extras.filters import CustomFieldFilterSet -from utilities.filters import NumericInFilter +from utilities.filters import NumericInFilter, TagFilter from .models import Tenant, TenantGroup @@ -31,9 +31,7 @@ class TenantFilter(CustomFieldFilterSet, django_filters.FilterSet): to_field_name='slug', label='Group (slug)', ) - tag = django_filters.CharFilter( - name='tags__slug', - ) + tag = TagFilter() class Meta: model = Tenant diff --git a/netbox/utilities/filters.py b/netbox/utilities/filters.py index 90cdcd9fc..a7f23d2f6 100644 --- a/netbox/utilities/filters.py +++ b/netbox/utilities/filters.py @@ -5,6 +5,7 @@ import itertools import django_filters from django import forms from django.utils.encoding import force_text +from taggit.models import Tag # @@ -68,3 +69,18 @@ class NullableModelMultipleChoiceField(forms.ModelMultipleChoiceField): stripped_value = value super(NullableModelMultipleChoiceField, self).clean(stripped_value) return value + + +class TagFilter(django_filters.ModelMultipleChoiceFilter): + """ + Match on one or more assigned tags. If multiple tags are specified (e.g. ?tag=foo&tag=bar), the queryset is filtered + to objects matching all tags. + """ + def __init__(self, *args, **kwargs): + + kwargs.setdefault('name', 'tags__slug') + kwargs.setdefault('to_field_name', 'slug') + kwargs.setdefault('conjoined', True) + kwargs.setdefault('queryset', Tag.objects.all()) + + super(TagFilter, self).__init__(*args, **kwargs) diff --git a/netbox/virtualization/filters.py b/netbox/virtualization/filters.py index 99df19aee..5f0f834cc 100644 --- a/netbox/virtualization/filters.py +++ b/netbox/virtualization/filters.py @@ -9,7 +9,7 @@ from netaddr.core import AddrFormatError from dcim.models import DeviceRole, Interface, Platform, Region, Site from extras.filters import CustomFieldFilterSet from tenancy.models import Tenant -from utilities.filters import NumericInFilter +from utilities.filters import NumericInFilter, TagFilter from .constants import VM_STATUS_CHOICES from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine @@ -64,9 +64,7 @@ class ClusterFilter(CustomFieldFilterSet): to_field_name='slug', label='Site (slug)', ) - tag = django_filters.CharFilter( - name='tags__slug', - ) + tag = TagFilter() class Meta: model = Cluster @@ -168,9 +166,7 @@ class VirtualMachineFilter(CustomFieldFilterSet): to_field_name='slug', label='Platform (slug)', ) - tag = django_filters.CharFilter( - name='tags__slug', - ) + tag = TagFilter() class Meta: model = VirtualMachine