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 #}
Support Contract
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 @@
Contract
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)