feat: add German localization
Internationalize plugin navigation, forms, tables, model labels, detail templates, choices, and validation messages. Ship a compiled German Django message catalog and bump the plugin version to 1.10.0.
This commit is contained in:
@@ -5,6 +5,7 @@
|
|||||||
### Added
|
### Added
|
||||||
|
|
||||||
* Optional tenant group and tenant assignments for software licenses
|
* Optional tenant group and tenant assignments for software licenses
|
||||||
|
* German translation for navigation, forms, tables, detail views, choices, and validation messages
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,9 @@ Lizenzen können optional einer NetBox-Mandantengruppe und einem Mandanten zugeo
|
|||||||
|
|
||||||
Diese Variante ist für **NetBox 4.6.5** vorgesehen.
|
Diese Variante ist für **NetBox 4.6.5** vorgesehen.
|
||||||
|
|
||||||
|
Die Oberfläche ist auf Englisch und Deutsch verfügbar. Die Sprache richtet sich nach der in NetBox für den
|
||||||
|
Benutzer ausgewählten Sprache.
|
||||||
|
|
||||||
## Funktionen
|
## Funktionen
|
||||||
|
|
||||||
- Softwareprodukte und Hersteller verwalten
|
- Softwareprodukte und Hersteller verwalten
|
||||||
@@ -54,7 +57,7 @@ Die installierte Version kann anschließend geprüft werden:
|
|||||||
/opt/netbox/venv/bin/pip show netbox-slm
|
/opt/netbox/venv/bin/pip show netbox-slm
|
||||||
```
|
```
|
||||||
|
|
||||||
Für diese Variante muss dort mindestens Version `1.9.1` stehen.
|
Für diese Variante muss dort mindestens Version `1.10.0` stehen.
|
||||||
|
|
||||||
### 3. Plugin in NetBox aktivieren
|
### 3. Plugin in NetBox aktivieren
|
||||||
|
|
||||||
|
|||||||
@@ -15,15 +15,16 @@ limitations under the License.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from netbox.plugins import PluginConfig
|
from netbox.plugins import PluginConfig
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
__version__ = "1.9.1"
|
__version__ = "1.10.0"
|
||||||
|
|
||||||
|
|
||||||
class SLMConfig(PluginConfig):
|
class SLMConfig(PluginConfig):
|
||||||
name = "netbox_slm"
|
name = "netbox_slm"
|
||||||
verbose_name = "Software Lifecycle Management"
|
verbose_name = _("Software Lifecycle Management")
|
||||||
version = __version__
|
version = __version__
|
||||||
description = "Software Lifecycle Management Netbox Plugin."
|
description = _("Software Lifecycle Management NetBox Plugin.")
|
||||||
author = "ICTU"
|
author = "ICTU"
|
||||||
author_email = "open-source-projects@ictu.nl"
|
author_email = "open-source-projects@ictu.nl"
|
||||||
base_url = "slm"
|
base_url = "slm"
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from rest_framework.serializers import SerializerMethodField, HyperlinkedIdentityField
|
from rest_framework.serializers import SerializerMethodField, HyperlinkedIdentityField
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
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
|
||||||
@@ -46,7 +47,7 @@ class SoftwareLicenseSerializer(NetBoxModelSerializer):
|
|||||||
tenant = attrs.get("tenant", getattr(self.instance, "tenant", None))
|
tenant = attrs.get("tenant", getattr(self.instance, "tenant", None))
|
||||||
if tenant_group and tenant and tenant.group_id != tenant_group.pk:
|
if tenant_group and tenant and tenant.group_id != tenant_group.pk:
|
||||||
raise serializers.ValidationError(
|
raise serializers.ValidationError(
|
||||||
{"tenant": "The selected tenant does not belong to the selected tenant group."}
|
{"tenant": _("The selected tenant does not belong to the selected tenant group.")}
|
||||||
)
|
)
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django_filters import CharFilter, ModelMultipleChoiceFilter, MultipleChoiceFilter
|
from django_filters import CharFilter, ModelMultipleChoiceFilter, MultipleChoiceFilter
|
||||||
|
|
||||||
from dcim.models import Device, Manufacturer
|
from dcim.models import Device, Manufacturer
|
||||||
@@ -55,7 +56,7 @@ class SoftwareProductVersionFilterSet(NetBoxModelFilterSet):
|
|||||||
|
|
||||||
software_product_id = ModelMultipleChoiceFilter(
|
software_product_id = ModelMultipleChoiceFilter(
|
||||||
queryset=SoftwareProduct.objects.all(),
|
queryset=SoftwareProduct.objects.all(),
|
||||||
label="Software Product",
|
label=_("Software Product"),
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@@ -82,12 +83,12 @@ class SoftwareProductInstallationFilterSet(NetBoxModelFilterSet):
|
|||||||
device_id = ModelMultipleChoiceFilter(queryset=Device.objects.all())
|
device_id = ModelMultipleChoiceFilter(queryset=Device.objects.all())
|
||||||
virtualmachine_id = ModelMultipleChoiceFilter(
|
virtualmachine_id = ModelMultipleChoiceFilter(
|
||||||
queryset=VirtualMachine.objects.all(),
|
queryset=VirtualMachine.objects.all(),
|
||||||
label="Virtual Machine",
|
label=_("Virtual Machine"),
|
||||||
)
|
)
|
||||||
cluster_id = ModelMultipleChoiceFilter(queryset=Cluster.objects.all())
|
cluster_id = ModelMultipleChoiceFilter(queryset=Cluster.objects.all())
|
||||||
software_product_id = ModelMultipleChoiceFilter(
|
software_product_id = ModelMultipleChoiceFilter(
|
||||||
queryset=SoftwareProduct.objects.all(),
|
queryset=SoftwareProduct.objects.all(),
|
||||||
label="Software Product",
|
label=_("Software Product"),
|
||||||
)
|
)
|
||||||
version_id = ModelMultipleChoiceFilter(queryset=SoftwareProductVersion.objects.all())
|
version_id = ModelMultipleChoiceFilter(queryset=SoftwareProductVersion.objects.all())
|
||||||
|
|
||||||
@@ -114,17 +115,17 @@ 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")
|
spdx_expression = CharFilter(lookup_expr="icontains", label=_("SPDX expression"))
|
||||||
stored_location = CharFilter(lookup_expr="icontains")
|
stored_location = CharFilter(lookup_expr="icontains")
|
||||||
|
|
||||||
software_product_id = ModelMultipleChoiceFilter(
|
software_product_id = ModelMultipleChoiceFilter(
|
||||||
queryset=SoftwareProduct.objects.all(),
|
queryset=SoftwareProduct.objects.all(),
|
||||||
label="Software Product",
|
label=_("Software Product"),
|
||||||
)
|
)
|
||||||
version_id = ModelMultipleChoiceFilter(queryset=SoftwareProductVersion.objects.all())
|
version_id = ModelMultipleChoiceFilter(queryset=SoftwareProductVersion.objects.all())
|
||||||
installation_id = ModelMultipleChoiceFilter(queryset=SoftwareProductInstallation.objects.all())
|
installation_id = ModelMultipleChoiceFilter(queryset=SoftwareProductInstallation.objects.all())
|
||||||
tenant_group_id = ModelMultipleChoiceFilter(queryset=TenantGroup.objects.all(), label="Tenant Group")
|
tenant_group_id = ModelMultipleChoiceFilter(queryset=TenantGroup.objects.all(), label=_("Tenant Group"))
|
||||||
tenant_id = ModelMultipleChoiceFilter(queryset=Tenant.objects.all(), label="Tenant")
|
tenant_id = ModelMultipleChoiceFilter(queryset=Tenant.objects.all(), label=_("Tenant"))
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = SoftwareLicense
|
model = SoftwareLicense
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
from django.forms import CharField, DateField, ChoiceField, IntegerField, NullBooleanField
|
from django.forms import CharField, DateField, ChoiceField, IntegerField, NullBooleanField
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from netbox.forms import NetBoxModelForm, NetBoxModelFilterSetForm, NetBoxModelImportForm, NetBoxModelBulkEditForm
|
from netbox.forms import NetBoxModelForm, NetBoxModelFilterSetForm, NetBoxModelImportForm, NetBoxModelBulkEditForm
|
||||||
from netbox_slm.models import (
|
from netbox_slm.models import (
|
||||||
@@ -31,9 +32,9 @@ class SoftwareLicenseForm(NetBoxModelForm):
|
|||||||
"software_product",
|
"software_product",
|
||||||
"version",
|
"version",
|
||||||
"installation",
|
"installation",
|
||||||
name="Software License",
|
name=_("Software License"),
|
||||||
),
|
),
|
||||||
FieldSet("tenant_group", "tenant", name="Tenancy"),
|
FieldSet("tenant_group", "tenant", name=_("Tenancy")),
|
||||||
FieldSet(
|
FieldSet(
|
||||||
"type",
|
"type",
|
||||||
"spdx_expression",
|
"spdx_expression",
|
||||||
@@ -43,12 +44,12 @@ class SoftwareLicenseForm(NetBoxModelForm):
|
|||||||
"expiration_date",
|
"expiration_date",
|
||||||
"support",
|
"support",
|
||||||
"license_amount",
|
"license_amount",
|
||||||
name="License Details",
|
name=_("License Details"),
|
||||||
),
|
),
|
||||||
FieldSet("tags", name="Tags"),
|
FieldSet("tags", name=_("Tags")),
|
||||||
)
|
)
|
||||||
|
|
||||||
spdx_expression = ChoiceField(required=False, choices=spdx_license_names(), label="SPDX expression")
|
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())
|
||||||
@@ -56,7 +57,7 @@ class SoftwareLicenseForm(NetBoxModelForm):
|
|||||||
software_product = DynamicModelChoiceField(
|
software_product = DynamicModelChoiceField(
|
||||||
queryset=SoftwareProduct.objects.all(),
|
queryset=SoftwareProduct.objects.all(),
|
||||||
required=True,
|
required=True,
|
||||||
label="Software Product",
|
label=_("Software Product"),
|
||||||
)
|
)
|
||||||
version = DynamicModelChoiceField(
|
version = DynamicModelChoiceField(
|
||||||
queryset=SoftwareProductVersion.objects.all(),
|
queryset=SoftwareProductVersion.objects.all(),
|
||||||
@@ -75,13 +76,13 @@ class SoftwareLicenseForm(NetBoxModelForm):
|
|||||||
tenant_group = DynamicModelChoiceField(
|
tenant_group = DynamicModelChoiceField(
|
||||||
queryset=TenantGroup.objects.all(),
|
queryset=TenantGroup.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
label="Tenant Group",
|
label=_("Tenant Group"),
|
||||||
)
|
)
|
||||||
tenant = DynamicModelChoiceField(
|
tenant = DynamicModelChoiceField(
|
||||||
queryset=Tenant.objects.all(),
|
queryset=Tenant.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
query_params={"group_id": "$tenant_group"},
|
query_params={"group_id": "$tenant_group"},
|
||||||
label="Tenant",
|
label=_("Tenant"),
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@@ -132,40 +133,42 @@ 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")
|
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)
|
||||||
|
|
||||||
software_product_id = DynamicModelMultipleChoiceField(
|
software_product_id = DynamicModelMultipleChoiceField(
|
||||||
queryset=SoftwareProduct.objects.all(),
|
queryset=SoftwareProduct.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
label="Software Product",
|
label=_("Software Product"),
|
||||||
)
|
)
|
||||||
version_id = DynamicModelMultipleChoiceField(
|
version_id = DynamicModelMultipleChoiceField(
|
||||||
queryset=SoftwareProductVersion.objects.all(),
|
queryset=SoftwareProductVersion.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
label="Version",
|
label=_("Version"),
|
||||||
)
|
)
|
||||||
installation_id = DynamicModelMultipleChoiceField(
|
installation_id = DynamicModelMultipleChoiceField(
|
||||||
queryset=SoftwareProductInstallation.objects.all(),
|
queryset=SoftwareProductInstallation.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
label="Installation",
|
label=_("Installation"),
|
||||||
)
|
)
|
||||||
tenant_group_id = DynamicModelMultipleChoiceField(
|
tenant_group_id = DynamicModelMultipleChoiceField(
|
||||||
queryset=TenantGroup.objects.all(),
|
queryset=TenantGroup.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
label="Tenant Group",
|
label=_("Tenant Group"),
|
||||||
)
|
)
|
||||||
tenant_id = DynamicModelMultipleChoiceField(
|
tenant_id = DynamicModelMultipleChoiceField(
|
||||||
queryset=Tenant.objects.all(),
|
queryset=Tenant.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
query_params={"group_id": "$tenant_group_id"},
|
query_params={"group_id": "$tenant_group_id"},
|
||||||
label="Tenant",
|
label=_("Tenant"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class SoftwareLicenseBulkImportForm(NetBoxModelImportForm):
|
class SoftwareLicenseBulkImportForm(NetBoxModelImportForm):
|
||||||
support = NullBooleanField(required=False, help_text="Support (values other than 'True' and 'False' are ignored)")
|
support = NullBooleanField(
|
||||||
|
required=False, help_text=_("Support (values other than 'True' and 'False' are ignored)")
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = SoftwareLicense
|
model = SoftwareLicense
|
||||||
@@ -226,7 +229,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")
|
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())
|
||||||
@@ -237,7 +240,7 @@ class SoftwareLicenseBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
software_product = DynamicModelChoiceField(
|
software_product = DynamicModelChoiceField(
|
||||||
queryset=SoftwareProduct.objects.all(),
|
queryset=SoftwareProduct.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
label="Software Product",
|
label=_("Software Product"),
|
||||||
)
|
)
|
||||||
version = DynamicModelChoiceField(
|
version = DynamicModelChoiceField(
|
||||||
queryset=SoftwareProductVersion.objects.all(),
|
queryset=SoftwareProductVersion.objects.all(),
|
||||||
@@ -256,11 +259,11 @@ class SoftwareLicenseBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
tenant_group = DynamicModelChoiceField(
|
tenant_group = DynamicModelChoiceField(
|
||||||
queryset=TenantGroup.objects.all(),
|
queryset=TenantGroup.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
label="Tenant Group",
|
label=_("Tenant Group"),
|
||||||
)
|
)
|
||||||
tenant = DynamicModelChoiceField(
|
tenant = DynamicModelChoiceField(
|
||||||
queryset=Tenant.objects.all(),
|
queryset=Tenant.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
query_params={"group_id": "$tenant_group"},
|
query_params={"group_id": "$tenant_group"},
|
||||||
label="Tenant",
|
label=_("Tenant"),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
from django.forms import CharField
|
from django.forms import CharField
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from dcim.models import Manufacturer
|
from dcim.models import Manufacturer
|
||||||
from netbox.forms import NetBoxModelForm, NetBoxModelFilterSetForm, NetBoxModelImportForm, NetBoxModelBulkEditForm
|
from netbox.forms import NetBoxModelForm, NetBoxModelFilterSetForm, NetBoxModelImportForm, NetBoxModelBulkEditForm
|
||||||
@@ -46,7 +47,7 @@ class SoftwareProductFilterForm(NetBoxModelFilterSetForm):
|
|||||||
manufacturer_id = DynamicModelMultipleChoiceField(
|
manufacturer_id = DynamicModelMultipleChoiceField(
|
||||||
queryset=Manufacturer.objects.all(),
|
queryset=Manufacturer.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
label="Manufacturer",
|
label=_("Manufacturer"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
from django.forms import ValidationError
|
from django.forms import ValidationError
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from dcim.models import Device
|
from dcim.models import Device
|
||||||
from netbox.forms import NetBoxModelForm, NetBoxModelFilterSetForm, NetBoxModelImportForm, NetBoxModelBulkEditForm
|
from netbox.forms import NetBoxModelForm, NetBoxModelFilterSetForm, NetBoxModelImportForm, NetBoxModelBulkEditForm
|
||||||
@@ -22,13 +23,13 @@ class SoftwareProductInstallationForm(NetBoxModelForm):
|
|||||||
virtualmachine = DynamicModelChoiceField(
|
virtualmachine = DynamicModelChoiceField(
|
||||||
queryset=VirtualMachine.objects.all(),
|
queryset=VirtualMachine.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
label="Virtual Machine",
|
label=_("Virtual Machine"),
|
||||||
)
|
)
|
||||||
cluster = DynamicModelChoiceField(queryset=Cluster.objects.all(), required=False)
|
cluster = DynamicModelChoiceField(queryset=Cluster.objects.all(), required=False)
|
||||||
software_product = DynamicModelChoiceField(
|
software_product = DynamicModelChoiceField(
|
||||||
queryset=SoftwareProduct.objects.all(),
|
queryset=SoftwareProduct.objects.all(),
|
||||||
required=True,
|
required=True,
|
||||||
label="Software Product",
|
label=_("Software Product"),
|
||||||
)
|
)
|
||||||
version = DynamicModelChoiceField(
|
version = DynamicModelChoiceField(
|
||||||
queryset=SoftwareProductVersion.objects.all(),
|
queryset=SoftwareProductVersion.objects.all(),
|
||||||
@@ -54,8 +55,8 @@ class SoftwareProductInstallationForm(NetBoxModelForm):
|
|||||||
software_product = self.cleaned_data["software_product"]
|
software_product = self.cleaned_data["software_product"]
|
||||||
if version not in software_product.softwareproductversion_set.all():
|
if version not in software_product.softwareproductversion_set.all():
|
||||||
raise ValidationError(
|
raise ValidationError(
|
||||||
f"Version '{version}' doesn't exist on {software_product}, make sure you've "
|
_("Version '%(version)s' does not belong to %(product)s. Select a compatible version.")
|
||||||
f"selected a compatible version or first select the software product."
|
% {"version": version, "product": software_product}
|
||||||
)
|
)
|
||||||
return version
|
return version
|
||||||
|
|
||||||
@@ -73,27 +74,27 @@ class SoftwareProductInstallationFilterForm(NetBoxModelFilterSetForm):
|
|||||||
device_id = DynamicModelMultipleChoiceField(
|
device_id = DynamicModelMultipleChoiceField(
|
||||||
queryset=Device.objects.all(),
|
queryset=Device.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
label="Device",
|
label=_("Device"),
|
||||||
)
|
)
|
||||||
virtualmachine_id = DynamicModelMultipleChoiceField(
|
virtualmachine_id = DynamicModelMultipleChoiceField(
|
||||||
queryset=VirtualMachine.objects.all(),
|
queryset=VirtualMachine.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
label="Virtual Machine",
|
label=_("Virtual Machine"),
|
||||||
)
|
)
|
||||||
cluster_id = DynamicModelMultipleChoiceField(
|
cluster_id = DynamicModelMultipleChoiceField(
|
||||||
queryset=Cluster.objects.all(),
|
queryset=Cluster.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
label="Cluster",
|
label=_("Cluster"),
|
||||||
)
|
)
|
||||||
software_product_id = DynamicModelMultipleChoiceField(
|
software_product_id = DynamicModelMultipleChoiceField(
|
||||||
queryset=SoftwareProduct.objects.all(),
|
queryset=SoftwareProduct.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
label="Software Product",
|
label=_("Software Product"),
|
||||||
)
|
)
|
||||||
version_id = DynamicModelMultipleChoiceField(
|
version_id = DynamicModelMultipleChoiceField(
|
||||||
queryset=SoftwareProductVersion.objects.all(),
|
queryset=SoftwareProductVersion.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
label="Version",
|
label=_("Version"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -122,13 +123,13 @@ class SoftwareProductInstallationBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
virtualmachine = DynamicModelChoiceField(
|
virtualmachine = DynamicModelChoiceField(
|
||||||
queryset=VirtualMachine.objects.all(),
|
queryset=VirtualMachine.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
label="Virtual Machine",
|
label=_("Virtual Machine"),
|
||||||
)
|
)
|
||||||
cluster = DynamicModelChoiceField(queryset=Cluster.objects.all(), required=False)
|
cluster = DynamicModelChoiceField(queryset=Cluster.objects.all(), required=False)
|
||||||
software_product = DynamicModelChoiceField(
|
software_product = DynamicModelChoiceField(
|
||||||
queryset=SoftwareProduct.objects.all(),
|
queryset=SoftwareProduct.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
label="Software Product",
|
label=_("Software Product"),
|
||||||
)
|
)
|
||||||
version = DynamicModelChoiceField(
|
version = DynamicModelChoiceField(
|
||||||
queryset=SoftwareProductVersion.objects.all(),
|
queryset=SoftwareProductVersion.objects.all(),
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
from django.forms import DateField, CharField, MultipleChoiceField
|
from django.forms import DateField, CharField, MultipleChoiceField
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from dcim.models import Manufacturer
|
from dcim.models import Manufacturer
|
||||||
from netbox.forms import NetBoxModelForm, NetBoxModelFilterSetForm, NetBoxModelImportForm, NetBoxModelBulkEditForm
|
from netbox.forms import NetBoxModelForm, NetBoxModelFilterSetForm, NetBoxModelImportForm, NetBoxModelBulkEditForm
|
||||||
@@ -23,7 +24,7 @@ class SoftwareProductVersionForm(NetBoxModelForm):
|
|||||||
software_product = DynamicModelChoiceField(
|
software_product = DynamicModelChoiceField(
|
||||||
queryset=SoftwareProduct.objects.all(),
|
queryset=SoftwareProduct.objects.all(),
|
||||||
required=True,
|
required=True,
|
||||||
label="Software Product",
|
label=_("Software Product"),
|
||||||
widget=APISelect(attrs={"data-url": reverse_lazy("plugins-api:netbox_slm-api:softwareproduct-list")}),
|
widget=APISelect(attrs={"data-url": reverse_lazy("plugins-api:netbox_slm-api:softwareproduct-list")}),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -64,17 +65,19 @@ class SoftwareProductVersionFilterForm(NetBoxModelFilterSetForm):
|
|||||||
manufacturer_id = DynamicModelMultipleChoiceField(
|
manufacturer_id = DynamicModelMultipleChoiceField(
|
||||||
queryset=Manufacturer.objects.all(),
|
queryset=Manufacturer.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
label="Manufacturer",
|
label=_("Manufacturer"),
|
||||||
)
|
)
|
||||||
software_product_id = DynamicModelMultipleChoiceField(
|
software_product_id = DynamicModelMultipleChoiceField(
|
||||||
queryset=SoftwareProduct.objects.all(),
|
queryset=SoftwareProduct.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
label="Software Product",
|
label=_("Software Product"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class SoftwareProductVersionBulkImportForm(NetBoxModelImportForm):
|
class SoftwareProductVersionBulkImportForm(NetBoxModelImportForm):
|
||||||
release_type = CharField(help_text=f"Release type (possible values: {SoftwareReleaseTypes.values})")
|
release_type = CharField(
|
||||||
|
help_text=_("Release type (possible values: %(values)s)") % {"values": SoftwareReleaseTypes.values}
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = SoftwareProductVersion
|
model = SoftwareProductVersion
|
||||||
@@ -122,5 +125,5 @@ class SoftwareProductVersionBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
software_product = DynamicModelChoiceField(
|
software_product = DynamicModelChoiceField(
|
||||||
queryset=SoftwareProduct.objects.all(),
|
queryset=SoftwareProduct.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
label="Software Product",
|
label=_("Software Product"),
|
||||||
)
|
)
|
||||||
|
|||||||
Binary file not shown.
@@ -0,0 +1,263 @@
|
|||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: netbox-slm 1.9.1\n"
|
||||||
|
"Language: de\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
|
|
||||||
|
msgid "Add"
|
||||||
|
msgstr "Hinzufügen"
|
||||||
|
|
||||||
|
msgid "Import"
|
||||||
|
msgstr "Importieren"
|
||||||
|
|
||||||
|
msgid "Software Lifecycle"
|
||||||
|
msgstr "Software-Lebenszyklus"
|
||||||
|
|
||||||
|
msgid "Software Products"
|
||||||
|
msgstr "Softwareprodukte"
|
||||||
|
|
||||||
|
msgid "Software Product"
|
||||||
|
msgstr "Softwareprodukt"
|
||||||
|
|
||||||
|
msgid "Software Product Version"
|
||||||
|
msgstr "Softwareproduktversion"
|
||||||
|
|
||||||
|
msgid "Software Product Installation"
|
||||||
|
msgstr "Softwareproduktinstallation"
|
||||||
|
|
||||||
|
msgid "Software License"
|
||||||
|
msgstr "Softwarelizenz"
|
||||||
|
|
||||||
|
msgid "License Details"
|
||||||
|
msgstr "Lizenzdetails"
|
||||||
|
|
||||||
|
msgid "Versions"
|
||||||
|
msgstr "Versionen"
|
||||||
|
|
||||||
|
msgid "Version"
|
||||||
|
msgstr "Version"
|
||||||
|
|
||||||
|
msgid "Installations"
|
||||||
|
msgstr "Installationen"
|
||||||
|
|
||||||
|
msgid "Installation"
|
||||||
|
msgstr "Installation"
|
||||||
|
|
||||||
|
msgid "Add an installation"
|
||||||
|
msgstr "Installation hinzufügen"
|
||||||
|
|
||||||
|
msgid "Licenses"
|
||||||
|
msgstr "Lizenzen"
|
||||||
|
|
||||||
|
msgid "Tenancy"
|
||||||
|
msgstr "Mandantenzuordnung"
|
||||||
|
|
||||||
|
msgid "Tenant Group"
|
||||||
|
msgstr "Mandantengruppe"
|
||||||
|
|
||||||
|
msgid "Tenant"
|
||||||
|
msgstr "Mandant"
|
||||||
|
|
||||||
|
msgid "Manufacturer"
|
||||||
|
msgstr "Hersteller"
|
||||||
|
|
||||||
|
msgid "Device"
|
||||||
|
msgstr "Gerät"
|
||||||
|
|
||||||
|
msgid "Virtual Machine"
|
||||||
|
msgstr "Virtuelle Maschine"
|
||||||
|
|
||||||
|
msgid "Cluster"
|
||||||
|
msgstr "Cluster"
|
||||||
|
|
||||||
|
msgid "Name"
|
||||||
|
msgstr "Name"
|
||||||
|
|
||||||
|
msgid "Description"
|
||||||
|
msgstr "Beschreibung"
|
||||||
|
|
||||||
|
msgid "Type"
|
||||||
|
msgstr "Typ"
|
||||||
|
|
||||||
|
msgid "SPDX expression"
|
||||||
|
msgstr "SPDX-Ausdruck"
|
||||||
|
|
||||||
|
msgid "Stored location"
|
||||||
|
msgstr "Ablageort"
|
||||||
|
|
||||||
|
msgid "Start date"
|
||||||
|
msgstr "Startdatum"
|
||||||
|
|
||||||
|
msgid "Expiration date"
|
||||||
|
msgstr "Ablaufdatum"
|
||||||
|
|
||||||
|
msgid "Support"
|
||||||
|
msgstr "Support"
|
||||||
|
|
||||||
|
msgid "License amount"
|
||||||
|
msgstr "Lizenzanzahl"
|
||||||
|
|
||||||
|
msgid "Release date"
|
||||||
|
msgstr "Veröffentlichungsdatum"
|
||||||
|
|
||||||
|
msgid "Documentation URL"
|
||||||
|
msgstr "Dokumentations-URL"
|
||||||
|
|
||||||
|
msgid "End of support"
|
||||||
|
msgstr "Supportende"
|
||||||
|
|
||||||
|
msgid "Filename"
|
||||||
|
msgstr "Dateiname"
|
||||||
|
|
||||||
|
msgid "File checksum"
|
||||||
|
msgstr "Dateiprüfsumme"
|
||||||
|
|
||||||
|
msgid "Release type"
|
||||||
|
msgstr "Veröffentlichungstyp"
|
||||||
|
|
||||||
|
msgid "Release candidate"
|
||||||
|
msgstr "Veröffentlichungskandidat"
|
||||||
|
|
||||||
|
msgid "Stable release"
|
||||||
|
msgstr "Stabile Veröffentlichung"
|
||||||
|
|
||||||
|
msgid "Alpha"
|
||||||
|
msgstr "Alpha"
|
||||||
|
|
||||||
|
msgid "Beta"
|
||||||
|
msgstr "Beta"
|
||||||
|
|
||||||
|
msgid "Tags"
|
||||||
|
msgstr "Tags"
|
||||||
|
|
||||||
|
msgid "None"
|
||||||
|
msgstr "Keine"
|
||||||
|
|
||||||
|
msgid "Link"
|
||||||
|
msgstr "Link"
|
||||||
|
|
||||||
|
msgid "Support (values other than 'True' and 'False' are ignored)"
|
||||||
|
msgstr "Support (andere Werte als ‚True‘ und ‚False‘ werden ignoriert)"
|
||||||
|
|
||||||
|
msgid "Release type (possible values: %(values)s)"
|
||||||
|
msgstr "Veröffentlichungstyp (mögliche Werte: %(values)s)"
|
||||||
|
|
||||||
|
msgid "Version '%(version)s' does not belong to %(product)s. Select a compatible version."
|
||||||
|
msgstr "Version ‚%(version)s‘ gehört nicht zu %(product)s. Wähle eine kompatible Version aus."
|
||||||
|
|
||||||
|
msgid "Installation requires exactly one platform destination."
|
||||||
|
msgstr "Eine Installation benötigt genau ein Plattformziel."
|
||||||
|
|
||||||
|
msgid "%(value)s is not a known SPDX license expression"
|
||||||
|
msgstr "%(value)s ist kein bekannter SPDX-Lizenzausdruck"
|
||||||
|
|
||||||
|
msgid "The selected tenant does not belong to the selected tenant group."
|
||||||
|
msgstr "Der ausgewählte Mandant gehört nicht zur ausgewählten Mandantengruppe."
|
||||||
|
|
||||||
|
msgid "Software Lifecycle Management"
|
||||||
|
msgstr "Software-Lebenszyklusverwaltung"
|
||||||
|
|
||||||
|
msgid "Software Lifecycle Management NetBox Plugin."
|
||||||
|
msgstr "NetBox-Plugin zur Verwaltung des Software-Lebenszyklus."
|
||||||
|
|
||||||
|
msgid "name"
|
||||||
|
msgstr "Name"
|
||||||
|
|
||||||
|
msgid "comments"
|
||||||
|
msgstr "Kommentare"
|
||||||
|
|
||||||
|
msgid "description"
|
||||||
|
msgstr "Beschreibung"
|
||||||
|
|
||||||
|
msgid "manufacturer"
|
||||||
|
msgstr "Hersteller"
|
||||||
|
|
||||||
|
msgid "software product"
|
||||||
|
msgstr "Softwareprodukt"
|
||||||
|
|
||||||
|
msgid "software products"
|
||||||
|
msgstr "Softwareprodukte"
|
||||||
|
|
||||||
|
msgid "release date"
|
||||||
|
msgstr "Veröffentlichungsdatum"
|
||||||
|
|
||||||
|
msgid "documentation URL"
|
||||||
|
msgstr "Dokumentations-URL"
|
||||||
|
|
||||||
|
msgid "end of support"
|
||||||
|
msgstr "Supportende"
|
||||||
|
|
||||||
|
msgid "filename"
|
||||||
|
msgstr "Dateiname"
|
||||||
|
|
||||||
|
msgid "file checksum"
|
||||||
|
msgstr "Dateiprüfsumme"
|
||||||
|
|
||||||
|
msgid "file link"
|
||||||
|
msgstr "Dateilink"
|
||||||
|
|
||||||
|
msgid "release type"
|
||||||
|
msgstr "Veröffentlichungstyp"
|
||||||
|
|
||||||
|
msgid "software product version"
|
||||||
|
msgstr "Softwareproduktversion"
|
||||||
|
|
||||||
|
msgid "software product versions"
|
||||||
|
msgstr "Softwareproduktversionen"
|
||||||
|
|
||||||
|
msgid "device"
|
||||||
|
msgstr "Gerät"
|
||||||
|
|
||||||
|
msgid "virtual machine"
|
||||||
|
msgstr "Virtuelle Maschine"
|
||||||
|
|
||||||
|
msgid "cluster"
|
||||||
|
msgstr "Cluster"
|
||||||
|
|
||||||
|
msgid "version"
|
||||||
|
msgstr "Version"
|
||||||
|
|
||||||
|
msgid "software product installation"
|
||||||
|
msgstr "Softwareproduktinstallation"
|
||||||
|
|
||||||
|
msgid "software product installations"
|
||||||
|
msgstr "Softwareproduktinstallationen"
|
||||||
|
|
||||||
|
msgid "type"
|
||||||
|
msgstr "Typ"
|
||||||
|
|
||||||
|
msgid "stored location"
|
||||||
|
msgstr "Ablageort"
|
||||||
|
|
||||||
|
msgid "stored location URL"
|
||||||
|
msgstr "Ablageort-URL"
|
||||||
|
|
||||||
|
msgid "start date"
|
||||||
|
msgstr "Startdatum"
|
||||||
|
|
||||||
|
msgid "expiration date"
|
||||||
|
msgstr "Ablaufdatum"
|
||||||
|
|
||||||
|
msgid "support"
|
||||||
|
msgstr "Support"
|
||||||
|
|
||||||
|
msgid "license amount"
|
||||||
|
msgstr "Lizenzanzahl"
|
||||||
|
|
||||||
|
msgid "installation"
|
||||||
|
msgstr "Installation"
|
||||||
|
|
||||||
|
msgid "tenant group"
|
||||||
|
msgstr "Mandantengruppe"
|
||||||
|
|
||||||
|
msgid "tenant"
|
||||||
|
msgstr "Mandant"
|
||||||
|
|
||||||
|
msgid "software license"
|
||||||
|
msgstr "Softwarelizenz"
|
||||||
|
|
||||||
|
msgid "software licenses"
|
||||||
|
msgstr "Softwarelizenzen"
|
||||||
+91
-42
@@ -2,6 +2,7 @@ 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
|
from django.utils.html import format_html
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
from license_expression import Licensing, get_spdx_licensing
|
from license_expression import Licensing, get_spdx_licensing
|
||||||
|
|
||||||
from netbox.models import NetBoxModel
|
from netbox.models import NetBoxModel
|
||||||
@@ -21,14 +22,20 @@ class LaxURLField(models.URLField):
|
|||||||
|
|
||||||
|
|
||||||
class SoftwareProduct(NetBoxModel):
|
class SoftwareProduct(NetBoxModel):
|
||||||
name = models.CharField(max_length=128)
|
name = models.CharField(_("name"), max_length=128)
|
||||||
comments = models.TextField(blank=True)
|
comments = models.TextField(_("comments"), blank=True)
|
||||||
|
|
||||||
description = models.CharField(max_length=255, null=True, blank=True)
|
description = models.CharField(_("description"), max_length=255, null=True, blank=True)
|
||||||
manufacturer = models.ForeignKey(to="dcim.Manufacturer", on_delete=models.PROTECT, null=True, blank=True)
|
manufacturer = models.ForeignKey(
|
||||||
|
to="dcim.Manufacturer", verbose_name=_("manufacturer"), on_delete=models.PROTECT, null=True, blank=True
|
||||||
|
)
|
||||||
|
|
||||||
objects = RestrictedQuerySet.as_manager()
|
objects = RestrictedQuerySet.as_manager()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("software product")
|
||||||
|
verbose_name_plural = _("software products")
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
@@ -44,25 +51,26 @@ class SoftwareProduct(NetBoxModel):
|
|||||||
|
|
||||||
|
|
||||||
class SoftwareReleaseTypes(models.TextChoices):
|
class SoftwareReleaseTypes(models.TextChoices):
|
||||||
ALPHA = "A", "Alpha"
|
ALPHA = "A", _("Alpha")
|
||||||
BETA = "B", "Beta"
|
BETA = "B", _("Beta")
|
||||||
RELEASE_CANDIDATE = "RC", "Release candidate"
|
RELEASE_CANDIDATE = "RC", _("Release candidate")
|
||||||
STABLE = "S", "Stable release"
|
STABLE = "S", _("Stable release")
|
||||||
|
|
||||||
|
|
||||||
class SoftwareProductVersion(NetBoxModel):
|
class SoftwareProductVersion(NetBoxModel):
|
||||||
name = models.CharField(max_length=64)
|
name = models.CharField(_("name"), max_length=64)
|
||||||
comments = models.TextField(blank=True)
|
comments = models.TextField(_("comments"), blank=True)
|
||||||
|
|
||||||
description = models.CharField(max_length=255, null=True, blank=True)
|
description = models.CharField(_("description"), max_length=255, null=True, blank=True)
|
||||||
release_date = models.DateField(null=True, blank=True)
|
release_date = models.DateField(_("release date"), null=True, blank=True)
|
||||||
documentation_url = LaxURLField(max_length=1024, null=True, blank=True)
|
documentation_url = LaxURLField(_("documentation URL"), max_length=1024, null=True, blank=True)
|
||||||
end_of_support = models.DateField(null=True, blank=True)
|
end_of_support = models.DateField(_("end of support"), null=True, blank=True)
|
||||||
filename = models.CharField(max_length=64, null=True, blank=True)
|
filename = models.CharField(_("filename"), max_length=64, null=True, blank=True)
|
||||||
file_checksum = models.CharField(max_length=128, null=True, blank=True)
|
file_checksum = models.CharField(_("file checksum"), max_length=128, null=True, blank=True)
|
||||||
file_link = LaxURLField(max_length=1024, null=True, blank=True)
|
file_link = LaxURLField(_("file link"), max_length=1024, null=True, blank=True)
|
||||||
|
|
||||||
release_type = models.CharField(
|
release_type = models.CharField(
|
||||||
|
_("release type"),
|
||||||
max_length=3,
|
max_length=3,
|
||||||
choices=SoftwareReleaseTypes.choices,
|
choices=SoftwareReleaseTypes.choices,
|
||||||
default=SoftwareReleaseTypes.STABLE,
|
default=SoftwareReleaseTypes.STABLE,
|
||||||
@@ -70,11 +78,16 @@ class SoftwareProductVersion(NetBoxModel):
|
|||||||
|
|
||||||
software_product = models.ForeignKey(
|
software_product = models.ForeignKey(
|
||||||
to="netbox_slm.SoftwareProduct",
|
to="netbox_slm.SoftwareProduct",
|
||||||
|
verbose_name=_("software product"),
|
||||||
on_delete=models.PROTECT,
|
on_delete=models.PROTECT,
|
||||||
)
|
)
|
||||||
|
|
||||||
objects = RestrictedQuerySet.as_manager()
|
objects = RestrictedQuerySet.as_manager()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("software product version")
|
||||||
|
verbose_name_plural = _("software product versions")
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
@@ -88,15 +101,27 @@ class SoftwareProductVersion(NetBoxModel):
|
|||||||
|
|
||||||
|
|
||||||
class SoftwareProductInstallation(NetBoxModel):
|
class SoftwareProductInstallation(NetBoxModel):
|
||||||
comments = models.TextField(blank=True)
|
comments = models.TextField(_("comments"), blank=True)
|
||||||
|
|
||||||
device = models.ForeignKey(to="dcim.Device", on_delete=models.PROTECT, null=True, blank=True)
|
device = models.ForeignKey(
|
||||||
virtualmachine = models.ForeignKey(
|
to="dcim.Device", verbose_name=_("device"), on_delete=models.PROTECT, null=True, blank=True
|
||||||
to="virtualization.VirtualMachine", on_delete=models.PROTECT, null=True, blank=True
|
)
|
||||||
|
virtualmachine = models.ForeignKey(
|
||||||
|
to="virtualization.VirtualMachine",
|
||||||
|
verbose_name=_("virtual machine"),
|
||||||
|
on_delete=models.PROTECT,
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
)
|
||||||
|
cluster = models.ForeignKey(
|
||||||
|
to="virtualization.Cluster", verbose_name=_("cluster"), on_delete=models.PROTECT, null=True, blank=True
|
||||||
|
)
|
||||||
|
software_product = models.ForeignKey(
|
||||||
|
to="netbox_slm.SoftwareProduct", verbose_name=_("software product"), on_delete=models.PROTECT
|
||||||
|
)
|
||||||
|
version = models.ForeignKey(
|
||||||
|
to="netbox_slm.SoftwareProductVersion", verbose_name=_("version"), on_delete=models.PROTECT
|
||||||
)
|
)
|
||||||
cluster = models.ForeignKey(to="virtualization.Cluster", on_delete=models.PROTECT, 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)
|
|
||||||
|
|
||||||
objects = RestrictedQuerySet.as_manager()
|
objects = RestrictedQuerySet.as_manager()
|
||||||
|
|
||||||
@@ -104,6 +129,8 @@ class SoftwareProductInstallation(NetBoxModel):
|
|||||||
return f"{self.pk} ({self.platform})"
|
return f"{self.pk} ({self.platform})"
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
verbose_name = _("software product installation")
|
||||||
|
verbose_name_plural = _("software product installations")
|
||||||
constraints = [
|
constraints = [
|
||||||
models.CheckConstraint(
|
models.CheckConstraint(
|
||||||
name="%(app_label)s_%(class)s_platform",
|
name="%(app_label)s_%(class)s_platform",
|
||||||
@@ -112,7 +139,7 @@ class SoftwareProductInstallation(NetBoxModel):
|
|||||||
| models.Q(device__isnull=True, virtualmachine__isnull=False, cluster__isnull=True)
|
| models.Q(device__isnull=True, virtualmachine__isnull=False, cluster__isnull=True)
|
||||||
| models.Q(device__isnull=True, virtualmachine__isnull=True, cluster__isnull=False)
|
| models.Q(device__isnull=True, virtualmachine__isnull=True, cluster__isnull=False)
|
||||||
),
|
),
|
||||||
violation_error_message="Installation requires exactly one platform destination.",
|
violation_error_message=_("Installation requires exactly one platform destination."),
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -141,30 +168,45 @@ def spdx_license_names():
|
|||||||
def validate_spdx_expression(value):
|
def validate_spdx_expression(value):
|
||||||
expression_info = spdx_licensing.validate(value)
|
expression_info = spdx_licensing.validate(value)
|
||||||
if expression_info.errors:
|
if expression_info.errors:
|
||||||
raise ValidationError(f"{value} is not a known SPDX license expression")
|
raise ValidationError(_("%(value)s is not a known SPDX license expression") % {"value": value})
|
||||||
|
|
||||||
|
|
||||||
class SoftwareLicense(NetBoxModel):
|
class SoftwareLicense(NetBoxModel):
|
||||||
name = models.CharField(max_length=128)
|
name = models.CharField(_("name"), max_length=128)
|
||||||
comments = models.TextField(blank=True)
|
comments = models.TextField(_("comments"), blank=True)
|
||||||
|
|
||||||
description = models.CharField(max_length=255, null=True, blank=True)
|
description = models.CharField(_("description"), max_length=255, null=True, blank=True)
|
||||||
type = models.CharField(max_length=128)
|
type = models.CharField(_("type"), max_length=128)
|
||||||
spdx_expression = models.CharField(max_length=64, null=True, blank=True, validators=[validate_spdx_expression])
|
spdx_expression = models.CharField(
|
||||||
stored_location = models.CharField(max_length=255, null=True, blank=True)
|
_("SPDX expression"), max_length=64, null=True, blank=True, validators=[validate_spdx_expression]
|
||||||
stored_location_url = LaxURLField(max_length=1024, null=True, blank=True)
|
)
|
||||||
start_date = models.DateField(null=True, blank=True)
|
stored_location = models.CharField(_("stored location"), max_length=255, null=True, blank=True)
|
||||||
expiration_date = models.DateField(null=True, blank=True)
|
stored_location_url = LaxURLField(_("stored location URL"), max_length=1024, null=True, blank=True)
|
||||||
support = models.BooleanField(default=None, null=True, blank=True)
|
start_date = models.DateField(_("start date"), null=True, blank=True)
|
||||||
license_amount = models.PositiveIntegerField(default=None, null=True, blank=True)
|
expiration_date = models.DateField(_("expiration date"), null=True, blank=True)
|
||||||
|
support = models.BooleanField(_("support"), default=None, null=True, blank=True)
|
||||||
|
license_amount = models.PositiveIntegerField(_("license amount"), default=None, null=True, blank=True)
|
||||||
|
|
||||||
software_product = models.ForeignKey(to="netbox_slm.SoftwareProduct", on_delete=models.PROTECT)
|
software_product = models.ForeignKey(
|
||||||
version = models.ForeignKey(to="netbox_slm.SoftwareProductVersion", on_delete=models.PROTECT, null=True, blank=True)
|
to="netbox_slm.SoftwareProduct", verbose_name=_("software product"), on_delete=models.PROTECT
|
||||||
|
)
|
||||||
|
version = models.ForeignKey(
|
||||||
|
to="netbox_slm.SoftwareProductVersion",
|
||||||
|
verbose_name=_("version"),
|
||||||
|
on_delete=models.PROTECT,
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
)
|
||||||
installation = models.ForeignKey(
|
installation = models.ForeignKey(
|
||||||
to="netbox_slm.SoftwareProductInstallation", on_delete=models.SET_NULL, null=True, blank=True
|
to="netbox_slm.SoftwareProductInstallation",
|
||||||
|
verbose_name=_("installation"),
|
||||||
|
on_delete=models.SET_NULL,
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
)
|
)
|
||||||
tenant_group = models.ForeignKey(
|
tenant_group = models.ForeignKey(
|
||||||
to="tenancy.TenantGroup",
|
to="tenancy.TenantGroup",
|
||||||
|
verbose_name=_("tenant group"),
|
||||||
on_delete=models.PROTECT,
|
on_delete=models.PROTECT,
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
@@ -172,6 +214,7 @@ class SoftwareLicense(NetBoxModel):
|
|||||||
)
|
)
|
||||||
tenant = models.ForeignKey(
|
tenant = models.ForeignKey(
|
||||||
to="tenancy.Tenant",
|
to="tenancy.Tenant",
|
||||||
|
verbose_name=_("tenant"),
|
||||||
on_delete=models.PROTECT,
|
on_delete=models.PROTECT,
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
@@ -180,6 +223,10 @@ class SoftwareLicense(NetBoxModel):
|
|||||||
|
|
||||||
objects = RestrictedQuerySet.as_manager()
|
objects = RestrictedQuerySet.as_manager()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("software license")
|
||||||
|
verbose_name_plural = _("software licenses")
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
@@ -189,10 +236,12 @@ class SoftwareLicense(NetBoxModel):
|
|||||||
def clean(self):
|
def clean(self):
|
||||||
super().clean()
|
super().clean()
|
||||||
if self.tenant_group and self.tenant and self.tenant.group_id != self.tenant_group_id:
|
if self.tenant_group and self.tenant and self.tenant.group_id != self.tenant_group_id:
|
||||||
raise ValidationError({"tenant": "The selected tenant does not belong to the selected tenant group."})
|
raise ValidationError(
|
||||||
|
{"tenant": _("The selected tenant does not belong to the selected tenant group.")}
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def stored_location_txt(self):
|
def stored_location_txt(self):
|
||||||
if self.stored_location_url and not self.stored_location:
|
if self.stored_location_url and not self.stored_location:
|
||||||
return "Link"
|
return _("Link")
|
||||||
return self.stored_location
|
return self.stored_location
|
||||||
|
|||||||
+14
-13
@@ -1,21 +1,22 @@
|
|||||||
from netbox.plugins import PluginMenuButton, PluginMenuItem, PluginMenu, get_plugin_config
|
from netbox.plugins import PluginMenuButton, PluginMenuItem, PluginMenu, get_plugin_config
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
from . import SLMConfig
|
from . import SLMConfig
|
||||||
|
|
||||||
slm_items = (
|
slm_items = (
|
||||||
PluginMenuItem(
|
PluginMenuItem(
|
||||||
link="plugins:netbox_slm:softwareproduct_list",
|
link="plugins:netbox_slm:softwareproduct_list",
|
||||||
link_text="Software Products",
|
link_text=_("Software Products"),
|
||||||
permissions=["netbox_slm.add_softwareproduct"],
|
permissions=["netbox_slm.add_softwareproduct"],
|
||||||
buttons=(
|
buttons=(
|
||||||
PluginMenuButton(
|
PluginMenuButton(
|
||||||
"plugins:netbox_slm:softwareproduct_add",
|
"plugins:netbox_slm:softwareproduct_add",
|
||||||
"Add",
|
_("Add"),
|
||||||
"mdi mdi-plus-thick",
|
"mdi mdi-plus-thick",
|
||||||
permissions=["netbox_slm.add_softwareproduct"],
|
permissions=["netbox_slm.add_softwareproduct"],
|
||||||
),
|
),
|
||||||
PluginMenuButton(
|
PluginMenuButton(
|
||||||
"plugins:netbox_slm:softwareproduct_bulk_import",
|
"plugins:netbox_slm:softwareproduct_bulk_import",
|
||||||
"Import",
|
_("Import"),
|
||||||
"mdi mdi-upload",
|
"mdi mdi-upload",
|
||||||
permissions=["netbox_slm.import_softwareproduct"],
|
permissions=["netbox_slm.import_softwareproduct"],
|
||||||
),
|
),
|
||||||
@@ -23,18 +24,18 @@ slm_items = (
|
|||||||
),
|
),
|
||||||
PluginMenuItem(
|
PluginMenuItem(
|
||||||
link="plugins:netbox_slm:softwareproductversion_list",
|
link="plugins:netbox_slm:softwareproductversion_list",
|
||||||
link_text="Versions",
|
link_text=_("Versions"),
|
||||||
permissions=["netbox_slm.add_softwareproductversion"],
|
permissions=["netbox_slm.add_softwareproductversion"],
|
||||||
buttons=(
|
buttons=(
|
||||||
PluginMenuButton(
|
PluginMenuButton(
|
||||||
"plugins:netbox_slm:softwareproductversion_add",
|
"plugins:netbox_slm:softwareproductversion_add",
|
||||||
"Add",
|
_("Add"),
|
||||||
"mdi mdi-plus-thick",
|
"mdi mdi-plus-thick",
|
||||||
permissions=["netbox_slm.add_softwareproductversion"],
|
permissions=["netbox_slm.add_softwareproductversion"],
|
||||||
),
|
),
|
||||||
PluginMenuButton(
|
PluginMenuButton(
|
||||||
"plugins:netbox_slm:softwareproductversion_bulk_import",
|
"plugins:netbox_slm:softwareproductversion_bulk_import",
|
||||||
"Import",
|
_("Import"),
|
||||||
"mdi mdi-upload",
|
"mdi mdi-upload",
|
||||||
permissions=["netbox_slm.import_softwareproductversion"],
|
permissions=["netbox_slm.import_softwareproductversion"],
|
||||||
),
|
),
|
||||||
@@ -42,18 +43,18 @@ slm_items = (
|
|||||||
),
|
),
|
||||||
PluginMenuItem(
|
PluginMenuItem(
|
||||||
link="plugins:netbox_slm:softwareproductinstallation_list",
|
link="plugins:netbox_slm:softwareproductinstallation_list",
|
||||||
link_text="Installations",
|
link_text=_("Installations"),
|
||||||
permissions=["netbox_slm.add_softwareproductinstallation"],
|
permissions=["netbox_slm.add_softwareproductinstallation"],
|
||||||
buttons=(
|
buttons=(
|
||||||
PluginMenuButton(
|
PluginMenuButton(
|
||||||
"plugins:netbox_slm:softwareproductinstallation_add",
|
"plugins:netbox_slm:softwareproductinstallation_add",
|
||||||
"Add",
|
_("Add"),
|
||||||
"mdi mdi-plus-thick",
|
"mdi mdi-plus-thick",
|
||||||
permissions=["netbox_slm.add_softwareproductinstallation"],
|
permissions=["netbox_slm.add_softwareproductinstallation"],
|
||||||
),
|
),
|
||||||
PluginMenuButton(
|
PluginMenuButton(
|
||||||
"plugins:netbox_slm:softwareproductinstallation_bulk_import",
|
"plugins:netbox_slm:softwareproductinstallation_bulk_import",
|
||||||
"Import",
|
_("Import"),
|
||||||
"mdi mdi-upload",
|
"mdi mdi-upload",
|
||||||
permissions=["netbox_slm.import_softwareproductinstallation"],
|
permissions=["netbox_slm.import_softwareproductinstallation"],
|
||||||
),
|
),
|
||||||
@@ -61,18 +62,18 @@ slm_items = (
|
|||||||
),
|
),
|
||||||
PluginMenuItem(
|
PluginMenuItem(
|
||||||
link="plugins:netbox_slm:softwarelicense_list",
|
link="plugins:netbox_slm:softwarelicense_list",
|
||||||
link_text="Licenses",
|
link_text=_("Licenses"),
|
||||||
permissions=["netbox_slm.add_softwarelicense"],
|
permissions=["netbox_slm.add_softwarelicense"],
|
||||||
buttons=(
|
buttons=(
|
||||||
PluginMenuButton(
|
PluginMenuButton(
|
||||||
"plugins:netbox_slm:softwarelicense_add",
|
"plugins:netbox_slm:softwarelicense_add",
|
||||||
"Add",
|
_("Add"),
|
||||||
"mdi mdi-plus-thick",
|
"mdi mdi-plus-thick",
|
||||||
permissions=["netbox_slm.add_softwarelicense"],
|
permissions=["netbox_slm.add_softwarelicense"],
|
||||||
),
|
),
|
||||||
PluginMenuButton(
|
PluginMenuButton(
|
||||||
"plugins:netbox_slm:softwarelicense_bulk_import",
|
"plugins:netbox_slm:softwarelicense_bulk_import",
|
||||||
"Import",
|
_("Import"),
|
||||||
"mdi mdi-upload",
|
"mdi mdi-upload",
|
||||||
permissions=["netbox_slm.import_softwarelicense"],
|
permissions=["netbox_slm.import_softwarelicense"],
|
||||||
),
|
),
|
||||||
@@ -82,7 +83,7 @@ slm_items = (
|
|||||||
|
|
||||||
if get_plugin_config("netbox_slm", "top_level_menu"):
|
if get_plugin_config("netbox_slm", "top_level_menu"):
|
||||||
menu = PluginMenu(
|
menu = PluginMenu(
|
||||||
label="Software Lifecycle",
|
label=_("Software Lifecycle"),
|
||||||
groups=((SLMConfig.verbose_name, slm_items),),
|
groups=((SLMConfig.verbose_name, slm_items),),
|
||||||
icon_class="mdi mdi-content-save",
|
icon_class="mdi mdi-content-save",
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import django_tables2 as tables
|
import django_tables2 as tables
|
||||||
from django.db.models import Count, F, Value
|
from django.db.models import Count, F, Value
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from netbox.tables import NetBoxTable, ToggleColumn, columns
|
from netbox.tables import NetBoxTable, ToggleColumn, columns
|
||||||
from netbox_slm.models import SoftwareProduct, SoftwareProductVersion, SoftwareProductInstallation, SoftwareLicense
|
from netbox_slm.models import SoftwareProduct, SoftwareProductVersion, SoftwareProductInstallation, SoftwareLicense
|
||||||
@@ -45,7 +46,7 @@ class SoftwareProductVersionTable(NetBoxTable):
|
|||||||
|
|
||||||
pk = ToggleColumn()
|
pk = ToggleColumn()
|
||||||
name = tables.LinkColumn()
|
name = tables.LinkColumn()
|
||||||
software_product = tables.Column(accessor="software_product", verbose_name="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 +90,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", verbose_name="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,14 +138,14 @@ class SoftwareLicenseTable(NetBoxTable):
|
|||||||
name = tables.LinkColumn()
|
name = tables.LinkColumn()
|
||||||
|
|
||||||
type = tables.Column()
|
type = tables.Column()
|
||||||
spdx_expression = tables.Column(verbose_name="SPDX expression")
|
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", verbose_name="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)
|
||||||
tenant_group = tables.Column(accessor="tenant_group", verbose_name="Tenant Group", linkify=True)
|
tenant_group = tables.Column(accessor="tenant_group", verbose_name=_("Tenant Group"), linkify=True)
|
||||||
tenant = tables.Column(accessor="tenant", linkify=True)
|
tenant = tables.Column(accessor="tenant", linkify=True)
|
||||||
|
|
||||||
tags = columns.TagColumn(url_name="plugins:netbox_slm:softwarelicense_list")
|
tags = columns.TagColumn(url_name="plugins:netbox_slm:softwarelicense_list")
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
{% load helpers %}
|
{% load helpers %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h2 class="card-header">
|
<h2 class="card-header">
|
||||||
Installations
|
{% trans "Installations" %}
|
||||||
{% if perms.netbox_slm.add_softwareproductinstallation %}
|
{% if perms.netbox_slm.add_softwareproductinstallation %}
|
||||||
<div class="card-actions">
|
<div class="card-actions">
|
||||||
<a href="{% url 'plugins:netbox_slm:softwareproductinstallation_add' %}?{{ search_obj }}={{ object.pk }}" class="btn btn-ghost-primary btn-sm">
|
<a href="{% url 'plugins:netbox_slm:softwareproductinstallation_add' %}?{{ search_obj }}={{ object.pk }}" class="btn btn-ghost-primary btn-sm">
|
||||||
<span class="mdi mdi-plus-thick" aria-hidden="true"></span> Add an installation
|
<span class="mdi mdi-plus-thick" aria-hidden="true"></span> {% trans "Add an installation" %}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -3,53 +3,54 @@
|
|||||||
{% load static %}
|
{% load static %}
|
||||||
{% load helpers %}
|
{% load helpers %}
|
||||||
{% load plugins %}
|
{% load plugins %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row my-3">
|
<div class="row my-3">
|
||||||
<div class="col col-md-6">
|
<div class="col col-md-6">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h5 class="card-header">
|
<h5 class="card-header">
|
||||||
Software License
|
{% trans "Software License" %}
|
||||||
</h5>
|
</h5>
|
||||||
<table class="table table-hover attr-table">
|
<table class="table table-hover attr-table">
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Name</th>
|
<th scope="row">{% trans "Name" %}</th>
|
||||||
<td>{{ object.name }}</td>
|
<td>{{ object.name }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Description</th>
|
<th scope="row">{% trans "Description" %}</th>
|
||||||
<td>{{ object.description }}</td>
|
<td>{{ object.description }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Type</th>
|
<th scope="row">{% trans "Type" %}</th>
|
||||||
<td>{{ object.type }}</td>
|
<td>{{ object.type }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">SPDX expression</th>
|
<th scope="row">{% trans "SPDX expression" %}</th>
|
||||||
<td>{{ object.spdx_expression }}</td>
|
<td>{{ object.spdx_expression }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Software Product</th>
|
<th scope="row">{% trans "Software Product" %}</th>
|
||||||
<td>{{ object.software_product|linkify }}</td>
|
<td>{{ object.software_product|linkify }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Version</th>
|
<th scope="row">{% trans "Version" %}</th>
|
||||||
<td>{{ object.version|linkify }}</td>
|
<td>{{ object.version|linkify }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Installation</th>
|
<th scope="row">{% trans "Installation" %}</th>
|
||||||
<td>{{ object.installation|linkify }}</td>
|
<td>{{ object.installation|linkify }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Tenant Group</th>
|
<th scope="row">{% trans "Tenant Group" %}</th>
|
||||||
<td>{{ object.tenant_group|linkify }}</td>
|
<td>{{ object.tenant_group|linkify }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Tenant</th>
|
<th scope="row">{% trans "Tenant" %}</th>
|
||||||
<td>{{ object.tenant|linkify }}</td>
|
<td>{{ object.tenant|linkify }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Stored location</th>
|
<th scope="row">{% trans "Stored location" %}</th>
|
||||||
{% if object.stored_location_url %}
|
{% if object.stored_location_url %}
|
||||||
<td><a href="{{ object.stored_location_url }}">{{ object.stored_location_txt }}</a></td>
|
<td><a href="{{ object.stored_location_url }}">{{ object.stored_location_txt }}</a></td>
|
||||||
{% else %}
|
{% else %}
|
||||||
@@ -57,19 +58,19 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Start date</th>
|
<th scope="row">{% trans "Start date" %}</th>
|
||||||
<td>{{ object.start_date }}</td>
|
<td>{{ object.start_date }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Expiration date</th>
|
<th scope="row">{% trans "Expiration date" %}</th>
|
||||||
<td>{{ object.expiration_date }}</td>
|
<td>{{ object.expiration_date }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Support</th>
|
<th scope="row">{% trans "Support" %}</th>
|
||||||
<td>{{ object.support }}</td>
|
<td>{{ object.support }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">License amount</th>
|
<th scope="row">{% trans "License amount" %}</th>
|
||||||
<td>{{ object.license_amount }}</td>
|
<td>{{ object.license_amount }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@@ -3,60 +3,61 @@
|
|||||||
{% load static %}
|
{% load static %}
|
||||||
{% load helpers %}
|
{% load helpers %}
|
||||||
{% load plugins %}
|
{% load plugins %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row my-3">
|
<div class="row my-3">
|
||||||
<div class="col col-md-6">
|
<div class="col col-md-6">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h5 class="card-header">
|
<h5 class="card-header">
|
||||||
Software Product
|
{% trans "Software Product" %}
|
||||||
</h5>
|
</h5>
|
||||||
<table class="table table-hover attr-table">
|
<table class="table table-hover attr-table">
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Name</th>
|
<th scope="row">{% trans "Name" %}</th>
|
||||||
<td>{{ object.name }}</td>
|
<td>{{ object.name }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Description</th>
|
<th scope="row">{% trans "Description" %}</th>
|
||||||
<td>{{ object.description }}</td>
|
<td>{{ object.description }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Manufacturer</th>
|
<th scope="row">{% trans "Manufacturer" %}</th>
|
||||||
<td>{{ object.manufacturer|linkify }}</td>
|
<td>{{ object.manufacturer|linkify }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Versions</th>
|
<th scope="row">{% trans "Versions" %}</th>
|
||||||
<td>
|
<td>
|
||||||
{% for version in object.softwareproductversion_set.all %}
|
{% for version in object.softwareproductversion_set.all %}
|
||||||
<a href="{% url 'plugins:netbox_slm:softwareproductversion' pk=version.pk %}" class="btn btn-sm">
|
<a href="{% url 'plugins:netbox_slm:softwareproductversion' pk=version.pk %}" class="btn btn-sm">
|
||||||
{{ version }}
|
{{ version }}
|
||||||
</a>
|
</a>
|
||||||
{% empty %}
|
{% empty %}
|
||||||
None
|
{% trans "None" %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Installations</th>
|
<th scope="row">{% trans "Installations" %}</th>
|
||||||
<td>
|
<td>
|
||||||
{% for installation in object.softwareproductinstallation_set.all %}
|
{% for installation in object.softwareproductinstallation_set.all %}
|
||||||
<a href="{% url 'plugins:netbox_slm:softwareproductinstallation' pk=installation.pk %}" class="btn btn-sm">
|
<a href="{% url 'plugins:netbox_slm:softwareproductinstallation' pk=installation.pk %}" class="btn btn-sm">
|
||||||
{{ installation }}
|
{{ installation }}
|
||||||
</a>
|
</a>
|
||||||
{% empty %}
|
{% empty %}
|
||||||
None
|
{% trans "None" %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Licenses</th>
|
<th scope="row">{% trans "Licenses" %}</th>
|
||||||
<td>
|
<td>
|
||||||
{% for license in object.softwarelicense_set.all %}
|
{% for license in object.softwarelicense_set.all %}
|
||||||
<a href="{% url 'plugins:netbox_slm:softwarelicense' pk=license.pk %}" class="btn btn-sm">
|
<a href="{% url 'plugins:netbox_slm:softwarelicense' pk=license.pk %}" class="btn btn-sm">
|
||||||
{{ license }}
|
{{ license }}
|
||||||
</a>
|
</a>
|
||||||
{% empty %}
|
{% empty %}
|
||||||
None
|
{% trans "None" %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -3,48 +3,49 @@
|
|||||||
{% load static %}
|
{% load static %}
|
||||||
{% load helpers %}
|
{% load helpers %}
|
||||||
{% load plugins %}
|
{% load plugins %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row my-3">
|
<div class="row my-3">
|
||||||
<div class="col col-md-6">
|
<div class="col col-md-6">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h5 class="card-header">
|
<h5 class="card-header">
|
||||||
Software Product Installation
|
{% trans "Software Product Installation" %}
|
||||||
</h5>
|
</h5>
|
||||||
<table class="table table-hover attr-table">
|
<table class="table table-hover attr-table">
|
||||||
{% if object.device %}
|
{% if object.device %}
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Device</th>
|
<th scope="row">{% trans "Device" %}</th>
|
||||||
<td>{{ object.device|linkify }}</td>
|
<td>{{ object.device|linkify }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% elif object.virtualmachine %}
|
{% elif object.virtualmachine %}
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Virtual Machine</th>
|
<th scope="row">{% trans "Virtual Machine" %}</th>
|
||||||
<td>{{ object.virtualmachine|linkify }}</td>
|
<td>{{ object.virtualmachine|linkify }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% else %}
|
{% else %}
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Cluster</th>
|
<th scope="row">{% trans "Cluster" %}</th>
|
||||||
<td>{{ object.cluster|linkify }}</td>
|
<td>{{ object.cluster|linkify }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Software Product</th>
|
<th scope="row">{% trans "Software Product" %}</th>
|
||||||
<td>{{ object.software_product|linkify }}</td>
|
<td>{{ object.software_product|linkify }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Version</th>
|
<th scope="row">{% trans "Version" %}</th>
|
||||||
<td>{{ object.version|linkify }}</td>
|
<td>{{ object.version|linkify }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Licenses</th>
|
<th scope="row">{% trans "Licenses" %}</th>
|
||||||
<td>
|
<td>
|
||||||
{% for license in object.softwarelicense_set.all %}
|
{% for license in object.softwarelicense_set.all %}
|
||||||
<a href="{% url 'plugins:netbox_slm:softwarelicense' pk=license.pk %}" class="btn btn-sm">
|
<a href="{% url 'plugins:netbox_slm:softwarelicense' pk=license.pk %}" class="btn btn-sm">
|
||||||
{{ license }}
|
{{ license }}
|
||||||
</a>
|
</a>
|
||||||
{% empty %}
|
{% empty %}
|
||||||
None
|
{% trans "None" %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -3,37 +3,38 @@
|
|||||||
{% load static %}
|
{% load static %}
|
||||||
{% load helpers %}
|
{% load helpers %}
|
||||||
{% load plugins %}
|
{% load plugins %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row my-3">
|
<div class="row my-3">
|
||||||
<div class="col col-md-6">
|
<div class="col col-md-6">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h5 class="card-header">
|
<h5 class="card-header">
|
||||||
Software Product Version
|
{% trans "Software Product Version" %}
|
||||||
</h5>
|
</h5>
|
||||||
<table class="table table-hover attr-table">
|
<table class="table table-hover attr-table">
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Name</th>
|
<th scope="row">{% trans "Name" %}</th>
|
||||||
<td>{{ object.name }}</td>
|
<td>{{ object.name }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Description</th>
|
<th scope="row">{% trans "Description" %}</th>
|
||||||
<td>{{ object.description }}</td>
|
<td>{{ object.description }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Manufacturer</th>
|
<th scope="row">{% trans "Manufacturer" %}</th>
|
||||||
<td>{{ object.software_product.manufacturer|linkify }}</td>
|
<td>{{ object.software_product.manufacturer|linkify }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Software Product</th>
|
<th scope="row">{% trans "Software Product" %}</th>
|
||||||
<td>{{ object.software_product|linkify }}</td>
|
<td>{{ object.software_product|linkify }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Release date</th>
|
<th scope="row">{% trans "Release date" %}</th>
|
||||||
<td>{{ object.release_date }}</td>
|
<td>{{ object.release_date }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Documentation url</th>
|
<th scope="row">{% trans "Documentation URL" %}</th>
|
||||||
{% if object.documentation_url %}
|
{% if object.documentation_url %}
|
||||||
<td><a href="{{ object.documentation_url }}">{{ object.documentation_url }}</a></td>
|
<td><a href="{{ object.documentation_url }}">{{ object.documentation_url }}</a></td>
|
||||||
{% else %}
|
{% else %}
|
||||||
@@ -41,11 +42,11 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">End of support</th>
|
<th scope="row">{% trans "End of support" %}</th>
|
||||||
<td>{{ object.end_of_support }}</td>
|
<td>{{ object.end_of_support }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Filename</th>
|
<th scope="row">{% trans "Filename" %}</th>
|
||||||
{% if object.file_link %}
|
{% if object.file_link %}
|
||||||
<td><a href="{{ object.file_link }}">{{ object.filename }}</a></td>
|
<td><a href="{{ object.file_link }}">{{ object.filename }}</a></td>
|
||||||
{% else %}
|
{% else %}
|
||||||
@@ -53,34 +54,34 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">File checksum</th>
|
<th scope="row">{% trans "File checksum" %}</th>
|
||||||
<td>{{ object.file_checksum }}</td>
|
<td>{{ object.file_checksum }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Release type</th>
|
<th scope="row">{% trans "Release type" %}</th>
|
||||||
<td>{{ object.get_release_type_display }}</td>
|
<td>{{ object.get_release_type_display }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Installations</th>
|
<th scope="row">{% trans "Installations" %}</th>
|
||||||
<td>
|
<td>
|
||||||
{% for installation in object.softwareproductinstallation_set.all %}
|
{% for installation in object.softwareproductinstallation_set.all %}
|
||||||
<a href="{% url 'plugins:netbox_slm:softwareproductinstallation' pk=installation.pk %}" class="btn btn-sm">
|
<a href="{% url 'plugins:netbox_slm:softwareproductinstallation' pk=installation.pk %}" class="btn btn-sm">
|
||||||
{{ installation }}
|
{{ installation }}
|
||||||
</a>
|
</a>
|
||||||
{% empty %}
|
{% empty %}
|
||||||
None
|
{% trans "None" %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Licenses</th>
|
<th scope="row">{% trans "Licenses" %}</th>
|
||||||
<td>
|
<td>
|
||||||
{% for license in object.softwarelicense_set.all %}
|
{% for license in object.softwarelicense_set.all %}
|
||||||
<a href="{% url 'plugins:netbox_slm:softwarelicense' pk=license.pk %}" class="btn btn-sm">
|
<a href="{% url 'plugins:netbox_slm:softwarelicense' pk=license.pk %}" class="btn btn-sm">
|
||||||
{{ license }}
|
{{ license }}
|
||||||
</a>
|
</a>
|
||||||
{% empty %}
|
{% empty %}
|
||||||
None
|
{% trans "None" %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
|
from django.utils.translation import gettext, override
|
||||||
from netbox_slm.forms import SoftwareLicenseForm
|
from netbox_slm.forms import SoftwareLicenseForm
|
||||||
from tenancy.models import Tenant, TenantGroup
|
from tenancy.models import Tenant, TenantGroup
|
||||||
|
|
||||||
@@ -73,6 +74,12 @@ class ModelTestCase(SlmBaseTestCase):
|
|||||||
any("tenant_group" in fieldset.items and "tenant" in fieldset.items for fieldset in form.fieldsets)
|
any("tenant_group" in fieldset.items and "tenant" in fieldset.items for fieldset in form.fieldsets)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_german_translation(self):
|
||||||
|
with override("de"):
|
||||||
|
self.assertEqual("Softwarelizenz", gettext("Software License"))
|
||||||
|
self.assertEqual("Mandantengruppe", gettext("Tenant Group"))
|
||||||
|
self.assertEqual("Lizenzen", gettext("Licenses"))
|
||||||
|
|
||||||
def test_software_license_rejects_tenant_from_other_group(self):
|
def test_software_license_rejects_tenant_from_other_group(self):
|
||||||
other_group = TenantGroup.objects.create(name="other group", slug="other-group")
|
other_group = TenantGroup.objects.create(name="other group", slug="other-group")
|
||||||
other_tenant = Tenant.objects.create(name="other tenant", slug="other-tenant", group=other_group)
|
other_tenant = Tenant.objects.create(name="other tenant", slug="other-tenant", group=other_group)
|
||||||
|
|||||||
Reference in New Issue
Block a user