#303: First stab at implementing a natural ordering for sites, racks, and devices

This commit is contained in:
Jeremy Stretch 2016-07-20 10:07:32 -04:00
parent c643e3a74f
commit b8d7dd170e
3 changed files with 57 additions and 1 deletions

View File

@ -9,10 +9,12 @@ from django.db.models import Count, Q, ObjectDoesNotExist
from extras.rpc import RPC_CLIENTS from extras.rpc import RPC_CLIENTS
from utilities.fields import NullableCharField from utilities.fields import NullableCharField
from utilities.managers import NaturalOrderByManager
from utilities.models import CreatedUpdatedModel from utilities.models import CreatedUpdatedModel
from .fields import ASNField, MACAddressField from .fields import ASNField, MACAddressField
RACK_FACE_FRONT = 0 RACK_FACE_FRONT = 0
RACK_FACE_REAR = 1 RACK_FACE_REAR = 1
RACK_FACE_CHOICES = [ RACK_FACE_CHOICES = [
@ -137,6 +139,12 @@ def order_interfaces(queryset, sql_col, primary_ordering=tuple()):
}).order_by(*ordering) }).order_by(*ordering)
class SiteManager(NaturalOrderByManager):
def get_queryset(self):
return self.natural_order_by('name')
class Site(CreatedUpdatedModel): class Site(CreatedUpdatedModel):
""" """
A Site represents a geographic location within a network; typically a building or campus. The optional facility A Site represents a geographic location within a network; typically a building or campus. The optional facility
@ -150,6 +158,8 @@ class Site(CreatedUpdatedModel):
shipping_address = models.CharField(max_length=200, blank=True) shipping_address = models.CharField(max_length=200, blank=True)
comments = models.TextField(blank=True) comments = models.TextField(blank=True)
objects = SiteManager()
class Meta: class Meta:
ordering = ['name'] ordering = ['name']
@ -212,6 +222,12 @@ class RackGroup(models.Model):
return "{}?group_id={}".format(reverse('dcim:rack_list'), self.pk) return "{}?group_id={}".format(reverse('dcim:rack_list'), self.pk)
class RackManager(NaturalOrderByManager):
def get_queryset(self):
return self.natural_order_by('site__name', 'name')
class Rack(CreatedUpdatedModel): class Rack(CreatedUpdatedModel):
""" """
Devices are housed within Racks. Each rack has a defined height measured in rack units, and a front and rear face. Devices are housed within Racks. Each rack has a defined height measured in rack units, and a front and rear face.
@ -224,6 +240,8 @@ class Rack(CreatedUpdatedModel):
u_height = models.PositiveSmallIntegerField(default=42, verbose_name='Height (U)') u_height = models.PositiveSmallIntegerField(default=42, verbose_name='Height (U)')
comments = models.TextField(blank=True) comments = models.TextField(blank=True)
objects = RackManager()
class Meta: class Meta:
ordering = ['site', 'name'] ordering = ['site', 'name']
unique_together = [ unique_together = [
@ -583,6 +601,12 @@ class Platform(models.Model):
return "{}?platform={}".format(reverse('dcim:device_list'), self.slug) return "{}?platform={}".format(reverse('dcim:device_list'), self.slug)
class DeviceManager(NaturalOrderByManager):
def get_queryset(self):
return self.natural_order_by('name')
class Device(CreatedUpdatedModel): class Device(CreatedUpdatedModel):
""" """
A Device represents a piece of physical hardware mounted within a Rack. Each Device is assigned a DeviceType, A Device represents a piece of physical hardware mounted within a Rack. Each Device is assigned a DeviceType,
@ -612,6 +636,8 @@ class Device(CreatedUpdatedModel):
blank=True, null=True, verbose_name='Primary IPv6') blank=True, null=True, verbose_name='Primary IPv6')
comments = models.TextField(blank=True) comments = models.TextField(blank=True)
objects = DeviceManager()
class Meta: class Meta:
ordering = ['name'] ordering = ['name']
unique_together = ['rack', 'position', 'face'] unique_together = ['rack', 'position', 'face']

View File

@ -144,7 +144,7 @@ class RackGroupBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
# #
class RackListView(ObjectListView): class RackListView(ObjectListView):
queryset = Rack.objects.select_related('site').annotate(device_count=Count('devices', distinct=True)) queryset = Rack.objects.select_related('site', 'group').annotate(device_count=Count('devices', distinct=True))
filter = filters.RackFilter filter = filters.RackFilter
filter_form = forms.RackFilterForm filter_form = forms.RackFilterForm
table = tables.RackTable table = tables.RackTable

View File

@ -0,0 +1,30 @@
from django.db.models import Manager
class NaturalOrderByManager(Manager):
def natural_order_by(self, *fields):
"""
Attempt to order records naturally by segmenting a field into three parts:
1. Leading integer (if any)
2. Middle portion
3. Trailing integer (if any)
:param fields: The fields on which to order the queryset. The last field in the list will be ordered naturally.
"""
db_table = self.model._meta.db_table
primary_field = fields[-1]
id1 = '_{}_{}1'.format(db_table, primary_field)
id2 = '_{}_{}2'.format(db_table, primary_field)
id3 = '_{}_{}3'.format(db_table, primary_field)
queryset = super(NaturalOrderByManager, self).get_queryset().extra(select={
id1: "CAST(SUBSTRING({}.{} FROM '^(\d+)') AS integer)".format(db_table, primary_field),
id2: "SUBSTRING({}.{} FROM '^\d*(.*?)\d*$')".format(db_table, primary_field),
id3: "CAST(SUBSTRING({}.{} FROM '(\d+)$') AS integer)".format(db_table, primary_field),
})
ordering = fields[0:-1] + (id1, id2, id3)
return queryset.order_by(*ordering)