diff --git a/openwisp_ipam/base/models.py b/openwisp_ipam/base/models.py index 0953262..ec6233b 100644 --- a/openwisp_ipam/base/models.py +++ b/openwisp_ipam/base/models.py @@ -136,6 +136,18 @@ def _validate_overlapping_subnets(self): def _validate_master_subnet_consistency(self): if not self.master_subnet: return + subnet_version = ip_network(self.subnet).version + master_subnet_version = ip_network(self.master_subnet.subnet).version + if subnet_version != master_subnet_version: + raise ValidationError( + { + 'master_subnet': _( + f'IP version mismatch: Subnet {self.subnet} is IPv' + f'{subnet_version}, but Master Subnet ' + f'{self.master_subnet.subnet} is IPv{master_subnet_version}.' + ) + } + ) if not ip_network(self.subnet).subnet_of(ip_network(self.master_subnet.subnet)): raise ValidationError({'master_subnet': _('Invalid master subnet.')}) diff --git a/openwisp_ipam/tests/test_models.py b/openwisp_ipam/tests/test_models.py index 8ed7410..80bb206 100644 --- a/openwisp_ipam/tests/test_models.py +++ b/openwisp_ipam/tests/test_models.py @@ -1,6 +1,6 @@ import csv from io import StringIO -from ipaddress import IPv4Network, IPv6Network +from ipaddress import IPv4Network, IPv6Network, ip_network from django.core.exceptions import ValidationError from django.test import TestCase @@ -336,15 +336,27 @@ def test_retrieves_ipv6_ipnetwork_type(self): self.assertIsInstance(instance.subnet, IPv6Network) def test_incompatible_ipadresses(self): - instance = self._create_subnet(subnet='10.1.2.0/24') - try: - self._create_subnet(subnet='2001:db8::0/32', master_subnet=instance) - except TypeError as err: - self.assertEqual( - str(err), '2001:db8::/32 and 10.1.2.0/24 are not of the same version' + org = self._create_org(name='org', slug='org') + master = self._create_subnet( + name='IPv6', organization=org, subnet='2001:db8:85a3::64/128' + ) + subnet_ip = '192.166.45.0/24' + with self.assertRaises(ValidationError) as context_manager: + self._create_subnet( + name='IPv4', + organization=org, + subnet=subnet_ip, + master_subnet=master, ) - else: - self.fail('TypeError not raised') + message_dict = context_manager.exception.message_dict + master_version = ip_network(master.subnet).version + subnet_version = ip_network(subnet_ip).version + error_message = ( + f'IP version mismatch: Subnet {subnet_ip} is IPv{subnet_version}, ' + f'but Master Subnet {master.subnet} is IPv{master_version}.' + ) + self.assertIn('master_subnet', message_dict) + self.assertIn(error_message, message_dict['master_subnet']) def test_ipadresses_missing_attribute(self): instance = self._create_subnet(subnet='10.1.2.0/24')