| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620 |
- from django.test import TestCase
- from dcim.choices import (
- DeviceFaceChoices,
- DeviceStatusChoices,
- InterfaceModeChoices,
- InterfaceTypeChoices,
- PortTypeChoices,
- PowerOutletStatusChoices,
- )
- from dcim.forms import *
- from dcim.models import *
- from ipam.models import ASN, RIR, VLAN
- from utilities.exceptions import AbortRequest
- from utilities.forms.rendering import M2MAddRemoveFields
- from utilities.testing import create_test_device
- from virtualization.models import Cluster, ClusterGroup, ClusterType
- def get_id(model, slug):
- return model.objects.get(slug=slug).id
- class PowerOutletFormTestCase(TestCase):
- @classmethod
- def setUpTestData(cls):
- cls.site = site = Site.objects.create(name='Site 1', slug='site-1')
- cls.manufacturer = manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
- cls.role = role = DeviceRole.objects.create(
- name='Device Role 1', slug='device-role-1', color='ff0000'
- )
- cls.device_type = device_type = DeviceType.objects.create(
- manufacturer=manufacturer, model='Device Type 1', slug='device-type-1', u_height=1
- )
- cls.rack = rack = Rack.objects.create(name='Rack 1', site=site)
- cls.device = Device.objects.create(
- name='Device 1', device_type=device_type, role=role, site=site, rack=rack, position=1
- )
- def test_status_is_required(self):
- form = PowerOutletForm(data={
- 'device': self.device,
- 'module': None,
- 'name': 'New Enabled Outlet',
- })
- self.assertFalse(form.is_valid())
- self.assertIn('status', form.errors)
- def test_status_must_be_defined_choice(self):
- form = PowerOutletForm(data={
- 'device': self.device,
- 'module': None,
- 'name': 'New Enabled Outlet',
- 'status': 'this isn\'t a defined choice',
- })
- self.assertFalse(form.is_valid())
- self.assertIn('status', form.errors)
- self.assertTrue(form.errors['status'][-1].startswith('Select a valid choice.'))
- def test_status_recognizes_choices(self):
- for index, choice in enumerate(PowerOutletStatusChoices.CHOICES):
- form = PowerOutletForm(data={
- 'device': self.device,
- 'module': None,
- 'name': f'New Enabled Outlet {index + 1}',
- 'status': choice[0],
- })
- self.assertEqual({}, form.errors)
- self.assertTrue(form.is_valid())
- instance = form.save()
- self.assertEqual(instance.status, choice[0])
- class DeviceTestCase(TestCase):
- @classmethod
- def setUpTestData(cls):
- site = Site.objects.create(name='Site 1', slug='site-1')
- rack = Rack.objects.create(name='Rack 1', site=site)
- manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
- device_type = DeviceType.objects.create(
- manufacturer=manufacturer, model='Device Type 1', slug='device-type-1', u_height=1
- )
- role = DeviceRole.objects.create(
- name='Device Role 1', slug='device-role-1', color='ff0000'
- )
- Platform.objects.create(name='Platform 1', slug='platform-1')
- Device.objects.create(
- name='Device 1', device_type=device_type, role=role, site=site, rack=rack, position=1
- )
- cluster_type = ClusterType.objects.create(name='Cluster Type 1', slug='cluster-type-1')
- cluster_group = ClusterGroup.objects.create(name='Cluster Group 1', slug='cluster-group-1')
- Cluster.objects.create(name='Cluster 1', type=cluster_type, group=cluster_group)
- def test_racked_device(self):
- form = DeviceForm(data={
- 'name': 'New Device',
- 'role': DeviceRole.objects.first().pk,
- 'tenant': None,
- 'manufacturer': Manufacturer.objects.first().pk,
- 'device_type': DeviceType.objects.first().pk,
- 'site': Site.objects.first().pk,
- 'rack': Rack.objects.first().pk,
- 'face': DeviceFaceChoices.FACE_FRONT,
- 'position': 2,
- 'platform': Platform.objects.first().pk,
- 'status': DeviceStatusChoices.STATUS_ACTIVE,
- })
- self.assertTrue(form.is_valid())
- self.assertTrue(form.save())
- def test_racked_device_occupied(self):
- form = DeviceForm(data={
- 'name': 'test',
- 'role': DeviceRole.objects.first().pk,
- 'tenant': None,
- 'manufacturer': Manufacturer.objects.first().pk,
- 'device_type': DeviceType.objects.first().pk,
- 'site': Site.objects.first().pk,
- 'rack': Rack.objects.first().pk,
- 'face': DeviceFaceChoices.FACE_FRONT,
- 'position': 1,
- 'platform': Platform.objects.first().pk,
- 'status': DeviceStatusChoices.STATUS_ACTIVE,
- })
- self.assertFalse(form.is_valid())
- self.assertIn('position', form.errors)
- def test_non_racked_device(self):
- form = DeviceForm(data={
- 'name': 'New Device',
- 'role': DeviceRole.objects.first().pk,
- 'tenant': None,
- 'manufacturer': Manufacturer.objects.first().pk,
- 'device_type': DeviceType.objects.first().pk,
- 'site': Site.objects.first().pk,
- 'rack': None,
- 'face': None,
- 'position': None,
- 'platform': Platform.objects.first().pk,
- 'status': DeviceStatusChoices.STATUS_ACTIVE,
- })
- self.assertTrue(form.is_valid())
- self.assertTrue(form.save())
- def test_non_racked_device_with_face(self):
- form = DeviceForm(data={
- 'name': 'New Device',
- 'role': DeviceRole.objects.first().pk,
- 'tenant': None,
- 'manufacturer': Manufacturer.objects.first().pk,
- 'device_type': DeviceType.objects.first().pk,
- 'site': Site.objects.first().pk,
- 'rack': None,
- 'face': DeviceFaceChoices.FACE_REAR,
- 'platform': None,
- 'status': DeviceStatusChoices.STATUS_ACTIVE,
- })
- self.assertFalse(form.is_valid())
- self.assertIn('face', form.errors)
- def test_non_racked_device_with_position(self):
- form = DeviceForm(data={
- 'name': 'New Device',
- 'role': DeviceRole.objects.first().pk,
- 'tenant': None,
- 'manufacturer': Manufacturer.objects.first().pk,
- 'device_type': DeviceType.objects.first().pk,
- 'site': Site.objects.first().pk,
- 'rack': None,
- 'position': 10,
- 'platform': None,
- 'status': DeviceStatusChoices.STATUS_ACTIVE,
- })
- self.assertFalse(form.is_valid())
- self.assertIn('position', form.errors)
- class VCPositionTokenFormTestCase(TestCase):
- @classmethod
- def setUpTestData(cls):
- Site.objects.create(name='Site VC 1', slug='site-vc-1')
- manufacturer = Manufacturer.objects.create(name='Manufacturer VC 1', slug='manufacturer-vc-1')
- device_type = DeviceType.objects.create(
- manufacturer=manufacturer, model='Device Type VC 1', slug='device-type-vc-1'
- )
- DeviceRole.objects.create(name='Device Role VC 1', slug='device-role-vc-1', color='ff0000')
- InterfaceTemplate.objects.create(
- device_type=device_type,
- name='ge-{vc_position:0}/0/0',
- type='1000base-t',
- )
- VirtualChassis.objects.create(name='VC 1')
- def test_device_creation_in_vc_resolves_vc_position(self):
- form = DeviceForm(data={
- 'name': 'Device VC Form 1',
- 'role': DeviceRole.objects.first().pk,
- 'tenant': None,
- 'manufacturer': Manufacturer.objects.first().pk,
- 'device_type': DeviceType.objects.first().pk,
- 'site': Site.objects.first().pk,
- 'rack': None,
- 'face': None,
- 'position': None,
- 'platform': None,
- 'status': DeviceStatusChoices.STATUS_ACTIVE,
- 'virtual_chassis': VirtualChassis.objects.first().pk,
- 'vc_position': 2,
- })
- self.assertTrue(form.is_valid())
- device = form.save()
- self.assertTrue(device.interfaces.filter(name='ge-2/0/0').exists())
- def test_device_creation_not_in_vc_uses_fallback(self):
- form = DeviceForm(data={
- 'name': 'Device VC Form 2',
- 'role': DeviceRole.objects.first().pk,
- 'tenant': None,
- 'manufacturer': Manufacturer.objects.first().pk,
- 'device_type': DeviceType.objects.first().pk,
- 'site': Site.objects.first().pk,
- 'rack': None,
- 'face': None,
- 'position': None,
- 'platform': None,
- 'status': DeviceStatusChoices.STATUS_ACTIVE,
- })
- self.assertTrue(form.is_valid())
- device = form.save()
- self.assertTrue(device.interfaces.filter(name='ge-0/0/0').exists())
- def test_device_creation_duplicate_name_conflict(self):
- # With conflict
- device_type = DeviceType.objects.first()
- # to generate conflicts create an interface that will exist
- InterfaceTemplate.objects.create(
- device_type=device_type,
- name='ge-0/0/0',
- type='1000base-t',
- )
- form = DeviceForm(data={
- 'name': 'Device VC Form 3',
- 'role': DeviceRole.objects.first().pk,
- 'tenant': None,
- 'manufacturer': Manufacturer.objects.first().pk,
- 'device_type': device_type.pk,
- 'site': Site.objects.first().pk,
- 'rack': None,
- 'face': None,
- 'position': None,
- 'platform': None,
- 'status': DeviceStatusChoices.STATUS_ACTIVE,
- })
- self.assertTrue(form.is_valid())
- with self.assertRaises(AbortRequest):
- form.save()
- class FrontPortTestCase(TestCase):
- @classmethod
- def setUpTestData(cls):
- cls.device = create_test_device('Panel Device 1')
- cls.rear_ports = (
- RearPort(name='RearPort1', device=cls.device, type=PortTypeChoices.TYPE_8P8C),
- RearPort(name='RearPort2', device=cls.device, type=PortTypeChoices.TYPE_8P8C),
- RearPort(name='RearPort3', device=cls.device, type=PortTypeChoices.TYPE_8P8C),
- RearPort(name='RearPort4', device=cls.device, type=PortTypeChoices.TYPE_8P8C),
- )
- RearPort.objects.bulk_create(cls.rear_ports)
- def test_front_port_label_count_valid(self):
- """
- Test that generating an equal number of names and labels passes form validation.
- """
- front_port_data = {
- 'device': self.device.pk,
- 'name': 'FrontPort[1-4]',
- 'label': 'Port[1-4]',
- 'type': PortTypeChoices.TYPE_8P8C,
- 'positions': 1,
- 'rear_ports': [f'{rear_port.pk}:1' for rear_port in self.rear_ports],
- }
- form = FrontPortCreateForm(front_port_data)
- self.assertTrue(form.is_valid())
- def test_front_port_label_count_mismatch(self):
- """
- Check that attempting to generate a differing number of names and labels results in a validation error.
- """
- bad_front_port_data = {
- 'device': self.device.pk,
- 'name': 'FrontPort[1-4]',
- 'label': 'Port[1-2]',
- 'type': PortTypeChoices.TYPE_8P8C,
- 'positions': 1,
- 'rear_ports': [f'{rear_port.pk}:1' for rear_port in self.rear_ports],
- }
- form = FrontPortCreateForm(bad_front_port_data)
- self.assertFalse(form.is_valid())
- self.assertIn('label', form.errors)
- class InterfaceTestCase(TestCase):
- @classmethod
- def setUpTestData(cls):
- cls.device = create_test_device('Device 1')
- cls.vlans = (
- VLAN(name='VLAN 1', vid=1),
- VLAN(name='VLAN 2', vid=2),
- VLAN(name='VLAN 3', vid=3),
- )
- VLAN.objects.bulk_create(cls.vlans)
- cls.interface = Interface.objects.create(
- device=cls.device,
- name='Interface 1',
- type=InterfaceTypeChoices.TYPE_1GE_GBIC,
- mode=InterfaceModeChoices.MODE_TAGGED,
- )
- def test_interface_label_count_valid(self):
- """
- Test that generating an equal number of names and labels passes form validation.
- """
- interface_data = {
- 'device': self.device.pk,
- 'name': 'eth[0-9]',
- 'label': 'Interface[0-9]',
- 'type': InterfaceTypeChoices.TYPE_1GE_GBIC,
- }
- form = InterfaceCreateForm(interface_data)
- self.assertTrue(form.is_valid())
- def test_interface_label_count_mismatch(self):
- """
- Check that attempting to generate a differing number of names and labels results in a validation error.
- """
- bad_interface_data = {
- 'device': self.device.pk,
- 'name': 'eth[0-9]',
- 'label': 'Interface[0-1]',
- 'type': InterfaceTypeChoices.TYPE_1GE_GBIC,
- }
- form = InterfaceCreateForm(bad_interface_data)
- self.assertFalse(form.is_valid())
- self.assertIn('label', form.errors)
- def test_create_interface_mode_valid_data(self):
- """
- Test that saving valid interface mode and tagged/untagged vlans works properly
- """
- # Validate access mode
- data = {
- 'device': self.device.pk,
- 'name': 'ethernet1/1',
- 'type': InterfaceTypeChoices.TYPE_1GE_GBIC,
- 'mode': InterfaceModeChoices.MODE_ACCESS,
- 'untagged_vlan': self.vlans[0].pk
- }
- form = InterfaceCreateForm(data)
- self.assertTrue(form.is_valid())
- # Validate tagged vlans
- data = {
- 'device': self.device.pk,
- 'name': 'ethernet1/2',
- 'type': InterfaceTypeChoices.TYPE_1GE_GBIC,
- 'mode': InterfaceModeChoices.MODE_TAGGED,
- 'untagged_vlan': self.vlans[0].pk,
- 'tagged_vlans': [self.vlans[1].pk, self.vlans[2].pk]
- }
- form = InterfaceCreateForm(data)
- self.assertTrue(form.is_valid())
- # Validate tagged vlans
- data = {
- 'device': self.device.pk,
- 'name': 'ethernet1/3',
- 'type': InterfaceTypeChoices.TYPE_1GE_GBIC,
- 'mode': InterfaceModeChoices.MODE_TAGGED_ALL,
- 'untagged_vlan': self.vlans[0].pk,
- }
- form = InterfaceCreateForm(data)
- self.assertTrue(form.is_valid())
- def test_create_interface_mode_access_invalid_data(self):
- """
- Test that saving invalid interface mode and tagged/untagged vlans works properly
- """
- data = {
- 'device': self.device.pk,
- 'name': 'ethernet1/4',
- 'type': InterfaceTypeChoices.TYPE_1GE_GBIC,
- 'mode': InterfaceModeChoices.MODE_ACCESS,
- 'untagged_vlan': self.vlans[0].pk,
- 'tagged_vlans': [self.vlans[1].pk, self.vlans[2].pk]
- }
- form = InterfaceCreateForm(data)
- self.assertTrue(form.is_valid())
- self.assertIn('untagged_vlan', form.cleaned_data.keys())
- self.assertNotIn('tagged_vlans', form.cleaned_data.keys())
- self.assertNotIn('qinq_svlan', form.cleaned_data.keys())
- def test_edit_interface_mode_access_invalid_data(self):
- """
- Test that saving invalid interface mode and tagged/untagged vlans works properly
- """
- data = {
- 'device': self.device.pk,
- 'name': 'Ethernet 1/5',
- 'type': InterfaceTypeChoices.TYPE_1GE_GBIC,
- 'mode': InterfaceModeChoices.MODE_ACCESS,
- 'tagged_vlans': [self.vlans[0].pk, self.vlans[1].pk, self.vlans[2].pk]
- }
- form = InterfaceForm(data, instance=self.interface)
- self.assertTrue(form.is_valid())
- self.assertIn('untagged_vlan', form.cleaned_data.keys())
- self.assertNotIn('tagged_vlans', form.cleaned_data.keys())
- self.assertNotIn('qinq_svlan', form.cleaned_data.keys())
- def test_create_interface_mode_tagged_all_invalid_data(self):
- """
- Test that saving invalid interface mode and tagged/untagged vlans works properly
- """
- data = {
- 'device': self.device.pk,
- 'name': 'ethernet1/6',
- 'type': InterfaceTypeChoices.TYPE_1GE_GBIC,
- 'mode': InterfaceModeChoices.MODE_TAGGED_ALL,
- 'tagged_vlans': [self.vlans[0].pk, self.vlans[1].pk, self.vlans[2].pk]
- }
- form = InterfaceCreateForm(data)
- self.assertTrue(form.is_valid())
- self.assertIn('untagged_vlan', form.cleaned_data.keys())
- self.assertNotIn('tagged_vlans', form.cleaned_data.keys())
- self.assertNotIn('qinq_svlan', form.cleaned_data.keys())
- def test_edit_interface_mode_tagged_all_invalid_data(self):
- """
- Test that saving invalid interface mode and tagged/untagged vlans works properly
- """
- data = {
- 'device': self.device.pk,
- 'name': 'Ethernet 1/7',
- 'type': InterfaceTypeChoices.TYPE_1GE_GBIC,
- 'mode': InterfaceModeChoices.MODE_TAGGED_ALL,
- 'tagged_vlans': [self.vlans[0].pk, self.vlans[1].pk, self.vlans[2].pk]
- }
- form = InterfaceForm(data)
- self.assertTrue(form.is_valid())
- self.assertIn('untagged_vlan', form.cleaned_data.keys())
- self.assertNotIn('tagged_vlans', form.cleaned_data.keys())
- self.assertNotIn('qinq_svlan', form.cleaned_data.keys())
- def test_create_interface_mode_routed_invalid_data(self):
- """
- Test that saving invalid interface mode (routed) and tagged/untagged vlans works properly
- """
- data = {
- 'device': self.device.pk,
- 'name': 'ethernet1/6',
- 'type': InterfaceTypeChoices.TYPE_1GE_GBIC,
- 'mode': None,
- 'untagged_vlan': self.vlans[0].pk,
- 'tagged_vlans': [self.vlans[0].pk, self.vlans[1].pk, self.vlans[2].pk]
- }
- form = InterfaceCreateForm(data)
- self.assertTrue(form.is_valid())
- self.assertNotIn('untagged_vlan', form.cleaned_data.keys())
- self.assertNotIn('tagged_vlans', form.cleaned_data.keys())
- self.assertNotIn('qinq_svlan', form.cleaned_data.keys())
- def test_edit_interface_mode_routed_invalid_data(self):
- """
- Test that saving invalid interface mode (routed) and tagged/untagged vlans works properly
- """
- data = {
- 'device': self.device.pk,
- 'name': 'Ethernet 1/7',
- 'type': InterfaceTypeChoices.TYPE_1GE_GBIC,
- 'mode': None,
- 'untagged_vlan': self.vlans[0].pk,
- 'tagged_vlans': [self.vlans[0].pk, self.vlans[1].pk, self.vlans[2].pk]
- }
- form = InterfaceForm(data)
- self.assertTrue(form.is_valid())
- self.assertNotIn('untagged_vlan', form.cleaned_data.keys())
- self.assertNotIn('tagged_vlans', form.cleaned_data.keys())
- self.assertNotIn('qinq_svlan', form.cleaned_data.keys())
- class CableTestCase(TestCase):
- def test_invalid_side_designation_raises_value_error(self):
- """_clean_side rejects a side other than 'a' or 'b' with ValueError."""
- form = CableImportForm.__new__(CableImportForm)
- with self.assertRaisesMessage(ValueError, "Invalid side designation: c"):
- form._clean_side('c')
- class SiteFormTestCase(TestCase):
- """
- Tests for M2MAddRemoveFields using Site ASN assignments as the test case.
- Covers both simple mode (single multi-select field) and add/remove mode (dual fields).
- """
- @classmethod
- def setUpTestData(cls):
- cls.rir = RIR.objects.create(name='RIR 1', slug='rir-1')
- # Create 110 ASNs: 100 to pre-assign (triggering add/remove mode) plus 10 extras
- ASN.objects.bulk_create([ASN(asn=i, rir=cls.rir) for i in range(1, 111)])
- cls.asns = list(ASN.objects.order_by('asn'))
- def _site_data(self, **kwargs):
- data = {'name': 'Test Site', 'slug': 'test-site', 'status': 'active'}
- data.update(kwargs)
- return data
- def test_new_site_uses_simple_mode(self):
- """A form for a new site uses the single 'asns' field (simple mode)."""
- form = SiteForm(data=self._site_data())
- self.assertIn('asns', form.fields)
- self.assertNotIn('add_asns', form.fields)
- self.assertNotIn('remove_asns', form.fields)
- def test_existing_site_below_threshold_uses_simple_mode(self):
- """A form for an existing site with fewer than THRESHOLD ASNs uses simple mode."""
- site = Site.objects.create(name='Site 1', slug='site-1')
- site.asns.set(self.asns[:5])
- form = SiteForm(instance=site)
- self.assertIn('asns', form.fields)
- self.assertNotIn('add_asns', form.fields)
- self.assertNotIn('remove_asns', form.fields)
- def test_existing_site_at_threshold_uses_add_remove_mode(self):
- """A form for an existing site with THRESHOLD or more ASNs uses add/remove mode."""
- site = Site.objects.create(name='Site 2', slug='site-2')
- site.asns.set(self.asns[:M2MAddRemoveFields.THRESHOLD])
- form = SiteForm(instance=site)
- self.assertNotIn('asns', form.fields)
- self.assertIn('add_asns', form.fields)
- self.assertIn('remove_asns', form.fields)
- def test_simple_mode_assigns_asns_on_create(self):
- """Saving a new site via simple mode assigns the selected ASNs."""
- asn_pks = [asn.pk for asn in self.asns[:3]]
- form = SiteForm(data=self._site_data(asns=asn_pks))
- self.assertTrue(form.is_valid(), form.errors)
- site = form.save()
- self.assertEqual(set(site.asns.values_list('pk', flat=True)), set(asn_pks))
- def test_simple_mode_replaces_asns_on_edit(self):
- """Saving an existing site via simple mode replaces the current ASN assignments."""
- site = Site.objects.create(name='Site 3', slug='site-3')
- site.asns.set(self.asns[:3])
- new_asn_pks = [asn.pk for asn in self.asns[3:6]]
- form = SiteForm(
- data=self._site_data(name='Site 3', slug='site-3', asns=new_asn_pks),
- instance=site
- )
- self.assertTrue(form.is_valid(), form.errors)
- site = form.save()
- self.assertEqual(set(site.asns.values_list('pk', flat=True)), set(new_asn_pks))
- def test_add_remove_mode_adds_asns(self):
- """In add/remove mode, specifying 'add_asns' appends to current assignments."""
- site = Site.objects.create(name='Site 4', slug='site-4')
- site.asns.set(self.asns[:M2MAddRemoveFields.THRESHOLD])
- new_asn_pks = [asn.pk for asn in self.asns[M2MAddRemoveFields.THRESHOLD:]]
- form = SiteForm(
- data=self._site_data(name='Site 4', slug='site-4', add_asns=new_asn_pks),
- instance=site
- )
- self.assertTrue(form.is_valid(), form.errors)
- site = form.save()
- self.assertEqual(site.asns.count(), len(self.asns))
- def test_add_remove_mode_removes_asns(self):
- """In add/remove mode, specifying 'remove_asns' drops those assignments."""
- site = Site.objects.create(name='Site 5', slug='site-5')
- site.asns.set(self.asns[:M2MAddRemoveFields.THRESHOLD])
- remove_pks = [asn.pk for asn in self.asns[:5]]
- form = SiteForm(
- data=self._site_data(name='Site 5', slug='site-5', remove_asns=remove_pks),
- instance=site
- )
- self.assertTrue(form.is_valid(), form.errors)
- site = form.save()
- self.assertEqual(site.asns.count(), M2MAddRemoveFields.THRESHOLD - 5)
- self.assertFalse(site.asns.filter(pk__in=remove_pks).exists())
- def test_add_remove_mode_simultaneous_add_and_remove(self):
- """In add/remove mode, add and remove operations are applied together."""
- site = Site.objects.create(name='Site 6', slug='site-6')
- site.asns.set(self.asns[:M2MAddRemoveFields.THRESHOLD])
- add_pks = [asn.pk for asn in self.asns[M2MAddRemoveFields.THRESHOLD:M2MAddRemoveFields.THRESHOLD + 3]]
- remove_pks = [asn.pk for asn in self.asns[:3]]
- form = SiteForm(
- data=self._site_data(name='Site 6', slug='site-6', add_asns=add_pks, remove_asns=remove_pks),
- instance=site
- )
- self.assertTrue(form.is_valid(), form.errors)
- site = form.save()
- self.assertEqual(site.asns.count(), M2MAddRemoveFields.THRESHOLD)
- self.assertTrue(site.asns.filter(pk__in=add_pks).count() == 3)
- self.assertFalse(site.asns.filter(pk__in=remove_pks).exists())
|