From 306d853a16b1cce65f9195979edf2a0f15bcf967 Mon Sep 17 00:00:00 2001 From: wkoot <3715211+wkoot@users.noreply.github.com> Date: Thu, 13 Feb 2025 22:58:22 +0100 Subject: [PATCH] Fix unencoded search link of installations table cells (#53) Closes #48 Signed-off-by: wkoot <3715211+wkoot@users.noreply.github.com> --- .github/workflows/python.yml | 2 +- CHANGELOG.md | 1 + ci/docker-compose.ci.yml | 3 ++ netbox_slm/filtersets.py | 34 ++++++++----------- netbox_slm/forms/software_license.py | 12 +++++-- netbox_slm/forms/software_product.py | 7 +++- .../forms/software_product_installation.py | 7 +++- netbox_slm/forms/software_product_version.py | 7 +++- netbox_slm/models.py | 32 ++++++----------- netbox_slm/tests/test_models.py | 13 ++++--- pyproject.toml | 9 +++++ 11 files changed, 75 insertions(+), 52 deletions(-) diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index 0ff443d..5e2f422 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -32,4 +32,4 @@ jobs: run: ruff check netbox_slm - name: Run ruff format - run: ruff format netbox_slm + run: ruff format --check netbox_slm diff --git a/CHANGELOG.md b/CHANGELOG.md index 32281f4..97d45d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ ### Fixed * Model detail view listing of linked objects (#55) +* Search link of installations table cells (#48) ## [1.7.0](https://github.com/ICTU/netbox_slm/releases/tag/1.7.0) - 2024-07-09 diff --git a/ci/docker-compose.ci.yml b/ci/docker-compose.ci.yml index 0032d0c..ef73fbd 100644 --- a/ci/docker-compose.ci.yml +++ b/ci/docker-compose.ci.yml @@ -21,6 +21,9 @@ services: - netbox healthcheck: disable: true + volumes: + - ./config:/etc/netbox/config:ro + - ../netbox_slm:/opt/netbox/netbox/netbox_slm:ro netbox-housekeeping: image: netbox:slm depends_on: diff --git a/netbox_slm/filtersets.py b/netbox_slm/filtersets.py index 389e5fe..971ff2f 100644 --- a/netbox_slm/filtersets.py +++ b/netbox_slm/filtersets.py @@ -1,9 +1,15 @@ -from django_filters import CharFilter, ModelMultipleChoiceFilter, MultipleChoiceFilter from django.db.models import Q +from django_filters import CharFilter, ModelMultipleChoiceFilter, MultipleChoiceFilter from dcim.models import Device, Manufacturer from netbox.filtersets import NetBoxModelFilterSet -from netbox_slm.models import SoftwareProduct, SoftwareProductVersion, SoftwareProductInstallation, SoftwareLicense, SoftwareReleaseTypes +from netbox_slm.models import ( + SoftwareProduct, + SoftwareProductVersion, + SoftwareProductInstallation, + SoftwareLicense, + SoftwareReleaseTypes, +) from virtualization.models import VirtualMachine, Cluster @@ -13,9 +19,7 @@ class SoftwareProductFilterSet(NetBoxModelFilterSet): name = CharFilter(lookup_expr="icontains") description = CharFilter(lookup_expr="icontains") - manufacturer = ModelMultipleChoiceFilter( - queryset=Manufacturer.objects.all() - ) + manufacturer = ModelMultipleChoiceFilter(queryset=Manufacturer.objects.all()) class Meta: model = SoftwareProduct @@ -72,23 +76,17 @@ class SoftwareProductVersionFilterSet(NetBoxModelFilterSet): class SoftwareProductInstallationFilterSet(NetBoxModelFilterSet): """Filter capabilities for SoftwareProductInstallation instances.""" - device = ModelMultipleChoiceFilter( - queryset=Device.objects.all() - ) + device = ModelMultipleChoiceFilter(queryset=Device.objects.all()) virtualmachine = ModelMultipleChoiceFilter( queryset=VirtualMachine.objects.all(), label="Virtual Machine", ) - cluster = ModelMultipleChoiceFilter( - queryset=Cluster.objects.all() - ) + cluster = ModelMultipleChoiceFilter(queryset=Cluster.objects.all()) software_product = ModelMultipleChoiceFilter( queryset=SoftwareProduct.objects.all(), label="Software Product", ) - version = ModelMultipleChoiceFilter( - queryset=SoftwareProductVersion.objects.all() - ) + version = ModelMultipleChoiceFilter(queryset=SoftwareProductVersion.objects.all()) class Meta: model = SoftwareProductInstallation @@ -119,12 +117,8 @@ class SoftwareLicenseFilterSet(NetBoxModelFilterSet): queryset=SoftwareProduct.objects.all(), label="Software Product", ) - version = ModelMultipleChoiceFilter( - queryset=SoftwareProductVersion.objects.all() - ) - installation = ModelMultipleChoiceFilter( - queryset=SoftwareProductInstallation.objects.all() - ) + version = ModelMultipleChoiceFilter(queryset=SoftwareProductVersion.objects.all()) + installation = ModelMultipleChoiceFilter(queryset=SoftwareProductInstallation.objects.all()) class Meta: model = SoftwareLicense diff --git a/netbox_slm/forms/software_license.py b/netbox_slm/forms/software_license.py index 3287154..a22ae18 100644 --- a/netbox_slm/forms/software_license.py +++ b/netbox_slm/forms/software_license.py @@ -4,7 +4,13 @@ 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.constants import BOOLEAN_WITH_BLANK_CHOICES -from utilities.forms.fields import CommentField, DynamicModelChoiceField, TagFilterField, LaxURLField, DynamicModelMultipleChoiceField +from utilities.forms.fields import ( + CommentField, + DynamicModelChoiceField, + TagFilterField, + LaxURLField, + DynamicModelMultipleChoiceField, +) from utilities.forms.rendering import FieldSet from utilities.forms.widgets import APISelect, DatePicker @@ -67,7 +73,9 @@ class SoftwareLicenseFilterForm(NetBoxModelFilterSetForm): model = SoftwareLicense fieldsets = ( FieldSet("q", "filter_id", "tag"), - FieldSet("name", "description", "type", "stored_location", "support", "software_product", "version", "installation"), + FieldSet( + "name", "description", "type", "stored_location", "support", "software_product", "version", "installation" + ), ) selector_fields = ("q", "filter_id", "name") diff --git a/netbox_slm/forms/software_product.py b/netbox_slm/forms/software_product.py index 95e33c3..958cae4 100644 --- a/netbox_slm/forms/software_product.py +++ b/netbox_slm/forms/software_product.py @@ -3,7 +3,12 @@ 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, DynamicModelMultipleChoiceField +from utilities.forms.fields import ( + CommentField, + DynamicModelChoiceField, + TagFilterField, + DynamicModelMultipleChoiceField, +) from utilities.forms.rendering import FieldSet diff --git a/netbox_slm/forms/software_product_installation.py b/netbox_slm/forms/software_product_installation.py index c9fdec8..61665ad 100644 --- a/netbox_slm/forms/software_product_installation.py +++ b/netbox_slm/forms/software_product_installation.py @@ -4,7 +4,12 @@ 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, DynamicModelMultipleChoiceField +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 diff --git a/netbox_slm/forms/software_product_version.py b/netbox_slm/forms/software_product_version.py index 615c74c..4444d1d 100644 --- a/netbox_slm/forms/software_product_version.py +++ b/netbox_slm/forms/software_product_version.py @@ -4,7 +4,12 @@ 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, SoftwareReleaseTypes -from utilities.forms.fields import CommentField, DynamicModelChoiceField, TagFilterField, DynamicModelMultipleChoiceField +from utilities.forms.fields import ( + CommentField, + DynamicModelChoiceField, + TagFilterField, + DynamicModelMultipleChoiceField, +) from utilities.forms.rendering import FieldSet from utilities.forms.widgets import APISelect, DatePicker diff --git a/netbox_slm/models.py b/netbox_slm/models.py index ac0d36c..1a6c39e 100644 --- a/netbox_slm/models.py +++ b/netbox_slm/models.py @@ -1,6 +1,6 @@ from django.db import models -from django.urls import reverse, reverse_lazy -from django.utils import safestring +from django.urls import reverse +from django.utils.html import format_html, urlencode from netbox.models import NetBoxModel from utilities.querysets import RestrictedQuerySet @@ -33,16 +33,10 @@ class SoftwareProduct(NetBoxModel): def get_installation_count(self): count = SoftwareProductInstallation.objects.filter(software_product_id=self.pk).count() - return ( - safestring.mark_safe( - '{count}'.format( - url=reverse_lazy("plugins:netbox_slm:softwareproductinstallation_list") + f"?q={self.name}", - count=count, - ) - ) - if count - else "0" - ) + query_string = urlencode(dict(software_product=self.pk)) + search_target = reverse("plugins:netbox_slm:softwareproductinstallation_list") + # Can be composed directly with reverse(query=) in Django 5.2, see https://code.djangoproject.com/ticket/25582 + return format_html(f"{count}") if count else "0" class SoftwareReleaseTypes(models.TextChoices): @@ -84,16 +78,10 @@ class SoftwareProductVersion(NetBoxModel): def get_installation_count(self): count = SoftwareProductInstallation.objects.filter(version_id=self.pk).count() - return ( - safestring.mark_safe( - '{count}'.format( - url=reverse_lazy("plugins:netbox_slm:softwareproductinstallation_list") + f"?q={self.name}", - count=count, - ) - ) - if count - else "0" - ) + query_string = urlencode(dict(version=self.pk)) + search_target = reverse("plugins:netbox_slm:softwareproductinstallation_list") + # Can be composed directly with reverse(query=) in Django 5.2, see https://code.djangoproject.com/ticket/25582 + return format_html(f"{count}") if count else "0" class SoftwareProductInstallation(NetBoxModel): diff --git a/netbox_slm/tests/test_models.py b/netbox_slm/tests/test_models.py index c033ac0..5ec1273 100644 --- a/netbox_slm/tests/test_models.py +++ b/netbox_slm/tests/test_models.py @@ -1,8 +1,8 @@ from django.test import TestCase from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Site -from virtualization.models import Cluster, ClusterType, VirtualMachine from netbox_slm.models import SoftwareProduct, SoftwareProductVersion, SoftwareProductInstallation, SoftwareLicense +from virtualization.models import Cluster, ClusterType, VirtualMachine class ModelTestCase(TestCase): @@ -49,9 +49,14 @@ class ModelTestCase(TestCase): self.assertEqual("/plugins/slm/licenses/1/", self.software_license.get_absolute_url()) def test_get_installation_count(self): - installation_ss = '1' - self.assertEqual(installation_ss.format(self.p_name), self.software_product.get_installation_count()) - self.assertEqual(installation_ss.format(self.v_name), self.software_product_version.get_installation_count()) + self.assertEqual( + f"1", + self.software_product.get_installation_count(), + ) + self.assertEqual( + f"1", + self.software_product_version.get_installation_count(), + ) def test_product_installation_methods(self): self.assertEqual("virtualmachine", self.software_product_installation.render_type()) diff --git a/pyproject.toml b/pyproject.toml index 0f3eab0..f60e9e0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -50,6 +50,15 @@ line-length = 120 target-version = "py312" src = ["netbox_slm"] +[tool.ruff.lint] +select = [ + "E", # pycodestyle + "F", # Pyflakes + "UP", # pyupgrade + "B", # flake8-bugbear + "SIM", # flake8-simplify +] + [tool.ruff.lint.per-file-ignores] "__init__.py" = [ "D104", # https://beta.ruff.rs/docs/rules/#pydocstyle-d - don't require doc strings in __init__.py files