diff --git a/CHANGELOG.md b/CHANGELOG.md index 9191db7..a4bbfef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Added * Optional tenant group and tenant assignments for software licenses +* German translation for navigation, forms, tables, detail views, choices, and validation messages ### Changed diff --git a/README.md b/README.md index a75e532..a3c838e 100644 --- a/README.md +++ b/README.md @@ -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. +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 - 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 ``` -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 diff --git a/netbox_slm/__init__.py b/netbox_slm/__init__.py index c6e0b71..9f6a914 100644 --- a/netbox_slm/__init__.py +++ b/netbox_slm/__init__.py @@ -15,15 +15,16 @@ limitations under the License. """ from netbox.plugins import PluginConfig +from django.utils.translation import gettext_lazy as _ -__version__ = "1.9.1" +__version__ = "1.10.0" class SLMConfig(PluginConfig): name = "netbox_slm" - verbose_name = "Software Lifecycle Management" + verbose_name = _("Software Lifecycle Management") version = __version__ - description = "Software Lifecycle Management Netbox Plugin." + description = _("Software Lifecycle Management NetBox Plugin.") author = "ICTU" author_email = "open-source-projects@ictu.nl" base_url = "slm" diff --git a/netbox_slm/api/serializers.py b/netbox_slm/api/serializers.py index 23c2d9c..393ad69 100644 --- a/netbox_slm/api/serializers.py +++ b/netbox_slm/api/serializers.py @@ -1,5 +1,6 @@ from rest_framework import serializers from rest_framework.serializers import SerializerMethodField, HyperlinkedIdentityField +from django.utils.translation import gettext_lazy as _ from netbox.api.serializers import NetBoxModelSerializer 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)) if tenant_group and tenant and tenant.group_id != tenant_group.pk: 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 diff --git a/netbox_slm/filtersets.py b/netbox_slm/filtersets.py index 311e111..3fdd089 100644 --- a/netbox_slm/filtersets.py +++ b/netbox_slm/filtersets.py @@ -1,4 +1,5 @@ from django.db.models import Q +from django.utils.translation import gettext_lazy as _ from django_filters import CharFilter, ModelMultipleChoiceFilter, MultipleChoiceFilter from dcim.models import Device, Manufacturer @@ -55,7 +56,7 @@ class SoftwareProductVersionFilterSet(NetBoxModelFilterSet): software_product_id = ModelMultipleChoiceFilter( queryset=SoftwareProduct.objects.all(), - label="Software Product", + label=_("Software Product"), ) class Meta: @@ -82,12 +83,12 @@ class SoftwareProductInstallationFilterSet(NetBoxModelFilterSet): device_id = ModelMultipleChoiceFilter(queryset=Device.objects.all()) virtualmachine_id = ModelMultipleChoiceFilter( queryset=VirtualMachine.objects.all(), - label="Virtual Machine", + label=_("Virtual Machine"), ) cluster_id = ModelMultipleChoiceFilter(queryset=Cluster.objects.all()) software_product_id = ModelMultipleChoiceFilter( queryset=SoftwareProduct.objects.all(), - label="Software Product", + label=_("Software Product"), ) version_id = ModelMultipleChoiceFilter(queryset=SoftwareProductVersion.objects.all()) @@ -114,17 +115,17 @@ class SoftwareLicenseFilterSet(NetBoxModelFilterSet): name = CharFilter(lookup_expr="icontains") description = CharFilter(lookup_expr="icontains") type = CharFilter(lookup_expr="icontains") - spdx_expression = CharFilter(lookup_expr="icontains", label="SPDX expression") + spdx_expression = CharFilter(lookup_expr="icontains", label=_("SPDX expression")) stored_location = CharFilter(lookup_expr="icontains") software_product_id = ModelMultipleChoiceFilter( queryset=SoftwareProduct.objects.all(), - label="Software Product", + label=_("Software Product"), ) version_id = ModelMultipleChoiceFilter(queryset=SoftwareProductVersion.objects.all()) installation_id = ModelMultipleChoiceFilter(queryset=SoftwareProductInstallation.objects.all()) - tenant_group_id = ModelMultipleChoiceFilter(queryset=TenantGroup.objects.all(), label="Tenant Group") - tenant_id = ModelMultipleChoiceFilter(queryset=Tenant.objects.all(), label="Tenant") + tenant_group_id = ModelMultipleChoiceFilter(queryset=TenantGroup.objects.all(), label=_("Tenant Group")) + tenant_id = ModelMultipleChoiceFilter(queryset=Tenant.objects.all(), label=_("Tenant")) class Meta: model = SoftwareLicense diff --git a/netbox_slm/forms/software_license.py b/netbox_slm/forms/software_license.py index 4ab14f4..fd48d67 100644 --- a/netbox_slm/forms/software_license.py +++ b/netbox_slm/forms/software_license.py @@ -1,5 +1,6 @@ from django.forms import CharField, DateField, ChoiceField, IntegerField, NullBooleanField from django.urls import reverse_lazy +from django.utils.translation import gettext_lazy as _ from netbox.forms import NetBoxModelForm, NetBoxModelFilterSetForm, NetBoxModelImportForm, NetBoxModelBulkEditForm from netbox_slm.models import ( @@ -31,9 +32,9 @@ class SoftwareLicenseForm(NetBoxModelForm): "software_product", "version", "installation", - name="Software License", + name=_("Software License"), ), - FieldSet("tenant_group", "tenant", name="Tenancy"), + FieldSet("tenant_group", "tenant", name=_("Tenancy")), FieldSet( "type", "spdx_expression", @@ -43,12 +44,12 @@ class SoftwareLicenseForm(NetBoxModelForm): "expiration_date", "support", "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) start_date = DateField(required=False, widget=DatePicker()) expiration_date = DateField(required=False, widget=DatePicker()) @@ -56,7 +57,7 @@ class SoftwareLicenseForm(NetBoxModelForm): software_product = DynamicModelChoiceField( queryset=SoftwareProduct.objects.all(), required=True, - label="Software Product", + label=_("Software Product"), ) version = DynamicModelChoiceField( queryset=SoftwareProductVersion.objects.all(), @@ -75,13 +76,13 @@ class SoftwareLicenseForm(NetBoxModelForm): tenant_group = DynamicModelChoiceField( queryset=TenantGroup.objects.all(), required=False, - label="Tenant Group", + label=_("Tenant Group"), ) tenant = DynamicModelChoiceField( queryset=Tenant.objects.all(), required=False, query_params={"group_id": "$tenant_group"}, - label="Tenant", + label=_("Tenant"), ) class Meta: @@ -132,40 +133,42 @@ class SoftwareLicenseFilterForm(NetBoxModelFilterSetForm): name = CharField(required=False) description = 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) support = ChoiceField(required=False, choices=BOOLEAN_WITH_BLANK_CHOICES) software_product_id = DynamicModelMultipleChoiceField( queryset=SoftwareProduct.objects.all(), required=False, - label="Software Product", + label=_("Software Product"), ) version_id = DynamicModelMultipleChoiceField( queryset=SoftwareProductVersion.objects.all(), required=False, - label="Version", + label=_("Version"), ) installation_id = DynamicModelMultipleChoiceField( queryset=SoftwareProductInstallation.objects.all(), required=False, - label="Installation", + label=_("Installation"), ) tenant_group_id = DynamicModelMultipleChoiceField( queryset=TenantGroup.objects.all(), required=False, - label="Tenant Group", + label=_("Tenant Group"), ) tenant_id = DynamicModelMultipleChoiceField( queryset=Tenant.objects.all(), required=False, query_params={"group_id": "$tenant_group_id"}, - label="Tenant", + label=_("Tenant"), ) 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: model = SoftwareLicense @@ -226,7 +229,7 @@ class SoftwareLicenseBulkEditForm(NetBoxModelBulkEditForm): comments = CommentField() 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_url = LaxURLField(required=False) start_date = DateField(required=False, widget=DatePicker()) @@ -237,7 +240,7 @@ class SoftwareLicenseBulkEditForm(NetBoxModelBulkEditForm): software_product = DynamicModelChoiceField( queryset=SoftwareProduct.objects.all(), required=False, - label="Software Product", + label=_("Software Product"), ) version = DynamicModelChoiceField( queryset=SoftwareProductVersion.objects.all(), @@ -256,11 +259,11 @@ class SoftwareLicenseBulkEditForm(NetBoxModelBulkEditForm): tenant_group = DynamicModelChoiceField( queryset=TenantGroup.objects.all(), required=False, - label="Tenant Group", + label=_("Tenant Group"), ) tenant = DynamicModelChoiceField( queryset=Tenant.objects.all(), required=False, query_params={"group_id": "$tenant_group"}, - label="Tenant", + label=_("Tenant"), ) diff --git a/netbox_slm/forms/software_product.py b/netbox_slm/forms/software_product.py index 1dc79f5..022772e 100644 --- a/netbox_slm/forms/software_product.py +++ b/netbox_slm/forms/software_product.py @@ -1,4 +1,5 @@ from django.forms import CharField +from django.utils.translation import gettext_lazy as _ from dcim.models import Manufacturer from netbox.forms import NetBoxModelForm, NetBoxModelFilterSetForm, NetBoxModelImportForm, NetBoxModelBulkEditForm @@ -46,7 +47,7 @@ class SoftwareProductFilterForm(NetBoxModelFilterSetForm): manufacturer_id = DynamicModelMultipleChoiceField( queryset=Manufacturer.objects.all(), required=False, - label="Manufacturer", + label=_("Manufacturer"), ) diff --git a/netbox_slm/forms/software_product_installation.py b/netbox_slm/forms/software_product_installation.py index b648f7f..9bdcb52 100644 --- a/netbox_slm/forms/software_product_installation.py +++ b/netbox_slm/forms/software_product_installation.py @@ -1,5 +1,6 @@ from django.forms import ValidationError from django.urls import reverse_lazy +from django.utils.translation import gettext_lazy as _ from dcim.models import Device from netbox.forms import NetBoxModelForm, NetBoxModelFilterSetForm, NetBoxModelImportForm, NetBoxModelBulkEditForm @@ -22,13 +23,13 @@ class SoftwareProductInstallationForm(NetBoxModelForm): virtualmachine = DynamicModelChoiceField( queryset=VirtualMachine.objects.all(), required=False, - label="Virtual Machine", + label=_("Virtual Machine"), ) cluster = DynamicModelChoiceField(queryset=Cluster.objects.all(), required=False) software_product = DynamicModelChoiceField( queryset=SoftwareProduct.objects.all(), required=True, - label="Software Product", + label=_("Software Product"), ) version = DynamicModelChoiceField( queryset=SoftwareProductVersion.objects.all(), @@ -54,8 +55,8 @@ class SoftwareProductInstallationForm(NetBoxModelForm): software_product = self.cleaned_data["software_product"] if version not in software_product.softwareproductversion_set.all(): raise ValidationError( - f"Version '{version}' doesn't exist on {software_product}, make sure you've " - f"selected a compatible version or first select the software product." + _("Version '%(version)s' does not belong to %(product)s. Select a compatible version.") + % {"version": version, "product": software_product} ) return version @@ -73,27 +74,27 @@ class SoftwareProductInstallationFilterForm(NetBoxModelFilterSetForm): device_id = DynamicModelMultipleChoiceField( queryset=Device.objects.all(), required=False, - label="Device", + label=_("Device"), ) virtualmachine_id = DynamicModelMultipleChoiceField( queryset=VirtualMachine.objects.all(), required=False, - label="Virtual Machine", + label=_("Virtual Machine"), ) cluster_id = DynamicModelMultipleChoiceField( queryset=Cluster.objects.all(), required=False, - label="Cluster", + label=_("Cluster"), ) software_product_id = DynamicModelMultipleChoiceField( queryset=SoftwareProduct.objects.all(), required=False, - label="Software Product", + label=_("Software Product"), ) version_id = DynamicModelMultipleChoiceField( queryset=SoftwareProductVersion.objects.all(), required=False, - label="Version", + label=_("Version"), ) @@ -122,13 +123,13 @@ class SoftwareProductInstallationBulkEditForm(NetBoxModelBulkEditForm): virtualmachine = DynamicModelChoiceField( queryset=VirtualMachine.objects.all(), required=False, - label="Virtual Machine", + label=_("Virtual Machine"), ) cluster = DynamicModelChoiceField(queryset=Cluster.objects.all(), required=False) software_product = DynamicModelChoiceField( queryset=SoftwareProduct.objects.all(), required=False, - label="Software Product", + label=_("Software Product"), ) version = DynamicModelChoiceField( queryset=SoftwareProductVersion.objects.all(), diff --git a/netbox_slm/forms/software_product_version.py b/netbox_slm/forms/software_product_version.py index 3510be3..32f2ecf 100644 --- a/netbox_slm/forms/software_product_version.py +++ b/netbox_slm/forms/software_product_version.py @@ -1,5 +1,6 @@ from django.forms import DateField, CharField, MultipleChoiceField from django.urls import reverse_lazy +from django.utils.translation import gettext_lazy as _ from dcim.models import Manufacturer from netbox.forms import NetBoxModelForm, NetBoxModelFilterSetForm, NetBoxModelImportForm, NetBoxModelBulkEditForm @@ -23,7 +24,7 @@ class SoftwareProductVersionForm(NetBoxModelForm): software_product = DynamicModelChoiceField( queryset=SoftwareProduct.objects.all(), required=True, - label="Software Product", + label=_("Software Product"), widget=APISelect(attrs={"data-url": reverse_lazy("plugins-api:netbox_slm-api:softwareproduct-list")}), ) @@ -64,17 +65,19 @@ class SoftwareProductVersionFilterForm(NetBoxModelFilterSetForm): manufacturer_id = DynamicModelMultipleChoiceField( queryset=Manufacturer.objects.all(), required=False, - label="Manufacturer", + label=_("Manufacturer"), ) software_product_id = DynamicModelMultipleChoiceField( queryset=SoftwareProduct.objects.all(), required=False, - label="Software Product", + label=_("Software Product"), ) 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: model = SoftwareProductVersion @@ -122,5 +125,5 @@ class SoftwareProductVersionBulkEditForm(NetBoxModelBulkEditForm): software_product = DynamicModelChoiceField( queryset=SoftwareProduct.objects.all(), required=False, - label="Software Product", + label=_("Software Product"), ) diff --git a/netbox_slm/locale/de/LC_MESSAGES/django.mo b/netbox_slm/locale/de/LC_MESSAGES/django.mo new file mode 100644 index 0000000..8303efd Binary files /dev/null and b/netbox_slm/locale/de/LC_MESSAGES/django.mo differ diff --git a/netbox_slm/locale/de/LC_MESSAGES/django.po b/netbox_slm/locale/de/LC_MESSAGES/django.po new file mode 100644 index 0000000..7207611 --- /dev/null +++ b/netbox_slm/locale/de/LC_MESSAGES/django.po @@ -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" diff --git a/netbox_slm/models.py b/netbox_slm/models.py index 466fb86..d6ebb52 100644 --- a/netbox_slm/models.py +++ b/netbox_slm/models.py @@ -2,6 +2,7 @@ from django.core.exceptions import ValidationError from django.db import models from django.urls import reverse from django.utils.html import format_html +from django.utils.translation import gettext_lazy as _ from license_expression import Licensing, get_spdx_licensing from netbox.models import NetBoxModel @@ -21,14 +22,20 @@ class LaxURLField(models.URLField): class SoftwareProduct(NetBoxModel): - name = models.CharField(max_length=128) - comments = models.TextField(blank=True) + name = models.CharField(_("name"), max_length=128) + comments = models.TextField(_("comments"), blank=True) - description = models.CharField(max_length=255, null=True, blank=True) - manufacturer = models.ForeignKey(to="dcim.Manufacturer", on_delete=models.PROTECT, null=True, blank=True) + description = models.CharField(_("description"), max_length=255, 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() + class Meta: + verbose_name = _("software product") + verbose_name_plural = _("software products") + def __str__(self): return self.name @@ -44,25 +51,26 @@ class SoftwareProduct(NetBoxModel): class SoftwareReleaseTypes(models.TextChoices): - ALPHA = "A", "Alpha" - BETA = "B", "Beta" - RELEASE_CANDIDATE = "RC", "Release candidate" - STABLE = "S", "Stable release" + ALPHA = "A", _("Alpha") + BETA = "B", _("Beta") + RELEASE_CANDIDATE = "RC", _("Release candidate") + STABLE = "S", _("Stable release") class SoftwareProductVersion(NetBoxModel): - name = models.CharField(max_length=64) - comments = models.TextField(blank=True) + name = models.CharField(_("name"), max_length=64) + comments = models.TextField(_("comments"), blank=True) - description = models.CharField(max_length=255, null=True, blank=True) - release_date = models.DateField(null=True, blank=True) - documentation_url = LaxURLField(max_length=1024, null=True, blank=True) - end_of_support = models.DateField(null=True, blank=True) - filename = models.CharField(max_length=64, null=True, blank=True) - file_checksum = models.CharField(max_length=128, null=True, blank=True) - file_link = LaxURLField(max_length=1024, null=True, blank=True) + description = models.CharField(_("description"), max_length=255, null=True, blank=True) + release_date = models.DateField(_("release date"), null=True, blank=True) + documentation_url = LaxURLField(_("documentation URL"), max_length=1024, null=True, blank=True) + end_of_support = models.DateField(_("end of support"), null=True, blank=True) + filename = models.CharField(_("filename"), max_length=64, null=True, blank=True) + file_checksum = models.CharField(_("file checksum"), max_length=128, null=True, blank=True) + file_link = LaxURLField(_("file link"), max_length=1024, null=True, blank=True) release_type = models.CharField( + _("release type"), max_length=3, choices=SoftwareReleaseTypes.choices, default=SoftwareReleaseTypes.STABLE, @@ -70,11 +78,16 @@ class SoftwareProductVersion(NetBoxModel): software_product = models.ForeignKey( to="netbox_slm.SoftwareProduct", + verbose_name=_("software product"), on_delete=models.PROTECT, ) objects = RestrictedQuerySet.as_manager() + class Meta: + verbose_name = _("software product version") + verbose_name_plural = _("software product versions") + def __str__(self): return self.name @@ -88,15 +101,27 @@ class SoftwareProductVersion(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) - virtualmachine = models.ForeignKey( - to="virtualization.VirtualMachine", on_delete=models.PROTECT, null=True, blank=True + device = models.ForeignKey( + to="dcim.Device", verbose_name=_("device"), 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() @@ -104,6 +129,8 @@ class SoftwareProductInstallation(NetBoxModel): return f"{self.pk} ({self.platform})" class Meta: + verbose_name = _("software product installation") + verbose_name_plural = _("software product installations") constraints = [ models.CheckConstraint( 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=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): expression_info = spdx_licensing.validate(value) 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): - name = models.CharField(max_length=128) - comments = models.TextField(blank=True) + name = models.CharField(_("name"), max_length=128) + comments = models.TextField(_("comments"), blank=True) - description = models.CharField(max_length=255, null=True, blank=True) - type = models.CharField(max_length=128) - spdx_expression = models.CharField(max_length=64, null=True, blank=True, validators=[validate_spdx_expression]) - stored_location = models.CharField(max_length=255, null=True, blank=True) - stored_location_url = LaxURLField(max_length=1024, null=True, blank=True) - start_date = models.DateField(null=True, blank=True) - expiration_date = models.DateField(null=True, blank=True) - support = models.BooleanField(default=None, null=True, blank=True) - license_amount = models.PositiveIntegerField(default=None, null=True, blank=True) + description = models.CharField(_("description"), max_length=255, null=True, blank=True) + type = models.CharField(_("type"), max_length=128) + spdx_expression = models.CharField( + _("SPDX expression"), max_length=64, null=True, blank=True, validators=[validate_spdx_expression] + ) + stored_location = models.CharField(_("stored location"), max_length=255, null=True, blank=True) + stored_location_url = LaxURLField(_("stored location URL"), max_length=1024, null=True, blank=True) + start_date = models.DateField(_("start date"), 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) - version = models.ForeignKey(to="netbox_slm.SoftwareProductVersion", 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, + null=True, + blank=True, + ) 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( to="tenancy.TenantGroup", + verbose_name=_("tenant group"), on_delete=models.PROTECT, null=True, blank=True, @@ -172,6 +214,7 @@ class SoftwareLicense(NetBoxModel): ) tenant = models.ForeignKey( to="tenancy.Tenant", + verbose_name=_("tenant"), on_delete=models.PROTECT, null=True, blank=True, @@ -180,6 +223,10 @@ class SoftwareLicense(NetBoxModel): objects = RestrictedQuerySet.as_manager() + class Meta: + verbose_name = _("software license") + verbose_name_plural = _("software licenses") + def __str__(self): return self.name @@ -189,10 +236,12 @@ class SoftwareLicense(NetBoxModel): def clean(self): super().clean() 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 def stored_location_txt(self): if self.stored_location_url and not self.stored_location: - return "Link" + return _("Link") return self.stored_location diff --git a/netbox_slm/navigation.py b/netbox_slm/navigation.py index 6ce7825..4e29655 100644 --- a/netbox_slm/navigation.py +++ b/netbox_slm/navigation.py @@ -1,21 +1,22 @@ from netbox.plugins import PluginMenuButton, PluginMenuItem, PluginMenu, get_plugin_config +from django.utils.translation import gettext_lazy as _ from . import SLMConfig slm_items = ( PluginMenuItem( link="plugins:netbox_slm:softwareproduct_list", - link_text="Software Products", + link_text=_("Software Products"), permissions=["netbox_slm.add_softwareproduct"], buttons=( PluginMenuButton( "plugins:netbox_slm:softwareproduct_add", - "Add", + _("Add"), "mdi mdi-plus-thick", permissions=["netbox_slm.add_softwareproduct"], ), PluginMenuButton( "plugins:netbox_slm:softwareproduct_bulk_import", - "Import", + _("Import"), "mdi mdi-upload", permissions=["netbox_slm.import_softwareproduct"], ), @@ -23,18 +24,18 @@ slm_items = ( ), PluginMenuItem( link="plugins:netbox_slm:softwareproductversion_list", - link_text="Versions", + link_text=_("Versions"), permissions=["netbox_slm.add_softwareproductversion"], buttons=( PluginMenuButton( "plugins:netbox_slm:softwareproductversion_add", - "Add", + _("Add"), "mdi mdi-plus-thick", permissions=["netbox_slm.add_softwareproductversion"], ), PluginMenuButton( "plugins:netbox_slm:softwareproductversion_bulk_import", - "Import", + _("Import"), "mdi mdi-upload", permissions=["netbox_slm.import_softwareproductversion"], ), @@ -42,18 +43,18 @@ slm_items = ( ), PluginMenuItem( link="plugins:netbox_slm:softwareproductinstallation_list", - link_text="Installations", + link_text=_("Installations"), permissions=["netbox_slm.add_softwareproductinstallation"], buttons=( PluginMenuButton( "plugins:netbox_slm:softwareproductinstallation_add", - "Add", + _("Add"), "mdi mdi-plus-thick", permissions=["netbox_slm.add_softwareproductinstallation"], ), PluginMenuButton( "plugins:netbox_slm:softwareproductinstallation_bulk_import", - "Import", + _("Import"), "mdi mdi-upload", permissions=["netbox_slm.import_softwareproductinstallation"], ), @@ -61,18 +62,18 @@ slm_items = ( ), PluginMenuItem( link="plugins:netbox_slm:softwarelicense_list", - link_text="Licenses", + link_text=_("Licenses"), permissions=["netbox_slm.add_softwarelicense"], buttons=( PluginMenuButton( "plugins:netbox_slm:softwarelicense_add", - "Add", + _("Add"), "mdi mdi-plus-thick", permissions=["netbox_slm.add_softwarelicense"], ), PluginMenuButton( "plugins:netbox_slm:softwarelicense_bulk_import", - "Import", + _("Import"), "mdi mdi-upload", permissions=["netbox_slm.import_softwarelicense"], ), @@ -82,7 +83,7 @@ slm_items = ( if get_plugin_config("netbox_slm", "top_level_menu"): menu = PluginMenu( - label="Software Lifecycle", + label=_("Software Lifecycle"), groups=((SLMConfig.verbose_name, slm_items),), icon_class="mdi mdi-content-save", ) diff --git a/netbox_slm/tables.py b/netbox_slm/tables.py index cb04259..0bd5667 100644 --- a/netbox_slm/tables.py +++ b/netbox_slm/tables.py @@ -1,5 +1,6 @@ import django_tables2 as tables 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_slm.models import SoftwareProduct, SoftwareProductVersion, SoftwareProductInstallation, SoftwareLicense @@ -45,7 +46,7 @@ class SoftwareProductVersionTable(NetBoxTable): pk = ToggleColumn() 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) installations = tables.Column(accessor="get_installation_count") @@ -89,7 +90,7 @@ class SoftwareProductInstallationTable(NetBoxTable): platform = tables.Column(accessor="platform", linkify=True) type = tables.Column(accessor="render_type") manufacturer = tables.Column(accessor="software_product__manufacturer", linkify=True) - software_product = tables.Column(accessor="software_product", 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) tags = columns.TagColumn(url_name="plugins:netbox_slm:softwareproductinstallation_list") @@ -137,14 +138,14 @@ class SoftwareLicenseTable(NetBoxTable): name = tables.LinkColumn() 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) 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) 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) tags = columns.TagColumn(url_name="plugins:netbox_slm:softwarelicense_list") diff --git a/netbox_slm/templates/netbox_slm/installations_card_include.html b/netbox_slm/templates/netbox_slm/installations_card_include.html index 896c995..3b7d09a 100644 --- a/netbox_slm/templates/netbox_slm/installations_card_include.html +++ b/netbox_slm/templates/netbox_slm/installations_card_include.html @@ -1,12 +1,13 @@ {% load helpers %} +{% load i18n %}

- Installations + {% trans "Installations" %} {% if perms.netbox_slm.add_softwareproductinstallation %} {% endif %} diff --git a/netbox_slm/templates/netbox_slm/softwarelicense.html b/netbox_slm/templates/netbox_slm/softwarelicense.html index c532e85..12093fe 100644 --- a/netbox_slm/templates/netbox_slm/softwarelicense.html +++ b/netbox_slm/templates/netbox_slm/softwarelicense.html @@ -3,53 +3,54 @@ {% load static %} {% load helpers %} {% load plugins %} +{% load i18n %} {% block content %}
- Software License + {% trans "Software License" %}
- + - + - + - + - + - + - + - + - + - + {% if object.stored_location_url %} {% else %} @@ -57,19 +58,19 @@ {% endif %} - + - + - + - +
Name{% trans "Name" %} {{ object.name }}
Description{% trans "Description" %} {{ object.description }}
Type{% trans "Type" %} {{ object.type }}
SPDX expression{% trans "SPDX expression" %} {{ object.spdx_expression }}
Software Product{% trans "Software Product" %} {{ object.software_product|linkify }}
Version{% trans "Version" %} {{ object.version|linkify }}
Installation{% trans "Installation" %} {{ object.installation|linkify }}
Tenant Group{% trans "Tenant Group" %} {{ object.tenant_group|linkify }}
Tenant{% trans "Tenant" %} {{ object.tenant|linkify }}
Stored location{% trans "Stored location" %}{{ object.stored_location_txt }}
Start date{% trans "Start date" %} {{ object.start_date }}
Expiration date{% trans "Expiration date" %} {{ object.expiration_date }}
Support{% trans "Support" %} {{ object.support }}
License amount{% trans "License amount" %} {{ object.license_amount }}
diff --git a/netbox_slm/templates/netbox_slm/softwareproduct.html b/netbox_slm/templates/netbox_slm/softwareproduct.html index 0337c94..58f1c71 100644 --- a/netbox_slm/templates/netbox_slm/softwareproduct.html +++ b/netbox_slm/templates/netbox_slm/softwareproduct.html @@ -3,60 +3,61 @@ {% load static %} {% load helpers %} {% load plugins %} +{% load i18n %} {% block content %}
- Software Product + {% trans "Software Product" %}
- + - + - + - + - + - + diff --git a/netbox_slm/templates/netbox_slm/softwareproductinstallation.html b/netbox_slm/templates/netbox_slm/softwareproductinstallation.html index d608a72..dbb82ae 100644 --- a/netbox_slm/templates/netbox_slm/softwareproductinstallation.html +++ b/netbox_slm/templates/netbox_slm/softwareproductinstallation.html @@ -3,48 +3,49 @@ {% load static %} {% load helpers %} {% load plugins %} +{% load i18n %} {% block content %}
- Software Product Installation + {% trans "Software Product Installation" %}
Name{% trans "Name" %} {{ object.name }}
Description{% trans "Description" %} {{ object.description }}
Manufacturer{% trans "Manufacturer" %} {{ object.manufacturer|linkify }}
Versions{% trans "Versions" %} {% for version in object.softwareproductversion_set.all %} {{ version }} {% empty %} - None + {% trans "None" %} {% endfor %}
Installations{% trans "Installations" %} {% for installation in object.softwareproductinstallation_set.all %} {{ installation }} {% empty %} - None + {% trans "None" %} {% endfor %}
Licenses{% trans "Licenses" %} {% for license in object.softwarelicense_set.all %} {{ license }} {% empty %} - None + {% trans "None" %} {% endfor %}
{% if object.device %} - + {% elif object.virtualmachine %} - + {% else %} - + {% endif %} - + - + - + diff --git a/netbox_slm/templates/netbox_slm/softwareproductversion.html b/netbox_slm/templates/netbox_slm/softwareproductversion.html index 6e75476..2602157 100644 --- a/netbox_slm/templates/netbox_slm/softwareproductversion.html +++ b/netbox_slm/templates/netbox_slm/softwareproductversion.html @@ -3,37 +3,38 @@ {% load static %} {% load helpers %} {% load plugins %} +{% load i18n %} {% block content %}
- Software Product Version + {% trans "Software Product Version" %}
Device{% trans "Device" %} {{ object.device|linkify }}
Virtual Machine{% trans "Virtual Machine" %} {{ object.virtualmachine|linkify }}
Cluster{% trans "Cluster" %} {{ object.cluster|linkify }}
Software Product{% trans "Software Product" %} {{ object.software_product|linkify }}
Version{% trans "Version" %} {{ object.version|linkify }}
Licenses{% trans "Licenses" %} {% for license in object.softwarelicense_set.all %} {{ license }} {% empty %} - None + {% trans "None" %} {% endfor %}
- + - + - + - + - + - + {% if object.documentation_url %} {% else %} @@ -41,11 +42,11 @@ {% endif %} - + - + {% if object.file_link %} {% else %} @@ -53,34 +54,34 @@ {% endif %} - + - + - + - + diff --git a/netbox_slm/tests/test_models.py b/netbox_slm/tests/test_models.py index e8d3fc5..a96d4ad 100644 --- a/netbox_slm/tests/test_models.py +++ b/netbox_slm/tests/test_models.py @@ -1,4 +1,5 @@ from django.core.exceptions import ValidationError +from django.utils.translation import gettext, override from netbox_slm.forms import SoftwareLicenseForm 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) ) + 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): 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)
Name{% trans "Name" %} {{ object.name }}
Description{% trans "Description" %} {{ object.description }}
Manufacturer{% trans "Manufacturer" %} {{ object.software_product.manufacturer|linkify }}
Software Product{% trans "Software Product" %} {{ object.software_product|linkify }}
Release date{% trans "Release date" %} {{ object.release_date }}
Documentation url{% trans "Documentation URL" %}{{ object.documentation_url }}
End of support{% trans "End of support" %} {{ object.end_of_support }}
Filename{% trans "Filename" %}{{ object.filename }}
File checksum{% trans "File checksum" %} {{ object.file_checksum }}
Release type{% trans "Release type" %} {{ object.get_release_type_display }}
Installations{% trans "Installations" %} {% for installation in object.softwareproductinstallation_set.all %} {{ installation }} {% empty %} - None + {% trans "None" %} {% endfor %}
Licenses{% trans "Licenses" %} {% for license in object.softwarelicense_set.all %} {{ license }} {% empty %} - None + {% trans "None" %} {% endfor %}