@@ -1,6 +1,6 @@
|
||||
from extras.plugins import PluginConfig
|
||||
|
||||
__version__ = "1.4"
|
||||
__version__ = "1.5"
|
||||
|
||||
|
||||
class SLMConfig(PluginConfig):
|
||||
|
||||
@@ -61,6 +61,7 @@ class SoftwareProductInstallationSerializer(NetBoxModelSerializer):
|
||||
"url",
|
||||
"device",
|
||||
"virtualmachine",
|
||||
"cluster",
|
||||
"software_product",
|
||||
"version",
|
||||
"tags",
|
||||
|
||||
@@ -74,5 +74,6 @@ class SoftwareLicenseFilterSet(NetBoxModelFilterSet):
|
||||
| Q(version__name__icontains=value)
|
||||
| Q(installation__device__name__icontains=value)
|
||||
| Q(installation__virtualmachine__name__icontains=value)
|
||||
| Q(installation__cluster__name__icontains=value)
|
||||
)
|
||||
return queryset.filter(qs_filter)
|
||||
|
||||
@@ -7,7 +7,7 @@ from netbox.forms import NetBoxModelForm, NetBoxModelImportForm, NetBoxModelBulk
|
||||
from netbox_slm.models import SoftwareProductInstallation, SoftwareProduct, SoftwareProductVersion
|
||||
from utilities.forms.fields import DynamicModelChoiceField, TagFilterField
|
||||
from utilities.forms.widgets import APISelect
|
||||
from virtualization.models import VirtualMachine
|
||||
from virtualization.models import VirtualMachine, Cluster
|
||||
|
||||
|
||||
class SoftwareProductInstallationForm(NetBoxModelForm):
|
||||
@@ -15,6 +15,7 @@ class SoftwareProductInstallationForm(NetBoxModelForm):
|
||||
|
||||
device = DynamicModelChoiceField(queryset=Device.objects.all(), required=False)
|
||||
virtualmachine = DynamicModelChoiceField(queryset=VirtualMachine.objects.all(), required=False)
|
||||
cluster = DynamicModelChoiceField(queryset=Cluster.objects.all(), required=False)
|
||||
software_product = DynamicModelChoiceField(
|
||||
queryset=SoftwareProduct.objects.all(),
|
||||
required=True,
|
||||
@@ -31,7 +32,7 @@ class SoftwareProductInstallationForm(NetBoxModelForm):
|
||||
|
||||
class Meta:
|
||||
model = SoftwareProductInstallation
|
||||
fields = ("device", "virtualmachine", "software_product", "version", "tags")
|
||||
fields = ("device", "virtualmachine", "cluster", "software_product", "version", "tags")
|
||||
|
||||
def clean_version(self):
|
||||
version = self.cleaned_data["version"]
|
||||
@@ -45,11 +46,6 @@ class SoftwareProductInstallationForm(NetBoxModelForm):
|
||||
)
|
||||
return version
|
||||
|
||||
def clean(self):
|
||||
if not any([self.cleaned_data["device"], self.cleaned_data["virtualmachine"]]):
|
||||
raise forms.ValidationError(_("Installation requires atleast one virtualmachine or device destination."))
|
||||
return super(SoftwareProductInstallationForm, self).clean()
|
||||
|
||||
|
||||
class SoftwareProductInstallationFilterForm(NetBoxModelFilterSetForm):
|
||||
model = SoftwareProductInstallation
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
# Generated by Django 4.2.5 on 2023-10-04 10:07
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('virtualization', '0036_virtualmachine_config_template'),
|
||||
('netbox_slm', '0006_softwarelicense_stored_location_url'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='softwareproductinstallation',
|
||||
name='cluster',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='virtualization.cluster'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='softwarelicense',
|
||||
name='installation',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='netbox_slm.softwareproductinstallation'),
|
||||
),
|
||||
migrations.AddConstraint(
|
||||
model_name='softwareproductinstallation',
|
||||
constraint=models.CheckConstraint(check=models.Q(models.Q(('cluster__isnull', True), ('device__isnull', False), ('virtualmachine__isnull', True)), models.Q(('cluster__isnull', True), ('device__isnull', True), ('virtualmachine__isnull', False)), models.Q(('cluster__isnull', False), ('device__isnull', True), ('virtualmachine__isnull', True)), _connector='OR'), name='netbox_slm_softwareproductinstallation_platform', violation_error_message='Installation requires exactly one platform destination.'),
|
||||
),
|
||||
]
|
||||
+21
-3
@@ -79,6 +79,7 @@ class SoftwareProductInstallation(NetBoxModel):
|
||||
virtualmachine = models.ForeignKey(
|
||||
to="virtualization.VirtualMachine", on_delete=models.PROTECT, null=True, blank=True
|
||||
)
|
||||
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)
|
||||
|
||||
@@ -87,15 +88,32 @@ class SoftwareProductInstallation(NetBoxModel):
|
||||
def __str__(self):
|
||||
return f"{self.pk} ({self.platform})"
|
||||
|
||||
class Meta:
|
||||
constraints = [
|
||||
models.CheckConstraint(
|
||||
name="%(app_label)s_%(class)s_platform",
|
||||
check=(
|
||||
models.Q(device__isnull=False, virtualmachine__isnull=True, 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)
|
||||
),
|
||||
violation_error_message="Installation requires exactly one platform destination.",
|
||||
)
|
||||
]
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse("plugins:netbox_slm:softwareproductinstallation", kwargs={"pk": self.pk})
|
||||
|
||||
@property
|
||||
def platform(self):
|
||||
return self.device or self.virtualmachine
|
||||
return self.device or self.virtualmachine or self.cluster
|
||||
|
||||
def render_type(self):
|
||||
return "device" if self.device else "virtualmachine"
|
||||
if self.device:
|
||||
return "device"
|
||||
if self.virtualmachine:
|
||||
return "virtualmachine"
|
||||
return "cluster"
|
||||
|
||||
|
||||
class SoftwareLicense(NetBoxModel):
|
||||
@@ -111,7 +129,7 @@ class SoftwareLicense(NetBoxModel):
|
||||
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)
|
||||
installation = models.ForeignKey(
|
||||
to="netbox_slm.SoftwareProductInstallation", on_delete=models.PROTECT, null=True, blank=True
|
||||
to="netbox_slm.SoftwareProductInstallation", on_delete=models.SET_NULL, null=True, blank=True
|
||||
)
|
||||
|
||||
objects = RestrictedQuerySet.as_manager()
|
||||
|
||||
+13
-5
@@ -1,5 +1,5 @@
|
||||
import django_tables2 as tables
|
||||
from django.db.models import Count
|
||||
from django.db.models import Count, F, Value
|
||||
|
||||
from netbox.tables import NetBoxTable, ToggleColumn, columns
|
||||
from netbox_slm.models import SoftwareProduct, SoftwareProductVersion, SoftwareProductInstallation, SoftwareLicense
|
||||
@@ -95,8 +95,10 @@ class SoftwareProductInstallationTable(NetBoxTable):
|
||||
|
||||
pk = ToggleColumn()
|
||||
name = tables.LinkColumn()
|
||||
|
||||
device = tables.Column(accessor="device", linkify=True)
|
||||
virtualmachine = tables.Column(accessor="virtualmachine", linkify=True)
|
||||
cluster = tables.Column(accessor="cluster", linkify=True)
|
||||
platform = tables.Column(accessor="platform", linkify=True)
|
||||
type = tables.Column(accessor="render_type")
|
||||
software_product = tables.Column(accessor="software_product", linkify=True)
|
||||
@@ -125,12 +127,18 @@ class SoftwareProductInstallationTable(NetBoxTable):
|
||||
)
|
||||
|
||||
def order_platform(self, queryset, is_descending):
|
||||
queryset = queryset.order_by(("device" if is_descending else "virtualmachine"))
|
||||
return queryset, True
|
||||
device_annotate = queryset.filter(device__isnull=False).annotate(platform_value=F("device__name"))
|
||||
vm_annotate = queryset.filter(virtualmachine__isnull=False).annotate(platform_value=F("virtualmachine__name"))
|
||||
cluster_annotate = queryset.filter(cluster__isnull=False).annotate(platform_value=F("cluster__name"))
|
||||
queryset_union = device_annotate.union(vm_annotate).union(cluster_annotate)
|
||||
return queryset_union.order_by(f"{'-' if is_descending else ''}platform_value"), True
|
||||
|
||||
def order_type(self, queryset, is_descending):
|
||||
queryset = queryset.order_by(("device" if is_descending else "virtualmachine"))
|
||||
return queryset, True
|
||||
device_annotate = queryset.filter(device__isnull=False).annotate(render_type=Value("device"))
|
||||
vm_annotate = queryset.filter(virtualmachine__isnull=False).annotate(render_type=Value("virtualmachine"))
|
||||
cluster_annotate = queryset.filter(cluster__isnull=False).annotate(render_type=Value("cluster"))
|
||||
queryset_union = device_annotate.union(vm_annotate).union(cluster_annotate)
|
||||
return queryset_union.order_by(f"{'-' if is_descending else ''}render_type"), True
|
||||
|
||||
def render_software_product(self, value, **kwargs):
|
||||
return f"{kwargs['record'].software_product.manufacturer.name} - {value}"
|
||||
|
||||
@@ -18,11 +18,16 @@
|
||||
<th scope="row">Device</th>
|
||||
<td>{{ object.device }}</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
{% elif object.virtualmachine %}
|
||||
<tr>
|
||||
<th scope="row">Virtualmachine</th>
|
||||
<td>{{ object.virtualmachine }}</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr>
|
||||
<th scope="row">Cluster</th>
|
||||
<td>{{ object.cluster }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
<tr>
|
||||
<th scope="row">Software Product</th>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
from django.test import TestCase
|
||||
|
||||
from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Site
|
||||
from virtualization.models import Cluster, ClusterType, VirtualMachine
|
||||
from netbox_slm.models import SoftwareProduct, SoftwareProductVersion, SoftwareProductInstallation, SoftwareLicense
|
||||
|
||||
|
||||
@@ -9,18 +11,29 @@ class ModelTestCase(TestCase):
|
||||
self.v_name = "test version"
|
||||
self.l_name = "test license"
|
||||
|
||||
manufacturer = Manufacturer.objects.create(name="test manufacturer")
|
||||
device_type = DeviceType.objects.create(model="test device type", manufacturer=manufacturer)
|
||||
device_role = DeviceRole.objects.create(name="test device role")
|
||||
site = Site.objects.create(name="test site")
|
||||
self.device = Device.objects.create(name="test device", device_type=device_type, role=device_role, site=site)
|
||||
self.vm = VirtualMachine.objects.create(name="test VM")
|
||||
cluster_type = ClusterType.objects.create(name="test cluster type")
|
||||
self.cluster = Cluster.objects.create(name="test cluster", type=cluster_type)
|
||||
self.test_url = "https://github.com/ICTU/netbox_slm"
|
||||
|
||||
self.software_product = SoftwareProduct.objects.create(name=self.p_name)
|
||||
self.software_product_version = SoftwareProductVersion.objects.create(
|
||||
name=self.v_name, software_product=self.software_product
|
||||
)
|
||||
self.software_product_installation = SoftwareProductInstallation.objects.create(
|
||||
software_product=self.software_product, version=self.software_product_version
|
||||
virtualmachine=self.vm, software_product=self.software_product, version=self.software_product_version
|
||||
)
|
||||
self.software_license = SoftwareLicense.objects.create(
|
||||
name=self.l_name,
|
||||
software_product=self.software_product,
|
||||
version=self.software_product_version,
|
||||
installation=self.software_product_installation,
|
||||
stored_location_url=self.test_url,
|
||||
)
|
||||
|
||||
def test_model_name(self):
|
||||
@@ -42,4 +55,23 @@ class ModelTestCase(TestCase):
|
||||
|
||||
def test_product_installation_methods(self):
|
||||
self.assertEqual("virtualmachine", self.software_product_installation.render_type())
|
||||
self.assertIsNone(self.software_product_installation.platform)
|
||||
self.assertEqual(self.vm, self.software_product_installation.platform)
|
||||
|
||||
self.software_product_installation.virtualmachine = None
|
||||
self.software_product_installation.device = self.device
|
||||
self.software_product_installation.save()
|
||||
self.assertEqual("device", self.software_product_installation.render_type())
|
||||
self.assertEqual(self.device, self.software_product_installation.platform)
|
||||
|
||||
self.software_product_installation.device = None
|
||||
self.software_product_installation.cluster = self.cluster
|
||||
self.software_product_installation.save()
|
||||
self.assertEqual("cluster", self.software_product_installation.render_type())
|
||||
self.assertEqual(self.cluster, self.software_product_installation.platform)
|
||||
|
||||
def test_software_license_stored_location_txt(self):
|
||||
self.assertEqual("Link", self.software_license.stored_location_txt)
|
||||
|
||||
self.software_license.stored_location = "GitHub"
|
||||
self.software_license.save()
|
||||
self.assertEqual("GitHub", self.software_license.stored_location_txt)
|
||||
|
||||
Reference in New Issue
Block a user