From 193f04a208bfd128f325fce8b418b5f8dcafa729 Mon Sep 17 00:00:00 2001 From: wkoot <3715211+wkoot@users.noreply.github.com> Date: Thu, 13 Feb 2025 22:16:32 +0100 Subject: [PATCH] Implement list filtering forms (#57) Closes #54 Signed-off-by: wkoot <3715211+wkoot@users.noreply.github.com> --- CHANGELOG.md | 1 + netbox_slm/api/serializers.py | 1 + netbox_slm/filtersets.py | 67 +++++++++++++++++-- netbox_slm/forms/software_license.py | 33 ++++++++- netbox_slm/forms/software_product.py | 18 ++++- .../forms/software_product_installation.py | 39 ++++++++++- netbox_slm/forms/software_product_version.py | 31 +++++++-- netbox_slm/tables.py | 2 + .../templates/netbox_slm/softwarelicense.html | 24 +++---- .../softwareproductinstallation.html | 2 +- .../netbox_slm/softwareproductversion.html | 8 +-- 11 files changed, 193 insertions(+), 33 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ea93ff..32281f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ * Toggle to show plugin as top level menu item (#42) * Bulk import except `LaxURLFields` (#4) * Changelog with backdated changes (#51) +* List filtering (#54) ### Changed diff --git a/netbox_slm/api/serializers.py b/netbox_slm/api/serializers.py index e6e1e0f..e5e18b1 100644 --- a/netbox_slm/api/serializers.py +++ b/netbox_slm/api/serializers.py @@ -60,6 +60,7 @@ class SoftwareProductSerializer(NetBoxModelSerializer): brief_fields = ("id", "display", "url", "name", "description") def get_display(self, obj): + # TODO - does not match form and filter views, display manufacturer separately? return f"{obj.manufacturer} - {obj}" diff --git a/netbox_slm/filtersets.py b/netbox_slm/filtersets.py index f116a2e..389e5fe 100644 --- a/netbox_slm/filtersets.py +++ b/netbox_slm/filtersets.py @@ -1,12 +1,22 @@ +from django_filters import CharFilter, ModelMultipleChoiceFilter, MultipleChoiceFilter from django.db.models import Q +from dcim.models import Device, Manufacturer from netbox.filtersets import NetBoxModelFilterSet -from netbox_slm.models import SoftwareProduct, SoftwareProductVersion, SoftwareProductInstallation, SoftwareLicense +from netbox_slm.models import SoftwareProduct, SoftwareProductVersion, SoftwareProductInstallation, SoftwareLicense, SoftwareReleaseTypes +from virtualization.models import VirtualMachine, Cluster class SoftwareProductFilterSet(NetBoxModelFilterSet): """Filter capabilities for SoftwareProduct instances.""" + name = CharFilter(lookup_expr="icontains") + description = CharFilter(lookup_expr="icontains") + + manufacturer = ModelMultipleChoiceFilter( + queryset=Manufacturer.objects.all() + ) + class Meta: model = SoftwareProduct fields = tuple() @@ -27,9 +37,24 @@ class SoftwareProductFilterSet(NetBoxModelFilterSet): class SoftwareProductVersionFilterSet(NetBoxModelFilterSet): """Filter capabilities for SoftwareProductVersion instances.""" + name = CharFilter(lookup_expr="icontains") + filename = CharFilter(lookup_expr="icontains") + + release_type = MultipleChoiceFilter(choices=SoftwareReleaseTypes.choices) + + manufacturer = ModelMultipleChoiceFilter( + field_name="software_product__manufacturer", + queryset=Manufacturer.objects.all(), + ) + + software_product = ModelMultipleChoiceFilter( + queryset=SoftwareProduct.objects.all(), + label="Software Product", + ) + class Meta: model = SoftwareProductVersion - fields = ("software_product",) + fields = tuple() def search(self, queryset, name, value): """Perform the filtered search.""" @@ -47,9 +72,27 @@ class SoftwareProductVersionFilterSet(NetBoxModelFilterSet): class SoftwareProductInstallationFilterSet(NetBoxModelFilterSet): """Filter capabilities for SoftwareProductInstallation instances.""" + device = ModelMultipleChoiceFilter( + queryset=Device.objects.all() + ) + virtualmachine = ModelMultipleChoiceFilter( + queryset=VirtualMachine.objects.all(), + label="Virtual Machine", + ) + cluster = ModelMultipleChoiceFilter( + queryset=Cluster.objects.all() + ) + software_product = ModelMultipleChoiceFilter( + queryset=SoftwareProduct.objects.all(), + label="Software Product", + ) + version = ModelMultipleChoiceFilter( + queryset=SoftwareProductVersion.objects.all() + ) + class Meta: model = SoftwareProductInstallation - fields = ("software_product",) + fields = tuple() def search(self, queryset, name, value): """Perform the filtered search.""" @@ -67,9 +110,25 @@ class SoftwareProductInstallationFilterSet(NetBoxModelFilterSet): class SoftwareLicenseFilterSet(NetBoxModelFilterSet): """Filter capabilities for SoftwareLicense instances.""" + name = CharFilter(lookup_expr="icontains") + description = CharFilter(lookup_expr="icontains") + type = CharFilter(lookup_expr="icontains") + stored_location = CharFilter(lookup_expr="icontains") + + software_product = ModelMultipleChoiceFilter( + queryset=SoftwareProduct.objects.all(), + label="Software Product", + ) + version = ModelMultipleChoiceFilter( + queryset=SoftwareProductVersion.objects.all() + ) + installation = ModelMultipleChoiceFilter( + queryset=SoftwareProductInstallation.objects.all() + ) + class Meta: model = SoftwareLicense - fields = tuple() + fields = ("support",) def search(self, queryset, name, value): """Perform the filtered search.""" diff --git a/netbox_slm/forms/software_license.py b/netbox_slm/forms/software_license.py index 92b5371..3287154 100644 --- a/netbox_slm/forms/software_license.py +++ b/netbox_slm/forms/software_license.py @@ -1,9 +1,10 @@ -from django.forms import DateField +from django.forms import CharField, DateField, ChoiceField from django.urls import reverse_lazy from netbox.forms import NetBoxModelForm, NetBoxModelFilterSetForm, NetBoxModelImportForm from netbox_slm.models import SoftwareProduct, SoftwareProductVersion, SoftwareProductInstallation, SoftwareLicense -from utilities.forms.fields import CommentField, DynamicModelChoiceField, TagFilterField, LaxURLField +from utilities.forms.constants import BOOLEAN_WITH_BLANK_CHOICES +from utilities.forms.fields import CommentField, DynamicModelChoiceField, TagFilterField, LaxURLField, DynamicModelMultipleChoiceField from utilities.forms.rendering import FieldSet from utilities.forms.widgets import APISelect, DatePicker @@ -18,6 +19,7 @@ class SoftwareLicenseForm(NetBoxModelForm): software_product = DynamicModelChoiceField( queryset=SoftwareProduct.objects.all(), required=True, + label="Software Product", widget=APISelect(attrs={"data-url": reverse_lazy("plugins-api:netbox_slm-api:softwareproduct-list")}), ) version = DynamicModelChoiceField( @@ -63,9 +65,34 @@ class SoftwareLicenseForm(NetBoxModelForm): class SoftwareLicenseFilterForm(NetBoxModelFilterSetForm): model = SoftwareLicense - fieldsets = (FieldSet(None, ("q", "tag")),) + fieldsets = ( + FieldSet("q", "filter_id", "tag"), + FieldSet("name", "description", "type", "stored_location", "support", "software_product", "version", "installation"), + ) + selector_fields = ("q", "filter_id", "name") + tag = TagFilterField(model) + name = CharField(required=False) + description = CharField(required=False) + type = CharField(required=False) + stored_location = CharField(required=False) + support = ChoiceField(required=False, choices=BOOLEAN_WITH_BLANK_CHOICES) + + software_product = DynamicModelMultipleChoiceField( + queryset=SoftwareProduct.objects.all(), + required=False, + label="Software Product", + ) + version = DynamicModelMultipleChoiceField( + queryset=SoftwareProductVersion.objects.all(), + required=False, + ) + installation = DynamicModelMultipleChoiceField( + queryset=SoftwareProductInstallation.objects.all(), + required=False, + ) + class SoftwareLicenseBulkImportForm(NetBoxModelImportForm): class Meta: diff --git a/netbox_slm/forms/software_product.py b/netbox_slm/forms/software_product.py index 0420b2d..95e33c3 100644 --- a/netbox_slm/forms/software_product.py +++ b/netbox_slm/forms/software_product.py @@ -1,7 +1,9 @@ +from django.forms import CharField + from dcim.models import Manufacturer from netbox.forms import NetBoxModelForm, NetBoxModelFilterSetForm, NetBoxModelImportForm from netbox_slm.models import SoftwareProduct -from utilities.forms.fields import CommentField, DynamicModelChoiceField, TagFilterField +from utilities.forms.fields import CommentField, DynamicModelChoiceField, TagFilterField, DynamicModelMultipleChoiceField from utilities.forms.rendering import FieldSet @@ -28,9 +30,21 @@ class SoftwareProductForm(NetBoxModelForm): class SoftwareProductFilterForm(NetBoxModelFilterSetForm): model = SoftwareProduct - fieldsets = (FieldSet(None, ("q", "tag")),) + fieldsets = ( + FieldSet("q", "filter_id", "tag"), + FieldSet("name", "description", "manufacturer"), + ) + selector_fields = ("q", "filter_id", "name") + tag = TagFilterField(model) + name = CharField(required=False) + description = CharField(required=False) + manufacturer = DynamicModelMultipleChoiceField( + queryset=Manufacturer.objects.all(), + required=False, + ) + class SoftwareProductBulkImportForm(NetBoxModelImportForm): class Meta: diff --git a/netbox_slm/forms/software_product_installation.py b/netbox_slm/forms/software_product_installation.py index 9baf048..c9fdec8 100644 --- a/netbox_slm/forms/software_product_installation.py +++ b/netbox_slm/forms/software_product_installation.py @@ -4,7 +4,7 @@ from django.urls import reverse_lazy from dcim.models import Device from netbox.forms import NetBoxModelForm, NetBoxModelFilterSetForm, NetBoxModelImportForm from netbox_slm.models import SoftwareProductInstallation, SoftwareProduct, SoftwareProductVersion -from utilities.forms.fields import CommentField, DynamicModelChoiceField, TagFilterField +from utilities.forms.fields import CommentField, DynamicModelChoiceField, TagFilterField, DynamicModelMultipleChoiceField from utilities.forms.rendering import FieldSet from utilities.forms.widgets import APISelect from virtualization.models import VirtualMachine, Cluster @@ -16,11 +16,16 @@ class SoftwareProductInstallationForm(NetBoxModelForm): comments = CommentField() device = DynamicModelChoiceField(queryset=Device.objects.all(), required=False) - virtualmachine = DynamicModelChoiceField(queryset=VirtualMachine.objects.all(), required=False) + virtualmachine = DynamicModelChoiceField( + queryset=VirtualMachine.objects.all(), + required=False, + label="Virtual Machine", + ) cluster = DynamicModelChoiceField(queryset=Cluster.objects.all(), required=False) software_product = DynamicModelChoiceField( queryset=SoftwareProduct.objects.all(), required=True, + label="Software Product", widget=APISelect(attrs={"data-url": reverse_lazy("plugins-api:netbox_slm-api:softwareproduct-list")}), ) version = DynamicModelChoiceField( @@ -57,9 +62,37 @@ class SoftwareProductInstallationForm(NetBoxModelForm): class SoftwareProductInstallationFilterForm(NetBoxModelFilterSetForm): model = SoftwareProductInstallation - fieldsets = (FieldSet(None, ("q", "tag")),) + fieldsets = ( + FieldSet("q", "filter_id", "tag"), + FieldSet("device", "virtualmachine", "cluster", "software_product", "version"), + ) + selector_fields = ("filter_id", "q") + tag = TagFilterField(model) + device = DynamicModelMultipleChoiceField( + queryset=Device.objects.all(), + required=False, + ) + virtualmachine = DynamicModelMultipleChoiceField( + queryset=VirtualMachine.objects.all(), + required=False, + label="Virtual Machine", + ) + cluster = DynamicModelMultipleChoiceField( + queryset=Cluster.objects.all(), + required=False, + ) + software_product = DynamicModelMultipleChoiceField( + queryset=SoftwareProduct.objects.all(), + required=False, + label="Software Product", + ) + version = DynamicModelMultipleChoiceField( + queryset=SoftwareProductVersion.objects.all(), + required=False, + ) + class SoftwareProductInstallationBulkImportForm(NetBoxModelImportForm): class Meta: diff --git a/netbox_slm/forms/software_product_version.py b/netbox_slm/forms/software_product_version.py index fcbbcad..615c74c 100644 --- a/netbox_slm/forms/software_product_version.py +++ b/netbox_slm/forms/software_product_version.py @@ -1,9 +1,10 @@ -from django.forms import DateField +from django.forms import DateField, CharField, MultipleChoiceField from django.urls import reverse_lazy +from dcim.models import Manufacturer from netbox.forms import NetBoxModelForm, NetBoxModelFilterSetForm, NetBoxModelImportForm -from netbox_slm.models import SoftwareProduct, SoftwareProductVersion -from utilities.forms.fields import CommentField, DynamicModelChoiceField, TagFilterField +from netbox_slm.models import SoftwareProduct, SoftwareProductVersion, SoftwareReleaseTypes +from utilities.forms.fields import CommentField, DynamicModelChoiceField, TagFilterField, DynamicModelMultipleChoiceField from utilities.forms.rendering import FieldSet from utilities.forms.widgets import APISelect, DatePicker @@ -18,6 +19,8 @@ class SoftwareProductVersionForm(NetBoxModelForm): software_product = DynamicModelChoiceField( queryset=SoftwareProduct.objects.all(), + required=True, + label="Software Product", widget=APISelect(attrs={"data-url": reverse_lazy("plugins-api:netbox_slm-api:softwareproduct-list")}), ) @@ -40,9 +43,29 @@ class SoftwareProductVersionForm(NetBoxModelForm): class SoftwareProductVersionFilterForm(NetBoxModelFilterSetForm): model = SoftwareProductVersion - fieldsets = (FieldSet(None, ("q", "tag")),) + fieldsets = ( + FieldSet("q", "filter_id", "tag"), + FieldSet("name", "filename", "release_type", "manufacturer", "software_product"), + ) + selector_fields = ("q", "filter_id", "name") + tag = TagFilterField(model) + name = CharField(required=False) + filename = CharField(required=False) + + release_type = MultipleChoiceField(required=False, choices=SoftwareReleaseTypes.choices) + + manufacturer = DynamicModelMultipleChoiceField( + queryset=Manufacturer.objects.all(), + required=False, + ) + software_product = DynamicModelMultipleChoiceField( + queryset=SoftwareProduct.objects.all(), + required=False, + label="Software Product", + ) + class SoftwareProductVersionBulkImportForm(NetBoxModelImportForm): class Meta: diff --git a/netbox_slm/tables.py b/netbox_slm/tables.py index df81a6e..894da60 100644 --- a/netbox_slm/tables.py +++ b/netbox_slm/tables.py @@ -145,6 +145,7 @@ class SoftwareProductInstallationTable(NetBoxTable): return queryset_union.order_by(f"{'-' if is_descending else ''}render_type"), True def render_software_product(self, value, **kwargs): + # TODO - does not match form and filter views, display manufacturer separately? return f"{kwargs['record'].software_product.manufacturer.name} - {value}" @@ -190,6 +191,7 @@ class SoftwareLicenseTable(NetBoxTable): ) def render_software_product(self, value, **kwargs): + # TODO - does not match form and filter views, display manufacturer separately? return f"{kwargs['record'].software_product.manufacturer.name} - {value}" def render_installation(self, **kwargs): diff --git a/netbox_slm/templates/netbox_slm/softwarelicense.html b/netbox_slm/templates/netbox_slm/softwarelicense.html index 0170edb..ced1c4d 100644 --- a/netbox_slm/templates/netbox_slm/softwarelicense.html +++ b/netbox_slm/templates/netbox_slm/softwarelicense.html @@ -20,6 +20,18 @@ Description {{ object.description }} + + Software Product + {{ object.software_product|linkify }} + + + Version + {{ object.version|linkify }} + + + Installation + {{ object.installation|linkify }} + Type {{ object.type }} @@ -40,14 +52,6 @@ Expiration date {{ object.expiration_date }} - - Software Product - {{ object.software_product|linkify }} - - - Version - {{ object.version|linkify }} - Support {{ object.support }} @@ -56,10 +60,6 @@ License amount {{ object.license_amount }} - - Installation - {{ object.installation|linkify }} - {% include 'inc/panels/custom_fields.html' %} diff --git a/netbox_slm/templates/netbox_slm/softwareproductinstallation.html b/netbox_slm/templates/netbox_slm/softwareproductinstallation.html index ddc35b8..d608a72 100644 --- a/netbox_slm/templates/netbox_slm/softwareproductinstallation.html +++ b/netbox_slm/templates/netbox_slm/softwareproductinstallation.html @@ -19,7 +19,7 @@ {% elif object.virtualmachine %} - Virtualmachine + Virtual Machine {{ object.virtualmachine|linkify }} {% else %} diff --git a/netbox_slm/templates/netbox_slm/softwareproductversion.html b/netbox_slm/templates/netbox_slm/softwareproductversion.html index 3cc2ca1..70c8747 100644 --- a/netbox_slm/templates/netbox_slm/softwareproductversion.html +++ b/netbox_slm/templates/netbox_slm/softwareproductversion.html @@ -16,14 +16,14 @@ Name {{ object.name }} - - Software Product - {{ object.software_product|linkify }} - Manufacturer {{ object.software_product.manufacturer|linkify }} + + Software Product + {{ object.software_product|linkify }} + Release date {{ object.release_date }}