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