Add SPDX license expressions (#62)
Closes #47 Signed-off-by: wkoot <3715211+wkoot@users.noreply.github.com>
This commit is contained in:
@@ -9,6 +9,7 @@
|
|||||||
* Changelog with backdated changes (#51)
|
* Changelog with backdated changes (#51)
|
||||||
* List filtering (#54)
|
* List filtering (#54)
|
||||||
* Show installations on native objects (#49)
|
* Show installations on native objects (#49)
|
||||||
|
* SPDX license expressions (#47)
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
from rest_framework import serializers
|
from rest_framework.serializers import SerializerMethodField, HyperlinkedIdentityField
|
||||||
|
|
||||||
from netbox.api.serializers import NetBoxModelSerializer
|
from netbox.api.serializers import NetBoxModelSerializer
|
||||||
from netbox_slm.models import SoftwareProduct, SoftwareProductVersion, SoftwareProductInstallation, SoftwareLicense
|
from netbox_slm.models import SoftwareProduct, SoftwareProductVersion, SoftwareProductInstallation, SoftwareLicense
|
||||||
|
|
||||||
|
|
||||||
class SoftwareLicenseSerializer(NetBoxModelSerializer):
|
class SoftwareLicenseSerializer(NetBoxModelSerializer):
|
||||||
display = serializers.SerializerMethodField()
|
display = SerializerMethodField()
|
||||||
url = serializers.HyperlinkedIdentityField(view_name="plugins-api:netbox_slm-api:softwarelicense-detail")
|
url = HyperlinkedIdentityField(view_name="plugins-api:netbox_slm-api:softwarelicense-detail")
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = SoftwareLicense
|
model = SoftwareLicense
|
||||||
@@ -17,6 +17,7 @@ class SoftwareLicenseSerializer(NetBoxModelSerializer):
|
|||||||
"name",
|
"name",
|
||||||
"description",
|
"description",
|
||||||
"type",
|
"type",
|
||||||
|
"spdx_expression",
|
||||||
"stored_location",
|
"stored_location",
|
||||||
"stored_location_url",
|
"stored_location_url",
|
||||||
"start_date",
|
"start_date",
|
||||||
@@ -39,8 +40,8 @@ class SoftwareLicenseSerializer(NetBoxModelSerializer):
|
|||||||
|
|
||||||
|
|
||||||
class SoftwareProductSerializer(NetBoxModelSerializer):
|
class SoftwareProductSerializer(NetBoxModelSerializer):
|
||||||
display = serializers.SerializerMethodField()
|
display = SerializerMethodField()
|
||||||
url = serializers.HyperlinkedIdentityField(view_name="plugins-api:netbox_slm-api:softwareproduct-detail")
|
url = HyperlinkedIdentityField(view_name="plugins-api:netbox_slm-api:softwareproduct-detail")
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = SoftwareProduct
|
model = SoftwareProduct
|
||||||
@@ -65,10 +66,8 @@ class SoftwareProductSerializer(NetBoxModelSerializer):
|
|||||||
|
|
||||||
|
|
||||||
class SoftwareProductInstallationSerializer(NetBoxModelSerializer):
|
class SoftwareProductInstallationSerializer(NetBoxModelSerializer):
|
||||||
display = serializers.SerializerMethodField()
|
display = SerializerMethodField()
|
||||||
url = serializers.HyperlinkedIdentityField(
|
url = HyperlinkedIdentityField(view_name="plugins-api:netbox_slm-api:softwareproductinstallation-detail")
|
||||||
view_name="plugins-api:netbox_slm-api:softwareproductinstallation-detail"
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = SoftwareProductInstallation
|
model = SoftwareProductInstallation
|
||||||
@@ -94,8 +93,8 @@ class SoftwareProductInstallationSerializer(NetBoxModelSerializer):
|
|||||||
|
|
||||||
|
|
||||||
class SoftwareProductVersionSerializer(NetBoxModelSerializer):
|
class SoftwareProductVersionSerializer(NetBoxModelSerializer):
|
||||||
display = serializers.SerializerMethodField()
|
display = SerializerMethodField()
|
||||||
url = serializers.HyperlinkedIdentityField(view_name="plugins-api:netbox_slm-api:softwareproductversion-detail")
|
url = HyperlinkedIdentityField(view_name="plugins-api:netbox_slm-api:softwareproductversion-detail")
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = SoftwareProductVersion
|
model = SoftwareProductVersion
|
||||||
|
|||||||
@@ -14,4 +14,5 @@ router.register("softwareproducts", SoftwareProductViewSet)
|
|||||||
router.register("softwareproductversions", SoftwareProductVersionViewSet)
|
router.register("softwareproductversions", SoftwareProductVersionViewSet)
|
||||||
router.register("softwareproductinstallations", SoftwareProductInstallationViewSet)
|
router.register("softwareproductinstallations", SoftwareProductInstallationViewSet)
|
||||||
router.register("softwarelicenses", SoftwareLicenseViewSet)
|
router.register("softwarelicenses", SoftwareLicenseViewSet)
|
||||||
|
|
||||||
urlpatterns = router.urls
|
urlpatterns = router.urls
|
||||||
|
|||||||
@@ -13,14 +13,15 @@ from netbox_slm.filtersets import (
|
|||||||
SoftwareProductInstallationFilterSet,
|
SoftwareProductInstallationFilterSet,
|
||||||
SoftwareLicenseFilterSet,
|
SoftwareLicenseFilterSet,
|
||||||
)
|
)
|
||||||
from netbox_slm.models import SoftwareProduct, SoftwareProductVersion, SoftwareProductInstallation, SoftwareLicense
|
from netbox_slm.models import (
|
||||||
|
SoftwareProduct,
|
||||||
|
SoftwareProductVersion,
|
||||||
|
SoftwareProductInstallation,
|
||||||
|
SoftwareLicense,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class NetboxSLMRootView(APIRootView):
|
class NetboxSLMRootView(APIRootView):
|
||||||
"""
|
|
||||||
NetboxSLM API root view
|
|
||||||
"""
|
|
||||||
|
|
||||||
def get_view_name(self):
|
def get_view_name(self):
|
||||||
return "NetboxSLM"
|
return "NetboxSLM"
|
||||||
|
|
||||||
|
|||||||
@@ -113,6 +113,7 @@ class SoftwareLicenseFilterSet(NetBoxModelFilterSet):
|
|||||||
name = CharFilter(lookup_expr="icontains")
|
name = CharFilter(lookup_expr="icontains")
|
||||||
description = CharFilter(lookup_expr="icontains")
|
description = CharFilter(lookup_expr="icontains")
|
||||||
type = CharFilter(lookup_expr="icontains")
|
type = CharFilter(lookup_expr="icontains")
|
||||||
|
spdx_expression = CharFilter(lookup_expr="icontains", label="SPDX expression")
|
||||||
stored_location = CharFilter(lookup_expr="icontains")
|
stored_location = CharFilter(lookup_expr="icontains")
|
||||||
|
|
||||||
software_product = ModelMultipleChoiceFilter(
|
software_product = ModelMultipleChoiceFilter(
|
||||||
|
|||||||
@@ -2,7 +2,13 @@ from django.forms import CharField, DateField, ChoiceField, IntegerField, NullBo
|
|||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
|
|
||||||
from netbox.forms import NetBoxModelForm, NetBoxModelFilterSetForm, NetBoxModelImportForm, NetBoxModelBulkEditForm
|
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.constants import BOOLEAN_WITH_BLANK_CHOICES
|
||||||
from utilities.forms.fields import (
|
from utilities.forms.fields import (
|
||||||
CommentField,
|
CommentField,
|
||||||
@@ -18,6 +24,7 @@ from utilities.forms.widgets import APISelect, DatePicker
|
|||||||
class SoftwareLicenseForm(NetBoxModelForm):
|
class SoftwareLicenseForm(NetBoxModelForm):
|
||||||
comments = CommentField()
|
comments = CommentField()
|
||||||
|
|
||||||
|
spdx_expression = ChoiceField(required=False, choices=spdx_license_names(), label="SPDX expression")
|
||||||
stored_location_url = LaxURLField(required=False)
|
stored_location_url = LaxURLField(required=False)
|
||||||
start_date = DateField(required=False, widget=DatePicker())
|
start_date = DateField(required=False, widget=DatePicker())
|
||||||
expiration_date = DateField(required=False, widget=DatePicker())
|
expiration_date = DateField(required=False, widget=DatePicker())
|
||||||
@@ -49,6 +56,7 @@ class SoftwareLicenseForm(NetBoxModelForm):
|
|||||||
"description",
|
"description",
|
||||||
"software_product",
|
"software_product",
|
||||||
"type",
|
"type",
|
||||||
|
"spdx_expression",
|
||||||
"stored_location",
|
"stored_location",
|
||||||
"stored_location_url",
|
"stored_location_url",
|
||||||
"start_date",
|
"start_date",
|
||||||
@@ -70,6 +78,7 @@ class SoftwareLicenseFilterForm(NetBoxModelFilterSetForm):
|
|||||||
"name",
|
"name",
|
||||||
"description",
|
"description",
|
||||||
"type",
|
"type",
|
||||||
|
"spdx_expression",
|
||||||
"stored_location",
|
"stored_location",
|
||||||
"support",
|
"support",
|
||||||
"software_product_id",
|
"software_product_id",
|
||||||
@@ -84,6 +93,7 @@ class SoftwareLicenseFilterForm(NetBoxModelFilterSetForm):
|
|||||||
name = CharField(required=False)
|
name = CharField(required=False)
|
||||||
description = CharField(required=False)
|
description = CharField(required=False)
|
||||||
type = CharField(required=False)
|
type = CharField(required=False)
|
||||||
|
spdx_expression = CharField(required=False, label="SPDX expression")
|
||||||
stored_location = CharField(required=False)
|
stored_location = CharField(required=False)
|
||||||
support = ChoiceField(required=False, choices=BOOLEAN_WITH_BLANK_CHOICES)
|
support = ChoiceField(required=False, choices=BOOLEAN_WITH_BLANK_CHOICES)
|
||||||
|
|
||||||
@@ -114,6 +124,7 @@ class SoftwareLicenseBulkImportForm(NetBoxModelImportForm):
|
|||||||
"description",
|
"description",
|
||||||
"software_product",
|
"software_product",
|
||||||
"type",
|
"type",
|
||||||
|
"spdx_expression",
|
||||||
"stored_location",
|
"stored_location",
|
||||||
"start_date",
|
"start_date",
|
||||||
"expiration_date",
|
"expiration_date",
|
||||||
@@ -130,6 +141,7 @@ class SoftwareLicenseBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
fieldsets = (
|
fieldsets = (
|
||||||
FieldSet(
|
FieldSet(
|
||||||
"type",
|
"type",
|
||||||
|
"spdx_expression",
|
||||||
"stored_location",
|
"stored_location",
|
||||||
"stored_location_url",
|
"stored_location_url",
|
||||||
"start_date",
|
"start_date",
|
||||||
@@ -143,6 +155,7 @@ class SoftwareLicenseBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
)
|
)
|
||||||
nullable_fields = (
|
nullable_fields = (
|
||||||
"type",
|
"type",
|
||||||
|
"spdx_expression",
|
||||||
"stored_location",
|
"stored_location",
|
||||||
"stored_location_url",
|
"stored_location_url",
|
||||||
"start_date",
|
"start_date",
|
||||||
@@ -157,6 +170,7 @@ class SoftwareLicenseBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
comments = CommentField()
|
comments = CommentField()
|
||||||
|
|
||||||
type = CharField(required=False)
|
type = CharField(required=False)
|
||||||
|
spdx_expression = ChoiceField(required=False, choices=spdx_license_names(), label="SPDX expression")
|
||||||
stored_location = CharField(required=False)
|
stored_location = CharField(required=False)
|
||||||
stored_location_url = LaxURLField(required=False)
|
stored_location_url = LaxURLField(required=False)
|
||||||
start_date = DateField(required=False, widget=DatePicker())
|
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.db import models
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.html import format_html, urlencode
|
from django.utils.html import format_html, urlencode
|
||||||
|
from license_expression import Licensing, get_spdx_licensing
|
||||||
|
|
||||||
from netbox.models import NetBoxModel
|
from netbox.models import NetBoxModel
|
||||||
from utilities.querysets import RestrictedQuerySet
|
from utilities.querysets import RestrictedQuerySet
|
||||||
from utilities.validators import EnhancedURLValidator
|
from utilities.validators import EnhancedURLValidator
|
||||||
|
|
||||||
|
spdx_licensing: Licensing = get_spdx_licensing()
|
||||||
|
|
||||||
|
|
||||||
class LaxURLField(models.URLField):
|
class LaxURLField(models.URLField):
|
||||||
"""
|
"""
|
||||||
@@ -129,12 +133,26 @@ class SoftwareProductInstallation(NetBoxModel):
|
|||||||
return "cluster"
|
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):
|
class SoftwareLicense(NetBoxModel):
|
||||||
name = models.CharField(max_length=128)
|
name = models.CharField(max_length=128)
|
||||||
comments = models.TextField(blank=True)
|
comments = models.TextField(blank=True)
|
||||||
|
|
||||||
description = models.CharField(max_length=255, null=True, blank=True)
|
description = models.CharField(max_length=255, null=True, blank=True)
|
||||||
type = models.CharField(max_length=128)
|
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 = models.CharField(max_length=255, null=True, blank=True)
|
||||||
stored_location_url = LaxURLField(max_length=1024, null=True, blank=True)
|
stored_location_url = LaxURLField(max_length=1024, null=True, blank=True)
|
||||||
start_date = models.DateField(null=True, blank=True)
|
start_date = models.DateField(null=True, blank=True)
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ class SoftwareProductVersionTable(NetBoxTable):
|
|||||||
|
|
||||||
pk = ToggleColumn()
|
pk = ToggleColumn()
|
||||||
name = tables.LinkColumn()
|
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)
|
manufacturer = tables.Column(accessor="software_product__manufacturer", linkify=True)
|
||||||
installations = tables.Column(accessor="get_installation_count")
|
installations = tables.Column(accessor="get_installation_count")
|
||||||
|
|
||||||
@@ -89,7 +89,7 @@ class SoftwareProductInstallationTable(NetBoxTable):
|
|||||||
platform = tables.Column(accessor="platform", linkify=True)
|
platform = tables.Column(accessor="platform", linkify=True)
|
||||||
type = tables.Column(accessor="render_type")
|
type = tables.Column(accessor="render_type")
|
||||||
manufacturer = tables.Column(accessor="software_product__manufacturer", linkify=True)
|
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)
|
version = tables.Column(accessor="version", linkify=True)
|
||||||
|
|
||||||
tags = columns.TagColumn(url_name="plugins:netbox_slm:softwareproductinstallation_list")
|
tags = columns.TagColumn(url_name="plugins:netbox_slm:softwareproductinstallation_list")
|
||||||
@@ -137,10 +137,11 @@ class SoftwareLicenseTable(NetBoxTable):
|
|||||||
name = tables.LinkColumn()
|
name = tables.LinkColumn()
|
||||||
|
|
||||||
type = tables.Column()
|
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)
|
stored_location = tables.Column(accessor="stored_location_txt", linkify=lambda record: record.stored_location_url)
|
||||||
|
|
||||||
manufacturer = tables.Column(accessor="software_product__manufacturer", linkify=True)
|
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)
|
version = tables.Column(accessor="version", linkify=True)
|
||||||
installation = tables.Column(accessor="installation", linkify=True)
|
installation = tables.Column(accessor="installation", linkify=True)
|
||||||
|
|
||||||
@@ -153,6 +154,7 @@ class SoftwareLicenseTable(NetBoxTable):
|
|||||||
"name",
|
"name",
|
||||||
"description",
|
"description",
|
||||||
"type",
|
"type",
|
||||||
|
"spdx_expression",
|
||||||
"stored_location",
|
"stored_location",
|
||||||
"start_date",
|
"start_date",
|
||||||
"expiration_date",
|
"expiration_date",
|
||||||
|
|||||||
@@ -24,6 +24,10 @@
|
|||||||
<th scope="row">Type</th>
|
<th scope="row">Type</th>
|
||||||
<td>{{ object.type }}</td>
|
<td>{{ object.type }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">SPDX expression</th>
|
||||||
|
<td>{{ object.spdx_expression }}</td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Software Product</th>
|
<th scope="row">Software Product</th>
|
||||||
<td>{{ object.software_product|linkify }}</td>
|
<td>{{ object.software_product|linkify }}</td>
|
||||||
|
|||||||
+3
-1
@@ -26,7 +26,9 @@ classifiers = [
|
|||||||
"Topic :: Internet :: WWW/HTTP",
|
"Topic :: Internet :: WWW/HTTP",
|
||||||
"Topic :: Internet :: WWW/HTTP :: Dynamic Content",
|
"Topic :: Internet :: WWW/HTTP :: Dynamic Content",
|
||||||
]
|
]
|
||||||
dependencies = []
|
dependencies = [
|
||||||
|
"license-expression == 30.4.1",
|
||||||
|
]
|
||||||
|
|
||||||
[project.optional-dependencies]
|
[project.optional-dependencies]
|
||||||
build = [
|
build = [
|
||||||
|
|||||||
Reference in New Issue
Block a user