From 410b8f6617c02cdaa8985f58bc574b470f179e87 Mon Sep 17 00:00:00 2001 From: wkoot <3715211+wkoot@users.noreply.github.com> Date: Mon, 17 Feb 2025 23:01:51 +0100 Subject: [PATCH] Render SoftwareProduct manufacturer consistently (#59) Closes #58 Signed-off-by: wkoot <3715211+wkoot@users.noreply.github.com> --- CHANGELOG.md | 1 + netbox_slm/api/serializers.py | 9 +++-- netbox_slm/filtersets.py | 3 ++ netbox_slm/forms/software_license.py | 17 ++++++-- netbox_slm/forms/software_product.py | 5 ++- .../forms/software_product_installation.py | 15 ++++--- netbox_slm/forms/software_product_version.py | 10 +++-- ...0009_softwareproductversion_description.py | 18 +++++++++ netbox_slm/models.py | 5 ++- netbox_slm/tables.py | 40 +++++-------------- .../netbox_slm/softwareproductversion.html | 4 ++ netbox_slm/tests/test_models.py | 4 +- 12 files changed, 78 insertions(+), 53 deletions(-) create mode 100644 netbox_slm/migrations/0009_softwareproductversion_description.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 97d45d7..505f5aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ ### Changed * Support NetBox v4.2.3 (#51) +* Render objects consistently (#58) ### Fixed diff --git a/netbox_slm/api/serializers.py b/netbox_slm/api/serializers.py index e5e18b1..044e202 100644 --- a/netbox_slm/api/serializers.py +++ b/netbox_slm/api/serializers.py @@ -49,6 +49,7 @@ class SoftwareProductSerializer(NetBoxModelSerializer): "display", "url", "name", + "description", "manufacturer", "description", "tags", @@ -60,8 +61,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}" + return f"{obj}" class SoftwareProductInstallationSerializer(NetBoxModelSerializer): @@ -87,7 +87,7 @@ class SoftwareProductInstallationSerializer(NetBoxModelSerializer): "created", "last_updated", ) - brief_fields = ("id", "display", "url", "name") + brief_fields = ("id", "display", "url") def get_display(self, obj): return f"{obj}" @@ -104,6 +104,7 @@ class SoftwareProductVersionSerializer(NetBoxModelSerializer): "display", "url", "name", + "description", "release_date", "documentation_url", "end_of_support", @@ -118,7 +119,7 @@ class SoftwareProductVersionSerializer(NetBoxModelSerializer): "created", "last_updated", ) - brief_fields = ("id", "display", "url", "name") + brief_fields = ("id", "display", "url", "name", "description") def get_display(self, obj): return f"{obj}" diff --git a/netbox_slm/filtersets.py b/netbox_slm/filtersets.py index 971ff2f..80319b4 100644 --- a/netbox_slm/filtersets.py +++ b/netbox_slm/filtersets.py @@ -42,6 +42,7 @@ class SoftwareProductVersionFilterSet(NetBoxModelFilterSet): """Filter capabilities for SoftwareProductVersion instances.""" name = CharFilter(lookup_expr="icontains") + description = CharFilter(lookup_expr="icontains") filename = CharFilter(lookup_expr="icontains") release_type = MultipleChoiceFilter(choices=SoftwareReleaseTypes.choices) @@ -66,6 +67,7 @@ class SoftwareProductVersionFilterSet(NetBoxModelFilterSet): return queryset qs_filter = ( Q(name__icontains=value) + | Q(description__icontains=value) | Q(software_product__name__icontains=value) | Q(software_product__manufacturer__name__icontains=value) | Q(comments__icontains=value) @@ -130,6 +132,7 @@ class SoftwareLicenseFilterSet(NetBoxModelFilterSet): return queryset qs_filter = ( Q(name__icontains=value) + | Q(description__icontains=value) | Q(software_product__name__icontains=value) | Q(version__name__icontains=value) | Q(installation__device__name__icontains=value) diff --git a/netbox_slm/forms/software_license.py b/netbox_slm/forms/software_license.py index a22ae18..88923a6 100644 --- a/netbox_slm/forms/software_license.py +++ b/netbox_slm/forms/software_license.py @@ -74,7 +74,14 @@ class SoftwareLicenseFilterForm(NetBoxModelFilterSetForm): fieldsets = ( FieldSet("q", "filter_id", "tag"), FieldSet( - "name", "description", "type", "stored_location", "support", "software_product", "version", "installation" + "name", + "description", + "type", + "stored_location", + "support", + "software_product_id", + "version_id", + "installation_id", ), ) selector_fields = ("q", "filter_id", "name") @@ -87,18 +94,20 @@ class SoftwareLicenseFilterForm(NetBoxModelFilterSetForm): stored_location = CharField(required=False) support = ChoiceField(required=False, choices=BOOLEAN_WITH_BLANK_CHOICES) - software_product = DynamicModelMultipleChoiceField( + software_product_id = DynamicModelMultipleChoiceField( queryset=SoftwareProduct.objects.all(), required=False, label="Software Product", ) - version = DynamicModelMultipleChoiceField( + version_id = DynamicModelMultipleChoiceField( queryset=SoftwareProductVersion.objects.all(), required=False, + label="Version", ) - installation = DynamicModelMultipleChoiceField( + installation_id = DynamicModelMultipleChoiceField( queryset=SoftwareProductInstallation.objects.all(), required=False, + label="Installation", ) diff --git a/netbox_slm/forms/software_product.py b/netbox_slm/forms/software_product.py index 958cae4..59f1e02 100644 --- a/netbox_slm/forms/software_product.py +++ b/netbox_slm/forms/software_product.py @@ -37,7 +37,7 @@ class SoftwareProductFilterForm(NetBoxModelFilterSetForm): model = SoftwareProduct fieldsets = ( FieldSet("q", "filter_id", "tag"), - FieldSet("name", "description", "manufacturer"), + FieldSet("name", "description", "manufacturer_id"), ) selector_fields = ("q", "filter_id", "name") @@ -45,9 +45,10 @@ class SoftwareProductFilterForm(NetBoxModelFilterSetForm): name = CharField(required=False) description = CharField(required=False) - manufacturer = DynamicModelMultipleChoiceField( + manufacturer_id = DynamicModelMultipleChoiceField( queryset=Manufacturer.objects.all(), required=False, + label="Manufacturer", ) diff --git a/netbox_slm/forms/software_product_installation.py b/netbox_slm/forms/software_product_installation.py index 61665ad..e4c357c 100644 --- a/netbox_slm/forms/software_product_installation.py +++ b/netbox_slm/forms/software_product_installation.py @@ -69,33 +69,36 @@ class SoftwareProductInstallationFilterForm(NetBoxModelFilterSetForm): model = SoftwareProductInstallation fieldsets = ( FieldSet("q", "filter_id", "tag"), - FieldSet("device", "virtualmachine", "cluster", "software_product", "version"), + FieldSet("device_id", "virtualmachine_id", "cluster_id", "software_product_id", "version_id"), ) selector_fields = ("filter_id", "q") tag = TagFilterField(model) - device = DynamicModelMultipleChoiceField( + device_id = DynamicModelMultipleChoiceField( queryset=Device.objects.all(), required=False, + label="Device", ) - virtualmachine = DynamicModelMultipleChoiceField( + virtualmachine_id = DynamicModelMultipleChoiceField( queryset=VirtualMachine.objects.all(), required=False, label="Virtual Machine", ) - cluster = DynamicModelMultipleChoiceField( + cluster_id = DynamicModelMultipleChoiceField( queryset=Cluster.objects.all(), required=False, + label="Cluster", ) - software_product = DynamicModelMultipleChoiceField( + software_product_id = DynamicModelMultipleChoiceField( queryset=SoftwareProduct.objects.all(), required=False, label="Software Product", ) - version = DynamicModelMultipleChoiceField( + version_id = DynamicModelMultipleChoiceField( queryset=SoftwareProductVersion.objects.all(), required=False, + label="Version", ) diff --git a/netbox_slm/forms/software_product_version.py b/netbox_slm/forms/software_product_version.py index 4444d1d..84ea0af 100644 --- a/netbox_slm/forms/software_product_version.py +++ b/netbox_slm/forms/software_product_version.py @@ -33,6 +33,7 @@ class SoftwareProductVersionForm(NetBoxModelForm): model = SoftwareProductVersion fields = ( "name", + "description", "software_product", "release_date", "documentation_url", @@ -50,22 +51,24 @@ class SoftwareProductVersionFilterForm(NetBoxModelFilterSetForm): model = SoftwareProductVersion fieldsets = ( FieldSet("q", "filter_id", "tag"), - FieldSet("name", "filename", "release_type", "manufacturer", "software_product"), + FieldSet("name", "description", "filename", "release_type", "manufacturer_id", "software_product_id"), ) selector_fields = ("q", "filter_id", "name") tag = TagFilterField(model) name = CharField(required=False) + description = CharField(required=False) filename = CharField(required=False) release_type = MultipleChoiceField(required=False, choices=SoftwareReleaseTypes.choices) - manufacturer = DynamicModelMultipleChoiceField( + manufacturer_id = DynamicModelMultipleChoiceField( queryset=Manufacturer.objects.all(), required=False, + label="Manufacturer", ) - software_product = DynamicModelMultipleChoiceField( + software_product_id = DynamicModelMultipleChoiceField( queryset=SoftwareProduct.objects.all(), required=False, label="Software Product", @@ -77,6 +80,7 @@ class SoftwareProductVersionBulkImportForm(NetBoxModelImportForm): model = SoftwareProductVersion fields = ( "name", + "description", "software_product", "release_date", "end_of_support", diff --git a/netbox_slm/migrations/0009_softwareproductversion_description.py b/netbox_slm/migrations/0009_softwareproductversion_description.py new file mode 100644 index 0000000..9c4d9f4 --- /dev/null +++ b/netbox_slm/migrations/0009_softwareproductversion_description.py @@ -0,0 +1,18 @@ +# Generated by Django 5.1.5 on 2025-02-17 21:35 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("netbox_slm", "0008_software_release_types_and_more"), + ] + + operations = [ + migrations.AddField( + model_name="softwareproductversion", + name="description", + field=models.CharField(blank=True, max_length=255, null=True), + ), + ] diff --git a/netbox_slm/models.py b/netbox_slm/models.py index 1a6c39e..7051012 100644 --- a/netbox_slm/models.py +++ b/netbox_slm/models.py @@ -33,7 +33,7 @@ class SoftwareProduct(NetBoxModel): def get_installation_count(self): count = SoftwareProductInstallation.objects.filter(software_product_id=self.pk).count() - query_string = urlencode(dict(software_product=self.pk)) + query_string = urlencode(dict(software_product_id=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" @@ -50,6 +50,7 @@ class SoftwareProductVersion(NetBoxModel): name = models.CharField(max_length=64) comments = models.TextField(blank=True) + description = models.CharField(max_length=255, null=True, blank=True) release_date = models.DateField(null=True, blank=True) documentation_url = LaxURLField(max_length=1024, null=True, blank=True) end_of_support = models.DateField(null=True, blank=True) @@ -78,7 +79,7 @@ class SoftwareProductVersion(NetBoxModel): def get_installation_count(self): count = SoftwareProductInstallation.objects.filter(version_id=self.pk).count() - query_string = urlencode(dict(version=self.pk)) + query_string = urlencode(dict(version_id=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" diff --git a/netbox_slm/tables.py b/netbox_slm/tables.py index 894da60..12931f8 100644 --- a/netbox_slm/tables.py +++ b/netbox_slm/tables.py @@ -20,8 +20,8 @@ class SoftwareProductTable(NetBoxTable): fields = ( "pk", "name", - "manufacturer", "description", + "manufacturer", "installations", "tags", ) @@ -29,16 +29,9 @@ class SoftwareProductTable(NetBoxTable): "pk", "name", "manufacturer", - "description", "installations", "tags", ) - sequence = ( - "manufacturer", - "name", - "description", - "installations", - ) def order_installations(self, queryset, is_descending): queryset = queryset.annotate(count=Count("softwareproductinstallation__id")).order_by( @@ -63,6 +56,7 @@ class SoftwareProductVersionTable(NetBoxTable): fields = ( "pk", "name", + "description", "software_product", "manufacturer", "release_date", @@ -74,18 +68,12 @@ class SoftwareProductVersionTable(NetBoxTable): default_columns = ( "pk", "name", - "software_product", "manufacturer", + "software_product", "release_date", "installations", "tags", ) - sequence = ( - "manufacturer", - "software_product", - "name", - "installations", - ) def order_installations(self, queryset, is_descending): queryset = queryset.annotate(count=Count("softwareproductinstallation__id")).order_by( @@ -98,13 +86,9 @@ class SoftwareProductInstallationTable(NetBoxTable): """Table for displaying SoftwareProductInstallation objects.""" pk = ToggleColumn() - name = tables.LinkColumn() - - device = tables.Column(accessor="device", linkify=True) - virtualmachine = tables.Column(accessor="virtualmachine", linkify=True) - cluster = tables.Column(accessor="cluster", linkify=True) platform = tables.Column(accessor="platform", linkify=True) type = tables.Column(accessor="render_type") + manufacturer = tables.Column(accessor="software_product__manufacturer", linkify=True) software_product = tables.Column(accessor="software_product", linkify=True) version = tables.Column(accessor="version", linkify=True) @@ -114,9 +98,9 @@ class SoftwareProductInstallationTable(NetBoxTable): model = SoftwareProductInstallation fields = ( "pk", - "name", "platform", "type", + "manufacturer", "software_product", "version", "tags", @@ -125,6 +109,7 @@ class SoftwareProductInstallationTable(NetBoxTable): "pk", "platform", "type", + "manufacturer", "software_product", "version", "tags", @@ -144,10 +129,6 @@ class SoftwareProductInstallationTable(NetBoxTable): queryset_union = device_annotate.union(vm_annotate).union(cluster_annotate) 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}" - class SoftwareLicenseTable(NetBoxTable): """Table for displaying SoftwareLicense objects.""" @@ -158,6 +139,7 @@ class SoftwareLicenseTable(NetBoxTable): type = tables.Column() stored_location = tables.Column(accessor="stored_location_txt", linkify=lambda record: record.stored_location_url) + manufacturer = tables.Column(accessor="software_product__manufacturer", linkify=True) software_product = tables.Column(accessor="software_product", linkify=True) version = tables.Column(accessor="version", linkify=True) installation = tables.Column(accessor="installation", linkify=True) @@ -174,6 +156,7 @@ class SoftwareLicenseTable(NetBoxTable): "stored_location", "start_date", "expiration_date", + "manufacturer", "software_product", "version", "installation", @@ -184,15 +167,12 @@ class SoftwareLicenseTable(NetBoxTable): default_columns = ( "pk", "name", - "expiration_date", + "manufacturer", "software_product", "installation", + "expiration_date", "tags", ) - 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): return f"{kwargs['record'].installation.platform}" diff --git a/netbox_slm/templates/netbox_slm/softwareproductversion.html b/netbox_slm/templates/netbox_slm/softwareproductversion.html index 70c8747..6e75476 100644 --- a/netbox_slm/templates/netbox_slm/softwareproductversion.html +++ b/netbox_slm/templates/netbox_slm/softwareproductversion.html @@ -16,6 +16,10 @@