diff --git a/netbox/circuits/migrations/0049_natural_ordering.py b/netbox/circuits/migrations/0049_natural_ordering.py
new file mode 100644
index 000000000..1b4f565e8
--- /dev/null
+++ b/netbox/circuits/migrations/0049_natural_ordering.py
@@ -0,0 +1,22 @@
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('circuits', '0048_circuitterminations_cached_relations'),
+ ('dcim', '0197_natural_sort_collation'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='provider',
+ name='name',
+ field=models.CharField(db_collation='natural_sort', max_length=100, unique=True),
+ ),
+ migrations.AlterField(
+ model_name='providernetwork',
+ name='name',
+ field=models.CharField(db_collation='natural_sort', max_length=100),
+ ),
+ ]
diff --git a/netbox/circuits/models/providers.py b/netbox/circuits/models/providers.py
index f0fe77b1a..be81caa54 100644
--- a/netbox/circuits/models/providers.py
+++ b/netbox/circuits/models/providers.py
@@ -21,7 +21,8 @@ class Provider(ContactsMixin, PrimaryModel):
verbose_name=_('name'),
max_length=100,
unique=True,
- help_text=_('Full name of the provider')
+ help_text=_('Full name of the provider'),
+ db_collation="natural_sort"
)
slug = models.SlugField(
verbose_name=_('slug'),
@@ -95,7 +96,8 @@ class ProviderNetwork(PrimaryModel):
"""
name = models.CharField(
verbose_name=_('name'),
- max_length=100
+ max_length=100,
+ db_collation="natural_sort"
)
provider = models.ForeignKey(
to='circuits.Provider',
diff --git a/netbox/core/tests/test_changelog.py b/netbox/core/tests/test_changelog.py
index c58968ee8..4914dbaf3 100644
--- a/netbox/core/tests/test_changelog.py
+++ b/netbox/core/tests/test_changelog.py
@@ -76,10 +76,6 @@ class ChangeLogViewTest(ModelViewTestCase):
self.assertEqual(oc.postchange_data['custom_fields']['cf2'], form_data['cf_cf2'])
self.assertEqual(oc.postchange_data['tags'], ['Tag 1', 'Tag 2'])
- # Check that private attributes were included in raw data but not display data
- self.assertIn('_name', oc.postchange_data)
- self.assertNotIn('_name', oc.postchange_data_clean)
-
def test_update_object(self):
site = Site(name='Site 1', slug='site-1')
site.save()
@@ -117,12 +113,6 @@ class ChangeLogViewTest(ModelViewTestCase):
self.assertEqual(oc.postchange_data['custom_fields']['cf2'], form_data['cf_cf2'])
self.assertEqual(oc.postchange_data['tags'], ['Tag 3'])
- # Check that private attributes were included in raw data but not display data
- self.assertIn('_name', oc.prechange_data)
- self.assertNotIn('_name', oc.prechange_data_clean)
- self.assertIn('_name', oc.postchange_data)
- self.assertNotIn('_name', oc.postchange_data_clean)
-
def test_delete_object(self):
site = Site(
name='Site 1',
@@ -153,10 +143,6 @@ class ChangeLogViewTest(ModelViewTestCase):
self.assertEqual(oc.prechange_data['tags'], ['Tag 1', 'Tag 2'])
self.assertEqual(oc.postchange_data, None)
- # Check that private attributes were included in raw data but not display data
- self.assertIn('_name', oc.prechange_data)
- self.assertNotIn('_name', oc.prechange_data_clean)
-
def test_bulk_update_objects(self):
sites = (
Site(name='Site 1', slug='site-1', status=SiteStatusChoices.STATUS_ACTIVE),
@@ -353,10 +339,6 @@ class ChangeLogAPITest(APITestCase):
self.assertEqual(oc.postchange_data['custom_fields'], data['custom_fields'])
self.assertEqual(oc.postchange_data['tags'], ['Tag 1', 'Tag 2'])
- # Check that private attributes were included in raw data but not display data
- self.assertIn('_name', oc.postchange_data)
- self.assertNotIn('_name', oc.postchange_data_clean)
-
def test_update_object(self):
site = Site(name='Site 1', slug='site-1')
site.save()
@@ -389,12 +371,6 @@ class ChangeLogAPITest(APITestCase):
self.assertEqual(oc.postchange_data['custom_fields'], data['custom_fields'])
self.assertEqual(oc.postchange_data['tags'], ['Tag 3'])
- # Check that private attributes were included in raw data but not display data
- self.assertIn('_name', oc.prechange_data)
- self.assertNotIn('_name', oc.prechange_data_clean)
- self.assertIn('_name', oc.postchange_data)
- self.assertNotIn('_name', oc.postchange_data_clean)
-
def test_delete_object(self):
site = Site(
name='Site 1',
@@ -423,10 +399,6 @@ class ChangeLogAPITest(APITestCase):
self.assertEqual(oc.prechange_data['tags'], ['Tag 1', 'Tag 2'])
self.assertEqual(oc.postchange_data, None)
- # Check that private attributes were included in raw data but not display data
- self.assertIn('_name', oc.prechange_data)
- self.assertNotIn('_name', oc.prechange_data_clean)
-
def test_bulk_create_objects(self):
data = (
{
diff --git a/netbox/dcim/graphql/types.py b/netbox/dcim/graphql/types.py
index 6493ec6b1..fc5f35780 100644
--- a/netbox/dcim/graphql/types.py
+++ b/netbox/dcim/graphql/types.py
@@ -76,7 +76,6 @@ class ComponentType(
"""
Base type for device/VM components
"""
- _name: str
device: Annotated["DeviceType", strawberry.lazy('dcim.graphql.types')]
@@ -93,7 +92,6 @@ class ComponentTemplateType(
"""
Base type for device/VM components
"""
- _name: str
device_type: Annotated["DeviceTypeType", strawberry.lazy('dcim.graphql.types')]
@@ -181,7 +179,7 @@ class ConsolePortType(ModularComponentType, CabledObjectMixin, PathEndpointMixin
filters=ConsolePortTemplateFilter
)
class ConsolePortTemplateType(ModularComponentTemplateType):
- _name: str
+ pass
@strawberry_django.type(
@@ -199,7 +197,7 @@ class ConsoleServerPortType(ModularComponentType, CabledObjectMixin, PathEndpoin
filters=ConsoleServerPortTemplateFilter
)
class ConsoleServerPortTemplateType(ModularComponentTemplateType):
- _name: str
+ pass
@strawberry_django.type(
@@ -208,7 +206,6 @@ class ConsoleServerPortTemplateType(ModularComponentTemplateType):
filters=DeviceFilter
)
class DeviceType(ConfigContextMixin, ImageAttachmentsMixin, ContactsMixin, NetBoxObjectType):
- _name: str
console_port_count: BigInt
console_server_port_count: BigInt
power_port_count: BigInt
@@ -273,7 +270,7 @@ class DeviceBayType(ComponentType):
filters=DeviceBayTemplateFilter
)
class DeviceBayTemplateType(ComponentTemplateType):
- _name: str
+ pass
@strawberry_django.type(
@@ -282,7 +279,6 @@ class DeviceBayTemplateType(ComponentTemplateType):
filters=InventoryItemTemplateFilter
)
class InventoryItemTemplateType(ComponentTemplateType):
- _name: str
role: Annotated["InventoryItemRoleType", strawberry.lazy('dcim.graphql.types')] | None
manufacturer: Annotated["ManufacturerType", strawberry.lazy('dcim.graphql.types')]
@@ -366,7 +362,6 @@ class FrontPortType(ModularComponentType, CabledObjectMixin):
filters=FrontPortTemplateFilter
)
class FrontPortTemplateType(ModularComponentTemplateType):
- _name: str
color: str
rear_port: Annotated["RearPortTemplateType", strawberry.lazy('dcim.graphql.types')]
@@ -377,6 +372,7 @@ class FrontPortTemplateType(ModularComponentTemplateType):
filters=InterfaceFilter
)
class InterfaceType(IPAddressesMixin, ModularComponentType, CabledObjectMixin, PathEndpointMixin):
+ _name: str
mac_address: str | None
wwn: str | None
parent: Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')] | None
@@ -527,7 +523,7 @@ class ModuleBayType(ModularComponentType):
filters=ModuleBayTemplateFilter
)
class ModuleBayTemplateType(ModularComponentTemplateType):
- _name: str
+ pass
@strawberry_django.type(
@@ -588,7 +584,6 @@ class PowerOutletType(ModularComponentType, CabledObjectMixin, PathEndpointMixin
filters=PowerOutletTemplateFilter
)
class PowerOutletTemplateType(ModularComponentTemplateType):
- _name: str
power_port: Annotated["PowerPortTemplateType", strawberry.lazy('dcim.graphql.types')] | None
@@ -620,8 +615,6 @@ class PowerPortType(ModularComponentType, CabledObjectMixin, PathEndpointMixin):
filters=PowerPortTemplateFilter
)
class PowerPortTemplateType(ModularComponentTemplateType):
- _name: str
-
poweroutlet_templates: List[Annotated["PowerOutletTemplateType", strawberry.lazy('dcim.graphql.types')]]
@@ -640,7 +633,6 @@ class RackTypeType(NetBoxObjectType):
filters=RackFilter
)
class RackType(VLANGroupsMixin, ImageAttachmentsMixin, ContactsMixin, NetBoxObjectType):
- _name: str
site: Annotated["SiteType", strawberry.lazy('dcim.graphql.types')]
location: Annotated["LocationType", strawberry.lazy('dcim.graphql.types')] | None
tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None
@@ -693,7 +685,6 @@ class RearPortType(ModularComponentType, CabledObjectMixin):
filters=RearPortTemplateFilter
)
class RearPortTemplateType(ModularComponentTemplateType):
- _name: str
color: str
frontport_templates: List[Annotated["FrontPortTemplateType", strawberry.lazy('dcim.graphql.types')]]
@@ -729,7 +720,6 @@ class RegionType(VLANGroupsMixin, ContactsMixin, OrganizationalObjectType):
filters=SiteFilter
)
class SiteType(VLANGroupsMixin, ImageAttachmentsMixin, ContactsMixin, NetBoxObjectType):
- _name: str
time_zone: str | None
region: Annotated["RegionType", strawberry.lazy('dcim.graphql.types')] | None
group: Annotated["SiteGroupType", strawberry.lazy('dcim.graphql.types')] | None
diff --git a/netbox/dcim/migrations/0197_natural_sort_collation.py b/netbox/dcim/migrations/0197_natural_sort_collation.py
new file mode 100644
index 000000000..a77632b37
--- /dev/null
+++ b/netbox/dcim/migrations/0197_natural_sort_collation.py
@@ -0,0 +1,17 @@
+from django.contrib.postgres.operations import CreateCollation
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('dcim', '0196_qinq_svlan'),
+ ]
+
+ operations = [
+ CreateCollation(
+ "natural_sort",
+ provider="icu",
+ locale="und-u-kn-true",
+ ),
+ ]
diff --git a/netbox/dcim/migrations/0198_natural_ordering.py b/netbox/dcim/migrations/0198_natural_ordering.py
new file mode 100644
index 000000000..83e94a195
--- /dev/null
+++ b/netbox/dcim/migrations/0198_natural_ordering.py
@@ -0,0 +1,318 @@
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('dcim', '0197_natural_sort_collation'),
+ ]
+
+ operations = [
+ migrations.AlterModelOptions(
+ name='site',
+ options={'ordering': ('name',)},
+ ),
+ migrations.AlterField(
+ model_name='site',
+ name='name',
+ field=models.CharField(db_collation='natural_sort', max_length=100, unique=True),
+ ),
+ migrations.AlterModelOptions(
+ name='consoleport',
+ options={'ordering': ('device', 'name')},
+ ),
+ migrations.AlterModelOptions(
+ name='consoleporttemplate',
+ options={'ordering': ('device_type', 'module_type', 'name')},
+ ),
+ migrations.AlterModelOptions(
+ name='consoleserverport',
+ options={'ordering': ('device', 'name')},
+ ),
+ migrations.AlterModelOptions(
+ name='consoleserverporttemplate',
+ options={'ordering': ('device_type', 'module_type', 'name')},
+ ),
+ migrations.AlterModelOptions(
+ name='device',
+ options={'ordering': ('name', 'pk')},
+ ),
+ migrations.AlterModelOptions(
+ name='devicebay',
+ options={'ordering': ('device', 'name')},
+ ),
+ migrations.AlterModelOptions(
+ name='devicebaytemplate',
+ options={'ordering': ('device_type', 'name')},
+ ),
+ migrations.AlterModelOptions(
+ name='frontport',
+ options={'ordering': ('device', 'name')},
+ ),
+ migrations.AlterModelOptions(
+ name='frontporttemplate',
+ options={'ordering': ('device_type', 'module_type', 'name')},
+ ),
+ migrations.AlterModelOptions(
+ name='interfacetemplate',
+ options={'ordering': ('device_type', 'module_type', 'name')},
+ ),
+ migrations.AlterModelOptions(
+ name='inventoryitem',
+ options={'ordering': ('device__id', 'parent__id', 'name')},
+ ),
+ migrations.AlterModelOptions(
+ name='inventoryitemtemplate',
+ options={'ordering': ('device_type__id', 'parent__id', 'name')},
+ ),
+ migrations.AlterModelOptions(
+ name='modulebay',
+ options={'ordering': ('device', 'name')},
+ ),
+ migrations.AlterModelOptions(
+ name='modulebaytemplate',
+ options={'ordering': ('device_type', 'module_type', 'name')},
+ ),
+ migrations.AlterModelOptions(
+ name='poweroutlet',
+ options={'ordering': ('device', 'name')},
+ ),
+ migrations.AlterModelOptions(
+ name='poweroutlettemplate',
+ options={'ordering': ('device_type', 'module_type', 'name')},
+ ),
+ migrations.AlterModelOptions(
+ name='powerport',
+ options={'ordering': ('device', 'name')},
+ ),
+ migrations.AlterModelOptions(
+ name='powerporttemplate',
+ options={'ordering': ('device_type', 'module_type', 'name')},
+ ),
+ migrations.AlterModelOptions(
+ name='rack',
+ options={'ordering': ('site', 'location', 'name', 'pk')},
+ ),
+ migrations.AlterModelOptions(
+ name='rearport',
+ options={'ordering': ('device', 'name')},
+ ),
+ migrations.AlterModelOptions(
+ name='rearporttemplate',
+ options={'ordering': ('device_type', 'module_type', 'name')},
+ ),
+ migrations.RemoveField(
+ model_name='consoleport',
+ name='_name',
+ ),
+ migrations.RemoveField(
+ model_name='consoleporttemplate',
+ name='_name',
+ ),
+ migrations.RemoveField(
+ model_name='consoleserverport',
+ name='_name',
+ ),
+ migrations.RemoveField(
+ model_name='consoleserverporttemplate',
+ name='_name',
+ ),
+ migrations.RemoveField(
+ model_name='device',
+ name='_name',
+ ),
+ migrations.RemoveField(
+ model_name='devicebay',
+ name='_name',
+ ),
+ migrations.RemoveField(
+ model_name='devicebaytemplate',
+ name='_name',
+ ),
+ migrations.RemoveField(
+ model_name='frontport',
+ name='_name',
+ ),
+ migrations.RemoveField(
+ model_name='frontporttemplate',
+ name='_name',
+ ),
+ migrations.RemoveField(
+ model_name='inventoryitem',
+ name='_name',
+ ),
+ migrations.RemoveField(
+ model_name='inventoryitemtemplate',
+ name='_name',
+ ),
+ migrations.RemoveField(
+ model_name='modulebay',
+ name='_name',
+ ),
+ migrations.RemoveField(
+ model_name='modulebaytemplate',
+ name='_name',
+ ),
+ migrations.RemoveField(
+ model_name='poweroutlet',
+ name='_name',
+ ),
+ migrations.RemoveField(
+ model_name='poweroutlettemplate',
+ name='_name',
+ ),
+ migrations.RemoveField(
+ model_name='powerport',
+ name='_name',
+ ),
+ migrations.RemoveField(
+ model_name='powerporttemplate',
+ name='_name',
+ ),
+ migrations.RemoveField(
+ model_name='rack',
+ name='_name',
+ ),
+ migrations.RemoveField(
+ model_name='rearport',
+ name='_name',
+ ),
+ migrations.RemoveField(
+ model_name='rearporttemplate',
+ name='_name',
+ ),
+ migrations.RemoveField(
+ model_name='site',
+ name='_name',
+ ),
+ migrations.AlterField(
+ model_name='consoleport',
+ name='name',
+ field=models.CharField(db_collation='natural_sort', max_length=64),
+ ),
+ migrations.AlterField(
+ model_name='consoleporttemplate',
+ name='name',
+ field=models.CharField(db_collation='natural_sort', max_length=64),
+ ),
+ migrations.AlterField(
+ model_name='consoleserverport',
+ name='name',
+ field=models.CharField(db_collation='natural_sort', max_length=64),
+ ),
+ migrations.AlterField(
+ model_name='consoleserverporttemplate',
+ name='name',
+ field=models.CharField(db_collation='natural_sort', max_length=64),
+ ),
+ migrations.AlterField(
+ model_name='device',
+ name='name',
+ field=models.CharField(blank=True, db_collation='natural_sort', max_length=64, null=True),
+ ),
+ migrations.AlterField(
+ model_name='devicebay',
+ name='name',
+ field=models.CharField(db_collation='natural_sort', max_length=64),
+ ),
+ migrations.AlterField(
+ model_name='devicebaytemplate',
+ name='name',
+ field=models.CharField(db_collation='natural_sort', max_length=64),
+ ),
+ migrations.AlterField(
+ model_name='frontport',
+ name='name',
+ field=models.CharField(db_collation='natural_sort', max_length=64),
+ ),
+ migrations.AlterField(
+ model_name='frontporttemplate',
+ name='name',
+ field=models.CharField(db_collation='natural_sort', max_length=64),
+ ),
+ migrations.AlterField(
+ model_name='interface',
+ name='name',
+ field=models.CharField(db_collation='natural_sort', max_length=64),
+ ),
+ migrations.AlterField(
+ model_name='interfacetemplate',
+ name='name',
+ field=models.CharField(db_collation='natural_sort', max_length=64),
+ ),
+ migrations.AlterField(
+ model_name='inventoryitem',
+ name='name',
+ field=models.CharField(db_collation='natural_sort', max_length=64),
+ ),
+ migrations.AlterField(
+ model_name='inventoryitemtemplate',
+ name='name',
+ field=models.CharField(db_collation='natural_sort', max_length=64),
+ ),
+ migrations.AlterField(
+ model_name='modulebay',
+ name='name',
+ field=models.CharField(db_collation='natural_sort', max_length=64),
+ ),
+ migrations.AlterField(
+ model_name='modulebaytemplate',
+ name='name',
+ field=models.CharField(db_collation='natural_sort', max_length=64),
+ ),
+ migrations.AlterField(
+ model_name='poweroutlet',
+ name='name',
+ field=models.CharField(db_collation='natural_sort', max_length=64),
+ ),
+ migrations.AlterField(
+ model_name='poweroutlettemplate',
+ name='name',
+ field=models.CharField(db_collation='natural_sort', max_length=64),
+ ),
+ migrations.AlterField(
+ model_name='powerport',
+ name='name',
+ field=models.CharField(db_collation='natural_sort', max_length=64),
+ ),
+ migrations.AlterField(
+ model_name='powerporttemplate',
+ name='name',
+ field=models.CharField(db_collation='natural_sort', max_length=64),
+ ),
+ migrations.AlterField(
+ model_name='rack',
+ name='name',
+ field=models.CharField(db_collation='natural_sort', max_length=100),
+ ),
+ migrations.AlterField(
+ model_name='rearport',
+ name='name',
+ field=models.CharField(db_collation='natural_sort', max_length=64),
+ ),
+ migrations.AlterField(
+ model_name='rearporttemplate',
+ name='name',
+ field=models.CharField(db_collation='natural_sort', max_length=64),
+ ),
+ migrations.AlterField(
+ model_name='powerfeed',
+ name='name',
+ field=models.CharField(db_collation='natural_sort', max_length=100),
+ ),
+ migrations.AlterField(
+ model_name='powerpanel',
+ name='name',
+ field=models.CharField(db_collation='natural_sort', max_length=100),
+ ),
+ migrations.AlterField(
+ model_name='virtualchassis',
+ name='name',
+ field=models.CharField(db_collation='natural_sort', max_length=64),
+ ),
+ migrations.AlterField(
+ model_name='virtualdevicecontext',
+ name='name',
+ field=models.CharField(db_collation='natural_sort', max_length=64),
+ ),
+ ]
diff --git a/netbox/dcim/models/device_component_templates.py b/netbox/dcim/models/device_component_templates.py
index 00555d49e..ddd4d2426 100644
--- a/netbox/dcim/models/device_component_templates.py
+++ b/netbox/dcim/models/device_component_templates.py
@@ -44,12 +44,8 @@ class ComponentTemplateModel(ChangeLoggedModel, TrackingModelMixin):
max_length=64,
help_text=_(
"{module} is accepted as a substitution for the module bay position when attached to a module type."
- )
- )
- _name = NaturalOrderingField(
- target_field='name',
- max_length=100,
- blank=True
+ ),
+ db_collation="natural_sort"
)
label = models.CharField(
verbose_name=_('label'),
@@ -65,7 +61,7 @@ class ComponentTemplateModel(ChangeLoggedModel, TrackingModelMixin):
class Meta:
abstract = True
- ordering = ('device_type', '_name')
+ ordering = ('device_type', 'name')
constraints = (
models.UniqueConstraint(
fields=('device_type', 'name'),
@@ -125,7 +121,7 @@ class ModularComponentTemplateModel(ComponentTemplateModel):
class Meta:
abstract = True
- ordering = ('device_type', 'module_type', '_name')
+ ordering = ('device_type', 'module_type', 'name')
constraints = (
models.UniqueConstraint(
fields=('device_type', 'name'),
@@ -782,7 +778,7 @@ class InventoryItemTemplate(MPTTModel, ComponentTemplateModel):
component_model = InventoryItem
class Meta:
- ordering = ('device_type__id', 'parent__id', '_name')
+ ordering = ('device_type__id', 'parent__id', 'name')
indexes = (
models.Index(fields=('component_type', 'component_id')),
)
diff --git a/netbox/dcim/models/device_components.py b/netbox/dcim/models/device_components.py
index 36fd02add..31278a13c 100644
--- a/netbox/dcim/models/device_components.py
+++ b/netbox/dcim/models/device_components.py
@@ -50,12 +50,8 @@ class ComponentModel(NetBoxModel):
)
name = models.CharField(
verbose_name=_('name'),
- max_length=64
- )
- _name = NaturalOrderingField(
- target_field='name',
- max_length=100,
- blank=True
+ max_length=64,
+ db_collation="natural_sort"
)
label = models.CharField(
verbose_name=_('label'),
@@ -71,7 +67,7 @@ class ComponentModel(NetBoxModel):
class Meta:
abstract = True
- ordering = ('device', '_name')
+ ordering = ('device', 'name')
constraints = (
models.UniqueConstraint(
fields=('device', 'name'),
@@ -1301,7 +1297,7 @@ class InventoryItem(MPTTModel, ComponentModel, TrackingModelMixin):
clone_fields = ('device', 'parent', 'role', 'manufacturer', 'status', 'part_id')
class Meta:
- ordering = ('device__id', 'parent__id', '_name')
+ ordering = ('device__id', 'parent__id', 'name')
indexes = (
models.Index(fields=('component_type', 'component_id')),
)
diff --git a/netbox/dcim/models/devices.py b/netbox/dcim/models/devices.py
index 47f4ee6c9..a836c5d37 100644
--- a/netbox/dcim/models/devices.py
+++ b/netbox/dcim/models/devices.py
@@ -23,7 +23,7 @@ from netbox.config import ConfigItem
from netbox.models import OrganizationalModel, PrimaryModel
from netbox.models.mixins import WeightMixin
from netbox.models.features import ContactsMixin, ImageAttachmentsMixin
-from utilities.fields import ColorField, CounterCacheField, NaturalOrderingField
+from utilities.fields import ColorField, CounterCacheField
from utilities.tracking import TrackingModelMixin
from .device_components import *
from .mixins import RenderConfigMixin
@@ -582,13 +582,8 @@ class Device(
verbose_name=_('name'),
max_length=64,
blank=True,
- null=True
- )
- _name = NaturalOrderingField(
- target_field='name',
- max_length=100,
- blank=True,
- null=True
+ null=True,
+ db_collation="natural_sort"
)
serial = models.CharField(
max_length=50,
@@ -775,7 +770,7 @@ class Device(
)
class Meta:
- ordering = ('_name', 'pk') # Name may be null
+ ordering = ('name', 'pk') # Name may be null
constraints = (
models.UniqueConstraint(
Lower('name'), 'site', 'tenant',
@@ -1320,7 +1315,8 @@ class VirtualChassis(PrimaryModel):
)
name = models.CharField(
verbose_name=_('name'),
- max_length=64
+ max_length=64,
+ db_collation="natural_sort"
)
domain = models.CharField(
verbose_name=_('domain'),
@@ -1382,7 +1378,8 @@ class VirtualDeviceContext(PrimaryModel):
)
name = models.CharField(
verbose_name=_('name'),
- max_length=64
+ max_length=64,
+ db_collation="natural_sort"
)
status = models.CharField(
verbose_name=_('status'),
diff --git a/netbox/dcim/models/power.py b/netbox/dcim/models/power.py
index d0c6b18b6..284cfe832 100644
--- a/netbox/dcim/models/power.py
+++ b/netbox/dcim/models/power.py
@@ -36,7 +36,8 @@ class PowerPanel(ContactsMixin, ImageAttachmentsMixin, PrimaryModel):
)
name = models.CharField(
verbose_name=_('name'),
- max_length=100
+ max_length=100,
+ db_collation="natural_sort"
)
prerequisite_models = (
@@ -86,7 +87,8 @@ class PowerFeed(PrimaryModel, PathEndpoint, CabledObjectModel):
)
name = models.CharField(
verbose_name=_('name'),
- max_length=100
+ max_length=100,
+ db_collation="natural_sort"
)
status = models.CharField(
verbose_name=_('status'),
diff --git a/netbox/dcim/models/racks.py b/netbox/dcim/models/racks.py
index 013dfb619..08b7f5a35 100644
--- a/netbox/dcim/models/racks.py
+++ b/netbox/dcim/models/racks.py
@@ -19,7 +19,7 @@ from netbox.models.mixins import WeightMixin
from netbox.models.features import ContactsMixin, ImageAttachmentsMixin
from utilities.conversion import to_grams
from utilities.data import array_to_string, drange
-from utilities.fields import ColorField, NaturalOrderingField
+from utilities.fields import ColorField
from .device_components import PowerPort
from .devices import Device, Module
from .power import PowerFeed
@@ -255,12 +255,8 @@ class Rack(ContactsMixin, ImageAttachmentsMixin, RackBase):
)
name = models.CharField(
verbose_name=_('name'),
- max_length=100
- )
- _name = NaturalOrderingField(
- target_field='name',
max_length=100,
- blank=True
+ db_collation="natural_sort"
)
facility_id = models.CharField(
max_length=50,
@@ -340,7 +336,7 @@ class Rack(ContactsMixin, ImageAttachmentsMixin, RackBase):
)
class Meta:
- ordering = ('site', 'location', '_name', 'pk') # (site, location, name) may be non-unique
+ ordering = ('site', 'location', 'name', 'pk') # (site, location, name) may be non-unique
constraints = (
# Name and facility_id must be unique *only* within a Location
models.UniqueConstraint(
diff --git a/netbox/dcim/models/sites.py b/netbox/dcim/models/sites.py
index a290f4119..0985a8d7a 100644
--- a/netbox/dcim/models/sites.py
+++ b/netbox/dcim/models/sites.py
@@ -8,7 +8,6 @@ from dcim.choices import *
from dcim.constants import *
from netbox.models import NestedGroupModel, PrimaryModel
from netbox.models.features import ContactsMixin, ImageAttachmentsMixin
-from utilities.fields import NaturalOrderingField
__all__ = (
'Location',
@@ -143,12 +142,8 @@ class Site(ContactsMixin, ImageAttachmentsMixin, PrimaryModel):
verbose_name=_('name'),
max_length=100,
unique=True,
- help_text=_("Full name of the site")
- )
- _name = NaturalOrderingField(
- target_field='name',
- max_length=100,
- blank=True
+ help_text=_("Full name of the site"),
+ db_collation="natural_sort"
)
slug = models.SlugField(
verbose_name=_('slug'),
@@ -245,7 +240,7 @@ class Site(ContactsMixin, ImageAttachmentsMixin, PrimaryModel):
)
class Meta:
- ordering = ('_name',)
+ ordering = ('name',)
verbose_name = _('site')
verbose_name_plural = _('sites')
diff --git a/netbox/dcim/tables/devices.py b/netbox/dcim/tables/devices.py
index fed33401c..b7634626d 100644
--- a/netbox/dcim/tables/devices.py
+++ b/netbox/dcim/tables/devices.py
@@ -132,7 +132,6 @@ class PlatformTable(NetBoxTable):
class DeviceTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable):
name = tables.TemplateColumn(
verbose_name=_('Name'),
- order_by=('_name',),
template_code=DEVICE_LINK,
linkify=True
)
@@ -288,7 +287,6 @@ class DeviceComponentTable(NetBoxTable):
name = tables.Column(
verbose_name=_('Name'),
linkify=True,
- order_by=('_name',)
)
device_status = columns.ChoiceFieldColumn(
accessor=tables.A('device__status'),
@@ -391,7 +389,6 @@ class DeviceConsolePortTable(ConsolePortTable):
name = tables.TemplateColumn(
verbose_name=_('Name'),
template_code=' {{ value }}',
- order_by=Accessor('_name'),
attrs={'td': {'class': 'text-nowrap'}}
)
actions = columns.ActionsColumn(
@@ -433,7 +430,6 @@ class DeviceConsoleServerPortTable(ConsoleServerPortTable):
verbose_name=_('Name'),
template_code=' '
'{{ value }}',
- order_by=Accessor('_name'),
attrs={'td': {'class': 'text-nowrap'}}
)
actions = columns.ActionsColumn(
@@ -482,7 +478,6 @@ class DevicePowerPortTable(PowerPortTable):
verbose_name=_('Name'),
template_code=' '
'{{ value }}',
- order_by=Accessor('_name'),
attrs={'td': {'class': 'text-nowrap'}}
)
actions = columns.ActionsColumn(
@@ -531,7 +526,6 @@ class DevicePowerOutletTable(PowerOutletTable):
name = tables.TemplateColumn(
verbose_name=_('Name'),
template_code=' {{ value }}',
- order_by=Accessor('_name'),
attrs={'td': {'class': 'text-nowrap'}}
)
actions = columns.ActionsColumn(
@@ -550,6 +544,11 @@ class DevicePowerOutletTable(PowerOutletTable):
class BaseInterfaceTable(NetBoxTable):
+ name = tables.Column(
+ verbose_name=_('Name'),
+ linkify=True,
+ order_by=('_name',)
+ )
enabled = columns.BooleanColumn(
verbose_name=_('Enabled'),
)
@@ -597,7 +596,7 @@ class BaseInterfaceTable(NetBoxTable):
return ",".join([str(obj) for obj in value.all()])
-class InterfaceTable(ModularDeviceComponentTable, BaseInterfaceTable, PathEndpointTable):
+class InterfaceTable(BaseInterfaceTable, ModularDeviceComponentTable, PathEndpointTable):
device = tables.Column(
verbose_name=_('Device'),
linkify={
@@ -736,7 +735,6 @@ class DeviceFrontPortTable(FrontPortTable):
verbose_name=_('Name'),
template_code=' '
'{{ value }}',
- order_by=Accessor('_name'),
attrs={'td': {'class': 'text-nowrap'}}
)
actions = columns.ActionsColumn(
@@ -783,7 +781,6 @@ class DeviceRearPortTable(RearPortTable):
verbose_name=_('Name'),
template_code=' '
'{{ value }}',
- order_by=Accessor('_name'),
attrs={'td': {'class': 'text-nowrap'}}
)
actions = columns.ActionsColumn(
@@ -846,7 +843,6 @@ class DeviceDeviceBayTable(DeviceBayTable):
verbose_name=_('Name'),
template_code=' {{ value }}',
- order_by=Accessor('_name'),
attrs={'td': {'class': 'text-nowrap'}}
)
actions = columns.ActionsColumn(
@@ -915,7 +911,6 @@ class DeviceModuleBayTable(ModuleBayTable):
name = columns.MPTTColumn(
verbose_name=_('Name'),
linkify=True,
- order_by=Accessor('_name')
)
actions = columns.ActionsColumn(
extra_buttons=MODULEBAY_BUTTONS
@@ -982,7 +977,6 @@ class DeviceInventoryItemTable(InventoryItemTable):
verbose_name=_('Name'),
template_code=''
'{{ value }}',
- order_by=Accessor('_name'),
attrs={'td': {'class': 'text-nowrap'}}
)
diff --git a/netbox/dcim/tables/devicetypes.py b/netbox/dcim/tables/devicetypes.py
index e8a4e35f1..a7f8f08e8 100644
--- a/netbox/dcim/tables/devicetypes.py
+++ b/netbox/dcim/tables/devicetypes.py
@@ -163,9 +163,7 @@ class ComponentTemplateTable(NetBoxTable):
id = tables.Column(
verbose_name=_('ID')
)
- name = tables.Column(
- order_by=('_name',)
- )
+ name = tables.Column()
class Meta(NetBoxTable.Meta):
exclude = ('id', )
@@ -220,6 +218,10 @@ class PowerOutletTemplateTable(ComponentTemplateTable):
class InterfaceTemplateTable(ComponentTemplateTable):
+ name = tables.Column(
+ verbose_name=_('Name'),
+ order_by=('_name',)
+ )
enabled = columns.BooleanColumn(
verbose_name=_('Enabled'),
)
diff --git a/netbox/dcim/tables/racks.py b/netbox/dcim/tables/racks.py
index a6b704161..dbd99ca24 100644
--- a/netbox/dcim/tables/racks.py
+++ b/netbox/dcim/tables/racks.py
@@ -111,7 +111,6 @@ class RackTypeTable(NetBoxTable):
class RackTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable):
name = tables.Column(
verbose_name=_('Name'),
- order_by=('_name',),
linkify=True
)
location = tables.Column(
diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py
index 9a821a384..7a5a771a9 100644
--- a/netbox/dcim/views.py
+++ b/netbox/dcim/views.py
@@ -688,8 +688,7 @@ class RackElevationListView(generic.ObjectListView):
sort = request.GET.get('sort', 'name')
if sort not in ORDERING_CHOICES:
sort = 'name'
- sort_field = sort.replace("name", "_name") # Use natural ordering
- racks = racks.order_by(sort_field)
+ racks = racks.order_by(sort)
# Pagination
per_page = get_paginate_count(request)
@@ -731,8 +730,8 @@ class RackView(GetRelatedModelsMixin, generic.ObjectView):
peer_racks = peer_racks.filter(location=instance.location)
else:
peer_racks = peer_racks.filter(location__isnull=True)
- next_rack = peer_racks.filter(_name__gt=instance._name).first()
- prev_rack = peer_racks.filter(_name__lt=instance._name).reverse().first()
+ next_rack = peer_racks.filter(name__gt=instance.name).first()
+ prev_rack = peer_racks.filter(name__lt=instance.name).reverse().first()
# Determine any additional parameters to pass when embedding the rack elevations
svg_extra = '&'.join([
diff --git a/netbox/ipam/migrations/0076_natural_ordering.py b/netbox/ipam/migrations/0076_natural_ordering.py
new file mode 100644
index 000000000..8c7bfaea1
--- /dev/null
+++ b/netbox/ipam/migrations/0076_natural_ordering.py
@@ -0,0 +1,32 @@
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('ipam', '0075_vlan_qinq'),
+ ('dcim', '0197_natural_sort_collation'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='asnrange',
+ name='name',
+ field=models.CharField(db_collation='natural_sort', max_length=100, unique=True),
+ ),
+ migrations.AlterField(
+ model_name='routetarget',
+ name='name',
+ field=models.CharField(db_collation='natural_sort', max_length=21, unique=True),
+ ),
+ migrations.AlterField(
+ model_name='vlangroup',
+ name='name',
+ field=models.CharField(db_collation='natural_sort', max_length=100),
+ ),
+ migrations.AlterField(
+ model_name='vrf',
+ name='name',
+ field=models.CharField(db_collation='natural_sort', max_length=100),
+ ),
+ ]
diff --git a/netbox/ipam/models/asns.py b/netbox/ipam/models/asns.py
index eb47426b2..c1d251301 100644
--- a/netbox/ipam/models/asns.py
+++ b/netbox/ipam/models/asns.py
@@ -16,7 +16,8 @@ class ASNRange(OrganizationalModel):
name = models.CharField(
verbose_name=_('name'),
max_length=100,
- unique=True
+ unique=True,
+ db_collation="natural_sort"
)
slug = models.SlugField(
verbose_name=_('slug'),
diff --git a/netbox/ipam/models/vlans.py b/netbox/ipam/models/vlans.py
index 7832cfc67..fa31fd608 100644
--- a/netbox/ipam/models/vlans.py
+++ b/netbox/ipam/models/vlans.py
@@ -35,7 +35,8 @@ class VLANGroup(OrganizationalModel):
"""
name = models.CharField(
verbose_name=_('name'),
- max_length=100
+ max_length=100,
+ db_collation="natural_sort"
)
slug = models.SlugField(
verbose_name=_('slug'),
diff --git a/netbox/ipam/models/vrfs.py b/netbox/ipam/models/vrfs.py
index 26afb7927..6a8b8d649 100644
--- a/netbox/ipam/models/vrfs.py
+++ b/netbox/ipam/models/vrfs.py
@@ -18,7 +18,8 @@ class VRF(PrimaryModel):
"""
name = models.CharField(
verbose_name=_('name'),
- max_length=100
+ max_length=100,
+ db_collation="natural_sort"
)
rd = models.CharField(
max_length=VRF_RD_MAX_LENGTH,
@@ -74,7 +75,8 @@ class RouteTarget(PrimaryModel):
verbose_name=_('name'),
max_length=VRF_RD_MAX_LENGTH, # Same format options as VRF RD (RFC 4360 section 4)
unique=True,
- help_text=_('Route target value (formatted in accordance with RFC 4360)')
+ help_text=_('Route target value (formatted in accordance with RFC 4360)'),
+ db_collation="natural_sort"
)
tenant = models.ForeignKey(
to='tenancy.Tenant',
diff --git a/netbox/tenancy/migrations/0017_natural_ordering.py b/netbox/tenancy/migrations/0017_natural_ordering.py
new file mode 100644
index 000000000..de1fb49aa
--- /dev/null
+++ b/netbox/tenancy/migrations/0017_natural_ordering.py
@@ -0,0 +1,27 @@
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('tenancy', '0016_charfield_null_choices'),
+ ('dcim', '0197_natural_sort_collation'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='contact',
+ name='name',
+ field=models.CharField(db_collation='natural_sort', max_length=100),
+ ),
+ migrations.AlterField(
+ model_name='tenant',
+ name='name',
+ field=models.CharField(db_collation='natural_sort', max_length=100),
+ ),
+ migrations.AlterField(
+ model_name='tenantgroup',
+ name='name',
+ field=models.CharField(db_collation='natural_sort', max_length=100, unique=True),
+ ),
+ ]
diff --git a/netbox/tenancy/models/contacts.py b/netbox/tenancy/models/contacts.py
index 24ffef0cf..3969c8317 100644
--- a/netbox/tenancy/models/contacts.py
+++ b/netbox/tenancy/models/contacts.py
@@ -56,7 +56,8 @@ class Contact(PrimaryModel):
)
name = models.CharField(
verbose_name=_('name'),
- max_length=100
+ max_length=100,
+ db_collation="natural_sort"
)
title = models.CharField(
verbose_name=_('title'),
diff --git a/netbox/tenancy/models/tenants.py b/netbox/tenancy/models/tenants.py
index 7a2d9c2f8..55f0c5933 100644
--- a/netbox/tenancy/models/tenants.py
+++ b/netbox/tenancy/models/tenants.py
@@ -18,7 +18,8 @@ class TenantGroup(NestedGroupModel):
name = models.CharField(
verbose_name=_('name'),
max_length=100,
- unique=True
+ unique=True,
+ db_collation="natural_sort"
)
slug = models.SlugField(
verbose_name=_('slug'),
@@ -39,7 +40,8 @@ class Tenant(ContactsMixin, PrimaryModel):
"""
name = models.CharField(
verbose_name=_('name'),
- max_length=100
+ max_length=100,
+ db_collation="natural_sort"
)
slug = models.SlugField(
verbose_name=_('slug'),
diff --git a/netbox/utilities/fields.py b/netbox/utilities/fields.py
index ee71223cb..1d16a1d3f 100644
--- a/netbox/utilities/fields.py
+++ b/netbox/utilities/fields.py
@@ -5,7 +5,6 @@ from django.db import models
from django.utils.safestring import mark_safe
from django.utils.translation import gettext_lazy as _
-from utilities.ordering import naturalize
from .forms.widgets import ColorSelect
from .validators import ColorValidator
@@ -40,7 +39,7 @@ class NaturalOrderingField(models.CharField):
"""
description = "Stores a representation of its target field suitable for natural ordering"
- def __init__(self, target_field, naturalize_function=naturalize, *args, **kwargs):
+ def __init__(self, target_field, naturalize_function, *args, **kwargs):
self.target_field = target_field
self.naturalize_function = naturalize_function
super().__init__(*args, **kwargs)
diff --git a/netbox/virtualization/graphql/types.py b/netbox/virtualization/graphql/types.py
index 6052c8936..8476eac7e 100644
--- a/netbox/virtualization/graphql/types.py
+++ b/netbox/virtualization/graphql/types.py
@@ -25,7 +25,6 @@ class ComponentType(NetBoxObjectType):
"""
Base type for device/VM components
"""
- _name: str
virtual_machine: Annotated["VirtualMachineType", strawberry.lazy('virtualization.graphql.types')]
@@ -77,7 +76,6 @@ class ClusterTypeType(OrganizationalObjectType):
filters=VirtualMachineFilter
)
class VirtualMachineType(ConfigContextMixin, ContactsMixin, NetBoxObjectType):
- _name: str
interface_count: BigInt
virtual_disk_count: BigInt
interface_count: BigInt
@@ -102,6 +100,7 @@ class VirtualMachineType(ConfigContextMixin, ContactsMixin, NetBoxObjectType):
filters=VMInterfaceFilter
)
class VMInterfaceType(IPAddressesMixin, ComponentType):
+ _name: str
mac_address: str | None
parent: Annotated["VMInterfaceType", strawberry.lazy('virtualization.graphql.types')] | None
bridge: Annotated["VMInterfaceType", strawberry.lazy('virtualization.graphql.types')] | None
diff --git a/netbox/virtualization/migrations/0046_natural_ordering.py b/netbox/virtualization/migrations/0046_natural_ordering.py
new file mode 100644
index 000000000..9284b6331
--- /dev/null
+++ b/netbox/virtualization/migrations/0046_natural_ordering.py
@@ -0,0 +1,43 @@
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('virtualization', '0045_clusters_cached_relations'),
+ ('dcim', '0197_natural_sort_collation'),
+ ]
+
+ operations = [
+ migrations.AlterModelOptions(
+ name='virtualmachine',
+ options={'ordering': ('name', 'pk')},
+ ),
+ migrations.AlterModelOptions(
+ name='virtualdisk',
+ options={'ordering': ('virtual_machine', 'name')},
+ ),
+ migrations.RemoveField(
+ model_name='virtualmachine',
+ name='_name',
+ ),
+ migrations.AlterField(
+ model_name='virtualdisk',
+ name='name',
+ field=models.CharField(db_collation='natural_sort', max_length=64),
+ ),
+ migrations.AlterField(
+ model_name='virtualmachine',
+ name='name',
+ field=models.CharField(db_collation='natural_sort', max_length=64),
+ ),
+ migrations.AlterField(
+ model_name='cluster',
+ name='name',
+ field=models.CharField(db_collation='natural_sort', max_length=100),
+ ),
+ migrations.RemoveField(
+ model_name='virtualdisk',
+ name='_name',
+ ),
+ ]
diff --git a/netbox/virtualization/models/clusters.py b/netbox/virtualization/models/clusters.py
index 601ee7f23..9f7b97e29 100644
--- a/netbox/virtualization/models/clusters.py
+++ b/netbox/virtualization/models/clusters.py
@@ -50,7 +50,8 @@ class Cluster(ContactsMixin, CachedScopeMixin, PrimaryModel):
"""
name = models.CharField(
verbose_name=_('name'),
- max_length=100
+ max_length=100,
+ db_collation="natural_sort"
)
type = models.ForeignKey(
verbose_name=_('type'),
diff --git a/netbox/virtualization/models/virtualmachines.py b/netbox/virtualization/models/virtualmachines.py
index 4ee41e403..ebfb2d6c5 100644
--- a/netbox/virtualization/models/virtualmachines.py
+++ b/netbox/virtualization/models/virtualmachines.py
@@ -69,12 +69,8 @@ class VirtualMachine(ContactsMixin, ImageAttachmentsMixin, RenderConfigMixin, Co
)
name = models.CharField(
verbose_name=_('name'),
- max_length=64
- )
- _name = NaturalOrderingField(
- target_field='name',
- max_length=100,
- blank=True
+ max_length=64,
+ db_collation="natural_sort"
)
status = models.CharField(
max_length=50,
@@ -152,7 +148,7 @@ class VirtualMachine(ContactsMixin, ImageAttachmentsMixin, RenderConfigMixin, Co
)
class Meta:
- ordering = ('_name', 'pk') # Name may be non-unique
+ ordering = ('name', 'pk') # Name may be non-unique
constraints = (
models.UniqueConstraint(
Lower('name'), 'cluster', 'tenant',
@@ -273,13 +269,8 @@ class ComponentModel(NetBoxModel):
)
name = models.CharField(
verbose_name=_('name'),
- max_length=64
- )
- _name = NaturalOrderingField(
- target_field='name',
- naturalize_function=naturalize_interface,
- max_length=100,
- blank=True
+ max_length=64,
+ db_collation="natural_sort"
)
description = models.CharField(
verbose_name=_('description'),
@@ -289,7 +280,6 @@ class ComponentModel(NetBoxModel):
class Meta:
abstract = True
- ordering = ('virtual_machine', CollateAsChar('_name'))
constraints = (
models.UniqueConstraint(
fields=('virtual_machine', 'name'),
@@ -311,10 +301,9 @@ class ComponentModel(NetBoxModel):
class VMInterface(ComponentModel, BaseInterface, TrackingModelMixin):
- virtual_machine = models.ForeignKey(
- to='virtualization.VirtualMachine',
- on_delete=models.CASCADE,
- related_name='interfaces' # Override ComponentModel
+ name = models.CharField(
+ verbose_name=_('name'),
+ max_length=64,
)
_name = NaturalOrderingField(
target_field='name',
@@ -322,6 +311,11 @@ class VMInterface(ComponentModel, BaseInterface, TrackingModelMixin):
max_length=100,
blank=True
)
+ virtual_machine = models.ForeignKey(
+ to='virtualization.VirtualMachine',
+ on_delete=models.CASCADE,
+ related_name='interfaces' # Override ComponentModel
+ )
ip_addresses = GenericRelation(
to='ipam.IPAddress',
content_type_field='assigned_object_type',
@@ -358,6 +352,7 @@ class VMInterface(ComponentModel, BaseInterface, TrackingModelMixin):
class Meta(ComponentModel.Meta):
verbose_name = _('interface')
verbose_name_plural = _('interfaces')
+ ordering = ('virtual_machine', CollateAsChar('_name'))
def clean(self):
super().clean()
@@ -416,3 +411,4 @@ class VirtualDisk(ComponentModel, TrackingModelMixin):
class Meta(ComponentModel.Meta):
verbose_name = _('virtual disk')
verbose_name_plural = _('virtual disks')
+ ordering = ('virtual_machine', 'name')
diff --git a/netbox/virtualization/tables/virtualmachines.py b/netbox/virtualization/tables/virtualmachines.py
index 4a3138711..26d32f8cf 100644
--- a/netbox/virtualization/tables/virtualmachines.py
+++ b/netbox/virtualization/tables/virtualmachines.py
@@ -53,7 +53,6 @@ VMINTERFACE_BUTTONS = """
class VirtualMachineTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable):
name = tables.Column(
verbose_name=_('Name'),
- order_by=('_name',),
linkify=True
)
status = columns.ChoiceFieldColumn(
diff --git a/netbox/vpn/migrations/0007_natural_ordering.py b/netbox/vpn/migrations/0007_natural_ordering.py
new file mode 100644
index 000000000..01dd4620f
--- /dev/null
+++ b/netbox/vpn/migrations/0007_natural_ordering.py
@@ -0,0 +1,47 @@
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('vpn', '0006_charfield_null_choices'),
+ ('dcim', '0197_natural_sort_collation'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='ikepolicy',
+ name='name',
+ field=models.CharField(db_collation='natural_sort', max_length=100, unique=True),
+ ),
+ migrations.AlterField(
+ model_name='ikeproposal',
+ name='name',
+ field=models.CharField(db_collation='natural_sort', max_length=100, unique=True),
+ ),
+ migrations.AlterField(
+ model_name='ipsecpolicy',
+ name='name',
+ field=models.CharField(db_collation='natural_sort', max_length=100, unique=True),
+ ),
+ migrations.AlterField(
+ model_name='ipsecprofile',
+ name='name',
+ field=models.CharField(db_collation='natural_sort', max_length=100, unique=True),
+ ),
+ migrations.AlterField(
+ model_name='ipsecproposal',
+ name='name',
+ field=models.CharField(db_collation='natural_sort', max_length=100, unique=True),
+ ),
+ migrations.AlterField(
+ model_name='l2vpn',
+ name='name',
+ field=models.CharField(db_collation='natural_sort', max_length=100, unique=True),
+ ),
+ migrations.AlterField(
+ model_name='tunnel',
+ name='name',
+ field=models.CharField(db_collation='natural_sort', max_length=100, unique=True),
+ ),
+ ]
diff --git a/netbox/vpn/models/crypto.py b/netbox/vpn/models/crypto.py
index 2b721ec29..8e991b578 100644
--- a/netbox/vpn/models/crypto.py
+++ b/netbox/vpn/models/crypto.py
@@ -22,7 +22,8 @@ class IKEProposal(PrimaryModel):
name = models.CharField(
verbose_name=_('name'),
max_length=100,
- unique=True
+ unique=True,
+ db_collation="natural_sort"
)
authentication_method = models.CharField(
verbose_name=('authentication method'),
@@ -67,7 +68,8 @@ class IKEPolicy(PrimaryModel):
name = models.CharField(
verbose_name=_('name'),
max_length=100,
- unique=True
+ unique=True,
+ db_collation="natural_sort"
)
version = models.PositiveSmallIntegerField(
verbose_name=_('version'),
@@ -125,7 +127,8 @@ class IPSecProposal(PrimaryModel):
name = models.CharField(
verbose_name=_('name'),
max_length=100,
- unique=True
+ unique=True,
+ db_collation="natural_sort"
)
encryption_algorithm = models.CharField(
verbose_name=_('encryption'),
@@ -176,7 +179,8 @@ class IPSecPolicy(PrimaryModel):
name = models.CharField(
verbose_name=_('name'),
max_length=100,
- unique=True
+ unique=True,
+ db_collation="natural_sort"
)
proposals = models.ManyToManyField(
to='vpn.IPSecProposal',
@@ -211,7 +215,8 @@ class IPSecProfile(PrimaryModel):
name = models.CharField(
verbose_name=_('name'),
max_length=100,
- unique=True
+ unique=True,
+ db_collation="natural_sort"
)
mode = models.CharField(
verbose_name=_('mode'),
diff --git a/netbox/vpn/models/l2vpn.py b/netbox/vpn/models/l2vpn.py
index b799ab32d..3e562531d 100644
--- a/netbox/vpn/models/l2vpn.py
+++ b/netbox/vpn/models/l2vpn.py
@@ -20,7 +20,8 @@ class L2VPN(ContactsMixin, PrimaryModel):
name = models.CharField(
verbose_name=_('name'),
max_length=100,
- unique=True
+ unique=True,
+ db_collation="natural_sort"
)
slug = models.SlugField(
verbose_name=_('slug'),
diff --git a/netbox/vpn/models/tunnels.py b/netbox/vpn/models/tunnels.py
index 3a0f1dc14..714024a81 100644
--- a/netbox/vpn/models/tunnels.py
+++ b/netbox/vpn/models/tunnels.py
@@ -31,7 +31,8 @@ class Tunnel(PrimaryModel):
name = models.CharField(
verbose_name=_('name'),
max_length=100,
- unique=True
+ unique=True,
+ db_collation="natural_sort"
)
status = models.CharField(
verbose_name=_('status'),
diff --git a/netbox/wireless/migrations/0012_natural_ordering.py b/netbox/wireless/migrations/0012_natural_ordering.py
new file mode 100644
index 000000000..da818bdd9
--- /dev/null
+++ b/netbox/wireless/migrations/0012_natural_ordering.py
@@ -0,0 +1,17 @@
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('wireless', '0011_wirelesslan__location_wirelesslan__region_and_more'),
+ ('dcim', '0197_natural_sort_collation'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='wirelesslangroup',
+ name='name',
+ field=models.CharField(db_collation='natural_sort', max_length=100, unique=True),
+ ),
+ ]
diff --git a/netbox/wireless/models.py b/netbox/wireless/models.py
index d78c893a6..61ff72bc1 100644
--- a/netbox/wireless/models.py
+++ b/netbox/wireless/models.py
@@ -52,7 +52,8 @@ class WirelessLANGroup(NestedGroupModel):
name = models.CharField(
verbose_name=_('name'),
max_length=100,
- unique=True
+ unique=True,
+ db_collation="natural_sort"
)
slug = models.SlugField(
verbose_name=_('slug'),