Fix MAC address pagination duplicates by adding 'pk' to model ordering

Add 'pk' to MACAddress model ordering to ensure deterministic results
when multiple MAC addresses have the same value. This prevents the same
MAC address from appearing on multiple pages during pagination.

The issue occurred because Django's default ordering by 'mac_address'
alone is non-deterministic when multiple records share the same MAC
address value, causing inconsistent pagination results when the same
MAC address is assigned to multiple interfaces on a device.

Added regression test that verifies MAC addresses with identical values
are properly ordered by their primary key, ensuring consistent pagination
behavior across the application.

Fixes netbox-community#19917
This commit is contained in:
Jad Seifeddine 2025-07-30 13:56:32 +10:00 committed by Jeremy Stretch
parent 2c09973e01
commit f25dd7aab7
3 changed files with 56 additions and 1 deletions

View File

@ -0,0 +1,19 @@
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('dcim', '0208_devicerole_uniqueness'),
]
operations = [
migrations.AlterModelOptions(
name='macaddress',
options={
'ordering': ('mac_address', 'pk'),
'verbose_name': 'MAC address',
'verbose_name_plural': 'MAC addresses'
},
),
]

View File

@ -1276,7 +1276,7 @@ class MACAddress(PrimaryModel):
) )
class Meta: class Meta:
ordering = ('mac_address',) ordering = ('mac_address', 'pk',)
verbose_name = _('MAC address') verbose_name = _('MAC address')
verbose_name_plural = _('MAC addresses') verbose_name_plural = _('MAC addresses')

View File

@ -37,6 +37,19 @@ class MACAddressTestCase(TestCase):
cls.interface.primary_mac_address = cls.mac_a cls.interface.primary_mac_address = cls.mac_a
cls.interface.save() cls.interface.save()
shared_mac = '1234567890ab'
interfaces = []
for i in range(10):
interfaces.append(Interface(
device=device, name=f'Interface {i:03d}', type='1000base-t'
))
Interface.objects.bulk_create(interfaces)
mac_addresses = []
for interface in interfaces:
mac_addresses.append(MACAddress(mac_address=shared_mac, assigned_object=interface))
MACAddress.objects.bulk_create(mac_addresses)
@tag('regression') @tag('regression')
def test_clean_will_not_allow_removal_of_assigned_object_if_primary(self): def test_clean_will_not_allow_removal_of_assigned_object_if_primary(self):
self.mac_a.assigned_object = None self.mac_a.assigned_object = None
@ -48,6 +61,29 @@ class MACAddressTestCase(TestCase):
self.mac_b.assigned_object = None self.mac_b.assigned_object = None
self.mac_b.clean() self.mac_b.clean()
@tag('regression')
def test_mac_address_ordering_with_duplicates(self):
"""
Test that MAC addresses with the same value are ordered by their primary key (pk).
This ensures deterministic ordering for pagination when multiple records share the same MAC address.
"""
mac_addresses = list(MACAddress.objects.all())
mac_address_groups = {}
for mac in mac_addresses:
if mac.mac_address not in mac_address_groups:
mac_address_groups[mac.mac_address] = []
mac_address_groups[mac.mac_address].append(mac)
for mac_value, macs in mac_address_groups.items():
if len(macs) > 1:
for i in range(1, len(macs)):
self.assertLess(
macs[i - 1].pk,
macs[i].pk,
f"MAC addresses with value {mac_value} are not ordered by primary key"
)
class LocationTestCase(TestCase): class LocationTestCase(TestCase):