Add SPDX license expressions (#62)

Closes #47

Signed-off-by: wkoot <3715211+wkoot@users.noreply.github.com>
This commit is contained in:
wkoot
2025-02-25 18:37:00 +01:00
committed by GitHub
parent ef45789b30
commit 334a76e640
11 changed files with 88 additions and 21 deletions
+1
View File
@@ -9,6 +9,7 @@
* Changelog with backdated changes (#51)
* List filtering (#54)
* Show installations on native objects (#49)
* SPDX license expressions (#47)
### Changed
+10 -11
View File
@@ -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
+1
View File
@@ -14,4 +14,5 @@ router.register("softwareproducts", SoftwareProductViewSet)
router.register("softwareproductversions", SoftwareProductVersionViewSet)
router.register("softwareproductinstallations", SoftwareProductInstallationViewSet)
router.register("softwarelicenses", SoftwareLicenseViewSet)
urlpatterns = router.urls
+6 -5
View File
@@ -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"
+1
View File
@@ -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(
+15 -1
View File
@@ -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],
),
),
]
+18
View File
@@ -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)
+5 -3
View File
@@ -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>
+3 -1
View File
@@ -26,7 +26,9 @@ classifiers = [
"Topic :: Internet :: WWW/HTTP",
"Topic :: Internet :: WWW/HTTP :: Dynamic Content",
]
dependencies = []
dependencies = [
"license-expression == 30.4.1",
]
[project.optional-dependencies]
build = [