From 2f28dec891ed83066dc7c7bd707af1d3ea770713 Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Mon, 13 Jan 2020 20:16:13 +0000 Subject: [PATCH] Tag filter field for filter forms --- netbox/circuits/forms.py | 12 ++++++++++-- netbox/dcim/forms.py | 31 ++++++++++++++++++++++++++++++- netbox/ipam/forms.py | 26 +++++++++++++++++++++++++- netbox/secrets/forms.py | 6 +++++- netbox/tenancy/forms.py | 6 +++++- netbox/utilities/forms.py | 17 +++++++++++++++++ netbox/virtualization/forms.py | 10 +++++++++- 7 files changed, 101 insertions(+), 7 deletions(-) diff --git a/netbox/circuits/forms.py b/netbox/circuits/forms.py index 4a5c06a6e..4438dbc1c 100644 --- a/netbox/circuits/forms.py +++ b/netbox/circuits/forms.py @@ -6,8 +6,8 @@ from extras.forms import AddRemoveTagsForm, CustomFieldForm, CustomFieldBulkEdit from tenancy.forms import TenancyFilterForm, TenancyForm from tenancy.models import Tenant from utilities.forms import ( - APISelect, APISelectMultiple, add_blank_choice, BootstrapMixin, CommentField, CSVChoiceField, - DatePicker, FilterChoiceField, SmallTextarea, SlugField, StaticSelect2, StaticSelect2Multiple + APISelect, APISelectMultiple, add_blank_choice, BootstrapMixin, CommentField, CSVChoiceField, DatePicker, + FilterChoiceField, SmallTextarea, SlugField, StaticSelect2, StaticSelect2Multiple, TagFilterField ) from .constants import * from .models import Circuit, CircuitTermination, CircuitType, Provider @@ -129,6 +129,10 @@ class ProviderFilterForm(BootstrapMixin, CustomFieldFilterForm): label='ASN' ) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields['tag'] = TagFilterField(self.model) + # # Circuit types @@ -333,6 +337,10 @@ class CircuitFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm label='Commit rate (Kbps)' ) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields['tag'] = TagFilterField(self.model) + # # Circuit terminations diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index f0b91c2f5..de7678b52 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -23,7 +23,8 @@ from utilities.forms import ( APISelect, APISelectMultiple, add_blank_choice, ArrayFieldSelectMultiple, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect, ChainedFieldsMixin, ChainedModelChoiceField, ColorSelect, CommentField, ComponentForm, ConfirmationForm, CSVChoiceField, ExpandableNameField, FilterChoiceField, FlexibleModelChoiceField, JSONField, - SelectWithPK, SmallTextarea, SlugField, StaticSelect2, StaticSelect2Multiple, BOOLEAN_WITH_BLANK_CHOICES + SelectWithPK, SmallTextarea, SlugField, StaticSelect2, StaticSelect2Multiple, TagFilterField, + BOOLEAN_WITH_BLANK_CHOICES ) from virtualization.models import Cluster, ClusterGroup from .constants import * @@ -335,6 +336,10 @@ class SiteFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm): ) ) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields['tag'] = TagFilterField(self.model) + # # Rack groups @@ -713,6 +718,10 @@ class RackFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm): ) ) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields['tag'] = TagFilterField(self.model) + # # Rack elevations @@ -1005,6 +1014,10 @@ class DeviceTypeFilterForm(BootstrapMixin, CustomFieldFilterForm): ) ) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields['tag'] = TagFilterField(self.model) + # # Device component templates @@ -1947,6 +1960,10 @@ class DeviceFilterForm(BootstrapMixin, LocalConfigContextFilterForm, TenancyFilt ) ) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields['tag'] = TagFilterField(self.model) + # # Bulk device component creation @@ -3405,6 +3422,10 @@ class InventoryItemFilterForm(BootstrapMixin, forms.Form): ) ) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields['tag'] = TagFilterField(self.model) + # # Virtual chassis @@ -3591,6 +3612,10 @@ class VirtualChassisFilterForm(BootstrapMixin, CustomFieldFilterForm): ) ) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields['tag'] = TagFilterField(self.model) + # # Power panels @@ -3967,3 +3992,7 @@ class PowerFeedFilterForm(BootstrapMixin, CustomFieldFilterForm): max_utilization = forms.IntegerField( required=False ) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields['tag'] = TagFilterField(self.model) diff --git a/netbox/ipam/forms.py b/netbox/ipam/forms.py index c3387a5aa..e64582b03 100644 --- a/netbox/ipam/forms.py +++ b/netbox/ipam/forms.py @@ -10,7 +10,7 @@ from tenancy.models import Tenant from utilities.forms import ( add_blank_choice, APISelect, APISelectMultiple, BootstrapMixin, BulkEditNullBooleanSelect, ChainedModelChoiceField, CSVChoiceField, DatePicker, ExpandableIPAddressField, FilterChoiceField, FlexibleModelChoiceField, ReturnURLForm, - SlugField, StaticSelect2, StaticSelect2Multiple, BOOLEAN_WITH_BLANK_CHOICES + SlugField, StaticSelect2, StaticSelect2Multiple, TagFilterField, BOOLEAN_WITH_BLANK_CHOICES ) from virtualization.models import VirtualMachine from .constants import * @@ -103,6 +103,10 @@ class VRFFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm): label='Search' ) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields['tag'] = TagFilterField(self.model) + # # RIRs @@ -232,6 +236,10 @@ class AggregateFilterForm(BootstrapMixin, CustomFieldFilterForm): ) ) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields['tag'] = TagFilterField(self.model) + # # Roles @@ -578,6 +586,10 @@ class PrefixFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm) label='Expand prefix hierarchy' ) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields['tag'] = TagFilterField(self.model) + # # IP addresses @@ -1006,6 +1018,10 @@ class IPAddressFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterFo ) ) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields['tag'] = TagFilterField(self.model) + # # VLAN groups @@ -1293,6 +1309,10 @@ class VLANFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm): ) ) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields['tag'] = TagFilterField(self.model) + # # Services @@ -1353,6 +1373,10 @@ class ServiceFilterForm(BootstrapMixin, CustomFieldFilterForm): required=False, ) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields['tag'] = TagFilterField(self.model) + class ServiceBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm): pk = forms.ModelMultipleChoiceField( diff --git a/netbox/secrets/forms.py b/netbox/secrets/forms.py index ed0f455c1..8b8467f04 100644 --- a/netbox/secrets/forms.py +++ b/netbox/secrets/forms.py @@ -7,7 +7,7 @@ from dcim.models import Device from extras.forms import AddRemoveTagsForm, CustomFieldBulkEditForm, CustomFieldFilterForm, CustomFieldForm from utilities.forms import ( APISelect, APISelectMultiple, BootstrapMixin, FilterChoiceField, FlexibleModelChoiceField, SlugField, - StaticSelect2Multiple + StaticSelect2Multiple, TagFilterField ) from .models import Secret, SecretRole, UserKey @@ -185,6 +185,10 @@ class SecretFilterForm(BootstrapMixin, CustomFieldFilterForm): ) ) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields['tag'] = TagFilterField(self.model) + # # UserKeys diff --git a/netbox/tenancy/forms.py b/netbox/tenancy/forms.py index f8aaa45e5..f398b965a 100644 --- a/netbox/tenancy/forms.py +++ b/netbox/tenancy/forms.py @@ -4,7 +4,7 @@ from taggit.forms import TagField from extras.forms import AddRemoveTagsForm, CustomFieldForm, CustomFieldBulkEditForm, CustomFieldFilterForm from utilities.forms import ( APISelect, APISelectMultiple, BootstrapMixin, ChainedFieldsMixin, ChainedModelChoiceField, CommentField, - FilterChoiceField, SlugField, + FilterChoiceField, SlugField, TagFilterField ) from .models import Tenant, TenantGroup @@ -114,6 +114,10 @@ class TenantFilterForm(BootstrapMixin, CustomFieldFilterForm): ) ) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields['tag'] = TagFilterField(self.model) + # # Form extensions diff --git a/netbox/utilities/forms.py b/netbox/utilities/forms.py index 39422c265..d1d19a6cb 100644 --- a/netbox/utilities/forms.py +++ b/netbox/utilities/forms.py @@ -6,6 +6,7 @@ from io import StringIO from django import forms from django.conf import settings from django.contrib.postgres.forms.jsonb import JSONField as _JSONField, InvalidJSONInput +from django.db.models import Count from mptt.forms import TreeNodeMultipleChoiceField from .constants import * @@ -596,6 +597,22 @@ class SlugField(forms.SlugField): self.widget.attrs['slug-source'] = slug_source +class TagFilterField(forms.MultipleChoiceField): + """ + A filter field for the tags of a model. Only the tags used by a model are displayed. + + :param model: The model of the filter + """ + widget = StaticSelect2Multiple + + def __init__(self, model, *args, **kwargs): + if hasattr(model, 'tags'): + tags = model.tags.annotate(count=Count('extras_taggeditem_items')).order_by('name') + choices = [(str(tag.slug), '{} ({})'.format(tag.name, tag.count)) for tag in tags] + + super().__init__(label='Tags', choices=choices, required=False, *args, **kwargs) + + class FilterChoiceIterator(forms.models.ModelChoiceIterator): def __iter__(self): diff --git a/netbox/virtualization/forms.py b/netbox/virtualization/forms.py index 427e676f6..36b84c7b1 100644 --- a/netbox/virtualization/forms.py +++ b/netbox/virtualization/forms.py @@ -13,7 +13,7 @@ from utilities.forms import ( add_blank_choice, APISelect, APISelectMultiple, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect, ChainedFieldsMixin, ChainedModelChoiceField, ChainedModelMultipleChoiceField, CommentField, ComponentForm, ConfirmationForm, CSVChoiceField, ExpandableNameField, FilterChoiceField, JSONField, SlugField, - SmallTextarea, StaticSelect2, StaticSelect2Multiple + SmallTextarea, StaticSelect2, StaticSelect2Multiple, TagFilterField ) from .constants import * from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine @@ -217,6 +217,10 @@ class ClusterFilterForm(BootstrapMixin, CustomFieldFilterForm): ) ) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields['tag'] = TagFilterField(self.model) + class ClusterAddDevicesForm(BootstrapMixin, ChainedFieldsMixin, forms.Form): region = forms.ModelChoiceField( @@ -623,6 +627,10 @@ class VirtualMachineFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFil label='MAC address' ) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields['tag'] = TagFilterField(self.model) + # # VM interfaces