diff --git a/.gitignore b/.gitignore index a5534c1..0d8ca00 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ ci/docker-compose.override.yml ci/reports out/production +*.iml diff --git a/ci/configuration/configuration.py b/ci/configuration/configuration.py index ecaae25..6e3128c 100644 --- a/ci/configuration/configuration.py +++ b/ci/configuration/configuration.py @@ -129,6 +129,7 @@ CSRF_TRUSTED_ORIGINS = list(filter(None, environ.get('CSRF_TRUSTED_ORIGINS', '') # sensitive information about your installation. Only enable debugging while performing testing. Never enable debugging # on a production system. DEBUG = environ.get('DEBUG', 'False').lower() == 'true' +DEVELOPER = environ.get('DEVELOPER', 'False').lower() == 'true' # Email settings EMAIL = { diff --git a/netbox_slm/__init__.py b/netbox_slm/__init__.py index 82f0b04..ea0a4d6 100644 --- a/netbox_slm/__init__.py +++ b/netbox_slm/__init__.py @@ -5,7 +5,7 @@ class SLMConfig(PluginConfig): name = 'netbox_slm' verbose_name = 'Software Lifecycle Management' description = 'Software Lifecycle Management Netbox Plugin.' - version = '1.3' + version = '1.4' author = 'ICTU' author_email = 'open-source-projects@ictu.nl' base_url = 'slm' diff --git a/netbox_slm/admin.py b/netbox_slm/admin.py index e69de29..4574834 100644 --- a/netbox_slm/admin.py +++ b/netbox_slm/admin.py @@ -0,0 +1,7 @@ +from django.apps import apps +from django.conf import settings +from django.contrib import admin + +if settings.DEBUG: + for model in apps.get_app_config("netbox_slm").get_models(): + admin.site.register(model) diff --git a/netbox_slm/api/serializers.py b/netbox_slm/api/serializers.py index c0fe19e..c1dbaa9 100644 --- a/netbox_slm/api/serializers.py +++ b/netbox_slm/api/serializers.py @@ -1,7 +1,7 @@ from rest_framework import serializers from netbox.api.serializers import NetBoxModelSerializer -from netbox_slm.models import SoftwareProduct, SoftwareProductVersion, SoftwareProductInstallation +from netbox_slm.models import SoftwareProduct, SoftwareProductVersion, SoftwareProductInstallation, SoftwareLicense class SoftwareProductSerializer(NetBoxModelSerializer): @@ -13,7 +13,8 @@ class SoftwareProductSerializer(NetBoxModelSerializer): class Meta: model = SoftwareProduct fields = [ - 'id', 'display', 'url', 'name', 'tags', 'custom_field_data', 'created', 'last_updated', + 'id', 'display', 'url', 'name', + 'tags', 'custom_field_data', 'created', 'last_updated', ] def get_display(self, obj): @@ -29,7 +30,8 @@ class SoftwareProductVersionSerializer(NetBoxModelSerializer): class Meta: model = SoftwareProductVersion fields = [ - 'id', 'display', 'url', 'name', 'software_product', 'tags', 'custom_field_data', 'created', 'last_updated', + 'id', 'display', 'url', 'name', 'software_product', + 'tags', 'custom_field_data', 'created', 'last_updated', ] def get_display(self, obj): @@ -45,8 +47,26 @@ class SoftwareProductInstallationSerializer(NetBoxModelSerializer): class Meta: model = SoftwareProductInstallation fields = [ - 'id', 'display', 'url', 'device', 'virtualmachine', 'software_product', 'version', 'tags', - 'custom_field_data', 'created', 'last_updated', + 'id', 'display', 'url', 'device', 'virtualmachine', 'software_product', 'version', + 'tags', 'custom_field_data', 'created', 'last_updated', + ] + + def get_display(self, obj): + return f"{obj}" + + +class SoftwareLicenseSerializer(NetBoxModelSerializer): + display = serializers.SerializerMethodField() + url = serializers.HyperlinkedIdentityField( + view_name="plugins-api:netbox_slm-api:softwarelicense-detail" + ) + + class Meta: + model = SoftwareLicense + fields = [ + 'id', 'display', 'url', 'name', 'description', 'type', 'stored_location', + 'start_date', 'expiration_date', 'software_product', 'version', 'installation', + 'tags', 'custom_field_data', 'created', 'last_updated', ] def get_display(self, obj): diff --git a/netbox_slm/api/urls.py b/netbox_slm/api/urls.py index aaa1d43..75cc757 100644 --- a/netbox_slm/api/urls.py +++ b/netbox_slm/api/urls.py @@ -4,6 +4,7 @@ from netbox_slm.api.views import ( SoftwareProductViewSet, SoftwareProductVersionViewSet, SoftwareProductInstallationViewSet, + SoftwareLicenseViewSet, ) router = NetBoxRouter() @@ -12,4 +13,5 @@ router.APIRootView = NetboxSLMRootView router.register("softwareproducts", SoftwareProductViewSet) router.register("softwareproductversions", SoftwareProductVersionViewSet) router.register("softwareproductinstallations", SoftwareProductInstallationViewSet) +router.register("softwarelicenses", SoftwareLicenseViewSet) urlpatterns = router.urls diff --git a/netbox_slm/api/views.py b/netbox_slm/api/views.py index 88b13b6..521cfc9 100644 --- a/netbox_slm/api/views.py +++ b/netbox_slm/api/views.py @@ -2,12 +2,18 @@ from rest_framework.routers import APIRootView from netbox.api.viewsets import NetBoxModelViewSet from netbox_slm.api.serializers import ( - SoftwareProductSerializer, SoftwareProductVersionSerializer, SoftwareProductInstallationSerializer, + SoftwareProductSerializer, + SoftwareProductVersionSerializer, + SoftwareProductInstallationSerializer, + SoftwareLicenseSerializer, ) from netbox_slm.filtersets import ( - SoftwareProductFilterSet, SoftwareProductVersionFilterSet, SoftwareProductInstallationFilterSet, + SoftwareProductFilterSet, + SoftwareProductVersionFilterSet, + SoftwareProductInstallationFilterSet, + SoftwareLicenseFilterSet, ) -from netbox_slm.models import SoftwareProduct, SoftwareProductVersion, SoftwareProductInstallation +from netbox_slm.models import SoftwareProduct, SoftwareProductVersion, SoftwareProductInstallation, SoftwareLicense class NetboxSLMRootView(APIRootView): @@ -35,3 +41,9 @@ class SoftwareProductInstallationViewSet(NetBoxModelViewSet): queryset = SoftwareProductInstallation.objects.all() serializer_class = SoftwareProductInstallationSerializer filterset_class = SoftwareProductInstallationFilterSet + + +class SoftwareLicenseViewSet(NetBoxModelViewSet): + queryset = SoftwareLicense.objects.all() + serializer_class = SoftwareLicenseSerializer + filterset_class = SoftwareLicenseFilterSet diff --git a/netbox_slm/filtersets.py b/netbox_slm/filtersets.py index 99778a8..a1745af 100644 --- a/netbox_slm/filtersets.py +++ b/netbox_slm/filtersets.py @@ -1,7 +1,7 @@ from django.db.models import Q from netbox.filtersets import NetBoxModelFilterSet -from netbox_slm.models import SoftwareProduct, SoftwareProductVersion, SoftwareProductInstallation +from netbox_slm.models import SoftwareProduct, SoftwareProductVersion, SoftwareProductInstallation, SoftwareLicense class SoftwareProductFilterSet(NetBoxModelFilterSet): @@ -43,7 +43,9 @@ class SoftwareProductInstallationFilterSet(NetBoxModelFilterSet): class Meta: model = SoftwareProductInstallation - fields = tuple() + fields = ( + "software_product", + ) def search(self, queryset, name, value): """Perform the filtered search.""" @@ -53,3 +55,22 @@ class SoftwareProductInstallationFilterSet(NetBoxModelFilterSet): Q(software_product__manufacturer__name__icontains=value) | \ Q(version__name__icontains=value) return queryset.filter(qs_filter) + + +class SoftwareLicenseFilterSet(NetBoxModelFilterSet): + """Filter capabilities for SoftwareLicense instances.""" + + class Meta: + model = SoftwareLicense + fields = tuple() + + def search(self, queryset, name, value): + """Perform the filtered search.""" + if not value.strip(): + return queryset + qs_filter = Q(name__icontains=value) | \ + Q(software_product__name__icontains=value) | \ + Q(version__name__icontains=value) | \ + Q(installation__device__name__icontains=value) | \ + Q(installation__virtualmachine__name__icontains=value) + return queryset.filter(qs_filter) diff --git a/netbox_slm/forms/__init__.py b/netbox_slm/forms/__init__.py new file mode 100644 index 0000000..b579da4 --- /dev/null +++ b/netbox_slm/forms/__init__.py @@ -0,0 +1,4 @@ +from .software_license import * +from .software_product import * +from .software_product_installation import * +from .software_product_version import * diff --git a/netbox_slm/forms/software_license.py b/netbox_slm/forms/software_license.py new file mode 100644 index 0000000..5b83b45 --- /dev/null +++ b/netbox_slm/forms/software_license.py @@ -0,0 +1,76 @@ +from django.urls import reverse_lazy + +from netbox.forms import NetBoxModelForm, NetBoxModelImportForm, NetBoxModelBulkEditForm, NetBoxModelFilterSetForm +from netbox_slm.models import SoftwareProduct, SoftwareProductVersion, SoftwareProductInstallation, SoftwareLicense +from utilities.forms import DynamicModelChoiceField, APISelect, TagFilterField + + +class SoftwareLicenseForm(NetBoxModelForm): + """Form for creating a new SoftwareLicense object.""" + + software_product = DynamicModelChoiceField( + queryset=SoftwareProduct.objects.all(), + required=True, + widget=APISelect( + attrs={"data-url": reverse_lazy("plugins-api:netbox_slm-api:softwareproduct-list")} + ), + ) + version = DynamicModelChoiceField( + queryset=SoftwareProductVersion.objects.all(), + required=False, + widget=APISelect( + attrs={"data-url": reverse_lazy("plugins-api:netbox_slm-api:softwareproductversion-list")} + ), + query_params={ + 'software_product': '$software_product', + } + ) + installation = DynamicModelChoiceField( + queryset=SoftwareProductInstallation.objects.all(), + required=False, + widget=APISelect( + attrs={"data-url": reverse_lazy("plugins-api:netbox_slm-api:softwareproductinstallation-list")} + ), + query_params={ + 'software_product': '$software_product', + } + ) + + class Meta: + model = SoftwareLicense + fields = ("name", "description", "type", "stored_location", "start_date", "expiration_date", + "software_product", "version", "installation", "tags") + + +class SoftwareLicenseFilterForm(NetBoxModelFilterSetForm): + model = SoftwareLicense + fieldsets = ( + (None, ('q', 'tag',)), + ) + + tag = TagFilterField(model) + + +class SoftwareLicenseImportForm(NetBoxModelImportForm): + class Meta: + model = SoftwareLicense + fields = ("name", "description", "type", "stored_location", "start_date", "expiration_date",) + + +class SoftwareLicenseBulkEditForm(NetBoxModelBulkEditForm): + software_product = DynamicModelChoiceField( + queryset=SoftwareProduct.objects.all(), + required=False + ) + version = DynamicModelChoiceField( + queryset=SoftwareProductVersion.objects.all(), + required=False + ) + installation = DynamicModelChoiceField( + queryset=SoftwareProductInstallation.objects.all(), + required=False + ) + model = SoftwareLicense + fieldsets = ( + (None, ('software_product', 'version', 'installation',)), + ) diff --git a/netbox_slm/forms/software_product.py b/netbox_slm/forms/software_product.py new file mode 100644 index 0000000..c5dc56d --- /dev/null +++ b/netbox_slm/forms/software_product.py @@ -0,0 +1,45 @@ +from django import forms + +from dcim.models import Manufacturer +from netbox.forms import NetBoxModelForm, NetBoxModelImportForm, NetBoxModelBulkEditForm, NetBoxModelFilterSetForm +from netbox_slm.models import SoftwareProduct +from utilities.forms import DynamicModelChoiceField, TagFilterField + + +class SoftwareProductForm(NetBoxModelForm): + """Form for creating a new SoftwareProduct object.""" + + manufacturer = DynamicModelChoiceField( + queryset=Manufacturer.objects.all(), + required=True, + ) + + class Meta: + model = SoftwareProduct + fields = ("name", "manufacturer", "description", "tags") + + +class SoftwareProductFilterForm(NetBoxModelFilterSetForm): + model = SoftwareProduct + fieldsets = ( + (None, ('q', 'tag')), + ) + + tag = TagFilterField(model) + + +class SoftwareProductImportForm(NetBoxModelImportForm): + class Meta: + model = SoftwareProduct + fields = ("name", "manufacturer",) + + +class SoftwareProductBulkEditForm(NetBoxModelBulkEditForm): + pk = forms.ModelMultipleChoiceField( + queryset=SoftwareProduct.objects.all(), + widget=forms.MultipleHiddenInput(), + ) + + class Meta: + model = SoftwareProduct + nullable_fields = [] diff --git a/netbox_slm/forms.py b/netbox_slm/forms/software_product_installation.py similarity index 57% rename from netbox_slm/forms.py rename to netbox_slm/forms/software_product_installation.py index 6c97d7f..47de64b 100644 --- a/netbox_slm/forms.py +++ b/netbox_slm/forms/software_product_installation.py @@ -2,94 +2,13 @@ from django import forms from django.urls import reverse_lazy from django.utils.translation import gettext as _ -from dcim.models import Manufacturer, Device +from dcim.models import Device from netbox.forms import NetBoxModelForm, NetBoxModelImportForm, NetBoxModelBulkEditForm, NetBoxModelFilterSetForm -from netbox_slm.models import SoftwareProduct, SoftwareProductVersion, SoftwareProductInstallation +from netbox_slm.models import SoftwareProductInstallation, SoftwareProduct, SoftwareProductVersion from utilities.forms import DynamicModelChoiceField, APISelect, TagFilterField from virtualization.models import VirtualMachine -class SoftwareProductForm(NetBoxModelForm): - """Form for creating a new SoftwareProduct object.""" - - manufacturer = DynamicModelChoiceField( - queryset=Manufacturer.objects.all(), - required=True, - ) - - class Meta: - model = SoftwareProduct - fields = ("name", "manufacturer", "description", "tags") - - -class SoftwareProductFilterForm(NetBoxModelFilterSetForm): - model = SoftwareProduct - fieldsets = ( - (None, ('q', 'tag')), - ) - - tag = TagFilterField(model) - - -class SoftwareProductImportForm(NetBoxModelImportForm): - class Meta: - model = SoftwareProduct - fields = ("name", "manufacturer",) - - -class SoftwareProductBulkEditForm(NetBoxModelBulkEditForm): - pk = forms.ModelMultipleChoiceField( - queryset=SoftwareProduct.objects.all(), - widget=forms.MultipleHiddenInput(), - ) - - class Meta: - model = SoftwareProduct - nullable_fields = [] - - -class SoftwareProductVersionForm(NetBoxModelForm): - """Form for creating a new SoftwareProductVersion object.""" - name = forms.CharField(label=_("Version")) - - software_product = DynamicModelChoiceField( - queryset=SoftwareProduct.objects.all(), - widget=APISelect( - attrs={"data-url": reverse_lazy("plugins-api:netbox_slm-api:softwareproduct-list")} - ), - ) - - class Meta: - model = SoftwareProductVersion - fields = ("name", "software_product", "tags") - - -class SoftwareProductVersionFilterForm(NetBoxModelFilterSetForm): - model = SoftwareProductVersion - fieldsets = ( - (None, ('q', 'tag')), - ) - - tag = TagFilterField(model) - - -class SoftwareProductVersionImportForm(NetBoxModelImportForm): - class Meta: - model = SoftwareProductVersion - fields = ("name",) - - -class SoftwareProductVersionBulkEditForm(NetBoxModelBulkEditForm): - pk = forms.ModelMultipleChoiceField( - queryset=SoftwareProduct.objects.all(), - widget=forms.MultipleHiddenInput(), - ) - - class Meta: - model = SoftwareProductVersion - nullable_fields = [] - - class SoftwareProductInstallationForm(NetBoxModelForm): """Form for creating a new SoftwareProductInstallation object.""" diff --git a/netbox_slm/forms/software_product_version.py b/netbox_slm/forms/software_product_version.py new file mode 100644 index 0000000..6eb61d5 --- /dev/null +++ b/netbox_slm/forms/software_product_version.py @@ -0,0 +1,49 @@ +from django import forms +from django.urls import reverse_lazy +from django.utils.translation import gettext as _ + +from netbox.forms import NetBoxModelForm, NetBoxModelImportForm, NetBoxModelBulkEditForm, NetBoxModelFilterSetForm +from netbox_slm.models import SoftwareProduct, SoftwareProductVersion +from utilities.forms import DynamicModelChoiceField, APISelect, TagFilterField + + +class SoftwareProductVersionForm(NetBoxModelForm): + """Form for creating a new SoftwareProductVersion object.""" + name = forms.CharField(label=_("Version")) + + software_product = DynamicModelChoiceField( + queryset=SoftwareProduct.objects.all(), + widget=APISelect( + attrs={"data-url": reverse_lazy("plugins-api:netbox_slm-api:softwareproduct-list")} + ), + ) + + class Meta: + model = SoftwareProductVersion + fields = ("name", "software_product", "tags") + + +class SoftwareProductVersionFilterForm(NetBoxModelFilterSetForm): + model = SoftwareProductVersion + fieldsets = ( + (None, ('q', 'tag')), + ) + + tag = TagFilterField(model) + + +class SoftwareProductVersionImportForm(NetBoxModelImportForm): + class Meta: + model = SoftwareProductVersion + fields = ("name",) + + +class SoftwareProductVersionBulkEditForm(NetBoxModelBulkEditForm): + pk = forms.ModelMultipleChoiceField( + queryset=SoftwareProduct.objects.all(), + widget=forms.MultipleHiddenInput(), + ) + + class Meta: + model = SoftwareProductVersion + nullable_fields = [] diff --git a/netbox_slm/migrations/0005_add_software_license.py b/netbox_slm/migrations/0005_add_software_license.py new file mode 100644 index 0000000..f042461 --- /dev/null +++ b/netbox_slm/migrations/0005_add_software_license.py @@ -0,0 +1,54 @@ +# Generated by Django 4.1.8 on 2023-06-27 08:10 + +from django.db import migrations, models +import django.db.models.deletion +import taggit.managers +import utilities.json + + +class Migration(migrations.Migration): + + dependencies = [ + ('extras', '0084_staging'), + ('netbox_slm', '0004_auto_20220415_0705'), + ] + + operations = [ + migrations.AlterField( + model_name='softwareproduct', + name='custom_field_data', + field=models.JSONField(blank=True, default=dict, encoder=utilities.json.CustomFieldJSONEncoder), + ), + migrations.AlterField( + model_name='softwareproductinstallation', + name='custom_field_data', + field=models.JSONField(blank=True, default=dict, encoder=utilities.json.CustomFieldJSONEncoder), + ), + migrations.AlterField( + model_name='softwareproductversion', + name='custom_field_data', + field=models.JSONField(blank=True, default=dict, encoder=utilities.json.CustomFieldJSONEncoder), + ), + migrations.CreateModel( + name='SoftwareLicense', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)), + ('created', models.DateTimeField(auto_now_add=True, null=True)), + ('last_updated', models.DateTimeField(auto_now=True, null=True)), + ('custom_field_data', models.JSONField(blank=True, default=dict, encoder=utilities.json.CustomFieldJSONEncoder)), + ('name', models.CharField(max_length=128)), + ('description', models.CharField(blank=True, max_length=255, null=True)), + ('type', models.CharField(max_length=128)), + ('stored_location', models.CharField(blank=True, max_length=255, null=True)), + ('start_date', models.DateField(blank=True, null=True)), + ('expiration_date', models.DateField(blank=True, null=True)), + ('installation', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='netbox_slm.softwareproductinstallation')), + ('software_product', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='netbox_slm.softwareproduct')), + ('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')), + ('version', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='netbox_slm.softwareproductversion')), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/netbox_slm/models.py b/netbox_slm/models.py index d2beac9..c264dae 100644 --- a/netbox_slm/models.py +++ b/netbox_slm/models.py @@ -33,11 +33,12 @@ class SoftwareProduct(NetBoxModel): class SoftwareProductVersion(NetBoxModel): + name = models.CharField(max_length=64) + software_product = models.ForeignKey( to='netbox_slm.SoftwareProduct', on_delete=models.PROTECT, ) - name = models.CharField(max_length=64) objects = RestrictedQuerySet.as_manager() @@ -80,13 +81,49 @@ class SoftwareProductInstallation(NetBoxModel): objects = RestrictedQuerySet.as_manager() def __str__(self): - return f"{self.pk}" + return f"{self.pk} ({self.platform})" def get_absolute_url(self): return reverse("plugins:netbox_slm:softwareproductinstallation", kwargs={"pk": self.pk}) - def get_platform(self): + @property + def platform(self): return self.device or self.virtualmachine def render_type(self): return 'device' if self.device else 'virtualmachine' + + +class SoftwareLicense(NetBoxModel): + name = models.CharField(max_length=128) + description = models.CharField(max_length=255, null=True, blank=True) + type = models.CharField(max_length=128) + + stored_location = models.CharField(max_length=255, null=True, blank=True) + start_date = models.DateField(null=True, blank=True) + expiration_date = models.DateField(null=True, blank=True) + + software_product = models.ForeignKey( + to='netbox_slm.SoftwareProduct', + on_delete=models.PROTECT, + ) + version = models.ForeignKey( + to='netbox_slm.SoftwareProductVersion', + on_delete=models.PROTECT, + null=True, + blank=True + ) + installation = models.ForeignKey( + to='netbox_slm.SoftwareProductInstallation', + on_delete=models.PROTECT, + null=True, + blank=True + ) + + objects = RestrictedQuerySet.as_manager() + + def __str__(self): + return self.name + + def get_absolute_url(self): + return reverse("plugins:netbox_slm:softwarelicense", kwargs={"pk": self.pk}) diff --git a/netbox_slm/navigation.py b/netbox_slm/navigation.py index d34579b..a0e77d9 100644 --- a/netbox_slm/navigation.py +++ b/netbox_slm/navigation.py @@ -62,4 +62,24 @@ menu_items = ( ), ) ), + PluginMenuItem( + link='plugins:netbox_slm:softwarelicense_list', + link_text='Licenses', + buttons=( + PluginMenuButton( + "plugins:netbox_slm:softwarelicense_add", + "Add", + "mdi mdi-plus-thick", + ButtonColorChoices.GREEN, + permissions=["netbox_slm.add_softwarelicense"], + ), + PluginMenuButton( + "plugins:netbox_slm:softwarelicense_import", + "Import", + "mdi mdi-upload", + ButtonColorChoices.CYAN, + permissions=["netbox_slm.add_softwarelicense"], + ), + ) + ), ) diff --git a/netbox_slm/tables.py b/netbox_slm/tables.py index bec0967..7d0731b 100644 --- a/netbox_slm/tables.py +++ b/netbox_slm/tables.py @@ -3,7 +3,7 @@ from django.db.models import Count from django_tables2.utils import Accessor from netbox.tables import NetBoxTable, ToggleColumn, columns -from netbox_slm.models import SoftwareProduct, SoftwareProductVersion, SoftwareProductInstallation +from netbox_slm.models import SoftwareProduct, SoftwareProductVersion, SoftwareProductInstallation, SoftwareLicense class SoftwareProductTable(NetBoxTable): @@ -118,7 +118,7 @@ class SoftwareProductInstallationTable(NetBoxTable): linkify=True ) platform = tables.Column( - accessor='get_platform', + accessor=Accessor('platform'), linkify=True ) type = tables.Column(accessor='render_type') @@ -165,3 +165,58 @@ class SoftwareProductInstallationTable(NetBoxTable): def render_software_product(self, value, **kwargs): return f"{kwargs['record'].software_product.manufacturer.name} - {value}" + + +class SoftwareLicenseTable(NetBoxTable): + """Table for displaying SoftwareLicense objects.""" + + pk = ToggleColumn() + name = tables.LinkColumn() + + type = tables.Column(accessor='render_type') + software_product = tables.Column( + accessor=Accessor('software_product'), + linkify=True + ) + version = tables.Column( + accessor=Accessor('version'), + linkify=True + ) + installation = tables.Column( + accessor=Accessor('installation'), + linkify=True + ) + + tags = columns.TagColumn( + url_name="plugins:netbox_slm:softwarelicense_list", + ) + + class Meta(NetBoxTable.Meta): + model = SoftwareLicense + fields = ( + "pk", + "name", + "description", + "type", + "stored_location", + "start_date", + "expiration_date", + "software_product", + "version", + "installation", + "tags", + ) + default_columns = ( + "pk", + "name", + "expiration_date", + "software_product", + "installation", + "tags", + ) + + def render_software_product(self, value, **kwargs): + 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/softwarelicense.html b/netbox_slm/templates/netbox_slm/softwarelicense.html new file mode 100644 index 0000000..04a39c5 --- /dev/null +++ b/netbox_slm/templates/netbox_slm/softwarelicense.html @@ -0,0 +1,65 @@ +{% extends 'generic/object.html' %} +{% load buttons %} +{% load static %} +{% load helpers %} +{% load plugins %} + +{% block content %} +
+
+
+
+ Software License +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Name{{ object.name }}
Description{{ object.description }}
Type{{ object.type }}
Stored location{{ object.stored_location }}
Start date{{ object.start_date }}
Expiration date{{ object.expiration_date }}
Software Product{{ object.software_product }}
Version{{ object.version }}
Installation{{ object.installation }}
+
+
+ {% include 'inc/panels/custom_fields.html' %} + {% include 'inc/panels/tags.html' %} + {% plugin_left_page object %} +
+
+
+
+ {% plugin_full_width_page object %} +
+
+{% endblock %} diff --git a/netbox_slm/templates/netbox_slm/softwareproduct.html b/netbox_slm/templates/netbox_slm/softwareproduct.html index b179995..55df9ec 100644 --- a/netbox_slm/templates/netbox_slm/softwareproduct.html +++ b/netbox_slm/templates/netbox_slm/softwareproduct.html @@ -15,7 +15,15 @@ - + + + + + + + + + @@ -34,7 +42,6 @@ {% include 'inc/panels/custom_fields.html' %} {% include 'inc/panels/tags.html' %} -{# {% include 'inc/panels/comments.html' %}#} {% plugin_left_page object %} diff --git a/netbox_slm/templates/netbox_slm/softwareproductinstallation.html b/netbox_slm/templates/netbox_slm/softwareproductinstallation.html index 6611cd2..9965e33 100644 --- a/netbox_slm/templates/netbox_slm/softwareproductinstallation.html +++ b/netbox_slm/templates/netbox_slm/softwareproductinstallation.html @@ -13,17 +13,17 @@
Name{{ object }}{{ object.name }}
Description{{ object.description }}
Manufacturer{{ object.manufacturer }}
Versions
- {% if object.device %} +{% if object.device %} - {% else %} +{% else %} - {% endif %} +{% endif %} @@ -37,7 +37,6 @@ {% include 'inc/panels/custom_fields.html' %} {% include 'inc/panels/tags.html' %} -{# {% include 'inc/panels/comments.html' %}#} {% plugin_left_page object %} diff --git a/netbox_slm/templates/netbox_slm/softwareproductversion.html b/netbox_slm/templates/netbox_slm/softwareproductversion.html index ec5cf9f..85d0f84 100644 --- a/netbox_slm/templates/netbox_slm/softwareproductversion.html +++ b/netbox_slm/templates/netbox_slm/softwareproductversion.html @@ -28,7 +28,6 @@ {% include 'inc/panels/custom_fields.html' %} {% include 'inc/panels/tags.html' %} -{# {% include 'inc/panels/comments.html' %}#} {% plugin_left_page object %} diff --git a/netbox_slm/tests/test_models.py b/netbox_slm/tests/test_models.py index 25062a1..41fdf75 100644 --- a/netbox_slm/tests/test_models.py +++ b/netbox_slm/tests/test_models.py @@ -1,12 +1,13 @@ from django.test import TestCase -from netbox_slm.models import SoftwareProduct, SoftwareProductVersion, SoftwareProductInstallation +from netbox_slm.models import SoftwareProduct, SoftwareProductVersion, SoftwareProductInstallation, SoftwareLicense class ModelTestCase(TestCase): def setUp(self): self.p_name = "test product" self.v_name = "test version" + self.l_name = "test license" self.software_product = SoftwareProduct.objects.create(name=self.p_name) self.software_product_version = SoftwareProductVersion.objects.create( @@ -15,16 +16,22 @@ class ModelTestCase(TestCase): self.software_product_installation = SoftwareProductInstallation.objects.create( software_product=self.software_product, version=self.software_product_version ) + self.software_license = SoftwareLicense.objects.create( + name=self.l_name, software_product=self.software_product, + version=self.software_product_version, installation=self.software_product_installation + ) def test_model_name(self): self.assertEqual(self.p_name, str(self.software_product)) self.assertEqual(self.v_name, str(self.software_product_version)) - self.assertTrue(str(self.software_product_installation).isnumeric()) # should be PK + self.assertTrue(str(self.software_product_installation)[0].isdigit()) # starts with PK + self.assertEqual(self.l_name, str(self.software_license)) def test_absolute_url(self): self.assertEqual("/plugins/slm/software-products/1/", self.software_product.get_absolute_url()) self.assertEqual("/plugins/slm/versions/1/", self.software_product_version.get_absolute_url()) self.assertEqual("/plugins/slm/installations/1/", self.software_product_installation.get_absolute_url()) + self.assertEqual("/plugins/slm/licenses/1/", self.software_license.get_absolute_url()) def test_get_installation_count(self): installation_ss = '1' @@ -33,4 +40,4 @@ class ModelTestCase(TestCase): def test_product_installation_methods(self): self.assertEqual("virtualmachine", self.software_product_installation.render_type()) - self.assertIsNone(self.software_product_installation.get_platform()) + self.assertIsNone(self.software_product_installation.platform) diff --git a/netbox_slm/urls.py b/netbox_slm/urls.py index d951389..02cb9ae 100644 --- a/netbox_slm/urls.py +++ b/netbox_slm/urls.py @@ -2,20 +2,26 @@ from django.urls import path from netbox.views.generic import ObjectChangeLogView from netbox_slm import views -from netbox_slm.models import SoftwareProduct, SoftwareProductVersion, SoftwareProductInstallation +from netbox_slm.models import SoftwareProduct, SoftwareProductVersion, SoftwareProductInstallation, SoftwareLicense urlpatterns = [ - # Software Products - path("software-products/", views.SoftwareProductListView.as_view(), name='softwareproduct_list'), - path("software-products/add/", views.SoftwareProductEditView.as_view(), name='softwareproduct_add'), - path("software-products/import/", views.SoftwareProductBulkImportView.as_view(), name="softwareproduct_import"), - path("software-products/edit/", views.SoftwareProductBulkEditView.as_view(), name="softwareproduct_bulk_edit"), + # SoftwareProduct + path("software-products/", views.SoftwareProductListView.as_view(), + name='softwareproduct_list'), + path("software-products/add/", views.SoftwareProductEditView.as_view(), + name='softwareproduct_add'), + path("software-products/import/", views.SoftwareProductBulkImportView.as_view(), + name="softwareproduct_import"), + path("software-products/edit/", views.SoftwareProductBulkEditView.as_view(), + name="softwareproduct_bulk_edit"), path("software-products/delete/", views.SoftwareProductBulkDeleteView.as_view(), name="softwareproduct_bulk_delete"), - path("software-products//", views.SoftwareProductView.as_view(), name="softwareproduct"), + path("software-products//", views.SoftwareProductView.as_view(), + name="softwareproduct"), path("software-products//delete/", views.SoftwareProductDeleteView.as_view(), name="softwareproduct_delete"), - path("software-products//edit/", views.SoftwareProductEditView.as_view(), name="softwareproduct_edit"), + path("software-products//edit/", views.SoftwareProductEditView.as_view(), + name="softwareproduct_edit"), path( "software-products//changelog/", ObjectChangeLogView.as_view(), @@ -23,18 +29,23 @@ urlpatterns = [ kwargs={"model": SoftwareProduct}, ), - # Software Product Versions - path("versions/", views.SoftwareProductVersionListView.as_view(), name='softwareproductversion_list'), - path("versions/add/", views.SoftwareProductVersionEditView.as_view(), name='softwareproductversion_add'), + # SoftwareProductVersion + path("versions/", views.SoftwareProductVersionListView.as_view(), + name='softwareproductversion_list'), + path("versions/add/", views.SoftwareProductVersionEditView.as_view(), + name='softwareproductversion_add'), path("versions/import/", views.SoftwareProductVersionBulkImportView.as_view(), name="softwareproductversion_import"), - path("versions/edit/", views.SoftwareProductVersionBulkEditView.as_view(), name="softwareproductversion_bulk_edit"), + path("versions/edit/", views.SoftwareProductVersionBulkEditView.as_view(), + name="softwareproductversion_bulk_edit"), path("versions/delete/", views.SoftwareProductVersionBulkDeleteView.as_view(), name="softwareproductversion_bulk_delete"), - path("versions//", views.SoftwareProductVersionView.as_view(), name="softwareproductversion"), + path("versions//", views.SoftwareProductVersionView.as_view(), + name="softwareproductversion"), path("versions//delete/", views.SoftwareProductVersionDeleteView.as_view(), name="softwareproductversion_delete"), - path("versions//edit/", views.SoftwareProductVersionEditView.as_view(), name="softwareproductversion_edit"), + path("versions//edit/", views.SoftwareProductVersionEditView.as_view(), + name="softwareproductversion_edit"), path( "versions//changelog/", ObjectChangeLogView.as_view(), @@ -42,7 +53,7 @@ urlpatterns = [ kwargs={"model": SoftwareProductVersion}, ), - # Software Product Versions + # SoftwareProductInstallation path("installations/", views.SoftwareProductInstallationListView.as_view(), name='softwareproductinstallation_list'), path("installations/add/", views.SoftwareProductInstallationEditView.as_view(), @@ -65,4 +76,28 @@ urlpatterns = [ name="softwareproductinstallation_changelog", kwargs={"model": SoftwareProductInstallation}, ), + + # SoftwareLicense + path("licenses/", views.SoftwareLicenseListView.as_view(), + name='softwarelicense_list'), + path("licenses/add/", views.SoftwareLicenseEditView.as_view(), + name='softwarelicense_add'), + path("licenses/import/", views.SoftwareLicenseBulkImportView.as_view(), + name="softwarelicense_import"), + path("licenses/edit/", views.SoftwareLicenseBulkEditView.as_view(), + name="softwarelicense_bulk_edit"), + path("licenses/delete/", views.SoftwareLicenseBulkDeleteView.as_view(), + name="softwarelicense_bulk_delete"), + path("licenses//", views.SoftwareLicenseView.as_view(), + name="softwarelicense"), + path("licenses//delete/", views.SoftwareLicenseDeleteView.as_view(), + name="softwarelicense_delete"), + path("licenses//edit/", views.SoftwareLicenseEditView.as_view(), + name="softwarelicense_edit"), + path( + "licenses//changelog/", + ObjectChangeLogView.as_view(), + name="softwarelicense_changelog", + kwargs={"model": SoftwareLicense}, + ), ] diff --git a/netbox_slm/views.py b/netbox_slm/views.py deleted file mode 100644 index 56a14de..0000000 --- a/netbox_slm/views.py +++ /dev/null @@ -1,150 +0,0 @@ -from netbox.views import generic -from netbox_slm import filtersets, forms, tables -from netbox_slm.models import SoftwareProduct, SoftwareProductVersion, SoftwareProductInstallation - - -class SoftwareProductListView(generic.ObjectListView): - """View for listing all existing SoftwareProducts.""" - - queryset = SoftwareProduct.objects.all() - filterset = filtersets.SoftwareProductFilterSet - filterset_form = forms.SoftwareProductFilterForm - table = tables.SoftwareProductTable - - -class SoftwareProductView(generic.ObjectView): - """Display SoftwareProduct details""" - - queryset = SoftwareProduct.objects.all() - - def get_extra_context(self, request, instance): - versions = instance.softwareproductversion_set.all() - return {"versions": versions} - - -class SoftwareProductEditView(generic.ObjectEditView): - """View for editing and creating a SoftwareProduct instance.""" - - queryset = SoftwareProduct.objects.all() - form = forms.SoftwareProductForm - - -class SoftwareProductDeleteView(generic.ObjectDeleteView): - """View for deleting a SoftwareProduct instance""" - - queryset = SoftwareProduct.objects.all() - - -class SoftwareProductBulkImportView(generic.BulkImportView): - queryset = SoftwareProduct.objects.all() - model_form = forms.SoftwareProductImportForm - table = tables.SoftwareProductTable - - -class SoftwareProductBulkEditView(generic.BulkEditView): - queryset = SoftwareProduct.objects.all() - filterset = filtersets.SoftwareProductFilterSet - filterset_form = forms.SoftwareProductFilterForm - table = tables.SoftwareProductTable - form = forms.SoftwareProductBulkEditForm - - -class SoftwareProductBulkDeleteView(generic.BulkDeleteView): - queryset = SoftwareProduct.objects.all() - table = tables.SoftwareProductTable - - -class SoftwareProductVersionListView(generic.ObjectListView): - """View for listing all existing SoftwareProductVersions.""" - - queryset = SoftwareProductVersion.objects.all() - filterset = filtersets.SoftwareProductVersionFilterSet - filterset_form = forms.SoftwareProductVersionFilterForm - table = tables.SoftwareProductVersionTable - - -class SoftwareProductVersionView(generic.ObjectView): - """Display SoftwareProductVersion details""" - - queryset = SoftwareProductVersion.objects.all() - - def get_extra_context(self, request, instance): - installation_count = instance.get_installation_count() - return {"installations": installation_count} - - -class SoftwareProductVersionEditView(generic.ObjectEditView): - """View for editing and creating a SoftwareProductVersion instance.""" - - queryset = SoftwareProductVersion.objects.all() - form = forms.SoftwareProductVersionForm - - -class SoftwareProductVersionDeleteView(generic.ObjectDeleteView): - """View for deleting a SoftwareProductVersion instance""" - - queryset = SoftwareProductVersion.objects.all() - - -class SoftwareProductVersionBulkImportView(generic.BulkImportView): - queryset = SoftwareProductVersion.objects.all() - model_form = forms.SoftwareProductVersionImportForm - table = tables.SoftwareProductVersionTable - - -class SoftwareProductVersionBulkEditView(generic.BulkEditView): - queryset = SoftwareProductVersion.objects.all() - filterset = filtersets.SoftwareProductVersionFilterSet - table = tables.SoftwareProductVersionTable - form = forms.SoftwareProductVersionBulkEditForm - - -class SoftwareProductVersionBulkDeleteView(generic.BulkDeleteView): - queryset = SoftwareProductVersion.objects.all() - table = tables.SoftwareProductVersionTable - - -class SoftwareProductInstallationListView(generic.ObjectListView): - """View for listing all existing SoftwareProductInstallations.""" - - queryset = SoftwareProductInstallation.objects.all() - filterset = filtersets.SoftwareProductInstallationFilterSet - filterset_form = forms.SoftwareProductInstallationFilterForm - table = tables.SoftwareProductInstallationTable - - -class SoftwareProductInstallationView(generic.ObjectView): - """Display SoftwareProductInstallation details""" - - queryset = SoftwareProductInstallation.objects.all() - - -class SoftwareProductInstallationEditView(generic.ObjectEditView): - """View for editing and creating a SoftwareProductInstallation instance.""" - - queryset = SoftwareProductInstallation.objects.all() - form = forms.SoftwareProductInstallationForm - - -class SoftwareProductInstallationDeleteView(generic.ObjectDeleteView): - """View for deleting a SoftwareProductInstallation instance""" - - queryset = SoftwareProductInstallation.objects.all() - - -class SoftwareProductInstallationBulkImportView(generic.BulkImportView): - queryset = SoftwareProductInstallation.objects.all() - model_form = forms.SoftwareProductInstallationImportForm - table = tables.SoftwareProductInstallationTable - - -class SoftwareProductInstallationBulkEditView(generic.BulkEditView): - queryset = SoftwareProductInstallation.objects.all() - filterset = filtersets.SoftwareProductInstallationFilterSet - table = tables.SoftwareProductInstallationTable - form = forms.SoftwareProductInstallationBulkEditForm - - -class SoftwareProductInstallationBulkDeleteView(generic.BulkDeleteView): - queryset = SoftwareProductInstallation.objects.all() - table = tables.SoftwareProductInstallationTable diff --git a/netbox_slm/views/__init__.py b/netbox_slm/views/__init__.py new file mode 100644 index 0000000..b579da4 --- /dev/null +++ b/netbox_slm/views/__init__.py @@ -0,0 +1,4 @@ +from .software_license import * +from .software_product import * +from .software_product_installation import * +from .software_product_version import * diff --git a/netbox_slm/views/software_license.py b/netbox_slm/views/software_license.py new file mode 100644 index 0000000..b74a4ac --- /dev/null +++ b/netbox_slm/views/software_license.py @@ -0,0 +1,49 @@ +from netbox.views import generic +from netbox_slm import filtersets, forms, tables +from netbox_slm.models import SoftwareLicense + + +class SoftwareLicenseListView(generic.ObjectListView): + """View for listing all existing SoftwareLicenses.""" + + queryset = SoftwareLicense.objects.all() + filterset = filtersets.SoftwareLicenseFilterSet + filterset_form = forms.SoftwareLicenseFilterForm + table = tables.SoftwareLicenseTable + + +class SoftwareLicenseView(generic.ObjectView): + """Display SoftwareLicense details""" + + queryset = SoftwareLicense.objects.all() + + +class SoftwareLicenseEditView(generic.ObjectEditView): + """View for editing and creating a SoftwareLicense instance.""" + + queryset = SoftwareLicense.objects.all() + form = forms.SoftwareLicenseForm + + +class SoftwareLicenseDeleteView(generic.ObjectDeleteView): + """View for deleting a SoftwareLicense instance""" + + queryset = SoftwareLicense.objects.all() + + +class SoftwareLicenseBulkImportView(generic.BulkImportView): + queryset = SoftwareLicense.objects.all() + model_form = forms.SoftwareLicenseImportForm + table = tables.SoftwareLicenseTable + + +class SoftwareLicenseBulkEditView(generic.BulkEditView): + queryset = SoftwareLicense.objects.all() + filterset = filtersets.SoftwareLicenseFilterSet + table = tables.SoftwareLicenseTable + form = forms.SoftwareLicenseBulkEditForm + + +class SoftwareLicenseBulkDeleteView(generic.BulkDeleteView): + queryset = SoftwareLicense.objects.all() + table = tables.SoftwareLicenseTable diff --git a/netbox_slm/views/software_product.py b/netbox_slm/views/software_product.py new file mode 100644 index 0000000..6b4954d --- /dev/null +++ b/netbox_slm/views/software_product.py @@ -0,0 +1,54 @@ +from netbox.views import generic +from netbox_slm import filtersets, forms, tables +from netbox_slm.models import SoftwareProduct + + +class SoftwareProductListView(generic.ObjectListView): + """View for listing all existing SoftwareProducts.""" + + queryset = SoftwareProduct.objects.all() + filterset = filtersets.SoftwareProductFilterSet + filterset_form = forms.SoftwareProductFilterForm + table = tables.SoftwareProductTable + + +class SoftwareProductView(generic.ObjectView): + """Display SoftwareProduct details""" + + queryset = SoftwareProduct.objects.all() + + def get_extra_context(self, request, instance): + versions = instance.softwareproductversion_set.all() + return {"versions": versions} + + +class SoftwareProductEditView(generic.ObjectEditView): + """View for editing and creating a SoftwareProduct instance.""" + + queryset = SoftwareProduct.objects.all() + form = forms.SoftwareProductForm + + +class SoftwareProductDeleteView(generic.ObjectDeleteView): + """View for deleting a SoftwareProduct instance""" + + queryset = SoftwareProduct.objects.all() + + +class SoftwareProductBulkImportView(generic.BulkImportView): + queryset = SoftwareProduct.objects.all() + model_form = forms.SoftwareProductImportForm + table = tables.SoftwareProductTable + + +class SoftwareProductBulkEditView(generic.BulkEditView): + queryset = SoftwareProduct.objects.all() + filterset = filtersets.SoftwareProductFilterSet + filterset_form = forms.SoftwareProductFilterForm + table = tables.SoftwareProductTable + form = forms.SoftwareProductBulkEditForm + + +class SoftwareProductBulkDeleteView(generic.BulkDeleteView): + queryset = SoftwareProduct.objects.all() + table = tables.SoftwareProductTable diff --git a/netbox_slm/views/software_product_installation.py b/netbox_slm/views/software_product_installation.py new file mode 100644 index 0000000..673e77e --- /dev/null +++ b/netbox_slm/views/software_product_installation.py @@ -0,0 +1,49 @@ +from netbox.views import generic +from netbox_slm import filtersets, forms, tables +from netbox_slm.models import SoftwareProductInstallation + + +class SoftwareProductInstallationListView(generic.ObjectListView): + """View for listing all existing SoftwareProductInstallations.""" + + queryset = SoftwareProductInstallation.objects.all() + filterset = filtersets.SoftwareProductInstallationFilterSet + filterset_form = forms.SoftwareProductInstallationFilterForm + table = tables.SoftwareProductInstallationTable + + +class SoftwareProductInstallationView(generic.ObjectView): + """Display SoftwareProductInstallation details""" + + queryset = SoftwareProductInstallation.objects.all() + + +class SoftwareProductInstallationEditView(generic.ObjectEditView): + """View for editing and creating a SoftwareProductInstallation instance.""" + + queryset = SoftwareProductInstallation.objects.all() + form = forms.SoftwareProductInstallationForm + + +class SoftwareProductInstallationDeleteView(generic.ObjectDeleteView): + """View for deleting a SoftwareProductInstallation instance""" + + queryset = SoftwareProductInstallation.objects.all() + + +class SoftwareProductInstallationBulkImportView(generic.BulkImportView): + queryset = SoftwareProductInstallation.objects.all() + model_form = forms.SoftwareProductInstallationImportForm + table = tables.SoftwareProductInstallationTable + + +class SoftwareProductInstallationBulkEditView(generic.BulkEditView): + queryset = SoftwareProductInstallation.objects.all() + filterset = filtersets.SoftwareProductInstallationFilterSet + table = tables.SoftwareProductInstallationTable + form = forms.SoftwareProductInstallationBulkEditForm + + +class SoftwareProductInstallationBulkDeleteView(generic.BulkDeleteView): + queryset = SoftwareProductInstallation.objects.all() + table = tables.SoftwareProductInstallationTable diff --git a/netbox_slm/views/software_product_version.py b/netbox_slm/views/software_product_version.py new file mode 100644 index 0000000..4f02bad --- /dev/null +++ b/netbox_slm/views/software_product_version.py @@ -0,0 +1,53 @@ +from netbox.views import generic +from netbox_slm import filtersets, forms, tables +from netbox_slm.models import SoftwareProductVersion + + +class SoftwareProductVersionListView(generic.ObjectListView): + """View for listing all existing SoftwareProductVersions.""" + + queryset = SoftwareProductVersion.objects.all() + filterset = filtersets.SoftwareProductVersionFilterSet + filterset_form = forms.SoftwareProductVersionFilterForm + table = tables.SoftwareProductVersionTable + + +class SoftwareProductVersionView(generic.ObjectView): + """Display SoftwareProductVersion details""" + + queryset = SoftwareProductVersion.objects.all() + + def get_extra_context(self, request, instance): + installation_count = instance.get_installation_count() + return {"installations": installation_count} + + +class SoftwareProductVersionEditView(generic.ObjectEditView): + """View for editing and creating a SoftwareProductVersion instance.""" + + queryset = SoftwareProductVersion.objects.all() + form = forms.SoftwareProductVersionForm + + +class SoftwareProductVersionDeleteView(generic.ObjectDeleteView): + """View for deleting a SoftwareProductVersion instance""" + + queryset = SoftwareProductVersion.objects.all() + + +class SoftwareProductVersionBulkImportView(generic.BulkImportView): + queryset = SoftwareProductVersion.objects.all() + model_form = forms.SoftwareProductVersionImportForm + table = tables.SoftwareProductVersionTable + + +class SoftwareProductVersionBulkEditView(generic.BulkEditView): + queryset = SoftwareProductVersion.objects.all() + filterset = filtersets.SoftwareProductVersionFilterSet + table = tables.SoftwareProductVersionTable + form = forms.SoftwareProductVersionBulkEditForm + + +class SoftwareProductVersionBulkDeleteView(generic.BulkDeleteView): + queryset = SoftwareProductVersion.objects.all() + table = tables.SoftwareProductVersionTable diff --git a/setup.cfg b/setup.cfg index 33ccb12..43b5b59 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = netbox-slm -version = 1.3 +version = 1.4 description = Software Lifecycle Management Netbox Plugin. author = ICTU author_email = open-source-projects@ictu.nl diff --git a/sonar-project.properties b/sonar-project.properties index fe9e86d..ed333fd 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -2,11 +2,11 @@ sonar.organization=ictu sonar.projectKey=ICTU_netbox_slm sonar.projectName=netbox-slm -sonar.projectVersion=1.3 +sonar.projectVersion=1.4 # Path is relative to the sonar-project.properties file sonar.sources=netbox_slm -sonar.python.version=3.9 +sonar.python.version=3.9, 3.10, 3.11 # Unit tests sonar.python.xunit.reportPath=ci/reports/junit.xml
Device {{ object.device }}
Virtualmachine {{ object.virtualmachine }}
Software Product {{ object.software_product }}