Add SPDX license expressions (#62)
Closes #47 Signed-off-by: wkoot <3715211+wkoot@users.noreply.github.com>
This commit is contained in:
@@ -1,12 +1,12 @@
|
||||
from rest_framework import serializers
|
||||
from rest_framework.serializers import SerializerMethodField, HyperlinkedIdentityField
|
||||
|
||||
from netbox.api.serializers import NetBoxModelSerializer
|
||||
from netbox_slm.models import SoftwareProduct, SoftwareProductVersion, SoftwareProductInstallation, SoftwareLicense
|
||||
|
||||
|
||||
class SoftwareLicenseSerializer(NetBoxModelSerializer):
|
||||
display = serializers.SerializerMethodField()
|
||||
url = serializers.HyperlinkedIdentityField(view_name="plugins-api:netbox_slm-api:softwarelicense-detail")
|
||||
display = SerializerMethodField()
|
||||
url = HyperlinkedIdentityField(view_name="plugins-api:netbox_slm-api:softwarelicense-detail")
|
||||
|
||||
class Meta:
|
||||
model = SoftwareLicense
|
||||
@@ -17,6 +17,7 @@ class SoftwareLicenseSerializer(NetBoxModelSerializer):
|
||||
"name",
|
||||
"description",
|
||||
"type",
|
||||
"spdx_expression",
|
||||
"stored_location",
|
||||
"stored_location_url",
|
||||
"start_date",
|
||||
@@ -39,8 +40,8 @@ class SoftwareLicenseSerializer(NetBoxModelSerializer):
|
||||
|
||||
|
||||
class SoftwareProductSerializer(NetBoxModelSerializer):
|
||||
display = serializers.SerializerMethodField()
|
||||
url = serializers.HyperlinkedIdentityField(view_name="plugins-api:netbox_slm-api:softwareproduct-detail")
|
||||
display = SerializerMethodField()
|
||||
url = HyperlinkedIdentityField(view_name="plugins-api:netbox_slm-api:softwareproduct-detail")
|
||||
|
||||
class Meta:
|
||||
model = SoftwareProduct
|
||||
@@ -65,10 +66,8 @@ class SoftwareProductSerializer(NetBoxModelSerializer):
|
||||
|
||||
|
||||
class SoftwareProductInstallationSerializer(NetBoxModelSerializer):
|
||||
display = serializers.SerializerMethodField()
|
||||
url = serializers.HyperlinkedIdentityField(
|
||||
view_name="plugins-api:netbox_slm-api:softwareproductinstallation-detail"
|
||||
)
|
||||
display = SerializerMethodField()
|
||||
url = HyperlinkedIdentityField(view_name="plugins-api:netbox_slm-api:softwareproductinstallation-detail")
|
||||
|
||||
class Meta:
|
||||
model = SoftwareProductInstallation
|
||||
@@ -94,8 +93,8 @@ class SoftwareProductInstallationSerializer(NetBoxModelSerializer):
|
||||
|
||||
|
||||
class SoftwareProductVersionSerializer(NetBoxModelSerializer):
|
||||
display = serializers.SerializerMethodField()
|
||||
url = serializers.HyperlinkedIdentityField(view_name="plugins-api:netbox_slm-api:softwareproductversion-detail")
|
||||
display = SerializerMethodField()
|
||||
url = HyperlinkedIdentityField(view_name="plugins-api:netbox_slm-api:softwareproductversion-detail")
|
||||
|
||||
class Meta:
|
||||
model = SoftwareProductVersion
|
||||
|
||||
@@ -14,4 +14,5 @@ router.register("softwareproducts", SoftwareProductViewSet)
|
||||
router.register("softwareproductversions", SoftwareProductVersionViewSet)
|
||||
router.register("softwareproductinstallations", SoftwareProductInstallationViewSet)
|
||||
router.register("softwarelicenses", SoftwareLicenseViewSet)
|
||||
|
||||
urlpatterns = router.urls
|
||||
|
||||
@@ -13,14 +13,15 @@ from netbox_slm.filtersets import (
|
||||
SoftwareProductInstallationFilterSet,
|
||||
SoftwareLicenseFilterSet,
|
||||
)
|
||||
from netbox_slm.models import SoftwareProduct, SoftwareProductVersion, SoftwareProductInstallation, SoftwareLicense
|
||||
from netbox_slm.models import (
|
||||
SoftwareProduct,
|
||||
SoftwareProductVersion,
|
||||
SoftwareProductInstallation,
|
||||
SoftwareLicense,
|
||||
)
|
||||
|
||||
|
||||
class NetboxSLMRootView(APIRootView):
|
||||
"""
|
||||
NetboxSLM API root view
|
||||
"""
|
||||
|
||||
def get_view_name(self):
|
||||
return "NetboxSLM"
|
||||
|
||||
|
||||
@@ -113,6 +113,7 @@ class SoftwareLicenseFilterSet(NetBoxModelFilterSet):
|
||||
name = CharFilter(lookup_expr="icontains")
|
||||
description = CharFilter(lookup_expr="icontains")
|
||||
type = CharFilter(lookup_expr="icontains")
|
||||
spdx_expression = CharFilter(lookup_expr="icontains", label="SPDX expression")
|
||||
stored_location = CharFilter(lookup_expr="icontains")
|
||||
|
||||
software_product = ModelMultipleChoiceFilter(
|
||||
|
||||
@@ -2,7 +2,13 @@ from django.forms import CharField, DateField, ChoiceField, IntegerField, NullBo
|
||||
from django.urls import reverse_lazy
|
||||
|
||||
from netbox.forms import NetBoxModelForm, NetBoxModelFilterSetForm, NetBoxModelImportForm, NetBoxModelBulkEditForm
|
||||
from netbox_slm.models import SoftwareProduct, SoftwareProductVersion, SoftwareProductInstallation, SoftwareLicense
|
||||
from netbox_slm.models import (
|
||||
SoftwareProduct,
|
||||
SoftwareProductVersion,
|
||||
SoftwareProductInstallation,
|
||||
SoftwareLicense,
|
||||
spdx_license_names,
|
||||
)
|
||||
from utilities.forms.constants import BOOLEAN_WITH_BLANK_CHOICES
|
||||
from utilities.forms.fields import (
|
||||
CommentField,
|
||||
@@ -18,6 +24,7 @@ from utilities.forms.widgets import APISelect, DatePicker
|
||||
class SoftwareLicenseForm(NetBoxModelForm):
|
||||
comments = CommentField()
|
||||
|
||||
spdx_expression = ChoiceField(required=False, choices=spdx_license_names(), label="SPDX expression")
|
||||
stored_location_url = LaxURLField(required=False)
|
||||
start_date = DateField(required=False, widget=DatePicker())
|
||||
expiration_date = DateField(required=False, widget=DatePicker())
|
||||
@@ -49,6 +56,7 @@ class SoftwareLicenseForm(NetBoxModelForm):
|
||||
"description",
|
||||
"software_product",
|
||||
"type",
|
||||
"spdx_expression",
|
||||
"stored_location",
|
||||
"stored_location_url",
|
||||
"start_date",
|
||||
@@ -70,6 +78,7 @@ class SoftwareLicenseFilterForm(NetBoxModelFilterSetForm):
|
||||
"name",
|
||||
"description",
|
||||
"type",
|
||||
"spdx_expression",
|
||||
"stored_location",
|
||||
"support",
|
||||
"software_product_id",
|
||||
@@ -84,6 +93,7 @@ class SoftwareLicenseFilterForm(NetBoxModelFilterSetForm):
|
||||
name = CharField(required=False)
|
||||
description = CharField(required=False)
|
||||
type = CharField(required=False)
|
||||
spdx_expression = CharField(required=False, label="SPDX expression")
|
||||
stored_location = CharField(required=False)
|
||||
support = ChoiceField(required=False, choices=BOOLEAN_WITH_BLANK_CHOICES)
|
||||
|
||||
@@ -114,6 +124,7 @@ class SoftwareLicenseBulkImportForm(NetBoxModelImportForm):
|
||||
"description",
|
||||
"software_product",
|
||||
"type",
|
||||
"spdx_expression",
|
||||
"stored_location",
|
||||
"start_date",
|
||||
"expiration_date",
|
||||
@@ -130,6 +141,7 @@ class SoftwareLicenseBulkEditForm(NetBoxModelBulkEditForm):
|
||||
fieldsets = (
|
||||
FieldSet(
|
||||
"type",
|
||||
"spdx_expression",
|
||||
"stored_location",
|
||||
"stored_location_url",
|
||||
"start_date",
|
||||
@@ -143,6 +155,7 @@ class SoftwareLicenseBulkEditForm(NetBoxModelBulkEditForm):
|
||||
)
|
||||
nullable_fields = (
|
||||
"type",
|
||||
"spdx_expression",
|
||||
"stored_location",
|
||||
"stored_location_url",
|
||||
"start_date",
|
||||
@@ -157,6 +170,7 @@ class SoftwareLicenseBulkEditForm(NetBoxModelBulkEditForm):
|
||||
comments = CommentField()
|
||||
|
||||
type = CharField(required=False)
|
||||
spdx_expression = ChoiceField(required=False, choices=spdx_license_names(), label="SPDX expression")
|
||||
stored_location = CharField(required=False)
|
||||
stored_location_url = LaxURLField(required=False)
|
||||
start_date = DateField(required=False, widget=DatePicker())
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
# Generated by Django 5.1.5 on 2025-02-24 18:03
|
||||
|
||||
import netbox_slm.models
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("netbox_slm", "0009_softwareproductversion_description"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="softwarelicense",
|
||||
name="spdx_expression",
|
||||
field=models.CharField(
|
||||
blank=True,
|
||||
max_length=64,
|
||||
null=True,
|
||||
validators=[netbox_slm.models.validate_spdx_expression],
|
||||
),
|
||||
),
|
||||
]
|
||||
@@ -1,11 +1,15 @@
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import models
|
||||
from django.urls import reverse
|
||||
from django.utils.html import format_html, urlencode
|
||||
from license_expression import Licensing, get_spdx_licensing
|
||||
|
||||
from netbox.models import NetBoxModel
|
||||
from utilities.querysets import RestrictedQuerySet
|
||||
from utilities.validators import EnhancedURLValidator
|
||||
|
||||
spdx_licensing: Licensing = get_spdx_licensing()
|
||||
|
||||
|
||||
class LaxURLField(models.URLField):
|
||||
"""
|
||||
@@ -129,12 +133,26 @@ class SoftwareProductInstallation(NetBoxModel):
|
||||
return "cluster"
|
||||
|
||||
|
||||
def spdx_license_names():
|
||||
names = [(item[0], item[0]) for item in spdx_licensing.known_symbols.items() if not item[1].is_exception]
|
||||
names.sort()
|
||||
names.insert(0, ("", "---------")) # set default value
|
||||
return names
|
||||
|
||||
|
||||
def validate_spdx_expression(value):
|
||||
expression_info = spdx_licensing.validate(value)
|
||||
if expression_info.errors:
|
||||
raise ValidationError(f"{value} is not a known SPDX license expression")
|
||||
|
||||
|
||||
class SoftwareLicense(NetBoxModel):
|
||||
name = models.CharField(max_length=128)
|
||||
comments = models.TextField(blank=True)
|
||||
|
||||
description = models.CharField(max_length=255, null=True, blank=True)
|
||||
type = models.CharField(max_length=128)
|
||||
spdx_expression = models.CharField(max_length=64, null=True, blank=True, validators=[validate_spdx_expression])
|
||||
stored_location = models.CharField(max_length=255, null=True, blank=True)
|
||||
stored_location_url = LaxURLField(max_length=1024, null=True, blank=True)
|
||||
start_date = models.DateField(null=True, blank=True)
|
||||
|
||||
@@ -45,7 +45,7 @@ class SoftwareProductVersionTable(NetBoxTable):
|
||||
|
||||
pk = ToggleColumn()
|
||||
name = tables.LinkColumn()
|
||||
software_product = tables.Column(accessor="software_product", linkify=True)
|
||||
software_product = tables.Column(accessor="software_product", verbose_name="Software Product", linkify=True)
|
||||
manufacturer = tables.Column(accessor="software_product__manufacturer", linkify=True)
|
||||
installations = tables.Column(accessor="get_installation_count")
|
||||
|
||||
@@ -89,7 +89,7 @@ class SoftwareProductInstallationTable(NetBoxTable):
|
||||
platform = tables.Column(accessor="platform", linkify=True)
|
||||
type = tables.Column(accessor="render_type")
|
||||
manufacturer = tables.Column(accessor="software_product__manufacturer", linkify=True)
|
||||
software_product = tables.Column(accessor="software_product", linkify=True)
|
||||
software_product = tables.Column(accessor="software_product", verbose_name="Software Product", linkify=True)
|
||||
version = tables.Column(accessor="version", linkify=True)
|
||||
|
||||
tags = columns.TagColumn(url_name="plugins:netbox_slm:softwareproductinstallation_list")
|
||||
@@ -137,10 +137,11 @@ class SoftwareLicenseTable(NetBoxTable):
|
||||
name = tables.LinkColumn()
|
||||
|
||||
type = tables.Column()
|
||||
spdx_expression = tables.Column(verbose_name="SPDX expression")
|
||||
stored_location = tables.Column(accessor="stored_location_txt", linkify=lambda record: record.stored_location_url)
|
||||
|
||||
manufacturer = tables.Column(accessor="software_product__manufacturer", linkify=True)
|
||||
software_product = tables.Column(accessor="software_product", linkify=True)
|
||||
software_product = tables.Column(accessor="software_product", verbose_name="Software Product", linkify=True)
|
||||
version = tables.Column(accessor="version", linkify=True)
|
||||
installation = tables.Column(accessor="installation", linkify=True)
|
||||
|
||||
@@ -153,6 +154,7 @@ class SoftwareLicenseTable(NetBoxTable):
|
||||
"name",
|
||||
"description",
|
||||
"type",
|
||||
"spdx_expression",
|
||||
"stored_location",
|
||||
"start_date",
|
||||
"expiration_date",
|
||||
|
||||
@@ -24,6 +24,10 @@
|
||||
<th scope="row">Type</th>
|
||||
<td>{{ object.type }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">SPDX expression</th>
|
||||
<td>{{ object.spdx_expression }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Software Product</th>
|
||||
<td>{{ object.software_product|linkify }}</td>
|
||||
|
||||
Reference in New Issue
Block a user