diff --git a/netbox_lifecycle/api/_serializers/contract.py b/netbox_lifecycle/api/_serializers/contract.py
index 3d3193e..95c45e7 100644
--- a/netbox_lifecycle/api/_serializers/contract.py
+++ b/netbox_lifecycle/api/_serializers/contract.py
@@ -1,6 +1,6 @@
from rest_framework import serializers
-from dcim.api.serializers_.devices import DeviceSerializer
+from dcim.api.serializers_.devices import DeviceSerializer, ModuleSerializer
from dcim.api.serializers_.manufacturers import ManufacturerSerializer
from netbox.api.serializers import NetBoxModelSerializer
from netbox_lifecycle.api._serializers.license import LicenseAssignmentSerializer
@@ -44,12 +44,13 @@ class SupportContractAssignmentSerializer(NetBoxModelSerializer):
contract = SupportContractSerializer(nested=True)
sku = SupportSKUSerializer(nested=True, required=False, allow_null=True)
device = DeviceSerializer(nested=True, required=False, allow_null=True)
+ module = ModuleSerializer(nested=True, required=False, allow_null=True)
license = LicenseAssignmentSerializer(nested=True, required=False, allow_null=True)
class Meta:
model = SupportContractAssignment
fields = (
- 'url', 'id', 'display', 'contract', 'sku', 'device', 'license', 'end', 'description', 'comments',
+ 'url', 'id', 'display', 'contract', 'sku', 'device', 'license', 'module', 'end', 'description', 'comments',
)
- brief_fields = ('url', 'id', 'display', 'contract', 'sku', 'device', 'license', )
+ brief_fields = ('url', 'id', 'display', 'contract', 'sku', 'device', 'license', 'module', )
diff --git a/netbox_lifecycle/filtersets/contract.py b/netbox_lifecycle/filtersets/contract.py
index 364a26b..7eece7a 100644
--- a/netbox_lifecycle/filtersets/contract.py
+++ b/netbox_lifecycle/filtersets/contract.py
@@ -2,7 +2,7 @@
from django.db.models import Q
from django.utils.translation import gettext as _
-from dcim.models import Manufacturer, Device
+from dcim.models import Manufacturer, Device, Module
from netbox.filtersets import NetBoxModelFilterSet
from netbox_lifecycle.models import Vendor, SupportContract, SupportContractAssignment, SupportSKU, LicenseAssignment, \
License
@@ -129,6 +129,11 @@ class SupportContractAssignmentFilterSet(NetBoxModelFilterSet):
to_field_name='name',
label=_('License (SKU)'),
)
+ module_id = django_filters.ModelMultipleChoiceFilter(
+ field_name='module',
+ queryset=Module.objects.all(),
+ label=_('Module (ID)'),
+ )
class Meta:
model = SupportContractAssignment
diff --git a/netbox_lifecycle/forms/filtersets.py b/netbox_lifecycle/forms/filtersets.py
index 50d2a9e..dec2ae9 100644
--- a/netbox_lifecycle/forms/filtersets.py
+++ b/netbox_lifecycle/forms/filtersets.py
@@ -3,7 +3,7 @@
from django.db.models import Q
from django.forms import DateField
-from dcim.models import Device, Manufacturer
+from dcim.models import Device, Manufacturer, Module
from netbox.forms import NetBoxModelFilterSetForm
from netbox_lifecycle.models import HardwareLifecycle, SupportContract, Vendor, License, LicenseAssignment, \
SupportContractAssignment, SupportSKU
@@ -138,6 +138,12 @@ class SupportContractAssignmentFilterForm(NetBoxModelFilterSetForm):
selector=True,
label=_('Devices'),
)
+ module_id = DynamicModelMultipleChoiceField(
+ queryset=Module.objects.all(),
+ required=False,
+ selector=True,
+ label=_('Modules'),
+ )
tag = TagFilterField(model)
diff --git a/netbox_lifecycle/forms/model_forms.py b/netbox_lifecycle/forms/model_forms.py
index d33ab17..f75a563 100644
--- a/netbox_lifecycle/forms/model_forms.py
+++ b/netbox_lifecycle/forms/model_forms.py
@@ -1,7 +1,7 @@
from django import forms
from django.utils.translation import gettext as _
-from dcim.models import DeviceType, ModuleType, Manufacturer, Device
+from dcim.models import DeviceType, ModuleType, Manufacturer, Device, Module
from netbox.forms import NetBoxModelForm
from netbox_lifecycle.models import HardwareLifecycle, Vendor, SupportContract, LicenseAssignment, License, \
SupportContractAssignment, SupportSKU
@@ -77,10 +77,16 @@ class SupportContractAssignmentForm(NetBoxModelForm):
selector=True,
label=_('License Assignment'),
)
+ module = DynamicModelChoiceField(
+ queryset=Module.objects.all(),
+ required=False,
+ selector=True,
+ label=_('Module'),
+ )
class Meta:
model = SupportContractAssignment
- fields = ('contract', 'sku', 'device', 'license', 'end', 'description', 'comments', 'tags', )
+ fields = ('contract', 'sku', 'device', 'license', 'module', 'end', 'description', 'comments', 'tags', )
widgets = {
'end': DatePicker(),
}
@@ -93,15 +99,22 @@ def clean(self):
# Handle object assignment
selected_objects = [
- field for field in ('device', 'license') if self.cleaned_data[field]
+ field for field in ('device', 'license', 'module') if self.cleaned_data[field]
]
if len(selected_objects) == 0:
raise forms.ValidationError({
- 'device': "You must select at least a device or license",
- 'license': "You must select at least a device or license"
+ 'device': "You must select at least a device, a license, or a module",
+ 'license': "You must select at least a device, a license, or a module",
+ 'module': "You must select at least a device, a license, or a module"
})
+ if self.cleaned_data.get('module'):
+ if self.cleaned_data.get('license') or self.cleaned_data.get('device'):
+ raise forms.ValidationError({
+ 'module': 'Selecting a Module excludes the selection of a Device or License'
+ })
+
if self.cleaned_data.get('license') and not self.cleaned_data.get('device'):
self.cleaned_data['device'] = self.cleaned_data.get('license').device
diff --git a/netbox_lifecycle/graphql/types.py b/netbox_lifecycle/graphql/types.py
index d2f9387..f71cb48 100644
--- a/netbox_lifecycle/graphql/types.py
+++ b/netbox_lifecycle/graphql/types.py
@@ -76,6 +76,7 @@ class SupportContractAssignmentType(NetBoxObjectType):
sku: SupportSKUType | None
device: DeviceType | None
license: LicenseType | None
+ module: ModuleType | None
end: str | None
diff --git a/netbox_lifecycle/migrations/0015_alter_supportcontractassignment_options_and_more.py b/netbox_lifecycle/migrations/0015_alter_supportcontractassignment_options_and_more.py
new file mode 100644
index 0000000..466d11e
--- /dev/null
+++ b/netbox_lifecycle/migrations/0015_alter_supportcontractassignment_options_and_more.py
@@ -0,0 +1,24 @@
+# Generated by Django 5.0.9 on 2025-03-13 16:40
+
+import django.db.models.deletion
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('dcim', '0190_nested_modules'),
+ ('netbox_lifecycle', '0014_rename_last_contract_date_and_more'),
+ ]
+
+ operations = [
+ migrations.AlterModelOptions(
+ name='supportcontractassignment',
+ options={'ordering': ['contract', 'device', 'license', 'module']},
+ ),
+ migrations.AddField(
+ model_name='supportcontractassignment',
+ name='module',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='contracts', to='dcim.module'),
+ ),
+ ]
diff --git a/netbox_lifecycle/models/contract.py b/netbox_lifecycle/models/contract.py
index f740b1f..c3a7b86 100644
--- a/netbox_lifecycle/models/contract.py
+++ b/netbox_lifecycle/models/contract.py
@@ -5,7 +5,7 @@
from django.urls import reverse
from django.utils.translation import gettext as _
-from dcim.choices import DeviceStatusChoices
+from dcim.choices import DeviceStatusChoices, ModuleStatusChoices
from netbox.models import PrimaryModel
@@ -138,6 +138,13 @@ class SupportContractAssignment(PrimaryModel):
blank=True,
related_name='contracts',
)
+ module = models.ForeignKey(
+ to='dcim.Module',
+ on_delete=models.SET_NULL,
+ null=True,
+ blank=True,
+ related_name='contracts',
+ )
end = models.DateField(
null=True,
blank=True,
@@ -153,16 +160,20 @@ class SupportContractAssignment(PrimaryModel):
'netbox_lifecycle.SupportSKU',
'netbox_lifecycle.License',
'dcim.Device',
+ 'dcim.Module',
)
class Meta:
- ordering = ['contract', 'device', 'license']
+ ordering = ['contract', 'device', 'license', 'module']
constraints = ()
def __str__(self):
if self.license and self.device:
return f'{self.device} ({self.license}): {self.contract.contract_id}'
- return f'{self.device}: {self.contract.contract_id}'
+ if self.device:
+ return f'{self.device}: {self.contract.contract_id}'
+ if self.module:
+ return f'{self.module}: {self.contract.contract_id}'
def get_absolute_url(self):
return reverse('plugins:netbox_lifecycle:supportcontractassignment', args=[self.pk])
@@ -178,16 +189,28 @@ def get_device_status_color(self):
return
return DeviceStatusChoices.colors.get(self.device.status)
+ def get_module_status_color(self):
+ if self.module is None:
+ return
+ return ModuleStatusChoices.colors.get(self.module.status)
+
def clean(self):
- if self.device and self.license and SupportContractAssignment.objects.filter(
+ if self.module:
+ if self.license or self.device:
+ raise ValidationError('Assigning a Module excludes the assigment of a Device or License')
+ elif SupportContractAssignment.objects.filter(
+ contract=self.contract, module=self.module
+ ).exclude(pk=self.pk).count() > 0:
+ raise ValidationError('Module must be unique')
+ elif self.device and self.license and not self.module and SupportContractAssignment.objects.filter(
contract=self.contract, device=self.device, license=self.license, sku=self.sku
).exclude(pk=self.pk).count() > 0:
raise ValidationError('Device or License must be unique')
- elif self.device and not self.license and SupportContractAssignment.objects.filter(
+ elif self.device and not self.license and not self.module and SupportContractAssignment.objects.filter(
contract=self.contract, device=self.device, license=self.license
).exclude(pk=self.pk).count() > 0:
raise ValidationError('Device must be unique')
- elif not self.device and self.license and SupportContractAssignment.objects.filter(
+ elif not self.device and self.license and not self.module and SupportContractAssignment.objects.filter(
contract=self.contract, device=self.device, license=self.license
).exclude(pk=self.pk).count() > 0:
raise ValidationError('License must be unique')
diff --git a/netbox_lifecycle/tables/contract.py b/netbox_lifecycle/tables/contract.py
index 13ef130..475ed88 100644
--- a/netbox_lifecycle/tables/contract.py
+++ b/netbox_lifecycle/tables/contract.py
@@ -81,7 +81,7 @@ class SupportContractAssignmentTable(NetBoxTable):
orderable=True,
)
device_serial = tables.Column(
- verbose_name=_('Serial Number'),
+ verbose_name=_('Device Serial Number'),
accessor='device__serial',
orderable=True,
)
@@ -102,6 +102,22 @@ class SupportContractAssignmentTable(NetBoxTable):
linkify=False,
orderable=True,
)
+ module = tables.Column(
+ verbose_name=_('Module'),
+ accessor='module',
+ linkify=True,
+ orderable=True,
+ )
+ module_serial = tables.Column(
+ verbose_name=_('Module Serial Number'),
+ accessor='module__serial',
+ orderable=True,
+ )
+ module_status = ChoiceFieldColumn(
+ verbose_name=_('Module Status'),
+ accessor='module__status',
+ orderable=True,
+ )
quantity = tables.Column(
verbose_name=_('License Quantity'),
accessor='license__quantity',
@@ -121,8 +137,8 @@ class Meta(NetBoxTable.Meta):
model = SupportContractAssignment
fields = (
'pk', 'contract', 'sku', 'device_name', 'license_name', 'device_model', 'device_serial', 'quantity',
- 'renewal', 'end', 'description', 'comments',
+ 'renewal', 'end', 'description', 'comments', 'module', 'module_serial', 'module_status'
)
default_columns = (
- 'pk', 'contract', 'sku', 'device_name', 'license_name', 'device_model', 'device_serial'
+ 'pk', 'contract', 'sku', 'device_name', 'license_name', 'module', 'device_model', 'device_serial'
)
diff --git a/netbox_lifecycle/template_content.py b/netbox_lifecycle/template_content.py
index 44837c7..76ee3b0 100644
--- a/netbox_lifecycle/template_content.py
+++ b/netbox_lifecycle/template_content.py
@@ -9,13 +9,14 @@
class DeviceHardwareInfoExtension(PluginTemplateExtension):
def right_page(self):
object = self.context.get('object')
- support_contract = contract.SupportContractAssignment.objects.filter(device_id=self.context['object'].id).first()
match self.kind:
case "device":
+ support_contract = contract.SupportContractAssignment.objects.filter(device_id=self.context['object'].id).first()
content_type = ContentType.objects.get(app_label="dcim", model="devicetype")
lifecycle_info = hardware.HardwareLifecycle.objects.filter(assigned_object_id=self.context['object'].device_type_id,
assigned_object_type_id=content_type.id).first()
case "module":
+ support_contract = contract.SupportContractAssignment.objects.filter(module_id=self.context['object'].id).first()
content_type = ContentType.objects.get(app_label="dcim", model="moduletype")
lifecycle_info = hardware.HardwareLifecycle.objects.filter(assigned_object_id=self.context['object'].module_type_id,
assigned_object_type_id=content_type.id).first()
@@ -53,7 +54,7 @@ class DeviceHardwareLifecycleInfo(DeviceHardwareInfoExtension):
kind = 'device'
-class ModuleHardwareLifecycleInfo(TypeInfoExtension):
+class ModuleHardwareLifecycleInfo(DeviceHardwareInfoExtension):
model = 'dcim.module'
kind = 'module'
diff --git a/netbox_lifecycle/templates/netbox_lifecycle/inc/support_contract_info.html b/netbox_lifecycle/templates/netbox_lifecycle/inc/support_contract_info.html
index 89399ba..9b4c89b 100644
--- a/netbox_lifecycle/templates/netbox_lifecycle/inc/support_contract_info.html
+++ b/netbox_lifecycle/templates/netbox_lifecycle/inc/support_contract_info.html
@@ -1,7 +1,7 @@
{% load filters %}
{% load helpers %}
-{# renders panel on object (device) with support contract info assigned to it #}
+{# renders panel on object (device, module) with support contract info assigned to it #}
diff --git a/netbox_lifecycle/templates/netbox_lifecycle/supportcontractassignment.html b/netbox_lifecycle/templates/netbox_lifecycle/supportcontractassignment.html
index 5abdc9b..f956171 100644
--- a/netbox_lifecycle/templates/netbox_lifecycle/supportcontractassignment.html
+++ b/netbox_lifecycle/templates/netbox_lifecycle/supportcontractassignment.html
@@ -29,6 +29,10 @@
License |
{{ object.license|linkify }} |
+
+ | Module |
+ {{ object.module|linkify }} |
+
| End Date |
{{ object.end }} |
diff --git a/netbox_lifecycle/templates/netbox_lifecycle/supportcontractassignment_edit.html b/netbox_lifecycle/templates/netbox_lifecycle/supportcontractassignment_edit.html
index f663529..7298201 100644
--- a/netbox_lifecycle/templates/netbox_lifecycle/supportcontractassignment_edit.html
+++ b/netbox_lifecycle/templates/netbox_lifecycle/supportcontractassignment_edit.html
@@ -27,6 +27,11 @@ Assignment
License
+
+
+
@@ -37,6 +42,9 @@ Assignment
{% render_field form.license %}
+
+ {% render_field form.module %}
+
diff --git a/netbox_lifecycle/tests/test_api.py b/netbox_lifecycle/tests/test_api.py
index 94824d1..d7f5600 100644
--- a/netbox_lifecycle/tests/test_api.py
+++ b/netbox_lifecycle/tests/test_api.py
@@ -1,11 +1,13 @@
from django.urls import reverse
from rest_framework import status
-from dcim.models import Manufacturer, DeviceType
+from dcim.models import Manufacturer, DeviceType, Module, ModuleBay, ModuleType
+from dcim.choices import ModuleStatusChoices
from utilities.testing import APIViewTestCases, APITestCase, create_test_device
from netbox_lifecycle.models import *
from netbox_lifecycle.utilities.gfk_mixins import DateFieldMixin
+from netbox_lifecycle.utilities.testing import create_test_module
class AppTest(APITestCase):
@@ -215,9 +217,9 @@ def setUpTestData(cls):
class SupportContractAssignmentTest(APIViewTestCases.APIViewTestCase):
model = SupportContractAssignment
view_namespace = "plugins-api:netbox_lifecycle"
- brief_fields = ['contract', 'device', 'display', 'id', 'license', 'sku', 'url', ]
+ brief_fields = ['contract', 'device', 'display', 'id', 'license', 'module', 'sku', 'url', ]
- user_permissions = ('netbox_lifecycle.view_supportcontract', 'netbox_lifecycle.view_vendor', 'dcim.view_device', )
+ user_permissions = ('netbox_lifecycle.view_supportcontract', 'netbox_lifecycle.view_vendor', 'dcim.view_device', 'dcim.view_module', )
bulk_update_data = {
'description': "A assignment description"
@@ -228,6 +230,7 @@ def setUpTestData(cls):
manufacturer = Manufacturer.objects.create(name='Manufacturer', slug='manufacturer')
vendor = Vendor.objects.create(name='Vendor')
device = create_test_device(name='Test Device')
+ module = create_test_module()
sku = SupportSKU.objects.create(sku='SKU', manufacturer=manufacturer)
@@ -238,6 +241,8 @@ def setUpTestData(cls):
SupportContract(vendor=vendor, contract_id='NB1000-4'),
SupportContract(vendor=vendor, contract_id='NB1000-5'),
SupportContract(vendor=vendor, contract_id='NB1000-6'),
+ SupportContract(vendor=vendor, contract_id='NB1000-7'),
+ SupportContract(vendor=vendor, contract_id='NB1000-8'),
]
SupportContract.objects.bulk_create(contracts)
@@ -245,24 +250,30 @@ def setUpTestData(cls):
assignments = [
SupportContractAssignment(contract=contracts[0], device=device, sku=sku),
SupportContractAssignment(contract=contracts[1], device=device, sku=sku),
- SupportContractAssignment(contract=contracts[2], device=device, sku=sku),
+ SupportContractAssignment(contract=contracts[2], module=module, sku=sku),
+ SupportContractAssignment(contract=contracts[3], module=module, sku=sku),
]
SupportContractAssignment.objects.bulk_create(assignments)
cls.create_data = [
{
- 'contract': contracts[3].pk,
+ 'contract': contracts[4].pk,
'device': device.pk,
'sku': sku.pk,
},
{
- 'contract': contracts[4].pk,
+ 'contract': contracts[5].pk,
'device': device.pk,
'sku': sku.pk,
},
{
- 'contract': contracts[5].pk,
- 'device': device.pk,
+ 'contract': contracts[6].pk,
+ 'module': module.pk,
+ 'sku': sku.pk,
+ },
+ {
+ 'contract': contracts[7].pk,
+ 'module': module.pk,
'sku': sku.pk,
},
]
diff --git a/netbox_lifecycle/tests/test_filtersets.py b/netbox_lifecycle/tests/test_filtersets.py
index 498c53d..3c6f165 100644
--- a/netbox_lifecycle/tests/test_filtersets.py
+++ b/netbox_lifecycle/tests/test_filtersets.py
@@ -1,6 +1,7 @@
from django.test import TestCase
-from dcim.models import Manufacturer, Device, DeviceType, ModuleType
+from dcim.models import Manufacturer, Device, DeviceType, ModuleBay, ModuleType, Module
+from dcim.choices import ModuleStatusChoices
from utilities.testing import create_test_device
from netbox_lifecycle.filtersets import *
@@ -111,6 +112,7 @@ def setUpTestData(cls):
device = create_test_device(name='Device')
license = License.objects.create(name='License', manufacturer=manufacturer)
license_assignment = LicenseAssignment.objects.create(license=license, vendor=vendor)
+ module = create_test_module()
skus = (
create_test_supportsku(sku='SKU 1', manufacturer=manufacturer),
@@ -123,6 +125,7 @@ def setUpTestData(cls):
create_test_supportcontract(contract_id='Contract 1', vendor=vendor),
create_test_supportcontract(contract_id='Contract 2', vendor=vendor),
create_test_supportcontract(contract_id='Contract 3', vendor=vendor),
+ create_test_supportcontract(contract_id='Contract 4', vendor=vendor),
)
assignments = (
@@ -132,6 +135,8 @@ def setUpTestData(cls):
SupportContractAssignment(contract=contracts[1], sku=skus[1], device=device),
SupportContractAssignment(contract=contracts[2], sku=skus[2], license=license_assignment),
SupportContractAssignment(contract=contracts[2], sku=skus[3], license=license_assignment),
+ SupportContractAssignment(contract=contracts[3], sku=skus[2], module=module),
+ SupportContractAssignment(contract=contracts[3], sku=skus[3], module=module),
)
SupportContractAssignment.objects.bulk_create(assignments)
@@ -177,6 +182,12 @@ def test_license(self):
params = {'license': [license.name, ]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ def test_module(self):
+ module = Module.objects.first()
+
+ params = {'module_id': [module.pk, ]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
class LicenseTestCase(TestCase):
queryset = License.objects.all()
diff --git a/netbox_lifecycle/tests/test_forms.py b/netbox_lifecycle/tests/test_forms.py
index 9426a67..5da8d00 100644
--- a/netbox_lifecycle/tests/test_forms.py
+++ b/netbox_lifecycle/tests/test_forms.py
@@ -1,10 +1,12 @@
from django.test import TestCase
-from dcim.models import Device, Manufacturer
+from dcim.models import Device, Manufacturer, Module, ModuleBay, ModuleType
+from dcim.choices import ModuleStatusChoices
from utilities.testing import create_test_device
from netbox_lifecycle.forms import *
from netbox_lifecycle.models import *
+from netbox_lifecycle.utilities.testing import create_test_module
class VendorTestCase(TestCase):
@@ -96,8 +98,9 @@ def setUpTestData(cls):
SupportContract.objects.create(vendor=vendor, contract_id='Contract')
license = License.objects.create(manufacturer=manufacturer)
license_assignment = LicenseAssignment.objects.create(license=license, vendor=vendor, device=device)
+ module = create_test_module()
- def test_assignment_fail_without_device_or_license(self):
+ def test_assignment_fail_without_device_or_license_or_module(self):
form = SupportContractAssignmentForm(data={
'contract': SupportContract.objects.first().pk,
'sku': SupportSKU.objects.first().pk,
@@ -127,6 +130,16 @@ def test_assignment_with_license(self):
self.assertTrue(form.is_valid())
self.assertTrue(form.save())
+ def test_assignment_with_module(self):
+ form = SupportContractAssignmentForm(data={
+ 'contract': SupportContract.objects.first().pk,
+ 'sku': SupportSKU.objects.first().pk,
+ 'vendor': Vendor.objects.first().pk,
+ 'module': Module.objects.first().pk,
+ })
+ self.assertTrue(form.is_valid())
+ self.assertTrue(form.save())
+
def test_assignment_with_device_and_license(self):
form = SupportContractAssignmentForm(data={
'contract': SupportContract.objects.first().pk,
@@ -149,3 +162,53 @@ def test_assignment_with_device_and_license_with_different_device(self):
self.assertFalse(form.is_valid())
with self.assertRaises(ValueError):
form.save()
+
+ def test_assignment_with_device_and_license_and_module_with_different_device(self):
+ form = SupportContractAssignmentForm(data={
+ 'contract': SupportContract.objects.first().pk,
+ 'sku': SupportSKU.objects.first().pk,
+ 'vendor': Vendor.objects.first().pk,
+ 'device': create_test_device(name='New Test Device'),
+ 'license': LicenseAssignment.objects.first().pk,
+ 'module': Module.objects.first().pk,
+ })
+ self.assertFalse(form.is_valid())
+ with self.assertRaises(ValueError):
+ form.save()
+
+ def test_assignment_with_device_and_module(self):
+ form = SupportContractAssignmentForm(data={
+ 'contract': SupportContract.objects.first().pk,
+ 'sku': SupportSKU.objects.first().pk,
+ 'vendor': Vendor.objects.first().pk,
+ 'device': create_test_device(name='New Test Device'),
+ 'module': Module.objects.first().pk,
+ })
+ self.assertFalse(form.is_valid())
+ with self.assertRaises(ValueError):
+ form.save()
+
+ def test_assignment_with_license_and_module(self):
+ form = SupportContractAssignmentForm(data={
+ 'contract': SupportContract.objects.first().pk,
+ 'sku': SupportSKU.objects.first().pk,
+ 'vendor': Vendor.objects.first().pk,
+ 'license': LicenseAssignment.objects.first().pk,
+ 'module': Module.objects.first().pk,
+ })
+ self.assertFalse(form.is_valid())
+ with self.assertRaises(ValueError):
+ form.save()
+
+ def test_assignment_with_device_and_license_and_module(self):
+ form = SupportContractAssignmentForm(data={
+ 'contract': SupportContract.objects.first().pk,
+ 'sku': SupportSKU.objects.first().pk,
+ 'vendor': Vendor.objects.first().pk,
+ 'device': Device.objects.first().pk,
+ 'license': LicenseAssignment.objects.first().pk,
+ 'module': Module.objects.first().pk,
+ })
+ self.assertFalse(form.is_valid())
+ with self.assertRaises(ValueError):
+ form.save()
\ No newline at end of file
diff --git a/netbox_lifecycle/tests/test_models.py b/netbox_lifecycle/tests/test_models.py
index 626d1e3..08c5aed 100644
--- a/netbox_lifecycle/tests/test_models.py
+++ b/netbox_lifecycle/tests/test_models.py
@@ -3,10 +3,14 @@
from django.core.exceptions import ValidationError
from django.test import TestCase
-from dcim.models import Manufacturer, Site, DeviceRole, DeviceType, Device
+from dcim.models import (
+ Manufacturer, Site, DeviceRole, DeviceType, Device, Module, ModuleBay, ModuleType
+)
+from dcim.choices import ModuleStatusChoices
from netbox_lifecycle.models import (
Vendor, SupportContract, SupportSKU, SupportContractAssignment, License, LicenseAssignment
)
+from netbox_lifecycle.utilities.testing import create_test_module
class SupportContractTestCase(TestCase):
@@ -123,6 +127,8 @@ def setUpTestData(cls):
vendor = Vendor.objects.create(name='Vendor 1')
license = License.objects.create(manufacturer=manufacturer, name='Test License')
+ module = create_test_module()
+
skus = (
SupportSKU(
manufacturer=manufacturer,
@@ -164,6 +170,51 @@ def test_contractassignment_creation(self):
contract.full_clean()
contract.save()
+ def test_module_contractassignment_creation(self):
+ contract = SupportContract.objects.first()
+ sku = SupportSKU.objects.first()
+ module = Module.objects.first()
+
+ contract = SupportContractAssignment(
+ contract=contract,
+ sku=sku,
+ module=module
+ )
+ contract.full_clean()
+ contract.save()
+
+ def test_module_and_license_contractassignment_creation(self):
+ contract = SupportContract.objects.first()
+ sku = SupportSKU.objects.first()
+ license = LicenseAssignment.objects.first()
+ module = Module.objects.first()
+
+ contract = SupportContractAssignment(
+ contract=contract,
+ sku=sku,
+ license=license,
+ module=module
+ )
+
+ with self.assertRaises(ValidationError):
+ contract.full_clean()
+
+ def test_module_and_device_contractassignment_creation(self):
+ contract = SupportContract.objects.first()
+ sku = SupportSKU.objects.first()
+ device = Device.objects.first()
+ module = Module.objects.first()
+
+ contract = SupportContractAssignment(
+ contract=contract,
+ sku=sku,
+ device=device,
+ module=module
+ )
+
+ with self.assertRaises(ValidationError):
+ contract.full_clean()
+
def test_supportcontract_duplicate_ids(self):
contract = SupportContract.objects.first()
sku = SupportSKU.objects.first()
@@ -190,3 +241,25 @@ def test_supportcontract_duplicate_ids(self):
contract2.license = license
contract2.full_clean()
contract2.save()
+
+ def test_module_supportcontract_duplicate_ids(self):
+ contract = SupportContract.objects.first()
+ sku = SupportSKU.objects.first()
+ module = Module.objects.first()
+
+ contract1 = SupportContractAssignment(
+ contract=contract,
+ sku=sku,
+ module=module
+ )
+ contract1.full_clean()
+ contract1.save()
+
+ contract2 = SupportContractAssignment(
+ contract=contract,
+ sku=sku,
+ module=module
+ )
+
+ with self.assertRaises(ValidationError):
+ contract2.full_clean()
diff --git a/netbox_lifecycle/utilities/testing.py b/netbox_lifecycle/utilities/testing.py
index 5d66dd3..e97cbc6 100644
--- a/netbox_lifecycle/utilities/testing.py
+++ b/netbox_lifecycle/utilities/testing.py
@@ -1,10 +1,13 @@
-from dcim.models import Manufacturer
+from dcim.models import Manufacturer, ModuleBay, ModuleType, Module
+from dcim.choices import ModuleStatusChoices
from netbox_lifecycle.models import Vendor, SupportSKU, SupportContract
+from utilities.testing import create_test_device
__all__ = (
'create_test_vendor',
'create_test_supportsku',
'create_test_supportcontract',
+ 'create_test_module',
)
@@ -32,3 +35,27 @@ def create_test_supportcontract(contract_id=None, vendor=None, start=None, renew
vendor = Vendor.objects.first()
return SupportContract.objects.create(vendor=vendor, contract_id=contract_id, start=start, renewal=renewal, end=end)
+
+
+def create_test_module(device=None, module_bay=None, module_type=None, status=None):
+ #For accurate testing purposes, the used Device here can't be one of existing objects
+ if device is None:
+ device = create_test_device(name='Test Module Device')
+
+ if module_bay is None:
+ module_bay = ModuleBay.objects.create(device=device, name='Test ModuleBay')
+
+ if module_type is None:
+ if ModuleType.objects.all().count() == 0:
+ if Manufacturer.objects.all().count() == 0:
+ manufacturer = Manufacturer.objects.create(name='Manufacturer', slug='manufacturer')
+ else:
+ manufacturer = Manufacturer.objects.first()
+ module_type = ModuleType.objects.create(manufacturer=manufacturer, model='Test ModuleType')
+ else:
+ module_type = ModuleType.objects.first()
+
+ if status is None:
+ status=ModuleStatusChoices.STATUS_ACTIVE
+
+ return Module.objects.create(device=device, module_bay=module_bay, module_type=module_type, status=status)