Просмотр исходного кода

Fixes #21176: Remove checkboxes from IP ranges in mixed-type tables

  When IP addresses and IP ranges are displayed together in a prefix's
  IP Addresses tab, only IP addresses should be selectable for bulk
  operations since the bulk delete form doesn't support mixed object types.

  - Override render_pk() in AnnotatedIPAddressTable to conditionally render
    checkboxes only for the table's primary model type (IPAddress)
  - Add warning comment to add_requested_prefixes() about fake Prefix objects
  - Add regression test to verify IPAddress has checkboxes but IPRange does not
Jason Novinger 3 недель назад
Родитель
Сommit
cedbeb7b19
3 измененных файлов с 49 добавлено и 0 удалено
  1. 5 0
      netbox/ipam/tables/ip.py
  2. 41 0
      netbox/ipam/tests/test_tables.py
  3. 3 0
      netbox/ipam/utils.py

+ 5 - 0
netbox/ipam/tables/ip.py

@@ -370,6 +370,11 @@ class AnnotatedIPAddressTable(IPAddressTable):
         verbose_name=_('IP Address')
         verbose_name=_('IP Address')
     )
     )
 
 
+    def render_pk(self, value, record, bound_column):
+        if type(record) is not self._meta.model:
+            return ''
+        return bound_column.column.render(value, bound_column, record)
+
     class Meta(IPAddressTable.Meta):
     class Meta(IPAddressTable.Meta):
         pass
         pass
 
 

+ 41 - 0
netbox/ipam/tests/test_tables.py

@@ -0,0 +1,41 @@
+from django.test import RequestFactory, TestCase
+from netaddr import IPNetwork
+
+from ipam.models import IPAddress, IPRange, Prefix
+from ipam.tables import AnnotatedIPAddressTable
+from ipam.utils import annotate_ip_space
+
+
+class AnnotatedIPAddressTableTest(TestCase):
+
+    @classmethod
+    def setUpTestData(cls):
+        cls.prefix = Prefix.objects.create(
+            prefix=IPNetwork('10.1.1.0/24'),
+            status='active'
+        )
+
+        cls.ip_address = IPAddress.objects.create(
+            address='10.1.1.1/24',
+            status='active'
+        )
+
+        cls.ip_range = IPRange.objects.create(
+            start_address=IPNetwork('10.1.1.2/24'),
+            end_address=IPNetwork('10.1.1.10/24'),
+            status='active'
+        )
+
+    def test_ipaddress_has_checkbox_iprange_does_not(self):
+        data = annotate_ip_space(self.prefix)
+        table = AnnotatedIPAddressTable(data, orderable=False)
+        table.columns.show('pk')
+
+        request = RequestFactory().get('/')
+        html = table.as_html(request)
+
+        ipaddress_checkbox_count = html.count(f'name="pk" value="{self.ip_address.pk}"')
+        self.assertEqual(ipaddress_checkbox_count, 1)
+
+        iprange_checkbox_count = html.count(f'name="pk" value="{self.ip_range.pk}"')
+        self.assertEqual(iprange_checkbox_count, 0)

+ 3 - 0
netbox/ipam/utils.py

@@ -49,6 +49,9 @@ def add_requested_prefixes(parent, prefix_list, show_available=True, show_assign
     if prefix_list and show_available:
     if prefix_list and show_available:
 
 
         # Find all unallocated space, add fake Prefix objects to child_prefixes.
         # Find all unallocated space, add fake Prefix objects to child_prefixes.
+        # IMPORTANT: These are unsaved Prefix instances (pk=None). If this is ever changed to use
+        # saved Prefix instances with real pks, bulk delete will fail for mixed-type selections
+        # due to single-model form validation. See: https://github.com/netbox-community/netbox/issues/21176
         available_prefixes = netaddr.IPSet(parent) ^ netaddr.IPSet([p.prefix for p in prefix_list])
         available_prefixes = netaddr.IPSet(parent) ^ netaddr.IPSet([p.prefix for p in prefix_list])
         available_prefixes = [Prefix(prefix=p, status=None) for p in available_prefixes.iter_cidrs()]
         available_prefixes = [Prefix(prefix=p, status=None) for p in available_prefixes.iter_cidrs()]
         child_prefixes = child_prefixes + available_prefixes
         child_prefixes = child_prefixes + available_prefixes