diff --git a/netbox_sys_plugin/api/serializers.py b/netbox_sys_plugin/api/serializers.py index f9086f87786975a050b109217dec2aa203e55479..a866275fafb976131c39446315bb1bb3ee198663 100644 --- a/netbox_sys_plugin/api/serializers.py +++ b/netbox_sys_plugin/api/serializers.py @@ -98,16 +98,78 @@ class VirtualMachineTypeSerializer(NetBoxModelSerializer): fields = '__all__' class VmAssignedVirtualMachineTypeSerializer(NetBoxModelSerializer): - id = serializers.IntegerField(read_only=True) - virtual_machine = NestedVirtualMachineSerializer(source='assigned_object', read_only=True) - virtual_machine_type = VirtualMachineTypeSerializer() - assigned_object_id = serializers.IntegerField(write_only=True) - assigned_object_type = serializers.CharField(write_only=True) - display = serializers.CharField(source="__str__") + """ + Serializer for VmAssignedVirtualMachineType. + Includes nested representations for related objects. + """ + virtual_machine_type = serializers.PrimaryKeyRelatedField( + queryset=VirtualMachineType.objects.all() + ) + assigned_object_type = serializers.PrimaryKeyRelatedField( + queryset=ContentType.objects.all() + ) + assigned_object_id = serializers.IntegerField() + + # Optional: Add any extra fields for enhanced representation + virtual_machine_type_name = serializers.SerializerMethodField() + assigned_object = serializers.SerializerMethodField() class Meta: model = VmAssignedVirtualMachineType - fields = '__all__' + fields = [ + 'id', + 'virtual_machine_type', + 'virtual_machine_type_name', + 'virtual_machine_type_assignment_desc', + 'assigned_object_type', + 'assigned_object_id', + 'assigned_object', + 'created', + 'last_updated', + ] + + def get_virtual_machine_type_name(self, obj): + """Get the name of the related VirtualMachineType.""" + return obj.virtual_machine_type.virtual_machine_type_name if obj.virtual_machine_type else None + + def get_assigned_object(self, obj): + """Get the assigned object representation.""" + assigned_object = obj.assigned_object + if isinstance(assigned_object, VirtualMachine): + return { + "id": assigned_object.id, + "name": assigned_object.name, + "cluster": assigned_object.cluster.name if assigned_object.cluster else None, + } + return None + + def validate(self, data): + virtual_machine_type = data.get("virtual_machine_type") + assigned_object_id = data.get("assigned_object_id") + assigned_object_type = data.get("assigned_object_type") + + # Validate uniqueness + if VmAssignedVirtualMachineType.objects.filter( + virtual_machine_type=virtual_machine_type, + assigned_object_id=assigned_object_id, + assigned_object_type=assigned_object_type + ).exists(): + raise serializers.ValidationError( + "The combination of virtual_machine_type, assigned_object_id, and assigned_object_type must be unique." + ) + + # Ensure VirtualMachineType.assigned_object matches the ClusterType of the Cluster associated with the VirtualMachine + if assigned_object_type.model == "virtualmachine": + virtual_machine = VirtualMachine.objects.get(id=assigned_object_id) + cluster_type_of_vm = virtual_machine.cluster.type # Get the ClusterType of the Cluster + virtual_machine_type_cluster_type = virtual_machine_type.assigned_object # ClusterType linked to the VirtualMachineType + + if cluster_type_of_vm != virtual_machine_type_cluster_type: + raise serializers.ValidationError( + "The ClusterType of the Cluster where the VirtualMachine is assigned " + "must match the ClusterType of the VirtualMachineType." + ) + return data class DomainNamesSerializer(NetBoxModelSerializer): id = serializers.IntegerField(read_only=True) diff --git a/netbox_sys_plugin/api/views.py b/netbox_sys_plugin/api/views.py index 9424ca1627bd80b106b16e85af82c618ee564b19..0e0c29403be8e9ad704df38c10067c54fe6d89ac 100644 --- a/netbox_sys_plugin/api/views.py +++ b/netbox_sys_plugin/api/views.py @@ -27,7 +27,7 @@ class VmAssignedVirtualMachineTypeViewSet(viewsets.ModelViewSet): queryset = VmAssignedVirtualMachineType.objects.prefetch_related(Prefetch( 'virtual_machine_type', queryset=VirtualMachineType.objects.all(), ),) - serializer_class = NestedVmAssignedVirtualMachineTypeSerializer + serializer_class = VmAssignedVirtualMachineTypeSerializer http_method_names = ["get", "post", "patch", "delete", "options"] class VirtualMachineTypeViewSet(viewsets.ModelViewSet): diff --git a/netbox_sys_plugin/tests/vm_assigned_virtual_machine_type/__init__.py b/netbox_sys_plugin/tests/vm_assigned_virtual_machine_type/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/netbox_sys_plugin/tests/vm_assigned_virtual_machine_type/test_vm_assigned_virtual_machine_type_api.py b/netbox_sys_plugin/tests/vm_assigned_virtual_machine_type/test_vm_assigned_virtual_machine_type_api.py new file mode 100644 index 0000000000000000000000000000000000000000..3850ea79fceab5da47504dc73f3299d0cf4a3b6c --- /dev/null +++ b/netbox_sys_plugin/tests/vm_assigned_virtual_machine_type/test_vm_assigned_virtual_machine_type_api.py @@ -0,0 +1,175 @@ +"""SYS Plugin VmAssignedVirtualMachineType API Test Case Class""" + +from users.models import ObjectPermission +from django.contrib.contenttypes.models import ContentType +from rest_framework import status +from virtualization.models import VirtualMachine, Cluster, ClusterType +from netbox_sys_plugin.models import VirtualMachineType, VmAssignedVirtualMachineType +from ..base import BaseAPITestCase + +class VmAssignedVirtualMachineTypeApiTestCase(BaseAPITestCase): + """Test suite for VmAssignedVirtualMachineType API""" + model = VmAssignedVirtualMachineType + brief_fields = ["virtual_machine_type", "assigned_object_id", "assigned_object_type"] + + @classmethod + def setUpTestData(cls): + """Set up test data for VmAssignedVirtualMachineType API""" + cls.vm_ct = ContentType.objects.get_for_model(VirtualMachine) + + # Create ClusterTypes + cls.cluster_type_aws = ClusterType.objects.create(name="Test ClusterType AWS", slug='test-cluster-type-aws') + cls.cluster_type_vmware = ClusterType.objects.create(name="Test ClusterType VMWare", slug='test-cluster-type-vmware') + + # Create Clusters linked to the ClusterTypes + cls.cluster_aws = Cluster.objects.create(name="Test Cluster AWS", type=cls.cluster_type_aws) + cls.cluster_vmware = Cluster.objects.create(name="Test Cluster VMWare", type=cls.cluster_type_vmware) + + # Create VirtualMachines linked to the Clusters + cls.virtual_machine = VirtualMachine.objects.create( + name="Test VM", + status="active", + cluster=cls.cluster_aws + ) + cls.virtual_machine2 = VirtualMachine.objects.create( + name="Test VM2", + status="active", + cluster=cls.cluster_aws + ) + cls.virtual_machine3 = VirtualMachine.objects.create( + name="Test VM3", + status="active", + cluster=cls.cluster_aws + ) + + cls.virtual_machine4 = VirtualMachine.objects.create( + name="Test VM4", + status="active", + cluster=cls.cluster_vmware + ) + + # Create VirtualMachineTypes + cls.virtual_machine_type_aws = VirtualMachineType.objects.create( + virtual_machine_type_name='AWS', + assigned_object=cls.cluster_type_aws, + virtual_machine_type_desc='AWS description' + ) + + cls.virtual_machine_type_vmware = VirtualMachineType.objects.create( + virtual_machine_type_name='VMWare', + assigned_object=cls.cluster_type_vmware, + virtual_machine_type_desc='VMWare description' + ) + + # Assign VirtualMachineTypes to VirtualMachines + VmAssignedVirtualMachineType.objects.create( + virtual_machine_type=cls.virtual_machine_type_aws, + assigned_object_id=cls.virtual_machine.id + ) + + + # Data for valid creation + cls.valid_create_data = [ + { + "virtual_machine_type": cls.virtual_machine_type_aws.pk, + "assigned_object_id": cls.virtual_machine2.id, + "assigned_object_type": cls.vm_ct.pk + } + ] + + # Data for invalid creation + cls.invalid_create_data = [ + { + "virtual_machine_type": None, + "assigned_object_type": cls.vm_ct.pk, + "assigned_object_id": cls.virtual_machine4.id, + }, + { + "virtual_machine_type": cls.virtual_machine_type_vmware.id, # assign virtual_machine_type diffrent than ClusertType of VM + "assigned_object_type": cls.vm_ct.pk, + "assigned_object_id": cls.virtual_machine3.id, + }, + ] + + def test_create_valid_virtual_machine_type_assignment(self): + """Test creating a valid VmAssignedVirtualMachineType""" + obj_perm = ObjectPermission( + name="Create VmAssignedVirtualMachineType Permission", + actions=["add", "view"], + ) + obj_perm.save() + obj_perm.users.add(self.user) + obj_perm.object_types.add(ContentType.objects.get_for_model(VmAssignedVirtualMachineType)) + + form_data = self.valid_create_data[0] + response = self.client.post(self._get_list_url(), form_data, format="json", **self.header) + self.assertHttpStatus(response, status.HTTP_201_CREATED) + self.assertEqual(response.data["assigned_object_id"], form_data["assigned_object_id"]) + self.assertEqual(response.data["virtual_machine_type"], form_data["virtual_machine_type"]) + + def test_update_virtual_machine_type_assignment(self): + """Test updating an existing VmAssignedVirtualMachineType""" + vm_assignment = VmAssignedVirtualMachineType.objects.first() + obj_perm = ObjectPermission( + name="Update VmAssignedVirtualMachineType Permission", + actions=["change", "view"], + ) + obj_perm.save() + obj_perm.users.add(self.user) + obj_perm.object_types.add(ContentType.objects.get_for_model(VmAssignedVirtualMachineType)) + + update_data = { + "virtual_machine_type": self.virtual_machine_type_aws.id, + "assigned_object_id": self.virtual_machine.id, + "assigned_object_type": self.vm_ct.pk + } + + response = self.client.patch(self._get_detail_url(vm_assignment), update_data, format="json", **self.header) + self.assertHttpStatus(response, status.HTTP_200_OK) + self.assertEqual(response.data["virtual_machine_type"], update_data["virtual_machine_type"]) + self.assertEqual(response.data["assigned_object_id"], update_data["assigned_object_id"]) + + def test_create_invalid_virtual_machine_type_assignment(self): + """Test creating invalid VmAssignedVirtualMachineType""" + obj_perm = ObjectPermission( + name="Create Invalid VmAssignedVirtualMachineType Permission", + actions=["add", "view"], + ) + obj_perm.save() + obj_perm.users.add(self.user) + obj_perm.object_types.add(ContentType.objects.get_for_model(VmAssignedVirtualMachineType)) + + # for form_data in self.invalid_create_data[0]: + response = self.client.post(self._get_list_url(), self.invalid_create_data[0], format="json", **self.header) + self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST) + self.assertIn('This field may not be null.', str(response.data)) + + def test_create_invalid_virtual_machine_type_assignment2(self): + """Test creating invalid VmAssignedVirtualMachineType""" + obj_perm = ObjectPermission( + name="Create Invalid VmAssignedVirtualMachineType Permission", + actions=["add", "view"], + ) + obj_perm.save() + obj_perm.users.add(self.user) + obj_perm.object_types.add(ContentType.objects.get_for_model(VmAssignedVirtualMachineType)) + + # for form_data in self.invalid_create_data[1]: + response = self.client.post(self._get_list_url(), self.invalid_create_data[1], format="json", **self.header) + self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST) + self.assertIn('The ClusterType of the Cluster where the VirtualMachine is assigned must match the ClusterType of the VirtualMachineType.', str(response.data)) + + def test_delete_virtual_machine_type_assignment(self): + """Test deleting a VmAssignedVirtualMachineType""" + vm_assignment = VmAssignedVirtualMachineType.objects.first() + obj_perm = ObjectPermission( + name="Delete VmAssignedVirtualMachineType Permission", + actions=["delete"], + ) + obj_perm.save() + obj_perm.users.add(self.user) + obj_perm.object_types.add(ContentType.objects.get_for_model(VmAssignedVirtualMachineType)) + + response = self.client.delete(self._get_detail_url(vm_assignment), **self.header) + self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT) + self.assertFalse(VmAssignedVirtualMachineType.objects.filter(id=vm_assignment.id).exists())