From 834fd408bdde93d1600ae3c73f466b732465bcd5 Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Sat, 11 Jan 2020 15:18:27 +0000 Subject: [PATCH 01/11] Fixes #2921: Replace tags filter with Select2 widget --- docs/release-notes/version-2.6.md | 1 + netbox/templates/circuits/circuit_list.html | 1 - netbox/templates/circuits/provider_list.html | 1 - netbox/templates/dcim/device_list.html | 1 - netbox/templates/dcim/devicetype_list.html | 1 - netbox/templates/dcim/powerfeed_list.html | 1 - netbox/templates/dcim/rack_list.html | 1 - netbox/templates/dcim/site_list.html | 1 - .../templates/dcim/virtualchassis_list.html | 1 - netbox/templates/inc/tags_panel.html | 13 ----------- netbox/templates/ipam/aggregate_list.html | 1 - netbox/templates/ipam/ipaddress_list.html | 1 - netbox/templates/ipam/prefix_list.html | 1 - netbox/templates/ipam/service_list.html | 1 - netbox/templates/ipam/vlan_list.html | 1 - netbox/templates/ipam/vrf_list.html | 1 - netbox/templates/secrets/secret_list.html | 1 - netbox/templates/tenancy/tenant_list.html | 1 - .../virtualization/cluster_list.html | 1 - .../virtualization/virtualmachine_list.html | 1 - netbox/utilities/views.py | 22 ++++++++++++------- 21 files changed, 15 insertions(+), 39 deletions(-) delete mode 100644 netbox/templates/inc/tags_panel.html diff --git a/docs/release-notes/version-2.6.md b/docs/release-notes/version-2.6.md index 88cd9c120..c313d7e1b 100644 --- a/docs/release-notes/version-2.6.md +++ b/docs/release-notes/version-2.6.md @@ -6,6 +6,7 @@ * [#2050](https://github.com/netbox-community/netbox/issues/2050) - Preview image attachments when hovering the link * [#2113](https://github.com/netbox-community/netbox/issues/2113) - Allow NAPALM driver settings to be changed with request headers * [#2589](https://github.com/netbox-community/netbox/issues/2589) - Toggle for showing available prefixes/ip addresses +* [#2921](https://github.com/netbox-community/netbox/issues/2921) - Replace tags filter with Select2 widget * [#3009](https://github.com/netbox-community/netbox/issues/3009) - Search by description when assigning IP address * [#3090](https://github.com/netbox-community/netbox/issues/3090) - Add filter field for device interfaces * [#3187](https://github.com/netbox-community/netbox/issues/3187) - Add rack selection field to rack elevations diff --git a/netbox/templates/circuits/circuit_list.html b/netbox/templates/circuits/circuit_list.html index d686bdf7a..169aab072 100644 --- a/netbox/templates/circuits/circuit_list.html +++ b/netbox/templates/circuits/circuit_list.html @@ -16,7 +16,6 @@
{% include 'inc/search_panel.html' %} - {% include 'inc/tags_panel.html' %}
{% endblock %} diff --git a/netbox/templates/circuits/provider_list.html b/netbox/templates/circuits/provider_list.html index e4ee7fb2b..4126f75ec 100644 --- a/netbox/templates/circuits/provider_list.html +++ b/netbox/templates/circuits/provider_list.html @@ -16,7 +16,6 @@
{% include 'inc/search_panel.html' %} - {% include 'inc/tags_panel.html' %}
{% endblock %} diff --git a/netbox/templates/dcim/device_list.html b/netbox/templates/dcim/device_list.html index 623d69aa2..8b991689f 100644 --- a/netbox/templates/dcim/device_list.html +++ b/netbox/templates/dcim/device_list.html @@ -16,7 +16,6 @@
{% include 'inc/search_panel.html' %} - {% include 'inc/tags_panel.html' %}
{% endblock %} diff --git a/netbox/templates/dcim/devicetype_list.html b/netbox/templates/dcim/devicetype_list.html index 3b8988ed8..75f587f5d 100644 --- a/netbox/templates/dcim/devicetype_list.html +++ b/netbox/templates/dcim/devicetype_list.html @@ -16,7 +16,6 @@
{% include 'inc/search_panel.html' %} - {% include 'inc/tags_panel.html' %}
{% endblock %} diff --git a/netbox/templates/dcim/powerfeed_list.html b/netbox/templates/dcim/powerfeed_list.html index cfe2c989c..e384cb2c2 100644 --- a/netbox/templates/dcim/powerfeed_list.html +++ b/netbox/templates/dcim/powerfeed_list.html @@ -16,7 +16,6 @@
{% include 'inc/search_panel.html' %} - {% include 'inc/tags_panel.html' %}
{% endblock %} diff --git a/netbox/templates/dcim/rack_list.html b/netbox/templates/dcim/rack_list.html index 72da3048e..2724e4427 100644 --- a/netbox/templates/dcim/rack_list.html +++ b/netbox/templates/dcim/rack_list.html @@ -16,7 +16,6 @@
{% include 'inc/search_panel.html' %} - {% include 'inc/tags_panel.html' %}
{% endblock %} diff --git a/netbox/templates/dcim/site_list.html b/netbox/templates/dcim/site_list.html index 64948a6f9..ef9e0e411 100644 --- a/netbox/templates/dcim/site_list.html +++ b/netbox/templates/dcim/site_list.html @@ -16,7 +16,6 @@
{% include 'inc/search_panel.html' %} - {% include 'inc/tags_panel.html' %}
{% endblock %} diff --git a/netbox/templates/dcim/virtualchassis_list.html b/netbox/templates/dcim/virtualchassis_list.html index 8c26f3c3e..55cfc1691 100644 --- a/netbox/templates/dcim/virtualchassis_list.html +++ b/netbox/templates/dcim/virtualchassis_list.html @@ -13,7 +13,6 @@
{% include 'inc/search_panel.html' %} - {% include 'inc/tags_panel.html' %}
{% endblock %} diff --git a/netbox/templates/inc/tags_panel.html b/netbox/templates/inc/tags_panel.html deleted file mode 100644 index a7923fbed..000000000 --- a/netbox/templates/inc/tags_panel.html +++ /dev/null @@ -1,13 +0,0 @@ -{% load helpers %} - -
-
- - Tags -
-
- {% for tag in tags %} - {{ tag }} {{ tag.count }} - {% endfor %} -
-
diff --git a/netbox/templates/ipam/aggregate_list.html b/netbox/templates/ipam/aggregate_list.html index aad747b2d..27363a56d 100644 --- a/netbox/templates/ipam/aggregate_list.html +++ b/netbox/templates/ipam/aggregate_list.html @@ -17,7 +17,6 @@
{% include 'inc/search_panel.html' %} - {% include 'inc/tags_panel.html' %}
Statistics diff --git a/netbox/templates/ipam/ipaddress_list.html b/netbox/templates/ipam/ipaddress_list.html index 12f227301..b7920a434 100644 --- a/netbox/templates/ipam/ipaddress_list.html +++ b/netbox/templates/ipam/ipaddress_list.html @@ -16,7 +16,6 @@
{% include 'inc/search_panel.html' %} - {% include 'inc/tags_panel.html' %}
{% endblock %} diff --git a/netbox/templates/ipam/prefix_list.html b/netbox/templates/ipam/prefix_list.html index b80af8e1d..f0754d37b 100644 --- a/netbox/templates/ipam/prefix_list.html +++ b/netbox/templates/ipam/prefix_list.html @@ -21,7 +21,6 @@
{% include 'inc/search_panel.html' %} - {% include 'inc/tags_panel.html' %}
{% endblock %} diff --git a/netbox/templates/ipam/service_list.html b/netbox/templates/ipam/service_list.html index a39bec22e..4aac520d9 100644 --- a/netbox/templates/ipam/service_list.html +++ b/netbox/templates/ipam/service_list.html @@ -12,7 +12,6 @@
{% include 'inc/search_panel.html' %} - {% include 'inc/tags_panel.html' %}
{% endblock %} diff --git a/netbox/templates/ipam/vlan_list.html b/netbox/templates/ipam/vlan_list.html index b4d313a8c..24d538f88 100644 --- a/netbox/templates/ipam/vlan_list.html +++ b/netbox/templates/ipam/vlan_list.html @@ -16,7 +16,6 @@
{% include 'inc/search_panel.html' %} - {% include 'inc/tags_panel.html' %}
{% endblock %} diff --git a/netbox/templates/ipam/vrf_list.html b/netbox/templates/ipam/vrf_list.html index 566e2f3e6..975c73a37 100644 --- a/netbox/templates/ipam/vrf_list.html +++ b/netbox/templates/ipam/vrf_list.html @@ -16,7 +16,6 @@
{% include 'inc/search_panel.html' %} - {% include 'inc/tags_panel.html' %}
{% endblock %} diff --git a/netbox/templates/secrets/secret_list.html b/netbox/templates/secrets/secret_list.html index b6d792765..ee631b439 100644 --- a/netbox/templates/secrets/secret_list.html +++ b/netbox/templates/secrets/secret_list.html @@ -15,7 +15,6 @@
{% include 'inc/search_panel.html' %} - {% include 'inc/tags_panel.html' %}
{% endblock %} diff --git a/netbox/templates/tenancy/tenant_list.html b/netbox/templates/tenancy/tenant_list.html index 91463c52c..a77636a5b 100644 --- a/netbox/templates/tenancy/tenant_list.html +++ b/netbox/templates/tenancy/tenant_list.html @@ -16,7 +16,6 @@
{% include 'inc/search_panel.html' %} - {% include 'inc/tags_panel.html' %}
{% endblock %} diff --git a/netbox/templates/virtualization/cluster_list.html b/netbox/templates/virtualization/cluster_list.html index 3fef90c03..6f5f058ad 100644 --- a/netbox/templates/virtualization/cluster_list.html +++ b/netbox/templates/virtualization/cluster_list.html @@ -16,7 +16,6 @@
{% include 'inc/search_panel.html' %} - {% include 'inc/tags_panel.html' %}
{% endblock %} diff --git a/netbox/templates/virtualization/virtualmachine_list.html b/netbox/templates/virtualization/virtualmachine_list.html index b10341547..821f956a2 100644 --- a/netbox/templates/virtualization/virtualmachine_list.html +++ b/netbox/templates/virtualization/virtualmachine_list.html @@ -16,7 +16,6 @@
{% include 'inc/search_panel.html' %} - {% include 'inc/tags_panel.html' %}
{% endblock %} diff --git a/netbox/utilities/views.py b/netbox/utilities/views.py index 1aa358fba..525fd92a9 100644 --- a/netbox/utilities/views.py +++ b/netbox/utilities/views.py @@ -8,7 +8,7 @@ from django.core.exceptions import ValidationError from django.db import transaction, IntegrityError from django.db.models import Count, ProtectedError from django.db.models.query import QuerySet -from django.forms import CharField, Form, ModelMultipleChoiceField, MultipleHiddenInput, Textarea +from django.forms import CharField, Form, ModelMultipleChoiceField, MultipleChoiceField, MultipleHiddenInput, Textarea from django.http import HttpResponse, HttpResponseServerError from django.shortcuts import get_object_or_404, redirect, render from django.template import loader @@ -24,7 +24,7 @@ from django_tables2 import RequestConfig from extras.models import CustomField, CustomFieldValue, ExportTemplate from extras.querysets import CustomFieldQueryset -from utilities.forms import BootstrapMixin, CSVDataField +from utilities.forms import BootstrapMixin, CSVDataField, StaticSelect2Multiple from utilities.utils import csv_format from .error_handlers import handle_protectederror from .forms import ConfirmationForm @@ -94,6 +94,7 @@ class ObjectListView(View): model = self.queryset.model content_type = ContentType.objects.get_for_model(model) + filter_form = self.filter_form(request.GET, label_suffix='') if self.filter_form else None if self.filter: self.queryset = self.filter(request.GET, self.queryset).qs @@ -142,11 +143,17 @@ class ObjectListView(View): if 'pk' in table.base_columns and (permissions['change'] or permissions['delete']): table.columns.show('pk') - # Construct queryset for tags list - if hasattr(model, 'tags'): + # Add the tags filter field to the from if the model has tags + if hasattr(model, 'tags') and filter_form: tags = model.tags.annotate(count=Count('extras_taggeditem_items')).order_by('name') - else: - tags = None + choices = [(str(tag.slug), '{} ({})'.format(tag.name, tag.count)) for tag in tags] + + filter_form.fields['tag'] = MultipleChoiceField( + label='Tags', + choices=choices, + required=False, + widget=StaticSelect2Multiple(), + ) # Apply the request context paginate = { @@ -159,8 +166,7 @@ class ObjectListView(View): 'content_type': content_type, 'table': table, 'permissions': permissions, - 'filter_form': self.filter_form(request.GET, label_suffix='') if self.filter_form else None, - 'tags': tags, + 'filter_form': filter_form, } context.update(self.extra_context()) From a8d9fe799b819c2866c3868078a92ae51340b210 Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Mon, 13 Jan 2020 19:06:05 +0000 Subject: [PATCH 02/11] Removed tags filter field from view --- netbox/utilities/views.py | 21 ++++----------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/netbox/utilities/views.py b/netbox/utilities/views.py index 525fd92a9..ffe6f78a9 100644 --- a/netbox/utilities/views.py +++ b/netbox/utilities/views.py @@ -6,9 +6,9 @@ from django.contrib import messages from django.contrib.contenttypes.models import ContentType from django.core.exceptions import ValidationError from django.db import transaction, IntegrityError -from django.db.models import Count, ProtectedError +from django.db.models import ProtectedError from django.db.models.query import QuerySet -from django.forms import CharField, Form, ModelMultipleChoiceField, MultipleChoiceField, MultipleHiddenInput, Textarea +from django.forms import CharField, Form, ModelMultipleChoiceField, MultipleHiddenInput, Textarea from django.http import HttpResponse, HttpResponseServerError from django.shortcuts import get_object_or_404, redirect, render from django.template import loader @@ -24,7 +24,7 @@ from django_tables2 import RequestConfig from extras.models import CustomField, CustomFieldValue, ExportTemplate from extras.querysets import CustomFieldQueryset -from utilities.forms import BootstrapMixin, CSVDataField, StaticSelect2Multiple +from utilities.forms import BootstrapMixin, CSVDataField from utilities.utils import csv_format from .error_handlers import handle_protectederror from .forms import ConfirmationForm @@ -94,7 +94,6 @@ class ObjectListView(View): model = self.queryset.model content_type = ContentType.objects.get_for_model(model) - filter_form = self.filter_form(request.GET, label_suffix='') if self.filter_form else None if self.filter: self.queryset = self.filter(request.GET, self.queryset).qs @@ -143,18 +142,6 @@ class ObjectListView(View): if 'pk' in table.base_columns and (permissions['change'] or permissions['delete']): table.columns.show('pk') - # Add the tags filter field to the from if the model has tags - if hasattr(model, 'tags') and filter_form: - 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] - - filter_form.fields['tag'] = MultipleChoiceField( - label='Tags', - choices=choices, - required=False, - widget=StaticSelect2Multiple(), - ) - # Apply the request context paginate = { 'paginator_class': EnhancedPaginator, @@ -166,7 +153,7 @@ class ObjectListView(View): 'content_type': content_type, 'table': table, 'permissions': permissions, - 'filter_form': filter_form, + 'filter_form': self.filter_form(request.GET, label_suffix='') if self.filter_form else None, } context.update(self.extra_context()) From 2f28dec891ed83066dc7c7bd707af1d3ea770713 Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Mon, 13 Jan 2020 20:16:13 +0000 Subject: [PATCH 03/11] 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 From 865e3e7c9f20dab7a994c662c7a210def5ad2c9e Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Mon, 13 Jan 2020 20:17:47 +0000 Subject: [PATCH 04/11] Updated changelog --- docs/release-notes/version-2.6.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/docs/release-notes/version-2.6.md b/docs/release-notes/version-2.6.md index c313d7e1b..e3aa122e5 100644 --- a/docs/release-notes/version-2.6.md +++ b/docs/release-notes/version-2.6.md @@ -1,4 +1,12 @@ -# v2.6.12 (FUTURE) +# v2.6.13 (FUTURE) + +## Enhancements + +* [#2921](https://github.com/netbox-community/netbox/issues/2921) - Replace tags filter with Select2 widget + +--- + +# v2.6.12 (2020-01-13) ## Enhancements @@ -6,7 +14,6 @@ * [#2050](https://github.com/netbox-community/netbox/issues/2050) - Preview image attachments when hovering the link * [#2113](https://github.com/netbox-community/netbox/issues/2113) - Allow NAPALM driver settings to be changed with request headers * [#2589](https://github.com/netbox-community/netbox/issues/2589) - Toggle for showing available prefixes/ip addresses -* [#2921](https://github.com/netbox-community/netbox/issues/2921) - Replace tags filter with Select2 widget * [#3009](https://github.com/netbox-community/netbox/issues/3009) - Search by description when assigning IP address * [#3090](https://github.com/netbox-community/netbox/issues/3090) - Add filter field for device interfaces * [#3187](https://github.com/netbox-community/netbox/issues/3187) - Add rack selection field to rack elevations From e10333bf2bc90465cee4e00cef6ad8c9ce032949 Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Tue, 14 Jan 2020 08:22:27 +0000 Subject: [PATCH 05/11] Fetch choices during form initialization --- netbox/circuits/forms.py | 10 ++-------- netbox/dcim/forms.py | 35 +++++++--------------------------- netbox/ipam/forms.py | 30 ++++++----------------------- netbox/secrets/forms.py | 5 +---- netbox/tenancy/forms.py | 5 +---- netbox/utilities/forms.py | 11 ++++++++--- netbox/virtualization/forms.py | 10 ++-------- 7 files changed, 27 insertions(+), 79 deletions(-) diff --git a/netbox/circuits/forms.py b/netbox/circuits/forms.py index 4438dbc1c..decb954d8 100644 --- a/netbox/circuits/forms.py +++ b/netbox/circuits/forms.py @@ -128,10 +128,7 @@ class ProviderFilterForm(BootstrapMixin, CustomFieldFilterForm): required=False, label='ASN' ) - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.fields['tag'] = TagFilterField(self.model) + tag = TagFilterField(model) # @@ -336,10 +333,7 @@ class CircuitFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm min_value=0, label='Commit rate (Kbps)' ) - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.fields['tag'] = TagFilterField(self.model) + tag = TagFilterField(model) # diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index 82dd99c3d..b9041e953 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -335,10 +335,7 @@ class SiteFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm): value_field="slug", ) ) - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.fields['tag'] = TagFilterField(self.model) + tag = TagFilterField(model) # @@ -717,10 +714,7 @@ class RackFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm): null_option=True, ) ) - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.fields['tag'] = TagFilterField(self.model) + tag = TagFilterField(model) # @@ -1013,10 +1007,7 @@ class DeviceTypeFilterForm(BootstrapMixin, CustomFieldFilterForm): choices=BOOLEAN_WITH_BLANK_CHOICES ) ) - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.fields['tag'] = TagFilterField(self.model) + tag = TagFilterField(model) # @@ -1959,10 +1950,7 @@ class DeviceFilterForm(BootstrapMixin, LocalConfigContextFilterForm, TenancyFilt choices=BOOLEAN_WITH_BLANK_CHOICES ) ) - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.fields['tag'] = TagFilterField(self.model) + tag = TagFilterField(model) # @@ -3435,10 +3423,7 @@ class InventoryItemFilterForm(BootstrapMixin, forms.Form): choices=BOOLEAN_WITH_BLANK_CHOICES ) ) - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.fields['tag'] = TagFilterField(self.model) + tag = TagFilterField(model) # @@ -3625,10 +3610,7 @@ class VirtualChassisFilterForm(BootstrapMixin, CustomFieldFilterForm): null_option=True, ) ) - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.fields['tag'] = TagFilterField(self.model) + tag = TagFilterField(model) # @@ -4006,7 +3988,4 @@ 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) + tag = TagFilterField(model) diff --git a/netbox/ipam/forms.py b/netbox/ipam/forms.py index e64582b03..46788e6be 100644 --- a/netbox/ipam/forms.py +++ b/netbox/ipam/forms.py @@ -102,10 +102,7 @@ class VRFFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm): required=False, label='Search' ) - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.fields['tag'] = TagFilterField(self.model) + tag = TagFilterField(model) # @@ -235,10 +232,7 @@ class AggregateFilterForm(BootstrapMixin, CustomFieldFilterForm): value_field="slug", ) ) - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.fields['tag'] = TagFilterField(self.model) + tag = TagFilterField(model) # @@ -585,10 +579,7 @@ class PrefixFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm) required=False, label='Expand prefix hierarchy' ) - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.fields['tag'] = TagFilterField(self.model) + tag = TagFilterField(model) # @@ -1017,10 +1008,7 @@ class IPAddressFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterFo choices=BOOLEAN_WITH_BLANK_CHOICES ) ) - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.fields['tag'] = TagFilterField(self.model) + tag = TagFilterField(model) # @@ -1308,10 +1296,7 @@ class VLANFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm): null_option=True, ) ) - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.fields['tag'] = TagFilterField(self.model) + tag = TagFilterField(model) # @@ -1372,10 +1357,7 @@ class ServiceFilterForm(BootstrapMixin, CustomFieldFilterForm): port = forms.IntegerField( required=False, ) - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.fields['tag'] = TagFilterField(self.model) + tag = TagFilterField(model) class ServiceBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm): diff --git a/netbox/secrets/forms.py b/netbox/secrets/forms.py index 8b8467f04..73ce55899 100644 --- a/netbox/secrets/forms.py +++ b/netbox/secrets/forms.py @@ -184,10 +184,7 @@ class SecretFilterForm(BootstrapMixin, CustomFieldFilterForm): value_field="slug", ) ) - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.fields['tag'] = TagFilterField(self.model) + tag = TagFilterField(model) # diff --git a/netbox/tenancy/forms.py b/netbox/tenancy/forms.py index f398b965a..0acf377a7 100644 --- a/netbox/tenancy/forms.py +++ b/netbox/tenancy/forms.py @@ -113,10 +113,7 @@ class TenantFilterForm(BootstrapMixin, CustomFieldFilterForm): null_option=True, ) ) - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.fields['tag'] = TagFilterField(self.model) + tag = TagFilterField(model) # diff --git a/netbox/utilities/forms.py b/netbox/utilities/forms.py index d1d19a6cb..2744b249f 100644 --- a/netbox/utilities/forms.py +++ b/netbox/utilities/forms.py @@ -606,11 +606,16 @@ class TagFilterField(forms.MultipleChoiceField): widget = StaticSelect2Multiple def __init__(self, model, *args, **kwargs): + # Only instanitate the field if the model supports tags (i.e. hide if not) 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] + self.model = model - super().__init__(label='Tags', choices=choices, required=False, *args, **kwargs) + # Choices are fetched during form initialization + super().__init__(label='Tags', choices=self._choices, required=False, *args, **kwargs) + + def _choices(self): + tags = self.model.tags.annotate(count=Count('extras_taggeditem_items')).order_by('name') + return [(str(tag.slug), '{} ({})'.format(tag.name, tag.count)) for tag in tags] class FilterChoiceIterator(forms.models.ModelChoiceIterator): diff --git a/netbox/virtualization/forms.py b/netbox/virtualization/forms.py index 36b84c7b1..e26f21480 100644 --- a/netbox/virtualization/forms.py +++ b/netbox/virtualization/forms.py @@ -216,10 +216,7 @@ class ClusterFilterForm(BootstrapMixin, CustomFieldFilterForm): null_option=True, ) ) - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.fields['tag'] = TagFilterField(self.model) + tag = TagFilterField(model) class ClusterAddDevicesForm(BootstrapMixin, ChainedFieldsMixin, forms.Form): @@ -626,10 +623,7 @@ class VirtualMachineFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFil required=False, label='MAC address' ) - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.fields['tag'] = TagFilterField(self.model) + tag = TagFilterField(model) # From 8f91e9b079b52121b5bf7617560faa8c7cdca773 Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Thu, 16 Jan 2020 15:34:11 +0000 Subject: [PATCH 06/11] Added #2921 changelog --- docs/release-notes/version-2.6.md | 8 -------- docs/release-notes/version-2.7.md | 1 + 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/docs/release-notes/version-2.6.md b/docs/release-notes/version-2.6.md index 62b78149f..9fd258b0f 100644 --- a/docs/release-notes/version-2.6.md +++ b/docs/release-notes/version-2.6.md @@ -1,11 +1,3 @@ -# v2.6.13 (FUTURE) - -## Enhancements - -* [#2921](https://github.com/netbox-community/netbox/issues/2921) - Replace tags filter with Select2 widget - ---- - # v2.6.12 (2020-01-13) ## Enhancements diff --git a/docs/release-notes/version-2.7.md b/docs/release-notes/version-2.7.md index ac9d81e2c..79dfe4967 100644 --- a/docs/release-notes/version-2.7.md +++ b/docs/release-notes/version-2.7.md @@ -226,6 +226,7 @@ PATCH) to maintain backward compatibility. This behavior will be discontinued be * [#1865](https://github.com/netbox-community/netbox/issues/1865) - Add console port and console server port types * [#2669](https://github.com/netbox-community/netbox/issues/2669) - Relax uniqueness constraint on device and VM names * [#2902](https://github.com/netbox-community/netbox/issues/2902) - Replace `supervisord` with `systemd` +* [#2921](https://github.com/netbox-community/netbox/issues/2921) - Replace tags filter with Select2 widget * [#3455](https://github.com/netbox-community/netbox/issues/3455) - Add tenant assignment to cluster * [#3520](https://github.com/netbox-community/netbox/issues/3520) - Add Jinja2 template support for Graphs * [#3525](https://github.com/netbox-community/netbox/issues/3525) - Enable IP address filtering with multiple address terms From 26ebed0182ad479f940e3dd7498aa0e2c3b4c3b6 Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Thu, 16 Jan 2020 15:42:31 +0000 Subject: [PATCH 07/11] Removed legacy work regarding inc/tags_panel.html --- netbox/templates/dcim/device_component_list.html | 1 - 1 file changed, 1 deletion(-) diff --git a/netbox/templates/dcim/device_component_list.html b/netbox/templates/dcim/device_component_list.html index 3936a1c19..28322973e 100644 --- a/netbox/templates/dcim/device_component_list.html +++ b/netbox/templates/dcim/device_component_list.html @@ -14,7 +14,6 @@
{% include 'inc/search_panel.html' %} - {% include 'inc/tags_panel.html' %}
{% endblock %} From e05cecb48182fe60faf52324bf79b929fb2082ba Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Thu, 16 Jan 2020 21:51:01 +0000 Subject: [PATCH 08/11] Moved into v2.7.1 --- docs/release-notes/version-2.7.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/release-notes/version-2.7.md b/docs/release-notes/version-2.7.md index 79dfe4967..938baaf74 100644 --- a/docs/release-notes/version-2.7.md +++ b/docs/release-notes/version-2.7.md @@ -1,3 +1,11 @@ +# v2.7.1 (FUTURE) + +## Enhancements + +* [#2921](https://github.com/netbox-community/netbox/issues/2921) - Replace tags filter with Select2 widget + +--- + # v2.7.0 (FUTURE) **Note:** NetBox v2.7 is the last major release that will support Python 3.5. Beginning with NetBox v2.8, Python 3.6 or @@ -226,7 +234,6 @@ PATCH) to maintain backward compatibility. This behavior will be discontinued be * [#1865](https://github.com/netbox-community/netbox/issues/1865) - Add console port and console server port types * [#2669](https://github.com/netbox-community/netbox/issues/2669) - Relax uniqueness constraint on device and VM names * [#2902](https://github.com/netbox-community/netbox/issues/2902) - Replace `supervisord` with `systemd` -* [#2921](https://github.com/netbox-community/netbox/issues/2921) - Replace tags filter with Select2 widget * [#3455](https://github.com/netbox-community/netbox/issues/3455) - Add tenant assignment to cluster * [#3520](https://github.com/netbox-community/netbox/issues/3520) - Add Jinja2 template support for Graphs * [#3525](https://github.com/netbox-community/netbox/issues/3525) - Enable IP address filtering with multiple address terms From 2375d66f75c8ad7f9acae49a57c7d5e0b9690656 Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Thu, 30 Jan 2020 17:45:03 +0000 Subject: [PATCH 09/11] Added TagFilterField to device components' filter forms --- netbox/dcim/forms.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index 794163a9e..fb9e033a7 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -2158,6 +2158,7 @@ class DeviceBulkAddInterfaceForm(DeviceBulkAddComponentForm): class ConsolePortFilterForm(DeviceComponentFilterForm): model = ConsolePort + tag = TagFilterField(model) class ConsolePortForm(BootstrapMixin, forms.ModelForm): @@ -2215,6 +2216,7 @@ class ConsolePortCSVForm(forms.ModelForm): class ConsoleServerPortFilterForm(DeviceComponentFilterForm): model = ConsoleServerPort + tag = TagFilterField(model) class ConsoleServerPortForm(BootstrapMixin, forms.ModelForm): @@ -2307,6 +2309,7 @@ class ConsoleServerPortCSVForm(forms.ModelForm): class PowerPortFilterForm(DeviceComponentFilterForm): model = PowerPort + tag = TagFilterField(model) class PowerPortForm(BootstrapMixin, forms.ModelForm): @@ -2374,6 +2377,7 @@ class PowerPortCSVForm(forms.ModelForm): class PowerOutletFilterForm(DeviceComponentFilterForm): model = PowerOutlet + tag = TagFilterField(model) class PowerOutletForm(BootstrapMixin, forms.ModelForm): @@ -2542,6 +2546,7 @@ class PowerOutletBulkDisconnectForm(ConfirmationForm): class InterfaceFilterForm(DeviceComponentFilterForm): model = Interface + tag = TagFilterField(model) class InterfaceForm(InterfaceCommonForm, BootstrapMixin, forms.ModelForm): @@ -2855,6 +2860,7 @@ class InterfaceBulkDisconnectForm(ConfirmationForm): class FrontPortFilterForm(DeviceComponentFilterForm): model = FrontPort + tag = TagFilterField(model) class FrontPortForm(BootstrapMixin, forms.ModelForm): @@ -3032,6 +3038,7 @@ class FrontPortBulkDisconnectForm(ConfirmationForm): class RearPortFilterForm(DeviceComponentFilterForm): model = RearPort + tag = TagFilterField(model) class RearPortForm(BootstrapMixin, forms.ModelForm): @@ -3636,6 +3643,7 @@ class CableFilterForm(BootstrapMixin, forms.Form): class DeviceBayFilterForm(DeviceComponentFilterForm): model = DeviceBay + tag = TagFilterField(model) class DeviceBayForm(BootstrapMixin, forms.ModelForm): From 5879671971000816970a12a65159afeae153627d Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Thu, 30 Jan 2020 17:49:42 +0000 Subject: [PATCH 10/11] Avoid overriding private attribute in super --- netbox/utilities/forms.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/netbox/utilities/forms.py b/netbox/utilities/forms.py index a1cd4024f..a6eca7382 100644 --- a/netbox/utilities/forms.py +++ b/netbox/utilities/forms.py @@ -571,16 +571,12 @@ class TagFilterField(forms.MultipleChoiceField): widget = StaticSelect2Multiple def __init__(self, model, *args, **kwargs): - # Only instanitate the field if the model supports tags (i.e. hide if not) - if hasattr(model, 'tags'): - self.model = model + def get_choices(): + tags = model.tags.annotate(count=Count('extras_taggeditem_items')).order_by('name') + return [(str(tag.slug), '{} ({})'.format(tag.name, tag.count)) for tag in tags] - # Choices are fetched during form initialization - super().__init__(label='Tags', choices=self._choices, required=False, *args, **kwargs) - - def _choices(self): - tags = self.model.tags.annotate(count=Count('extras_taggeditem_items')).order_by('name') - return [(str(tag.slug), '{} ({})'.format(tag.name, tag.count)) for tag in tags] + # Choices are fetched each time the form is initialized + super().__init__(label='Tags', choices=get_choices, required=False, *args, **kwargs) class FilterChoiceIterator(forms.models.ModelChoiceIterator): From 7897ebb2edf74e9d1044a4eb4227227b7639c82b Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Thu, 30 Jan 2020 17:52:30 +0000 Subject: [PATCH 11/11] Corrected changelog --- docs/release-notes/version-2.7.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/release-notes/version-2.7.md b/docs/release-notes/version-2.7.md index 80cad3254..7611ffce1 100644 --- a/docs/release-notes/version-2.7.md +++ b/docs/release-notes/version-2.7.md @@ -6,13 +6,16 @@ * [#4049](https://github.com/netbox-community/netbox/issues/4049) - Restore missing `tags` field in IPAM service serializer * [#4056](https://github.com/netbox-community/netbox/issues/4056) - Repair schema migration for Rack.outer_unit (from #3569) +## Enhancements + +* [#2921](https://github.com/netbox-community/netbox/issues/2921) - Replace tags filter with Select2 widget + --- # v2.7.3 (2020-01-28) ## Enhancements -* [#2921](https://github.com/netbox-community/netbox/issues/2921) - Replace tags filter with Select2 widget * [#3310](https://github.com/netbox-community/netbox/issues/3310) - Pre-select site/rack for B side when creating a new cable * [#3338](https://github.com/netbox-community/netbox/issues/3338) - Include circuit terminations in API representation of circuits * [#3509](https://github.com/netbox-community/netbox/issues/3509) - Add IP address variables for custom scripts