From cc0830bf281fd0fd85df349d50e3108d06930278 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Thu, 16 Sep 2021 16:04:46 -0400 Subject: [PATCH] Closes #7087: Add search/filter forms for all organizational models --- docs/release-notes/version-3.0.md | 1 + netbox/circuits/forms.py | 12 +++++++ netbox/circuits/views.py | 2 ++ netbox/dcim/forms.py | 36 +++++++++++++++++++ netbox/dcim/views.py | 6 ++++ netbox/ipam/forms.py | 27 ++++++++++++-- netbox/ipam/views.py | 2 ++ netbox/project-static/dist/rack_elevation.css | 2 +- netbox/virtualization/forms.py | 24 +++++++++++++ netbox/virtualization/views.py | 4 +++ 10 files changed, 113 insertions(+), 3 deletions(-) diff --git a/docs/release-notes/version-3.0.md b/docs/release-notes/version-3.0.md index 04bd95a8c..5d104de04 100644 --- a/docs/release-notes/version-3.0.md +++ b/docs/release-notes/version-3.0.md @@ -8,6 +8,7 @@ * [#6387](https://github.com/netbox-community/netbox/issues/6387) - Add xDSL interface type * [#6988](https://github.com/netbox-community/netbox/issues/6988) - Order tenants alphabetically without regard to group assignment * [#7032](https://github.com/netbox-community/netbox/issues/7032) - Add URM port types +* [#7087](https://github.com/netbox-community/netbox/issues/7087) - Add search/filter forms for all organizational models * [#7208](https://github.com/netbox-community/netbox/issues/7208) - Add navigation breadcrumbs for custom scripts & reports * [#7239](https://github.com/netbox-community/netbox/issues/7239) - Redirect global search to filtered object list when an object type is selected diff --git a/netbox/circuits/forms.py b/netbox/circuits/forms.py index 56cd46d4a..f43a3cfff 100644 --- a/netbox/circuits/forms.py +++ b/netbox/circuits/forms.py @@ -266,6 +266,18 @@ class CircuitTypeCSVForm(CustomFieldModelCSVForm): } +class CircuitTypeFilterForm(BootstrapMixin, CustomFieldModelFilterForm): + model = CircuitType + field_groups = [ + ['q'], + ] + q = forms.CharField( + required=False, + widget=forms.TextInput(attrs={'placeholder': _('All Fields')}), + label=_('Search') + ) + + # # Circuits # diff --git a/netbox/circuits/views.py b/netbox/circuits/views.py index dfbfe68a4..3460d4626 100644 --- a/netbox/circuits/views.py +++ b/netbox/circuits/views.py @@ -144,6 +144,8 @@ class CircuitTypeListView(generic.ObjectListView): queryset = CircuitType.objects.annotate( circuit_count=count_related(Circuit, 'type') ) + filterset = filtersets.CircuitTypeFilterSet + filterset_form = forms.CircuitTypeFilterForm table = tables.CircuitTypeTable diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index c1f8eccf8..935c3c9b3 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -696,6 +696,18 @@ class RackRoleBulkEditForm(BootstrapMixin, CustomFieldModelBulkEditForm): nullable_fields = ['color', 'description'] +class RackRoleFilterForm(BootstrapMixin, CustomFieldModelFilterForm): + model = RackRole + field_groups = [ + ['q'], + ] + q = forms.CharField( + required=False, + widget=forms.TextInput(attrs={'placeholder': _('All Fields')}), + label=_('Search') + ) + + # # Racks # @@ -1240,6 +1252,18 @@ class ManufacturerBulkEditForm(BootstrapMixin, CustomFieldModelBulkEditForm): nullable_fields = ['description'] +class ManufacturerFilterForm(BootstrapMixin, CustomFieldModelFilterForm): + model = Manufacturer + field_groups = [ + ['q'], + ] + q = forms.CharField( + required=False, + widget=forms.TextInput(attrs={'placeholder': _('All Fields')}), + label=_('Search') + ) + + # # Device types # @@ -2076,6 +2100,18 @@ class DeviceRoleBulkEditForm(BootstrapMixin, CustomFieldModelBulkEditForm): nullable_fields = ['color', 'description'] +class DeviceRoleFilterForm(BootstrapMixin, CustomFieldModelFilterForm): + model = DeviceRole + field_groups = [ + ['q'], + ] + q = forms.CharField( + required=False, + widget=forms.TextInput(attrs={'placeholder': _('All Fields')}), + label=_('Search') + ) + + # # Platforms # diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index f9e1cc9fe..4305d3c63 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -440,6 +440,8 @@ class RackRoleListView(generic.ObjectListView): queryset = RackRole.objects.annotate( rack_count=count_related(Rack, 'role') ) + filterset = filtersets.RackRoleFilterSet + filterset_form = forms.RackRoleFilterForm table = tables.RackRoleTable @@ -684,6 +686,8 @@ class ManufacturerListView(generic.ObjectListView): inventoryitem_count=count_related(InventoryItem, 'manufacturer'), platform_count=count_related(Platform, 'manufacturer') ) + filterset = filtersets.ManufacturerFilterSet + filterset_form = forms.ManufacturerFilterForm table = tables.ManufacturerTable @@ -1149,6 +1153,8 @@ class DeviceRoleListView(generic.ObjectListView): device_count=count_related(Device, 'device_role'), vm_count=count_related(VirtualMachine, 'role') ) + filterset = filtersets.DeviceRoleFilterSet + filterset_form = forms.DeviceRoleFilterForm table = tables.DeviceRoleTable diff --git a/netbox/ipam/forms.py b/netbox/ipam/forms.py index 4d5b3ad73..c72884b3c 100644 --- a/netbox/ipam/forms.py +++ b/netbox/ipam/forms.py @@ -256,7 +256,17 @@ class RIRBulkEditForm(BootstrapMixin, CustomFieldModelBulkEditForm): nullable_fields = ['is_private', 'description'] -class RIRFilterForm(BootstrapMixin, forms.Form): +class RIRFilterForm(BootstrapMixin, CustomFieldModelFilterForm): + model = RIR + field_groups = [ + ['q'], + ['is_private'], + ] + q = forms.CharField( + required=False, + widget=forms.TextInput(attrs={'placeholder': _('All Fields')}), + label=_('Search') + ) is_private = forms.NullBooleanField( required=False, label=_('Private'), @@ -413,6 +423,18 @@ class RoleBulkEditForm(BootstrapMixin, CustomFieldModelBulkEditForm): nullable_fields = ['description'] +class RoleFilterForm(BootstrapMixin, CustomFieldModelFilterForm): + model = Role + field_groups = [ + ['q'], + ] + q = forms.CharField( + required=False, + widget=forms.TextInput(attrs={'placeholder': _('All Fields')}), + label=_('Search') + ) + + # # Prefixes # @@ -1460,11 +1482,12 @@ class VLANGroupBulkEditForm(BootstrapMixin, CustomFieldModelBulkEditForm): nullable_fields = ['site', 'description'] -class VLANGroupFilterForm(BootstrapMixin, forms.Form): +class VLANGroupFilterForm(BootstrapMixin, CustomFieldModelFilterForm): field_groups = [ ['q'], ['region', 'sitegroup', 'site', 'location', 'rack'] ] + model = VLANGroup q = forms.CharField( required=False, widget=forms.TextInput(attrs={'placeholder': _('All Fields')}), diff --git a/netbox/ipam/views.py b/netbox/ipam/views.py index f84760418..64ca5bc43 100644 --- a/netbox/ipam/views.py +++ b/netbox/ipam/views.py @@ -283,6 +283,8 @@ class RoleListView(generic.ObjectListView): prefix_count=count_related(Prefix, 'role'), vlan_count=count_related(VLAN, 'role') ) + filterset = filtersets.RoleFilterSet + filterset_form = forms.RoleFilterForm table = tables.RoleTable diff --git a/netbox/project-static/dist/rack_elevation.css b/netbox/project-static/dist/rack_elevation.css index 4f9361489..eb9a6e237 100644 --- a/netbox/project-static/dist/rack_elevation.css +++ b/netbox/project-static/dist/rack_elevation.css @@ -1 +1 @@ -svg{--nbx-rack-bg: #e9ecef;--nbx-rack-border: #000;--nbx-rack-slot-bg: #e9ecef;--nbx-rack-slot-border: #adb5bd;--nbx-rack-slot-hover-bg: #ced4da;--nbx-rack-link-color: #0d6efd;--nbx-rack-unit-color: #6c757d}svg[data-netbox-color-mode=dark]{--nbx-rack-bg: #343a40;--nbx-rack-border: #6c757d;--nbx-rack-slot-bg: #343a40;--nbx-rack-slot-border: #495057;--nbx-rack-slot-hover-bg: #212529;--nbx-rack-link-color: #9ec5fe;--nbx-rack-unit-color: #6c757d}*{font-family:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:.875rem}rect{box-sizing:border-box}text{text-anchor:middle;dominant-baseline:middle}svg .unit{margin:0;padding:5px 0;fill:var(--nbx-rack-unit-color)}svg .hidden{visibility:hidden}svg .rack{fill:none;stroke-width:2px;stroke:var(--nbx-rack-border);background-color:var(--nbx-rack-bg)}svg .slot{fill:var(--nbx-rack-slot-bg);stroke:var(--nbx-rack-slot-border)}svg .slot:hover{fill:var(--nbx-rack-slot-hover-bg)}svg .slot+.add-device{fill:var(--nbx-rack-link-color);opacity:0;pointer-events:none}svg .slot:hover+.add-device{opacity:1}svg .slot.reserved:hover[class]+.add-device{fill:#000}svg .slot.reserved[class],svg .slot.reserved:hover[class]{fill:url(#reserved)}svg .slot.occupied[class],svg .slot.occupied:hover[class]{fill:url(#occupied)}svg .slot.blocked[class],svg .slot.blocked:hover[class]{fill:url(#blocked)}svg .slot.blocked:hover+.add-device{opacity:0} +svg{--nbx-rack-bg: #e9ecef;--nbx-rack-border: #000;--nbx-rack-slot-bg: #e9ecef;--nbx-rack-slot-border: #adb5bd;--nbx-rack-slot-hover-bg: #ced4da;--nbx-rack-link-color: #0d6efd;--nbx-rack-unit-color: #6c757d}svg[data-netbox-color-mode=dark]{--nbx-rack-bg: #343a40;--nbx-rack-border: #6c757d;--nbx-rack-slot-bg: #343a40;--nbx-rack-slot-border: #495057;--nbx-rack-slot-hover-bg: #212529;--nbx-rack-link-color: #9ec5fe;--nbx-rack-unit-color: #6c757d}*{font-family:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:.875rem}rect{box-sizing:border-box}text{text-anchor:middle;dominant-baseline:middle}svg .unit{margin:0;padding:5px 0;fill:var(--nbx-rack-unit-color)}svg a{overflow:hidden}svg .hidden{visibility:hidden}svg .rack{fill:none;stroke-width:2px;stroke:var(--nbx-rack-border);background-color:var(--nbx-rack-bg)}svg .slot{fill:var(--nbx-rack-slot-bg);stroke:var(--nbx-rack-slot-border)}svg .slot:hover{fill:var(--nbx-rack-slot-hover-bg)}svg .slot+.add-device{fill:var(--nbx-rack-link-color);opacity:0;pointer-events:none}svg .slot:hover+.add-device{opacity:1}svg .slot.reserved:hover[class]+.add-device{fill:#000}svg .slot.reserved[class],svg .slot.reserved:hover[class]{fill:url(#reserved)}svg .slot.occupied[class],svg .slot.occupied:hover[class]{fill:url(#occupied)}svg .slot.blocked[class],svg .slot.blocked:hover[class]{fill:url(#blocked)}svg .slot.blocked:hover+.add-device{opacity:0} diff --git a/netbox/virtualization/forms.py b/netbox/virtualization/forms.py index efb787eca..1ecef4280 100644 --- a/netbox/virtualization/forms.py +++ b/netbox/virtualization/forms.py @@ -61,6 +61,18 @@ class ClusterTypeBulkEditForm(BootstrapMixin, CustomFieldModelBulkEditForm): nullable_fields = ['description'] +class ClusterTypeFilterForm(BootstrapMixin, CustomFieldModelFilterForm): + model = ClusterType + field_groups = [ + ['q'], + ] + q = forms.CharField( + required=False, + widget=forms.TextInput(attrs={'placeholder': _('All Fields')}), + label=_('Search') + ) + + # # Cluster groups # @@ -97,6 +109,18 @@ class ClusterGroupBulkEditForm(BootstrapMixin, CustomFieldModelBulkEditForm): nullable_fields = ['description'] +class ClusterGroupFilterForm(BootstrapMixin, CustomFieldModelFilterForm): + model = ClusterGroup + field_groups = [ + ['q'], + ] + q = forms.CharField( + required=False, + widget=forms.TextInput(attrs={'placeholder': _('All Fields')}), + label=_('Search') + ) + + # # Clusters # diff --git a/netbox/virtualization/views.py b/netbox/virtualization/views.py index 51eba0992..115ca0a29 100644 --- a/netbox/virtualization/views.py +++ b/netbox/virtualization/views.py @@ -24,6 +24,8 @@ class ClusterTypeListView(generic.ObjectListView): queryset = ClusterType.objects.annotate( cluster_count=count_related(Cluster, 'type') ) + filterset = filtersets.ClusterTypeFilterSet + filterset_form = forms.ClusterTypeFilterForm table = tables.ClusterTypeTable @@ -86,6 +88,8 @@ class ClusterGroupListView(generic.ObjectListView): queryset = ClusterGroup.objects.annotate( cluster_count=count_related(Cluster, 'group') ) + filterset = filtersets.ClusterGroupFilterSet + filterset_form = forms.ClusterGroupFilterForm table = tables.ClusterGroupTable