fix: clarify installation platform selection
Group device, virtual machine, and cluster as the required platform destination and validate that exactly one is selected with an actionable localized error.
This commit is contained in:
@@ -2,6 +2,10 @@
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Fixed
|
||||
|
||||
* Make the required single platform destination explicit when creating an installation
|
||||
|
||||
### Added
|
||||
|
||||
* Optional tenant group and tenant assignments for software licenses
|
||||
|
||||
@@ -63,7 +63,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.11.0` stehen.
|
||||
Für diese Variante muss dort mindestens Version `1.11.1` stehen.
|
||||
|
||||
### 3. Plugin in NetBox aktivieren
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ limitations under the License.
|
||||
from netbox.plugins import PluginConfig
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
__version__ = "1.11.0"
|
||||
__version__ = "1.11.1"
|
||||
|
||||
|
||||
class SLMConfig(PluginConfig):
|
||||
|
||||
@@ -18,14 +18,28 @@ from virtualization.models import Cluster, VirtualMachine
|
||||
|
||||
class SoftwareProductInstallationForm(NetBoxModelForm):
|
||||
comments = CommentField()
|
||||
fieldsets = (
|
||||
FieldSet("device", "virtualmachine", "cluster", name=_("Platform Destination (select exactly one)")),
|
||||
FieldSet("software_product", "version", name=_("Software")),
|
||||
FieldSet("tags", name=_("Tags")),
|
||||
)
|
||||
|
||||
device = DynamicModelChoiceField(queryset=Device.objects.all(), required=False)
|
||||
device = DynamicModelChoiceField(
|
||||
queryset=Device.objects.all(),
|
||||
required=False,
|
||||
help_text=_("Select either a device, a virtual machine, or a cluster."),
|
||||
)
|
||||
virtualmachine = DynamicModelChoiceField(
|
||||
queryset=VirtualMachine.objects.all(),
|
||||
required=False,
|
||||
label=_("Virtual Machine"),
|
||||
help_text=_("Leave empty when another platform destination is selected."),
|
||||
)
|
||||
cluster = DynamicModelChoiceField(
|
||||
queryset=Cluster.objects.all(),
|
||||
required=False,
|
||||
help_text=_("Leave empty when another platform destination is selected."),
|
||||
)
|
||||
cluster = DynamicModelChoiceField(queryset=Cluster.objects.all(), required=False)
|
||||
software_product = DynamicModelChoiceField(
|
||||
queryset=SoftwareProduct.objects.all(),
|
||||
required=True,
|
||||
@@ -60,6 +74,17 @@ class SoftwareProductInstallationForm(NetBoxModelForm):
|
||||
)
|
||||
return version
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super().clean()
|
||||
destinations = [
|
||||
cleaned_data.get("device"),
|
||||
cleaned_data.get("virtualmachine"),
|
||||
cleaned_data.get("cluster"),
|
||||
]
|
||||
if sum(destination is not None for destination in destinations) != 1:
|
||||
raise ValidationError(_("Select exactly one platform destination: device, virtual machine, or cluster."))
|
||||
return cleaned_data
|
||||
|
||||
|
||||
class SoftwareProductInstallationFilterForm(NetBoxModelFilterSetForm):
|
||||
model = SoftwareProductInstallation
|
||||
|
||||
Binary file not shown.
@@ -321,3 +321,18 @@ msgstr "Erneuerungsintervall und Einheit müssen gemeinsam angegeben werden."
|
||||
|
||||
msgid "A lifetime license cannot have a renewal interval or expiration date."
|
||||
msgstr "Eine Lifetime-Lizenz darf kein Erneuerungsintervall und kein Ablaufdatum haben."
|
||||
|
||||
msgid "Platform Destination (select exactly one)"
|
||||
msgstr "Plattformziel (genau eines auswählen)"
|
||||
|
||||
msgid "Software"
|
||||
msgstr "Software"
|
||||
|
||||
msgid "Select either a device, a virtual machine, or a cluster."
|
||||
msgstr "Wähle entweder ein Gerät, eine virtuelle Maschine oder einen Cluster aus."
|
||||
|
||||
msgid "Leave empty when another platform destination is selected."
|
||||
msgstr "Leer lassen, wenn ein anderes Plattformziel ausgewählt wurde."
|
||||
|
||||
msgid "Select exactly one platform destination: device, virtual machine, or cluster."
|
||||
msgstr "Wähle genau ein Plattformziel aus: Gerät, virtuelle Maschine oder Cluster."
|
||||
|
||||
@@ -146,7 +146,9 @@ 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=_(
|
||||
"Select exactly one platform destination: device, virtual machine, or cluster."
|
||||
),
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
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, SoftwareProductInstallationForm
|
||||
from netbox_slm.models import RenewalIntervalUnits, SoftwareLicense
|
||||
from tenancy.models import Tenant, TenantGroup
|
||||
|
||||
@@ -56,6 +56,25 @@ class ModelTestCase(SlmBaseTestCase):
|
||||
self.assertEqual("cluster", self.software_product_installation.render_type())
|
||||
self.assertEqual(self.cluster, self.software_product_installation.platform)
|
||||
|
||||
def test_installation_form_requires_exactly_one_platform_destination(self):
|
||||
base_data = {
|
||||
"software_product": self.software_product.pk,
|
||||
"version": self.software_product_version.pk,
|
||||
}
|
||||
|
||||
no_destination_form = SoftwareProductInstallationForm(data=base_data)
|
||||
self.assertFalse(no_destination_form.is_valid())
|
||||
self.assertIn("Select exactly one platform destination", str(no_destination_form.non_field_errors()))
|
||||
|
||||
one_destination_form = SoftwareProductInstallationForm(data={**base_data, "device": self.device.pk})
|
||||
self.assertTrue(one_destination_form.is_valid(), one_destination_form.errors)
|
||||
|
||||
multiple_destinations_form = SoftwareProductInstallationForm(
|
||||
data={**base_data, "device": self.device.pk, "cluster": self.cluster.pk}
|
||||
)
|
||||
self.assertFalse(multiple_destinations_form.is_valid())
|
||||
self.assertIn("Select exactly one platform destination", str(multiple_destinations_form.non_field_errors()))
|
||||
|
||||
def test_software_license_stored_location_txt(self):
|
||||
self.assertEqual("Link", self.software_license.stored_location_txt)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user