Add SoftwareLicense (#20)

This commit is contained in:
wkoot
2023-06-30 15:11:28 +02:00
committed by GitHub
parent 5b96496b46
commit 6c512e0e9b
31 changed files with 771 additions and 277 deletions
+1
View File
@@ -9,3 +9,4 @@
ci/docker-compose.override.yml
ci/reports
out/production
*.iml
+1
View File
@@ -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 = {
+1 -1
View File
@@ -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'
+7
View File
@@ -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)
+25 -5
View File
@@ -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):
+2
View File
@@ -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
+15 -3
View File
@@ -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
+23 -2
View File
@@ -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)
+4
View File
@@ -0,0 +1,4 @@
from .software_license import *
from .software_product import *
from .software_product_installation import *
from .software_product_version import *
+76
View File
@@ -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',)),
)
+45
View File
@@ -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 = []
@@ -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."""
@@ -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 = []
@@ -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,
},
),
]
+40 -3
View File
@@ -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})
+20
View File
@@ -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"],
),
)
),
)
+57 -2
View File
@@ -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}"
@@ -0,0 +1,65 @@
{% extends 'generic/object.html' %}
{% load buttons %}
{% load static %}
{% load helpers %}
{% load plugins %}
{% block content %}
<div class="row my-3">
<div class="col col-md-6">
<div class="card">
<h5 class="card-header">
Software License
</h5>
<div class="card-body">
<table class="table table-hover attr-table">
<tr>
<th scope="row">Name</th>
<td>{{ object.name }}</td>
</tr>
<tr>
<th scope="row">Description</th>
<td>{{ object.description }}</td>
</tr>
<tr>
<th scope="row">Type</th>
<td>{{ object.type }}</td>
</tr>
<tr>
<th scope="row">Stored location</th>
<td>{{ object.stored_location }}</td>
</tr>
<tr>
<th scope="row">Start date</th>
<td>{{ object.start_date }}</td>
</tr>
<tr>
<th scope="row">Expiration date</th>
<td>{{ object.expiration_date }}</td>
</tr>
<tr>
<th scope="row">Software Product</th>
<td>{{ object.software_product }}</td>
</tr>
<tr>
<th scope="row">Version</th>
<td>{{ object.version }}</td>
</tr>
<tr>
<th scope="row">Installation</th>
<td>{{ object.installation }}</td>
</tr>
</table>
</div>
</div>
{% include 'inc/panels/custom_fields.html' %}
{% include 'inc/panels/tags.html' %}
{% plugin_left_page object %}
</div>
</div>
<div class="row">
<div class="col col-md-12">
{% plugin_full_width_page object %}
</div>
</div>
{% endblock %}
@@ -15,7 +15,15 @@
<table class="table table-hover attr-table">
<tr>
<th scope="row">Name</th>
<td>{{ object }}</td>
<td>{{ object.name }}</td>
</tr>
<tr>
<th scope="row">Description</th>
<td>{{ object.description }}</td>
</tr>
<tr>
<th scope="row">Manufacturer</th>
<td>{{ object.manufacturer }}</td>
</tr>
<tr>
<th scope="row">Versions</th>
@@ -34,7 +42,6 @@
</div>
{% include 'inc/panels/custom_fields.html' %}
{% include 'inc/panels/tags.html' %}
{# {% include 'inc/panels/comments.html' %}#}
{% plugin_left_page object %}
</div>
</div>
@@ -13,17 +13,17 @@
</h5>
<div class="card-body">
<table class="table table-hover attr-table">
{% if object.device %}
{% if object.device %}
<tr>
<th scope="row">Device</th>
<td>{{ object.device }}</td>
</tr>
{% else %}
{% else %}
<tr>
<th scope="row">Virtualmachine</th>
<td>{{ object.virtualmachine }}</td>
</tr>
{% endif %}
{% endif %}
<tr>
<th scope="row">Software Product</th>
<td>{{ object.software_product }}</td>
@@ -37,7 +37,6 @@
</div>
{% include 'inc/panels/custom_fields.html' %}
{% include 'inc/panels/tags.html' %}
{# {% include 'inc/panels/comments.html' %}#}
{% plugin_left_page object %}
</div>
</div>
@@ -28,7 +28,6 @@
</div>
{% include 'inc/panels/custom_fields.html' %}
{% include 'inc/panels/tags.html' %}
{# {% include 'inc/panels/comments.html' %}#}
{% plugin_left_page object %}
</div>
</div>
+10 -3
View File
@@ -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 = '<a href="/plugins/slm/installations/?q={}">1</a>'
@@ -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)
+50 -15
View File
@@ -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/<int:pk>/", views.SoftwareProductView.as_view(), name="softwareproduct"),
path("software-products/<int:pk>/", views.SoftwareProductView.as_view(),
name="softwareproduct"),
path("software-products/<int:pk>/delete/", views.SoftwareProductDeleteView.as_view(),
name="softwareproduct_delete"),
path("software-products/<int:pk>/edit/", views.SoftwareProductEditView.as_view(), name="softwareproduct_edit"),
path("software-products/<int:pk>/edit/", views.SoftwareProductEditView.as_view(),
name="softwareproduct_edit"),
path(
"software-products/<int:pk>/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/<int:pk>/", views.SoftwareProductVersionView.as_view(), name="softwareproductversion"),
path("versions/<int:pk>/", views.SoftwareProductVersionView.as_view(),
name="softwareproductversion"),
path("versions/<int:pk>/delete/", views.SoftwareProductVersionDeleteView.as_view(),
name="softwareproductversion_delete"),
path("versions/<int:pk>/edit/", views.SoftwareProductVersionEditView.as_view(), name="softwareproductversion_edit"),
path("versions/<int:pk>/edit/", views.SoftwareProductVersionEditView.as_view(),
name="softwareproductversion_edit"),
path(
"versions/<int:pk>/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/<int:pk>/", views.SoftwareLicenseView.as_view(),
name="softwarelicense"),
path("licenses/<int:pk>/delete/", views.SoftwareLicenseDeleteView.as_view(),
name="softwarelicense_delete"),
path("licenses/<int:pk>/edit/", views.SoftwareLicenseEditView.as_view(),
name="softwarelicense_edit"),
path(
"licenses/<int:pk>/changelog/",
ObjectChangeLogView.as_view(),
name="softwarelicense_changelog",
kwargs={"model": SoftwareLicense},
),
]
-150
View File
@@ -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
+4
View File
@@ -0,0 +1,4 @@
from .software_license import *
from .software_product import *
from .software_product_installation import *
from .software_product_version import *
+49
View File
@@ -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
+54
View File
@@ -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
@@ -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
@@ -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
+1 -1
View File
@@ -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
+2 -2
View File
@@ -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