@@ -1,6 +1,6 @@
|
|||||||
from extras.plugins import PluginConfig
|
from extras.plugins import PluginConfig
|
||||||
|
|
||||||
__version__ = "1.4"
|
__version__ = "1.5"
|
||||||
|
|
||||||
|
|
||||||
class SLMConfig(PluginConfig):
|
class SLMConfig(PluginConfig):
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ class SoftwareProductInstallationSerializer(NetBoxModelSerializer):
|
|||||||
"url",
|
"url",
|
||||||
"device",
|
"device",
|
||||||
"virtualmachine",
|
"virtualmachine",
|
||||||
|
"cluster",
|
||||||
"software_product",
|
"software_product",
|
||||||
"version",
|
"version",
|
||||||
"tags",
|
"tags",
|
||||||
|
|||||||
@@ -74,5 +74,6 @@ class SoftwareLicenseFilterSet(NetBoxModelFilterSet):
|
|||||||
| Q(version__name__icontains=value)
|
| Q(version__name__icontains=value)
|
||||||
| Q(installation__device__name__icontains=value)
|
| Q(installation__device__name__icontains=value)
|
||||||
| Q(installation__virtualmachine__name__icontains=value)
|
| Q(installation__virtualmachine__name__icontains=value)
|
||||||
|
| Q(installation__cluster__name__icontains=value)
|
||||||
)
|
)
|
||||||
return queryset.filter(qs_filter)
|
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 netbox_slm.models import SoftwareProductInstallation, SoftwareProduct, SoftwareProductVersion
|
||||||
from utilities.forms.fields import DynamicModelChoiceField, TagFilterField
|
from utilities.forms.fields import DynamicModelChoiceField, TagFilterField
|
||||||
from utilities.forms.widgets import APISelect
|
from utilities.forms.widgets import APISelect
|
||||||
from virtualization.models import VirtualMachine
|
from virtualization.models import VirtualMachine, Cluster
|
||||||
|
|
||||||
|
|
||||||
class SoftwareProductInstallationForm(NetBoxModelForm):
|
class SoftwareProductInstallationForm(NetBoxModelForm):
|
||||||
@@ -15,6 +15,7 @@ class SoftwareProductInstallationForm(NetBoxModelForm):
|
|||||||
|
|
||||||
device = DynamicModelChoiceField(queryset=Device.objects.all(), required=False)
|
device = DynamicModelChoiceField(queryset=Device.objects.all(), required=False)
|
||||||
virtualmachine = DynamicModelChoiceField(queryset=VirtualMachine.objects.all(), required=False)
|
virtualmachine = DynamicModelChoiceField(queryset=VirtualMachine.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,
|
||||||
@@ -31,7 +32,7 @@ class SoftwareProductInstallationForm(NetBoxModelForm):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = SoftwareProductInstallation
|
model = SoftwareProductInstallation
|
||||||
fields = ("device", "virtualmachine", "software_product", "version", "tags")
|
fields = ("device", "virtualmachine", "cluster", "software_product", "version", "tags")
|
||||||
|
|
||||||
def clean_version(self):
|
def clean_version(self):
|
||||||
version = self.cleaned_data["version"]
|
version = self.cleaned_data["version"]
|
||||||
@@ -45,11 +46,6 @@ class SoftwareProductInstallationForm(NetBoxModelForm):
|
|||||||
)
|
)
|
||||||
return version
|
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):
|
class SoftwareProductInstallationFilterForm(NetBoxModelFilterSetForm):
|
||||||
model = SoftwareProductInstallation
|
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(
|
virtualmachine = models.ForeignKey(
|
||||||
to="virtualization.VirtualMachine", on_delete=models.PROTECT, null=True, blank=True
|
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)
|
software_product = models.ForeignKey(to="netbox_slm.SoftwareProduct", on_delete=models.PROTECT)
|
||||||
version = models.ForeignKey(to="netbox_slm.SoftwareProductVersion", 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):
|
def __str__(self):
|
||||||
return f"{self.pk} ({self.platform})"
|
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):
|
def get_absolute_url(self):
|
||||||
return reverse("plugins:netbox_slm:softwareproductinstallation", kwargs={"pk": self.pk})
|
return reverse("plugins:netbox_slm:softwareproductinstallation", kwargs={"pk": self.pk})
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def platform(self):
|
def platform(self):
|
||||||
return self.device or self.virtualmachine
|
return self.device or self.virtualmachine or self.cluster
|
||||||
|
|
||||||
def render_type(self):
|
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):
|
class SoftwareLicense(NetBoxModel):
|
||||||
@@ -111,7 +129,7 @@ class SoftwareLicense(NetBoxModel):
|
|||||||
software_product = models.ForeignKey(to="netbox_slm.SoftwareProduct", on_delete=models.PROTECT)
|
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)
|
version = models.ForeignKey(to="netbox_slm.SoftwareProductVersion", on_delete=models.PROTECT, null=True, blank=True)
|
||||||
installation = models.ForeignKey(
|
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()
|
objects = RestrictedQuerySet.as_manager()
|
||||||
|
|||||||
+13
-5
@@ -1,5 +1,5 @@
|
|||||||
import django_tables2 as tables
|
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.tables import NetBoxTable, ToggleColumn, columns
|
||||||
from netbox_slm.models import SoftwareProduct, SoftwareProductVersion, SoftwareProductInstallation, SoftwareLicense
|
from netbox_slm.models import SoftwareProduct, SoftwareProductVersion, SoftwareProductInstallation, SoftwareLicense
|
||||||
@@ -95,8 +95,10 @@ class SoftwareProductInstallationTable(NetBoxTable):
|
|||||||
|
|
||||||
pk = ToggleColumn()
|
pk = ToggleColumn()
|
||||||
name = tables.LinkColumn()
|
name = tables.LinkColumn()
|
||||||
|
|
||||||
device = tables.Column(accessor="device", linkify=True)
|
device = tables.Column(accessor="device", linkify=True)
|
||||||
virtualmachine = tables.Column(accessor="virtualmachine", linkify=True)
|
virtualmachine = tables.Column(accessor="virtualmachine", linkify=True)
|
||||||
|
cluster = tables.Column(accessor="cluster", linkify=True)
|
||||||
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")
|
||||||
software_product = tables.Column(accessor="software_product", linkify=True)
|
software_product = tables.Column(accessor="software_product", linkify=True)
|
||||||
@@ -125,12 +127,18 @@ class SoftwareProductInstallationTable(NetBoxTable):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def order_platform(self, queryset, is_descending):
|
def order_platform(self, queryset, is_descending):
|
||||||
queryset = queryset.order_by(("device" if is_descending else "virtualmachine"))
|
device_annotate = queryset.filter(device__isnull=False).annotate(platform_value=F("device__name"))
|
||||||
return queryset, True
|
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):
|
def order_type(self, queryset, is_descending):
|
||||||
queryset = queryset.order_by(("device" if is_descending else "virtualmachine"))
|
device_annotate = queryset.filter(device__isnull=False).annotate(render_type=Value("device"))
|
||||||
return queryset, True
|
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):
|
def render_software_product(self, value, **kwargs):
|
||||||
return f"{kwargs['record'].software_product.manufacturer.name} - {value}"
|
return f"{kwargs['record'].software_product.manufacturer.name} - {value}"
|
||||||
|
|||||||
@@ -18,11 +18,16 @@
|
|||||||
<th scope="row">Device</th>
|
<th scope="row">Device</th>
|
||||||
<td>{{ object.device }}</td>
|
<td>{{ object.device }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% else %}
|
{% elif object.virtualmachine %}
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Virtualmachine</th>
|
<th scope="row">Virtualmachine</th>
|
||||||
<td>{{ object.virtualmachine }}</td>
|
<td>{{ object.virtualmachine }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
{% else %}
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Cluster</th>
|
||||||
|
<td>{{ object.cluster }}</td>
|
||||||
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Software Product</th>
|
<th scope="row">Software Product</th>
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
from django.test import TestCase
|
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
|
from netbox_slm.models import SoftwareProduct, SoftwareProductVersion, SoftwareProductInstallation, SoftwareLicense
|
||||||
|
|
||||||
|
|
||||||
@@ -9,18 +11,29 @@ class ModelTestCase(TestCase):
|
|||||||
self.v_name = "test version"
|
self.v_name = "test version"
|
||||||
self.l_name = "test license"
|
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 = SoftwareProduct.objects.create(name=self.p_name)
|
||||||
self.software_product_version = SoftwareProductVersion.objects.create(
|
self.software_product_version = SoftwareProductVersion.objects.create(
|
||||||
name=self.v_name, software_product=self.software_product
|
name=self.v_name, software_product=self.software_product
|
||||||
)
|
)
|
||||||
self.software_product_installation = SoftwareProductInstallation.objects.create(
|
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(
|
self.software_license = SoftwareLicense.objects.create(
|
||||||
name=self.l_name,
|
name=self.l_name,
|
||||||
software_product=self.software_product,
|
software_product=self.software_product,
|
||||||
version=self.software_product_version,
|
version=self.software_product_version,
|
||||||
installation=self.software_product_installation,
|
installation=self.software_product_installation,
|
||||||
|
stored_location_url=self.test_url,
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_model_name(self):
|
def test_model_name(self):
|
||||||
@@ -42,4 +55,23 @@ class ModelTestCase(TestCase):
|
|||||||
|
|
||||||
def test_product_installation_methods(self):
|
def test_product_installation_methods(self):
|
||||||
self.assertEqual("virtualmachine", self.software_product_installation.render_type())
|
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