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 %}
+
+
+
+
+
+
+
+ | 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 @@
| Name |
- {{ object }} |
+ {{ object.name }} |
+
+
+ | Description |
+ {{ object.description }} |
+
+
+ | Manufacturer |
+ {{ object.manufacturer }} |
| Versions |
@@ -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 @@
- {% if object.device %}
+{% if object.device %}
| Device |
{{ object.device }} |
- {% else %}
+{% else %}
| Virtualmachine |
{{ object.virtualmachine }} |
- {% endif %}
+{% endif %}
| Software Product |
{{ object.software_product }} |
@@ -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