diff --git a/base_requirements.txt b/base_requirements.txt index 415b15346..5770bab48 100644 --- a/base_requirements.txt +++ b/base_requirements.txt @@ -12,7 +12,6 @@ drf-yasg[validation] graphviz # py-gfm requires Markdown<3.0 Markdown<3.0 -natsort netaddr Pillow psycopg2-binary diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index 8e0ab5a99..e36554613 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -1,5 +1,4 @@ import re -from operator import attrgetter from django import forms from django.contrib.auth.models import User @@ -8,7 +7,6 @@ from django.contrib.postgres.forms.array import SimpleArrayField from django.core.exceptions import ObjectDoesNotExist from django.db.models import Count, Q from mptt.forms import TreeNodeChoiceField -from natsort import natsorted from taggit.forms import TagField from timezone_field import TimeZoneFormField @@ -803,7 +801,7 @@ class FrontPortTemplateCreateForm(ComponentForm): # Populate rear port choices choices = [] - rear_ports = natsorted(RearPortTemplate.objects.filter(device_type=self.parent), key=attrgetter('name')) + rear_ports = RearPortTemplate.objects.filter(device_type=self.parent) for rear_port in rear_ports: for i in range(1, rear_port.positions + 1): if (rear_port.pk, i) not in occupied_port_positions: @@ -1687,7 +1685,7 @@ class FrontPortCreateForm(ComponentForm): # Populate rear port choices choices = [] - rear_ports = natsorted(RearPort.objects.filter(device=self.parent), key=attrgetter('name')) + rear_ports = RearPort.objects.filter(device=self.parent) for rear_port in rear_ports: for i in range(1, rear_port.positions + 1): if (rear_port.pk, i) not in occupied_port_positions: diff --git a/netbox/dcim/querysets.py b/netbox/dcim/managers.py similarity index 83% rename from netbox/dcim/querysets.py rename to netbox/dcim/managers.py index 84d309719..bb2057c94 100644 --- a/netbox/dcim/querysets.py +++ b/netbox/dcim/managers.py @@ -3,6 +3,7 @@ from django.db.models.expressions import RawSQL from .constants import NONCONNECTABLE_IFACE_TYPES +# Regular expressions for parsing Interface names TYPE_RE = r"SUBSTRING({} FROM '^([^0-9\.:]+)')" SLOT_RE = r"COALESCE(CAST(SUBSTRING({} FROM '^(?:[^0-9]+)?(\d{{1,9}})/') AS integer), NULL)" SUBSLOT_RE = r"COALESCE(CAST(SUBSTRING({} FROM '^(?:[^0-9\.:]+)?\d{{1,9}}/(\d{{1,9}})') AS integer), NULL)" @@ -13,6 +14,22 @@ CHANNEL_RE = r"COALESCE(CAST(SUBSTRING({} FROM '^.*:(\d{{1,9}})(\.\d{{1,9}})?$') VC_RE = r"COALESCE(CAST(SUBSTRING({} FROM '^.*\.(\d{{1,9}})$') AS integer), 0)" +class DeviceComponentManager(Manager): + + def get_queryset(self): + + queryset = super(DeviceComponentManager, self).get_queryset() + table_name = self.model._meta.db_table + sql = r"CONCAT(REGEXP_REPLACE({}.name, '\d+$', ''), LPAD(SUBSTRING({}.name FROM '\d+$'), 8, '0'))" + + # Pad any trailing digits to effect natural sorting + return queryset.extra( + select={ + 'name_padded': sql.format(table_name, table_name), + } + ).order_by('name_padded') + + class InterfaceQuerySet(QuerySet): def connectable(self): diff --git a/netbox/dcim/models.py b/netbox/dcim/models.py index a79118077..2d7e0549a 100644 --- a/netbox/dcim/models.py +++ b/netbox/dcim/models.py @@ -22,7 +22,7 @@ from utilities.models import ChangeLoggedModel from utilities.utils import serialize_object, to_meters from .constants import * from .fields import ASNField, MACAddressField -from .querysets import InterfaceManager +from .managers import DeviceComponentManager, InterfaceManager class ComponentTemplateModel(models.Model): @@ -965,6 +965,8 @@ class ConsolePortTemplate(ComponentTemplateModel): max_length=50 ) + objects = DeviceComponentManager() + class Meta: ordering = ['device_type', 'name'] unique_together = ['device_type', 'name'] @@ -986,6 +988,8 @@ class ConsoleServerPortTemplate(ComponentTemplateModel): max_length=50 ) + objects = DeviceComponentManager() + class Meta: ordering = ['device_type', 'name'] unique_together = ['device_type', 'name'] @@ -1007,6 +1011,8 @@ class PowerPortTemplate(ComponentTemplateModel): max_length=50 ) + objects = DeviceComponentManager() + class Meta: ordering = ['device_type', 'name'] unique_together = ['device_type', 'name'] @@ -1028,6 +1034,8 @@ class PowerOutletTemplate(ComponentTemplateModel): max_length=50 ) + objects = DeviceComponentManager() + class Meta: ordering = ['device_type', 'name'] unique_together = ['device_type', 'name'] @@ -1057,7 +1065,7 @@ class InterfaceTemplate(ComponentTemplateModel): verbose_name='Management only' ) - objects = InterfaceManager + objects = InterfaceManager() class Meta: ordering = ['device_type', 'name'] @@ -1092,6 +1100,8 @@ class FrontPortTemplate(ComponentTemplateModel): validators=[MinValueValidator(1), MaxValueValidator(64)] ) + objects = DeviceComponentManager() + class Meta: ordering = ['device_type', 'name'] unique_together = [ @@ -1139,6 +1149,8 @@ class RearPortTemplate(ComponentTemplateModel): validators=[MinValueValidator(1), MaxValueValidator(64)] ) + objects = DeviceComponentManager() + class Meta: ordering = ['device_type', 'name'] unique_together = ['device_type', 'name'] @@ -1160,6 +1172,8 @@ class DeviceBayTemplate(ComponentTemplateModel): max_length=50 ) + objects = DeviceComponentManager() + class Meta: ordering = ['device_type', 'name'] unique_together = ['device_type', 'name'] @@ -1693,6 +1707,7 @@ class ConsolePort(CableTermination, ComponentModel): default=CONNECTION_STATUS_CONNECTED ) + objects = DeviceComponentManager() tags = TaggableManager() csv_headers = ['console_server', 'connected_endpoint', 'device', 'console_port', 'connection_status'] @@ -1721,16 +1736,6 @@ class ConsolePort(CableTermination, ComponentModel): # Console server ports # -class ConsoleServerPortManager(models.Manager): - - def get_queryset(self): - # Pad any trailing digits to effect natural sorting - return super(ConsoleServerPortManager, self).get_queryset().extra(select={ - 'name_padded': r"CONCAT(REGEXP_REPLACE(dcim_consoleserverport.name, '\d+$', ''), " - r"LPAD(SUBSTRING(dcim_consoleserverport.name FROM '\d+$'), 8, '0'))", - }).order_by('device', 'name_padded') - - class ConsoleServerPort(CableTermination, ComponentModel): """ A physical port within a Device (typically a designated console server) which provides access to ConsolePorts. @@ -1744,7 +1749,7 @@ class ConsoleServerPort(CableTermination, ComponentModel): max_length=50 ) - objects = ConsoleServerPortManager() + objects = DeviceComponentManager() tags = TaggableManager() class Meta: @@ -1785,6 +1790,7 @@ class PowerPort(CableTermination, ComponentModel): default=CONNECTION_STATUS_CONNECTED ) + objects = DeviceComponentManager() tags = TaggableManager() csv_headers = ['pdu', 'connected_endpoint', 'device', 'power_port', 'connection_status'] @@ -1813,16 +1819,6 @@ class PowerPort(CableTermination, ComponentModel): # Power outlets # -class PowerOutletManager(models.Manager): - - def get_queryset(self): - # Pad any trailing digits to effect natural sorting - return super(PowerOutletManager, self).get_queryset().extra(select={ - 'name_padded': r"CONCAT(REGEXP_REPLACE(dcim_poweroutlet.name, '\d+$', ''), " - r"LPAD(SUBSTRING(dcim_poweroutlet.name FROM '\d+$'), 8, '0'))", - }).order_by('device', 'name_padded') - - class PowerOutlet(CableTermination, ComponentModel): """ A physical power outlet (output) within a Device which provides power to a PowerPort. @@ -1836,7 +1832,7 @@ class PowerOutlet(CableTermination, ComponentModel): max_length=50 ) - objects = PowerOutletManager() + objects = DeviceComponentManager() tags = TaggableManager() class Meta: @@ -2125,6 +2121,7 @@ class FrontPort(CableTermination, ComponentModel): validators=[MinValueValidator(1), MaxValueValidator(64)] ) + objects = DeviceComponentManager() tags = TaggableManager() class Meta: @@ -2174,6 +2171,7 @@ class RearPort(CableTermination, ComponentModel): validators=[MinValueValidator(1), MaxValueValidator(64)] ) + objects = DeviceComponentManager() tags = TaggableManager() class Meta: @@ -2209,6 +2207,7 @@ class DeviceBay(ComponentModel): null=True ) + objects = DeviceComponentManager() tags = TaggableManager() class Meta: diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index 045562e30..c67962f01 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -1,5 +1,3 @@ -from operator import attrgetter - from django.contrib import messages from django.contrib.auth.mixins import PermissionRequiredMixin from django.core.paginator import EmptyPage, PageNotAnInteger @@ -11,7 +9,6 @@ from django.urls import reverse from django.utils.html import escape from django.utils.safestring import mark_safe from django.views.generic import View -from natsort import natsorted from circuits.models import Circuit from extras.models import Graph, TopologyMap, GRAPH_TYPE_INTERFACE, GRAPH_TYPE_SITE @@ -535,19 +532,19 @@ class DeviceTypeView(View): # Component tables consoleport_table = tables.ConsolePortTemplateTable( - natsorted(ConsolePortTemplate.objects.filter(device_type=devicetype), key=attrgetter('name')), + ConsolePortTemplate.objects.filter(device_type=devicetype), orderable=False ) consoleserverport_table = tables.ConsoleServerPortTemplateTable( - natsorted(ConsoleServerPortTemplate.objects.filter(device_type=devicetype), key=attrgetter('name')), + ConsoleServerPortTemplate.objects.filter(device_type=devicetype), orderable=False ) powerport_table = tables.PowerPortTemplateTable( - natsorted(PowerPortTemplate.objects.filter(device_type=devicetype), key=attrgetter('name')), + PowerPortTemplate.objects.filter(device_type=devicetype), orderable=False ) poweroutlet_table = tables.PowerOutletTemplateTable( - natsorted(PowerOutletTemplate.objects.filter(device_type=devicetype), key=attrgetter('name')), + PowerOutletTemplate.objects.filter(device_type=devicetype), orderable=False ) interface_table = tables.InterfaceTemplateTable( @@ -555,15 +552,15 @@ class DeviceTypeView(View): orderable=False ) front_port_table = tables.FrontPortTemplateTable( - natsorted(FrontPortTemplate.objects.filter(device_type=devicetype), key=attrgetter('name')), + FrontPortTemplate.objects.filter(device_type=devicetype), orderable=False ) rear_port_table = tables.RearPortTemplateTable( - natsorted(RearPortTemplate.objects.filter(device_type=devicetype), key=attrgetter('name')), + RearPortTemplate.objects.filter(device_type=devicetype), orderable=False ) devicebay_table = tables.DeviceBayTemplateTable( - natsorted(DeviceBayTemplate.objects.filter(device_type=devicetype), key=attrgetter('name')), + DeviceBayTemplate.objects.filter(device_type=devicetype), orderable=False ) if request.user.has_perm('dcim.change_devicetype'): @@ -880,19 +877,13 @@ class DeviceView(View): vc_members = [] # Console ports - console_ports = natsorted( - device.consoleports.select_related('connected_endpoint__device', 'cable'), - key=attrgetter('name') - ) + console_ports = device.consoleports.select_related('connected_endpoint__device', 'cable') # Console server ports consoleserverports = device.consoleserverports.select_related('connected_endpoint__device', 'cable') # Power ports - power_ports = natsorted( - device.powerports.select_related('connected_endpoint__device', 'cable'), - key=attrgetter('name') - ) + power_ports = device.powerports.select_related('connected_endpoint__device', 'cable') # Power outlets poweroutlets = device.poweroutlets.select_related('connected_endpoint__device', 'cable') @@ -911,10 +902,7 @@ class DeviceView(View): rear_ports = device.rearports.select_related('cable') # Device bays - device_bays = natsorted( - device.device_bays.select_related('installed_device__device_type__manufacturer'), - key=attrgetter('name') - ) + device_bays = device.device_bays.select_related('installed_device__device_type__manufacturer') # Services services = device.services.all() diff --git a/requirements.txt b/requirements.txt index 4c5068c9b..37f26a7b8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,7 +11,6 @@ djangorestframework==3.9.0 drf-yasg[validation]==1.11.0 graphviz==0.10.1 Markdown==2.6.11 -natsort==5.4.1 netaddr==0.7.19 Pillow==5.3.0 psycopg2-binary==2.7.5