mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-22 20:12:00 -06:00
Merge pull request #1526 from tarkatronic/order_naturally_enhancements
Fixes #1523 - Interface.objects.order_naturally() enhancements
This commit is contained in:
commit
d7b0ba57e0
@ -13,6 +13,7 @@ from django.core.exceptions import ValidationError
|
|||||||
from django.core.validators import MaxValueValidator, MinValueValidator
|
from django.core.validators import MaxValueValidator, MinValueValidator
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import Count, Q, ObjectDoesNotExist
|
from django.db.models import Count, Q, ObjectDoesNotExist
|
||||||
|
from django.db.models.expressions import RawSQL
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.encoding import python_2_unicode_compatible
|
from django.utils.encoding import python_2_unicode_compatible
|
||||||
|
|
||||||
@ -642,15 +643,16 @@ class InterfaceQuerySet(models.QuerySet):
|
|||||||
To order interfaces naturally, the `name` field is split into six distinct components: leading text (type),
|
To order interfaces naturally, the `name` field is split into six distinct components: leading text (type),
|
||||||
slot, subslot, position, channel, and virtual circuit:
|
slot, subslot, position, channel, and virtual circuit:
|
||||||
|
|
||||||
{type}{slot}/{subslot}/{position}:{channel}.{vc}
|
{type}{slot}/{subslot}/{position}/{subposition}:{channel}.{vc}
|
||||||
|
|
||||||
Components absent from the interface name are ignored. For example, an interface named GigabitEthernet0/1 would
|
Components absent from the interface name are ignored. For example, an interface named GigabitEthernet1/2/3
|
||||||
be parsed as follows:
|
would be parsed as follows:
|
||||||
|
|
||||||
name = 'GigabitEthernet'
|
name = 'GigabitEthernet'
|
||||||
slot = None
|
slot = 1
|
||||||
subslot = 0
|
subslot = 2
|
||||||
position = 1
|
position = 3
|
||||||
|
subposition = 0
|
||||||
channel = None
|
channel = None
|
||||||
vc = 0
|
vc = 0
|
||||||
|
|
||||||
@ -659,17 +661,31 @@ class InterfaceQuerySet(models.QuerySet):
|
|||||||
"""
|
"""
|
||||||
sql_col = '{}.name'.format(self.model._meta.db_table)
|
sql_col = '{}.name'.format(self.model._meta.db_table)
|
||||||
ordering = {
|
ordering = {
|
||||||
IFACE_ORDERING_POSITION: ('_slot', '_subslot', '_position', '_channel', '_vc', '_type', 'name'),
|
IFACE_ORDERING_POSITION: ('_slot', '_subslot', '_position', '_subposition', '_channel', '_vc', '_type', 'name'),
|
||||||
IFACE_ORDERING_NAME: ('_type', '_slot', '_subslot', '_position', '_channel', '_vc', 'name'),
|
IFACE_ORDERING_NAME: ('_type', '_slot', '_subslot', '_position', '_subposition', '_channel', '_vc', 'name'),
|
||||||
}[method]
|
}[method]
|
||||||
return self.extra(select={
|
|
||||||
'_type': "SUBSTRING({} FROM '^([^0-9]+)')".format(sql_col),
|
fields = {
|
||||||
'_slot': "CAST(SUBSTRING({} FROM '([0-9]+)\/[0-9]+\/[0-9]+(:[0-9]+)?(\.[0-9]+)?$') AS integer)".format(sql_col),
|
'_type': RawSQL(r"SUBSTRING({} FROM '^([^0-9]+)')".format(sql_col), []),
|
||||||
'_subslot': "CAST(SUBSTRING({} FROM '([0-9]+)\/[0-9]+(:[0-9]+)?(\.[0-9]+)?$') AS integer)".format(sql_col),
|
'_slot': RawSQL(r"CAST(SUBSTRING({} FROM '^(?:[^0-9]+)([0-9]+)') AS integer)".format(sql_col), []),
|
||||||
'_position': "CAST(SUBSTRING({} FROM '([0-9]+)(:[0-9]+)?(\.[0-9]+)?$') AS integer)".format(sql_col),
|
'_subslot': RawSQL(
|
||||||
'_channel': "COALESCE(CAST(SUBSTRING({} FROM ':([0-9]+)(\.[0-9]+)?$') AS integer), 0)".format(sql_col),
|
r"COALESCE(CAST(SUBSTRING({} FROM '^(?:[^0-9]+)(?:[0-9]+)\/([0-9]+)') AS integer), 0)".format(
|
||||||
'_vc': "COALESCE(CAST(SUBSTRING({} FROM '\.([0-9]+)$') AS integer), 0)".format(sql_col),
|
sql_col), []
|
||||||
}).order_by(*ordering)
|
),
|
||||||
|
'_position': RawSQL(
|
||||||
|
r"COALESCE(CAST(SUBSTRING({} FROM '^(?:[^0-9]+)(?:[0-9]+\/){{2}}([0-9]+)') AS integer), 0)".format(
|
||||||
|
sql_col), []
|
||||||
|
),
|
||||||
|
'_subposition': RawSQL(
|
||||||
|
r"COALESCE(CAST(SUBSTRING({} FROM '^(?:[^0-9]+)(?:[0-9]+\/){{3}}([0-9]+)') AS integer), 0)".format(
|
||||||
|
sql_col), []
|
||||||
|
),
|
||||||
|
'_channel': RawSQL(
|
||||||
|
r"COALESCE(CAST(SUBSTRING({} FROM ':([0-9]+)(\.[0-9]+)?$') AS integer), 0)".format(sql_col), []),
|
||||||
|
'_vc': RawSQL(r"COALESCE(CAST(SUBSTRING({} FROM '\.([0-9]+)$') AS integer), 0)".format(sql_col), []),
|
||||||
|
}
|
||||||
|
|
||||||
|
return self.annotate(**fields).order_by(*ordering)
|
||||||
|
|
||||||
def connectable(self):
|
def connectable(self):
|
||||||
"""
|
"""
|
||||||
|
@ -98,3 +98,108 @@ class RackTestCase(TestCase):
|
|||||||
face=None,
|
face=None,
|
||||||
)
|
)
|
||||||
self.assertTrue(pdu)
|
self.assertTrue(pdu)
|
||||||
|
|
||||||
|
|
||||||
|
class InterfaceTestCase(TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
|
||||||
|
self.site = Site.objects.create(
|
||||||
|
name='TestSite1',
|
||||||
|
slug='my-test-site'
|
||||||
|
)
|
||||||
|
self.rack = Rack.objects.create(
|
||||||
|
name='TestRack1',
|
||||||
|
facility_id='A101',
|
||||||
|
site=self.site,
|
||||||
|
u_height=42
|
||||||
|
)
|
||||||
|
self.manufacturer = Manufacturer.objects.create(
|
||||||
|
name='Acme',
|
||||||
|
slug='acme'
|
||||||
|
)
|
||||||
|
|
||||||
|
self.device_type = DeviceType.objects.create(
|
||||||
|
manufacturer=self.manufacturer,
|
||||||
|
model='FrameForwarder 2048',
|
||||||
|
slug='ff2048'
|
||||||
|
)
|
||||||
|
self.role = DeviceRole.objects.create(
|
||||||
|
name='Switch',
|
||||||
|
slug='switch',
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_interface_order_natural(self):
|
||||||
|
device1 = Device.objects.create(
|
||||||
|
name='TestSwitch1',
|
||||||
|
device_type=self.device_type,
|
||||||
|
device_role=self.role,
|
||||||
|
site=self.site,
|
||||||
|
rack=self.rack,
|
||||||
|
position=10,
|
||||||
|
face=RACK_FACE_REAR,
|
||||||
|
)
|
||||||
|
interface1 = Interface.objects.create(
|
||||||
|
device=device1,
|
||||||
|
name='Ethernet1/3/1'
|
||||||
|
)
|
||||||
|
interface2 = Interface.objects.create(
|
||||||
|
device=device1,
|
||||||
|
name='Ethernet1/5/1'
|
||||||
|
)
|
||||||
|
interface3 = Interface.objects.create(
|
||||||
|
device=device1,
|
||||||
|
name='Ethernet1/4'
|
||||||
|
)
|
||||||
|
interface4 = Interface.objects.create(
|
||||||
|
device=device1,
|
||||||
|
name='Ethernet1/3/2/4'
|
||||||
|
)
|
||||||
|
interface5 = Interface.objects.create(
|
||||||
|
device=device1,
|
||||||
|
name='Ethernet1/3/2/1'
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
list(Interface.objects.all().order_naturally()),
|
||||||
|
[interface1, interface5, interface4, interface3, interface2]
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_interface_order_natural_subinterfaces(self):
|
||||||
|
device1 = Device.objects.create(
|
||||||
|
name='TestSwitch1',
|
||||||
|
device_type=self.device_type,
|
||||||
|
device_role=self.role,
|
||||||
|
site=self.site,
|
||||||
|
rack=self.rack,
|
||||||
|
position=10,
|
||||||
|
face=RACK_FACE_REAR,
|
||||||
|
)
|
||||||
|
interface1 = Interface.objects.create(
|
||||||
|
device=device1,
|
||||||
|
name='GigabitEthernet0/0/3'
|
||||||
|
)
|
||||||
|
interface2 = Interface.objects.create(
|
||||||
|
device=device1,
|
||||||
|
name='GigabitEthernet0/0/2.2'
|
||||||
|
)
|
||||||
|
interface3 = Interface.objects.create(
|
||||||
|
device=device1,
|
||||||
|
name='GigabitEthernet0/0/0.120'
|
||||||
|
)
|
||||||
|
interface4 = Interface.objects.create(
|
||||||
|
device=device1,
|
||||||
|
name='GigabitEthernet0/0/0'
|
||||||
|
)
|
||||||
|
interface5 = Interface.objects.create(
|
||||||
|
device=device1,
|
||||||
|
name='GigabitEthernet0/0/1.117'
|
||||||
|
)
|
||||||
|
interface6 = Interface.objects.create(
|
||||||
|
device=device1,
|
||||||
|
name='GigabitEthernet0'
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
list(Interface.objects.all().order_naturally()),
|
||||||
|
[interface6, interface4, interface3, interface5, interface2, interface1]
|
||||||
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user