Compare commits

...

4 Commits

Author SHA1 Message Date
Jeremy Stretch
4f2f61c90d Reindex migrations 2025-10-24 15:25:45 -04:00
Jeremy Stretch
a34553325e Add migrations to remove indexes and alter field collations 2025-10-24 15:23:58 -04:00
Jeremy Stretch
06052f8eaa Use case-insensitive collations on fields considered for uniqueness 2025-10-24 15:23:58 -04:00
Jeremy Stretch
dac0a06f4f Introduce case-insensitive collations 2025-10-24 15:23:58 -04:00
39 changed files with 1083 additions and 81 deletions

View File

@@ -0,0 +1,97 @@
from django.db import migrations, models
PATTERN_OPS_INDEXES = [
'circuits_circuitgroup_name_ec8ac1e5_like',
'circuits_circuitgroup_slug_61ca866b_like',
'circuits_circuittype_name_8256ea9a_like',
'circuits_circuittype_slug_9b4b3cf9_like',
'circuits_provider_name_8f2514f5_like',
'circuits_provider_slug_c3c0aa10_like',
'circuits_virtualcircuittype_name_5184db16_like',
'circuits_virtualcircuittype_slug_75d5c661_like',
]
def remove_indexes(apps, schema_editor):
for idx in PATTERN_OPS_INDEXES:
schema_editor.execute(f'DROP INDEX IF EXISTS {idx}')
class Migration(migrations.Migration):
dependencies = [
('circuits', '0052_extend_circuit_abs_distance_upper_limit'),
('dcim', '0217_ci_collations'),
]
operations = [
migrations.RunPython(
code=remove_indexes,
reverse_code=migrations.RunPython.noop,
),
migrations.AlterField(
model_name='circuit',
name='cid',
field=models.CharField(db_collation='case_insensitive', max_length=100),
),
migrations.AlterField(
model_name='circuitgroup',
name='name',
field=models.CharField(db_collation='ci_natural_sort', max_length=100, unique=True),
),
migrations.AlterField(
model_name='circuitgroup',
name='slug',
field=models.SlugField(db_collation='case_insensitive', max_length=100, unique=True),
),
migrations.AlterField(
model_name='circuittype',
name='name',
field=models.CharField(db_collation='ci_natural_sort', max_length=100, unique=True),
),
migrations.AlterField(
model_name='circuittype',
name='slug',
field=models.SlugField(db_collation='case_insensitive', max_length=100, unique=True),
),
migrations.AlterField(
model_name='provider',
name='name',
field=models.CharField(db_collation='ci_natural_sort', max_length=100, unique=True),
),
migrations.AlterField(
model_name='provider',
name='slug',
field=models.SlugField(db_collation='case_insensitive', max_length=100, unique=True),
),
migrations.AlterField(
model_name='provideraccount',
name='account',
field=models.CharField(db_collation='ci_natural_sort', max_length=100),
),
migrations.AlterField(
model_name='provideraccount',
name='name',
field=models.CharField(blank=True, db_collation='ci_natural_sort', max_length=100),
),
migrations.AlterField(
model_name='providernetwork',
name='name',
field=models.CharField(db_collation='ci_natural_sort', max_length=100),
),
migrations.AlterField(
model_name='virtualcircuit',
name='cid',
field=models.CharField(db_collation='case_insensitive', max_length=100),
),
migrations.AlterField(
model_name='virtualcircuittype',
name='name',
field=models.CharField(db_collation='ci_natural_sort', max_length=100, unique=True),
),
migrations.AlterField(
model_name='virtualcircuittype',
name='slug',
field=models.SlugField(db_collation='case_insensitive', max_length=100, unique=True),
),
]

View File

@@ -41,9 +41,10 @@ class Circuit(ContactsMixin, ImageAttachmentsMixin, DistanceMixin, PrimaryModel)
ProviderAccount. Circuit port speed and commit rate are measured in Kbps. ProviderAccount. Circuit port speed and commit rate are measured in Kbps.
""" """
cid = models.CharField( cid = models.CharField(
max_length=100,
verbose_name=_('circuit ID'), verbose_name=_('circuit ID'),
help_text=_('Unique circuit ID') max_length=100,
db_collation='case_insensitive',
help_text=_('Unique circuit ID'),
) )
provider = models.ForeignKey( provider = models.ForeignKey(
to='circuits.Provider', to='circuits.Provider',

View File

@@ -21,13 +21,14 @@ class Provider(ContactsMixin, PrimaryModel):
verbose_name=_('name'), verbose_name=_('name'),
max_length=100, max_length=100,
unique=True, unique=True,
db_collation='ci_natural_sort',
help_text=_('Full name of the provider'), help_text=_('Full name of the provider'),
db_collation="natural_sort"
) )
slug = models.SlugField( slug = models.SlugField(
verbose_name=_('slug'), verbose_name=_('slug'),
max_length=100, max_length=100,
unique=True unique=True,
db_collation='case_insensitive',
) )
asns = models.ManyToManyField( asns = models.ManyToManyField(
to='ipam.ASN', to='ipam.ASN',
@@ -56,13 +57,15 @@ class ProviderAccount(ContactsMixin, PrimaryModel):
related_name='accounts' related_name='accounts'
) )
account = models.CharField( account = models.CharField(
verbose_name=_('account ID'),
max_length=100, max_length=100,
verbose_name=_('account ID') db_collation='ci_natural_sort',
) )
name = models.CharField( name = models.CharField(
verbose_name=_('name'), verbose_name=_('name'),
max_length=100, max_length=100,
blank=True db_collation='ci_natural_sort',
blank=True,
) )
clone_fields = ('provider', ) clone_fields = ('provider', )
@@ -97,7 +100,7 @@ class ProviderNetwork(PrimaryModel):
name = models.CharField( name = models.CharField(
verbose_name=_('name'), verbose_name=_('name'),
max_length=100, max_length=100,
db_collation="natural_sort" db_collation='ci_natural_sort',
) )
provider = models.ForeignKey( provider = models.ForeignKey(
to='circuits.Provider', to='circuits.Provider',

View File

@@ -34,9 +34,10 @@ class VirtualCircuit(PrimaryModel):
A virtual connection between two or more endpoints, delivered across one or more physical circuits. A virtual connection between two or more endpoints, delivered across one or more physical circuits.
""" """
cid = models.CharField( cid = models.CharField(
max_length=100,
verbose_name=_('circuit ID'), verbose_name=_('circuit ID'),
help_text=_('Unique circuit ID') max_length=100,
db_collation='case_insensitive',
help_text=_('Unique circuit ID'),
) )
provider_network = models.ForeignKey( provider_network = models.ForeignKey(
to='circuits.ProviderNetwork', to='circuits.ProviderNetwork',

View File

@@ -0,0 +1,30 @@
from django.db import migrations, models
PATTERN_OPS_INDEXES = [
'core_datasource_name_17788499_like',
]
def remove_indexes(apps, schema_editor):
for idx in PATTERN_OPS_INDEXES:
schema_editor.execute(f'DROP INDEX IF EXISTS {idx}')
class Migration(migrations.Migration):
dependencies = [
('core', '0019_configrevision_active'),
('dcim', '0217_ci_collations'),
]
operations = [
migrations.RunPython(
code=remove_indexes,
reverse_code=migrations.RunPython.noop,
),
migrations.AlterField(
model_name='datasource',
name='name',
field=models.CharField(db_collation='ci_natural_sort', max_length=100, unique=True),
),
]

View File

@@ -38,7 +38,8 @@ class DataSource(JobsMixin, PrimaryModel):
name = models.CharField( name = models.CharField(
verbose_name=_('name'), verbose_name=_('name'),
max_length=100, max_length=100,
unique=True unique=True,
db_collation='ci_natural_sort',
) )
type = models.CharField( type = models.CharField(
verbose_name=_('type'), verbose_name=_('type'),

View File

@@ -0,0 +1,26 @@
from django.contrib.postgres.operations import CreateCollation
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('dcim', '0216_poweroutlettemplate_color'),
]
operations = [
# Create a case-insensitive collation
CreateCollation(
'case_insensitive',
provider='icu',
locale='und-u-ks-level2',
deterministic=False,
),
# Create a case-insensitive collation with natural sorting
CreateCollation(
'ci_natural_sort',
provider='icu',
locale='und-u-kn-true-ks-level2',
deterministic=False,
),
]

View File

@@ -0,0 +1,311 @@
from django.db import migrations, models
PATTERN_OPS_INDEXES = [
'dcim_devicerole_slug_7952643b_like',
'dcim_devicetype_slug_448745bd_like',
'dcim_inventoryitemrole_name_4c8cfe6d_like',
'dcim_inventoryitemrole_slug_3556c227_like',
'dcim_location_slug_352c5472_like',
'dcim_manufacturer_name_841fcd92_like',
'dcim_manufacturer_slug_00430749_like',
'dcim_moduletypeprofile_name_1709c36e_like',
'dcim_platform_slug_b0908ae4_like',
'dcim_rackrole_name_9077cfcc_like',
'dcim_rackrole_slug_40bbcd3a_like',
'dcim_racktype_slug_6bbb384a_like',
'dcim_region_slug_ff078a66_like',
'dcim_site_name_8fe66c76_like',
'dcim_site_slug_4412c762_like',
'dcim_sitegroup_slug_a11d2b04_like',
]
def remove_indexes(apps, schema_editor):
for idx in PATTERN_OPS_INDEXES:
schema_editor.execute(f'DROP INDEX IF EXISTS {idx}')
class Migration(migrations.Migration):
dependencies = [
('dcim', '0217_ci_collations'),
('extras', '0134_ci_collations'),
('ipam', '0083_ci_collations'),
('tenancy', '0021_ci_collations'),
('virtualization', '0048_populate_mac_addresses'),
]
operations = [
migrations.RunPython(
code=remove_indexes,
reverse_code=migrations.RunPython.noop,
),
migrations.RemoveConstraint(
model_name='device',
name='dcim_device_unique_name_site_tenant',
),
migrations.RemoveConstraint(
model_name='device',
name='dcim_device_unique_name_site',
),
migrations.AlterField(
model_name='consoleport',
name='name',
field=models.CharField(db_collation='ci_natural_sort', max_length=64),
),
migrations.AlterField(
model_name='consoleporttemplate',
name='name',
field=models.CharField(db_collation='ci_natural_sort', max_length=64),
),
migrations.AlterField(
model_name='consoleserverport',
name='name',
field=models.CharField(db_collation='ci_natural_sort', max_length=64),
),
migrations.AlterField(
model_name='consoleserverporttemplate',
name='name',
field=models.CharField(db_collation='ci_natural_sort', max_length=64),
),
migrations.AlterField(
model_name='device',
name='name',
field=models.CharField(blank=True, db_collation='ci_natural_sort', max_length=64, null=True),
),
migrations.AlterField(
model_name='devicebay',
name='name',
field=models.CharField(db_collation='ci_natural_sort', max_length=64),
),
migrations.AlterField(
model_name='devicebaytemplate',
name='name',
field=models.CharField(db_collation='ci_natural_sort', max_length=64),
),
migrations.AlterField(
model_name='devicerole',
name='name',
field=models.CharField(db_collation='ci_natural_sort', max_length=100),
),
migrations.AlterField(
model_name='devicerole',
name='slug',
field=models.SlugField(db_collation='case_insensitive', max_length=100),
),
migrations.AlterField(
model_name='devicetype',
name='model',
field=models.CharField(db_collation='case_insensitive', max_length=100),
),
migrations.AlterField(
model_name='devicetype',
name='slug',
field=models.SlugField(db_collation='case_insensitive', max_length=100),
),
migrations.AlterField(
model_name='frontport',
name='name',
field=models.CharField(db_collation='ci_natural_sort', max_length=64),
),
migrations.AlterField(
model_name='frontporttemplate',
name='name',
field=models.CharField(db_collation='ci_natural_sort', max_length=64),
),
migrations.AlterField(
model_name='interface',
name='name',
field=models.CharField(db_collation='ci_natural_sort', max_length=64),
),
migrations.AlterField(
model_name='interfacetemplate',
name='name',
field=models.CharField(db_collation='ci_natural_sort', max_length=64),
),
migrations.AlterField(
model_name='inventoryitem',
name='name',
field=models.CharField(db_collation='ci_natural_sort', max_length=64),
),
migrations.AlterField(
model_name='inventoryitemrole',
name='name',
field=models.CharField(db_collation='ci_natural_sort', max_length=100, unique=True),
),
migrations.AlterField(
model_name='inventoryitemrole',
name='slug',
field=models.SlugField(db_collation='case_insensitive', max_length=100, unique=True),
),
migrations.AlterField(
model_name='inventoryitemtemplate',
name='name',
field=models.CharField(db_collation='ci_natural_sort', max_length=64),
),
migrations.AlterField(
model_name='location',
name='name',
field=models.CharField(db_collation='ci_natural_sort', max_length=100),
),
migrations.AlterField(
model_name='location',
name='slug',
field=models.SlugField(db_collation='case_insensitive', max_length=100),
),
migrations.AlterField(
model_name='manufacturer',
name='name',
field=models.CharField(db_collation='ci_natural_sort', max_length=100, unique=True),
),
migrations.AlterField(
model_name='manufacturer',
name='slug',
field=models.SlugField(db_collation='case_insensitive', max_length=100, unique=True),
),
migrations.AlterField(
model_name='modulebay',
name='name',
field=models.CharField(db_collation='ci_natural_sort', max_length=64),
),
migrations.AlterField(
model_name='modulebaytemplate',
name='name',
field=models.CharField(db_collation='ci_natural_sort', max_length=64),
),
migrations.AlterField(
model_name='moduletype',
name='model',
field=models.CharField(db_collation='ci_natural_sort', max_length=100),
),
migrations.AlterField(
model_name='moduletypeprofile',
name='name',
field=models.CharField(db_collation='ci_natural_sort', max_length=100, unique=True),
),
migrations.AlterField(
model_name='platform',
name='name',
field=models.CharField(db_collation='ci_natural_sort', max_length=100),
),
migrations.AlterField(
model_name='platform',
name='slug',
field=models.SlugField(db_collation='case_insensitive', max_length=100),
),
migrations.AlterField(
model_name='powerfeed',
name='name',
field=models.CharField(db_collation='ci_natural_sort', max_length=100),
),
migrations.AlterField(
model_name='poweroutlet',
name='name',
field=models.CharField(db_collation='ci_natural_sort', max_length=64),
),
migrations.AlterField(
model_name='poweroutlettemplate',
name='name',
field=models.CharField(db_collation='ci_natural_sort', max_length=64),
),
migrations.AlterField(
model_name='powerpanel',
name='name',
field=models.CharField(db_collation='ci_natural_sort', max_length=100),
),
migrations.AlterField(
model_name='powerport',
name='name',
field=models.CharField(db_collation='ci_natural_sort', max_length=64),
),
migrations.AlterField(
model_name='powerporttemplate',
name='name',
field=models.CharField(db_collation='ci_natural_sort', max_length=64),
),
migrations.AlterField(
model_name='rack',
name='name',
field=models.CharField(db_collation='ci_natural_sort', max_length=100),
),
migrations.AlterField(
model_name='rackrole',
name='name',
field=models.CharField(db_collation='ci_natural_sort', max_length=100, unique=True),
),
migrations.AlterField(
model_name='rackrole',
name='slug',
field=models.SlugField(db_collation='case_insensitive', max_length=100, unique=True),
),
migrations.AlterField(
model_name='racktype',
name='model',
field=models.CharField(db_collation='ci_natural_sort', max_length=100),
),
migrations.AlterField(
model_name='racktype',
name='slug',
field=models.SlugField(db_collation='case_insensitive', max_length=100, unique=True),
),
migrations.AlterField(
model_name='rearport',
name='name',
field=models.CharField(db_collation='ci_natural_sort', max_length=64),
),
migrations.AlterField(
model_name='rearporttemplate',
name='name',
field=models.CharField(db_collation='ci_natural_sort', max_length=64),
),
migrations.AlterField(
model_name='region',
name='name',
field=models.CharField(db_collation='ci_natural_sort', max_length=100),
),
migrations.AlterField(
model_name='region',
name='slug',
field=models.SlugField(db_collation='case_insensitive', max_length=100),
),
migrations.AlterField(
model_name='site',
name='name',
field=models.CharField(db_collation='ci_natural_sort', max_length=100, unique=True),
),
migrations.AlterField(
model_name='site',
name='slug',
field=models.SlugField(db_collation='case_insensitive', max_length=100, unique=True),
),
migrations.AlterField(
model_name='sitegroup',
name='name',
field=models.CharField(db_collation='ci_natural_sort', max_length=100),
),
migrations.AlterField(
model_name='sitegroup',
name='slug',
field=models.SlugField(db_collation='case_insensitive', max_length=100),
),
migrations.AlterField(
model_name='virtualdevicecontext',
name='name',
field=models.CharField(db_collation='ci_natural_sort', max_length=64),
),
migrations.AddConstraint(
model_name='device',
constraint=models.UniqueConstraint(
models.F('name'), models.F('site'), models.F('tenant'), name='dcim_device_unique_name_site_tenant'
),
),
migrations.AddConstraint(
model_name='device',
constraint=models.UniqueConstraint(
models.F('name'),
models.F('site'),
condition=models.Q(('tenant__isnull', True)),
name='dcim_device_unique_name_site',
violation_error_message='Device name must be unique per site.',
),
),
]

View File

@@ -43,10 +43,10 @@ class ComponentTemplateModel(ChangeLoggedModel, TrackingModelMixin):
name = models.CharField( name = models.CharField(
verbose_name=_('name'), verbose_name=_('name'),
max_length=64, max_length=64,
db_collation='ci_natural_sort',
help_text=_( help_text=_(
"{module} is accepted as a substitution for the module bay position when attached to a module type." "{module} is accepted as a substitution for the module bay position when attached to a module type."
), ),
db_collation="natural_sort"
) )
label = models.CharField( label = models.CharField(
verbose_name=_('label'), verbose_name=_('label'),

View File

@@ -52,7 +52,7 @@ class ComponentModel(NetBoxModel):
name = models.CharField( name = models.CharField(
verbose_name=_('name'), verbose_name=_('name'),
max_length=64, max_length=64,
db_collation="natural_sort" db_collation='ci_natural_sort',
) )
label = models.CharField( label = models.CharField(
verbose_name=_('label'), verbose_name=_('label'),

View File

@@ -1,8 +1,7 @@
import decimal import decimal
import yaml
from functools import cached_property from functools import cached_property
import yaml
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
@@ -10,7 +9,6 @@ from django.core.files.storage import default_storage
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 F, ProtectedError, prefetch_related_objects from django.db.models import F, ProtectedError, prefetch_related_objects
from django.db.models.functions import Lower
from django.db.models.signals import post_save from django.db.models.signals import post_save
from django.urls import reverse from django.urls import reverse
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
@@ -25,8 +23,8 @@ from extras.querysets import ConfigContextModelQuerySet
from netbox.choices import ColorChoices from netbox.choices import ColorChoices
from netbox.config import ConfigItem from netbox.config import ConfigItem
from netbox.models import NestedGroupModel, OrganizationalModel, PrimaryModel from netbox.models import NestedGroupModel, OrganizationalModel, PrimaryModel
from netbox.models.mixins import WeightMixin
from netbox.models.features import ContactsMixin, ImageAttachmentsMixin from netbox.models.features import ContactsMixin, ImageAttachmentsMixin
from netbox.models.mixins import WeightMixin
from utilities.fields import ColorField, CounterCacheField from utilities.fields import ColorField, CounterCacheField
from utilities.prefetch import get_prefetchable_fields from utilities.prefetch import get_prefetchable_fields
from utilities.tracking import TrackingModelMixin from utilities.tracking import TrackingModelMixin
@@ -34,7 +32,6 @@ from .device_components import *
from .mixins import RenderConfigMixin from .mixins import RenderConfigMixin
from .modules import Module from .modules import Module
__all__ = ( __all__ = (
'Device', 'Device',
'DeviceRole', 'DeviceRole',
@@ -83,11 +80,13 @@ class DeviceType(ImageAttachmentsMixin, PrimaryModel, WeightMixin):
) )
model = models.CharField( model = models.CharField(
verbose_name=_('model'), verbose_name=_('model'),
max_length=100 max_length=100,
db_collation='case_insensitive',
) )
slug = models.SlugField( slug = models.SlugField(
verbose_name=_('slug'), verbose_name=_('slug'),
max_length=100 max_length=100,
db_collation='case_insensitive',
) )
default_platform = models.ForeignKey( default_platform = models.ForeignKey(
to='dcim.Platform', to='dcim.Platform',
@@ -525,7 +524,7 @@ class Device(
max_length=64, max_length=64,
blank=True, blank=True,
null=True, null=True,
db_collation="natural_sort" db_collation='ci_natural_sort',
) )
serial = models.CharField( serial = models.CharField(
max_length=50, max_length=50,
@@ -721,11 +720,11 @@ class Device(
ordering = ('name', 'pk') # Name may be null ordering = ('name', 'pk') # Name may be null
constraints = ( constraints = (
models.UniqueConstraint( models.UniqueConstraint(
Lower('name'), 'site', 'tenant', 'name', 'site', 'tenant',
name='%(app_label)s_%(class)s_unique_name_site_tenant' name='%(app_label)s_%(class)s_unique_name_site_tenant'
), ),
models.UniqueConstraint( models.UniqueConstraint(
Lower('name'), 'site', 'name', 'site',
name='%(app_label)s_%(class)s_unique_name_site', name='%(app_label)s_%(class)s_unique_name_site',
condition=Q(tenant__isnull=True), condition=Q(tenant__isnull=True),
violation_error_message=_("Device name must be unique per site.") violation_error_message=_("Device name must be unique per site.")
@@ -1119,7 +1118,7 @@ class VirtualChassis(PrimaryModel):
name = models.CharField( name = models.CharField(
verbose_name=_('name'), verbose_name=_('name'),
max_length=64, max_length=64,
db_collation="natural_sort" db_collation='natural_sort',
) )
domain = models.CharField( domain = models.CharField(
verbose_name=_('domain'), verbose_name=_('domain'),
@@ -1182,7 +1181,7 @@ class VirtualDeviceContext(PrimaryModel):
name = models.CharField( name = models.CharField(
verbose_name=_('name'), verbose_name=_('name'),
max_length=64, max_length=64,
db_collation="natural_sort" db_collation='ci_natural_sort',
) )
status = models.CharField( status = models.CharField(
verbose_name=_('status'), verbose_name=_('status'),

View File

@@ -31,7 +31,8 @@ class ModuleTypeProfile(PrimaryModel):
name = models.CharField( name = models.CharField(
verbose_name=_('name'), verbose_name=_('name'),
max_length=100, max_length=100,
unique=True unique=True,
db_collation='ci_natural_sort',
) )
schema = models.JSONField( schema = models.JSONField(
blank=True, blank=True,
@@ -72,7 +73,8 @@ class ModuleType(ImageAttachmentsMixin, PrimaryModel, WeightMixin):
) )
model = models.CharField( model = models.CharField(
verbose_name=_('model'), verbose_name=_('model'),
max_length=100 max_length=100,
db_collation='ci_natural_sort',
) )
part_number = models.CharField( part_number = models.CharField(
verbose_name=_('part number'), verbose_name=_('part number'),

View File

@@ -37,7 +37,7 @@ class PowerPanel(ContactsMixin, ImageAttachmentsMixin, PrimaryModel):
name = models.CharField( name = models.CharField(
verbose_name=_('name'), verbose_name=_('name'),
max_length=100, max_length=100,
db_collation="natural_sort" db_collation='ci_natural_sort',
) )
prerequisite_models = ( prerequisite_models = (
@@ -88,7 +88,7 @@ class PowerFeed(PrimaryModel, PathEndpoint, CabledObjectModel):
name = models.CharField( name = models.CharField(
verbose_name=_('name'), verbose_name=_('name'),
max_length=100, max_length=100,
db_collation="natural_sort" db_collation='ci_natural_sort',
) )
status = models.CharField( status = models.CharField(
verbose_name=_('status'), verbose_name=_('status'),

View File

@@ -137,12 +137,14 @@ class RackType(RackBase):
) )
model = models.CharField( model = models.CharField(
verbose_name=_('model'), verbose_name=_('model'),
max_length=100 max_length=100,
db_collation='ci_natural_sort',
) )
slug = models.SlugField( slug = models.SlugField(
verbose_name=_('slug'), verbose_name=_('slug'),
max_length=100, max_length=100,
unique=True unique=True,
db_collation='case_insensitive',
) )
clone_fields = ( clone_fields = (
@@ -262,7 +264,7 @@ class Rack(ContactsMixin, ImageAttachmentsMixin, RackBase):
name = models.CharField( name = models.CharField(
verbose_name=_('name'), verbose_name=_('name'),
max_length=100, max_length=100,
db_collation="natural_sort" db_collation='ci_natural_sort',
) )
facility_id = models.CharField( facility_id = models.CharField(
max_length=50, max_length=50,

View File

@@ -142,13 +142,14 @@ class Site(ContactsMixin, ImageAttachmentsMixin, PrimaryModel):
verbose_name=_('name'), verbose_name=_('name'),
max_length=100, max_length=100,
unique=True, unique=True,
help_text=_("Full name of the site"), db_collation='ci_natural_sort',
db_collation="natural_sort" help_text=_("Full name of the site")
) )
slug = models.SlugField( slug = models.SlugField(
verbose_name=_('slug'), verbose_name=_('slug'),
max_length=100, max_length=100,
unique=True unique=True,
db_collation='case_insensitive',
) )
status = models.CharField( status = models.CharField(
verbose_name=_('status'), verbose_name=_('status'),

View File

@@ -0,0 +1,114 @@
import django.core.validators
import re
from django.db import migrations, models
PATTERN_OPS_INDEXES = [
'extras_configcontext_name_4bbfe25d_like',
'extras_configcontextprofile_name_070de83b_like',
'extras_customfield_name_2fe72707_like',
'extras_customfieldchoiceset_name_963e63ea_like',
'extras_customlink_name_daed2d18_like',
'extras_eventrule_name_899453c6_like',
'extras_notificationgroup_name_70b0a3f9_like',
'extras_savedfilter_name_8a4bbd09_like',
'extras_savedfilter_slug_4f93a959_like',
'extras_tag_name_9550b3d9_like',
'extras_tag_slug_aaa5b7e9_like',
'extras_webhook_name_82cf60b5_like',
]
def remove_indexes(apps, schema_editor):
for idx in PATTERN_OPS_INDEXES:
schema_editor.execute(f'DROP INDEX IF EXISTS {idx}')
class Migration(migrations.Migration):
dependencies = [
('extras', '0133_make_cf_minmax_decimal'),
('dcim', '0217_ci_collations'),
]
operations = [
migrations.RunPython(
code=remove_indexes,
reverse_code=migrations.RunPython.noop,
),
migrations.AlterField(
model_name='configcontext',
name='name',
field=models.CharField(db_collation='ci_natural_sort', max_length=100, unique=True),
),
migrations.AlterField(
model_name='configcontextprofile',
name='name',
field=models.CharField(db_collation='ci_natural_sort', max_length=100, unique=True),
),
migrations.AlterField(
model_name='customfield',
name='name',
field=models.CharField(
db_collation='ci_natural_sort',
max_length=50,
unique=True,
validators=[
django.core.validators.RegexValidator(
flags=re.RegexFlag['IGNORECASE'],
message='Only alphanumeric characters and underscores are allowed.',
regex='^[a-z0-9_]+$',
),
django.core.validators.RegexValidator(
flags=re.RegexFlag['IGNORECASE'],
inverse_match=True,
message='Double underscores are not permitted in custom field names.',
regex='__',
),
],
),
),
migrations.AlterField(
model_name='customfieldchoiceset',
name='name',
field=models.CharField(db_collation='ci_natural_sort', max_length=100, unique=True),
),
migrations.AlterField(
model_name='customlink',
name='name',
field=models.CharField(db_collation='ci_natural_sort', max_length=100, unique=True),
),
migrations.AlterField(
model_name='eventrule',
name='name',
field=models.CharField(db_collation='ci_natural_sort', max_length=150, unique=True),
),
migrations.AlterField(
model_name='notificationgroup',
name='name',
field=models.CharField(db_collation='ci_natural_sort', max_length=100, unique=True),
),
migrations.AlterField(
model_name='savedfilter',
name='name',
field=models.CharField(db_collation='ci_natural_sort', max_length=100, unique=True),
),
migrations.AlterField(
model_name='savedfilter',
name='slug',
field=models.SlugField(db_collation='case_insensitive', max_length=100, unique=True),
),
migrations.AlterField(
model_name='tag',
name='name',
field=models.CharField(db_collation='ci_natural_sort', max_length=100, unique=True),
),
migrations.AlterField(
model_name='tag',
name='slug',
field=models.SlugField(allow_unicode=True, db_collation='case_insensitive', max_length=100, unique=True),
),
migrations.AlterField(
model_name='webhook',
name='name',
field=models.CharField(db_collation='ci_natural_sort', max_length=150, unique=True),
),
]

View File

@@ -35,7 +35,8 @@ class ConfigContextProfile(SyncedDataMixin, PrimaryModel):
name = models.CharField( name = models.CharField(
verbose_name=_('name'), verbose_name=_('name'),
max_length=100, max_length=100,
unique=True unique=True,
db_collation='ci_natural_sort',
) )
description = models.CharField( description = models.CharField(
verbose_name=_('description'), verbose_name=_('description'),
@@ -77,7 +78,8 @@ class ConfigContext(SyncedDataMixin, CloningMixin, CustomLinksMixin, ChangeLogge
name = models.CharField( name = models.CharField(
verbose_name=_('name'), verbose_name=_('name'),
max_length=100, max_length=100,
unique=True unique=True,
db_collation='ci_natural_sort',
) )
profile = models.ForeignKey( profile = models.ForeignKey(
to='extras.ConfigContextProfile', to='extras.ConfigContextProfile',

View File

@@ -94,6 +94,7 @@ class CustomField(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel):
verbose_name=_('name'), verbose_name=_('name'),
max_length=50, max_length=50,
unique=True, unique=True,
db_collation='ci_natural_sort',
help_text=_('Internal field name'), help_text=_('Internal field name'),
validators=( validators=(
RegexValidator( RegexValidator(
@@ -779,7 +780,8 @@ class CustomFieldChoiceSet(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel
""" """
name = models.CharField( name = models.CharField(
max_length=100, max_length=100,
unique=True unique=True,
db_collation='ci_natural_sort',
) )
description = models.CharField( description = models.CharField(
max_length=200, max_length=200,

View File

@@ -59,7 +59,8 @@ class EventRule(CustomFieldsMixin, ExportTemplatesMixin, TagsMixin, ChangeLogged
name = models.CharField( name = models.CharField(
verbose_name=_('name'), verbose_name=_('name'),
max_length=150, max_length=150,
unique=True unique=True,
db_collation='ci_natural_sort',
) )
description = models.CharField( description = models.CharField(
verbose_name=_('description'), verbose_name=_('description'),
@@ -164,7 +165,8 @@ class Webhook(CustomFieldsMixin, ExportTemplatesMixin, TagsMixin, ChangeLoggedMo
name = models.CharField( name = models.CharField(
verbose_name=_('name'), verbose_name=_('name'),
max_length=150, max_length=150,
unique=True unique=True,
db_collation='ci_natural_sort',
) )
description = models.CharField( description = models.CharField(
verbose_name=_('description'), verbose_name=_('description'),
@@ -307,7 +309,8 @@ class CustomLink(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel):
name = models.CharField( name = models.CharField(
verbose_name=_('name'), verbose_name=_('name'),
max_length=100, max_length=100,
unique=True unique=True,
db_collation='ci_natural_sort',
) )
enabled = models.BooleanField( enabled = models.BooleanField(
verbose_name=_('enabled'), verbose_name=_('enabled'),
@@ -468,12 +471,14 @@ class SavedFilter(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel):
name = models.CharField( name = models.CharField(
verbose_name=_('name'), verbose_name=_('name'),
max_length=100, max_length=100,
unique=True unique=True,
db_collation='ci_natural_sort',
) )
slug = models.SlugField( slug = models.SlugField(
verbose_name=_('slug'), verbose_name=_('slug'),
max_length=100, max_length=100,
unique=True unique=True,
db_collation='case_insensitive',
) )
description = models.CharField( description = models.CharField(
verbose_name=_('description'), verbose_name=_('description'),

View File

@@ -125,7 +125,8 @@ class NotificationGroup(ChangeLoggedModel):
name = models.CharField( name = models.CharField(
verbose_name=_('name'), verbose_name=_('name'),
max_length=100, max_length=100,
unique=True unique=True,
db_collation='ci_natural_sort',
) )
description = models.CharField( description = models.CharField(
verbose_name=_('description'), verbose_name=_('description'),

View File

@@ -2,7 +2,7 @@ from django.conf import settings
from django.db import models from django.db import models
from django.urls import reverse from django.urls import reverse
from django.utils.text import slugify from django.utils.text import slugify
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _, pgettext_lazy
from taggit.models import TagBase, GenericTaggedItemBase from taggit.models import TagBase, GenericTaggedItemBase
from netbox.choices import ColorChoices from netbox.choices import ColorChoices
@@ -25,6 +25,21 @@ class Tag(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel, TagBase):
id = models.BigAutoField( id = models.BigAutoField(
primary_key=True primary_key=True
) )
# Override TagBase.name to set db_collation
name = models.CharField(
verbose_name=pgettext_lazy("A tag name", "name"),
unique=True,
max_length=100,
db_collation='ci_natural_sort',
)
# Override TagBase.slug to set db_collation
slug = models.SlugField(
verbose_name=pgettext_lazy("A tag slug", "slug"),
unique=True,
max_length=100,
allow_unicode=True,
db_collation='case_insensitive',
)
color = ColorField( color = ColorField(
verbose_name=_('color'), verbose_name=_('color'),
default=ColorChoices.COLOR_GREY default=ColorChoices.COLOR_GREY

View File

@@ -0,0 +1,100 @@
from django.db import migrations, models
PATTERN_OPS_INDEXES = [
'ipam_asnrange_name_c7585e73_like',
'ipam_asnrange_slug_c8a7d8a1_like',
'ipam_rir_name_64a71982_like',
'ipam_rir_slug_ff1a369a_like',
'ipam_role_name_13784849_like',
'ipam_role_slug_309ca14c_like',
'ipam_routetarget_name_212be79f_like',
'ipam_servicetemplate_name_1a2f3410_like',
'ipam_vlangroup_slug_40abcf6b_like',
'ipam_vlantranslationpolicy_name_17e0a007_like',
'ipam_vrf_rd_0ac1bde1_like',
]
def remove_indexes(apps, schema_editor):
for idx in PATTERN_OPS_INDEXES:
schema_editor.execute(f'DROP INDEX IF EXISTS {idx}')
class Migration(migrations.Migration):
dependencies = [
('ipam', '0082_add_prefix_network_containment_indexes'),
('dcim', '0217_ci_collations'),
]
operations = [
migrations.RunPython(
code=remove_indexes,
reverse_code=migrations.RunPython.noop,
),
migrations.AlterField(
model_name='asnrange',
name='name',
field=models.CharField(db_collation='ci_natural_sort', max_length=100, unique=True),
),
migrations.AlterField(
model_name='asnrange',
name='slug',
field=models.SlugField(db_collation='case_insensitive', max_length=100, unique=True),
),
migrations.AlterField(
model_name='rir',
name='name',
field=models.CharField(db_collation='ci_natural_sort', max_length=100, unique=True),
),
migrations.AlterField(
model_name='rir',
name='slug',
field=models.SlugField(db_collation='case_insensitive', max_length=100, unique=True),
),
migrations.AlterField(
model_name='role',
name='name',
field=models.CharField(db_collation='ci_natural_sort', max_length=100, unique=True),
),
migrations.AlterField(
model_name='role',
name='slug',
field=models.SlugField(db_collation='case_insensitive', max_length=100, unique=True),
),
migrations.AlterField(
model_name='routetarget',
name='name',
field=models.CharField(db_collation='ci_natural_sort', max_length=21, unique=True),
),
migrations.AlterField(
model_name='servicetemplate',
name='name',
field=models.CharField(db_collation='ci_natural_sort', max_length=100, unique=True),
),
migrations.AlterField(
model_name='vlan',
name='name',
field=models.CharField(db_collation='ci_natural_sort', max_length=64),
),
migrations.AlterField(
model_name='vlangroup',
name='name',
field=models.CharField(db_collation='ci_natural_sort', max_length=100),
),
migrations.AlterField(
model_name='vlangroup',
name='slug',
field=models.SlugField(db_collation='case_insensitive', max_length=100),
),
migrations.AlterField(
model_name='vlantranslationpolicy',
name='name',
field=models.CharField(db_collation='ci_natural_sort', max_length=100, unique=True),
),
migrations.AlterField(
model_name='vrf',
name='rd',
field=models.CharField(blank=True, db_collation='case_insensitive', max_length=21, null=True, unique=True),
),
]

View File

@@ -18,12 +18,7 @@ class ASNRange(OrganizationalModel):
verbose_name=_('name'), verbose_name=_('name'),
max_length=100, max_length=100,
unique=True, unique=True,
db_collation="natural_sort" db_collation='ci_natural_sort',
)
slug = models.SlugField(
verbose_name=_('slug'),
max_length=100,
unique=True
) )
rir = models.ForeignKey( rir = models.ForeignKey(
to='ipam.RIR', to='ipam.RIR',

View File

@@ -50,7 +50,8 @@ class ServiceTemplate(ServiceBase, PrimaryModel):
name = models.CharField( name = models.CharField(
verbose_name=_('name'), verbose_name=_('name'),
max_length=100, max_length=100,
unique=True unique=True,
db_collation='ci_natural_sort',
) )
class Meta: class Meta:

View File

@@ -37,11 +37,12 @@ class VLANGroup(OrganizationalModel):
name = models.CharField( name = models.CharField(
verbose_name=_('name'), verbose_name=_('name'),
max_length=100, max_length=100,
db_collation="natural_sort" db_collation='ci_natural_sort',
) )
slug = models.SlugField( slug = models.SlugField(
verbose_name=_('slug'), verbose_name=_('slug'),
max_length=100 max_length=100,
db_collation='case_insensitive',
) )
scope_type = models.ForeignKey( scope_type = models.ForeignKey(
to='contenttypes.ContentType', to='contenttypes.ContentType',
@@ -214,7 +215,8 @@ class VLAN(PrimaryModel):
) )
name = models.CharField( name = models.CharField(
verbose_name=_('name'), verbose_name=_('name'),
max_length=64 max_length=64,
db_collation='ci_natural_sort',
) )
tenant = models.ForeignKey( tenant = models.ForeignKey(
to='tenancy.Tenant', to='tenancy.Tenant',
@@ -362,6 +364,7 @@ class VLANTranslationPolicy(PrimaryModel):
verbose_name=_('name'), verbose_name=_('name'),
max_length=100, max_length=100,
unique=True, unique=True,
db_collation='ci_natural_sort',
) )
class Meta: class Meta:

View File

@@ -19,11 +19,12 @@ class VRF(PrimaryModel):
name = models.CharField( name = models.CharField(
verbose_name=_('name'), verbose_name=_('name'),
max_length=100, max_length=100,
db_collation="natural_sort" db_collation='natural_sort',
) )
rd = models.CharField( rd = models.CharField(
max_length=VRF_RD_MAX_LENGTH, max_length=VRF_RD_MAX_LENGTH,
unique=True, unique=True,
db_collation='case_insensitive',
blank=True, blank=True,
null=True, null=True,
verbose_name=_('route distinguisher'), verbose_name=_('route distinguisher'),
@@ -75,8 +76,8 @@ class RouteTarget(PrimaryModel):
verbose_name=_('name'), verbose_name=_('name'),
max_length=VRF_RD_MAX_LENGTH, # Same format options as VRF RD (RFC 4360 section 4) max_length=VRF_RD_MAX_LENGTH, # Same format options as VRF RD (RFC 4360 section 4)
unique=True, unique=True,
db_collation='ci_natural_sort',
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( tenant = models.ForeignKey(
to='tenancy.Tenant', to='tenancy.Tenant',

View File

@@ -153,11 +153,13 @@ class NestedGroupModel(NetBoxFeatureSet, MPTTModel):
) )
name = models.CharField( name = models.CharField(
verbose_name=_('name'), verbose_name=_('name'),
max_length=100 max_length=100,
db_collation='ci_natural_sort',
) )
slug = models.SlugField( slug = models.SlugField(
verbose_name=_('slug'), verbose_name=_('slug'),
max_length=100 max_length=100,
db_collation='case_insensitive',
) )
description = models.CharField( description = models.CharField(
verbose_name=_('description'), verbose_name=_('description'),
@@ -202,12 +204,14 @@ class OrganizationalModel(NetBoxModel):
name = models.CharField( name = models.CharField(
verbose_name=_('name'), verbose_name=_('name'),
max_length=100, max_length=100,
unique=True unique=True,
db_collation='ci_natural_sort',
) )
slug = models.SlugField( slug = models.SlugField(
verbose_name=_('slug'), verbose_name=_('slug'),
max_length=100, max_length=100,
unique=True unique=True,
db_collation='case_insensitive',
) )
description = models.CharField( description = models.CharField(
verbose_name=_('description'), verbose_name=_('description'),

View File

@@ -0,0 +1,70 @@
from django.db import migrations, models
PATTERN_OPS_INDEXES = [
'tenancy_contactgroup_slug_5b0f3e75_like',
'tenancy_contactrole_name_44b01a1f_like',
'tenancy_contactrole_slug_c5837d7d_like',
'tenancy_tenant_slug_0716575e_like',
'tenancy_tenantgroup_name_53363199_like',
'tenancy_tenantgroup_slug_e2af1cb6_like',
]
def remove_indexes(apps, schema_editor):
for idx in PATTERN_OPS_INDEXES:
schema_editor.execute(f'DROP INDEX IF EXISTS {idx}')
class Migration(migrations.Migration):
dependencies = [
('tenancy', '0020_remove_contactgroupmembership'),
('dcim', '0217_ci_collations'),
]
operations = [
migrations.RunPython(
code=remove_indexes,
reverse_code=migrations.RunPython.noop,
),
migrations.AlterField(
model_name='contactgroup',
name='name',
field=models.CharField(db_collation='ci_natural_sort', max_length=100),
),
migrations.AlterField(
model_name='contactgroup',
name='slug',
field=models.SlugField(db_collation='case_insensitive', max_length=100),
),
migrations.AlterField(
model_name='contactrole',
name='name',
field=models.CharField(db_collation='ci_natural_sort', max_length=100, unique=True),
),
migrations.AlterField(
model_name='contactrole',
name='slug',
field=models.SlugField(db_collation='case_insensitive', max_length=100, unique=True),
),
migrations.AlterField(
model_name='tenant',
name='name',
field=models.CharField(db_collation='ci_natural_sort', max_length=100),
),
migrations.AlterField(
model_name='tenant',
name='slug',
field=models.SlugField(db_collation='case_insensitive', max_length=100),
),
migrations.AlterField(
model_name='tenantgroup',
name='name',
field=models.CharField(db_collation='ci_natural_sort', max_length=100, unique=True),
),
migrations.AlterField(
model_name='tenantgroup',
name='slug',
field=models.SlugField(db_collation='case_insensitive', max_length=100, unique=True),
),
]

View File

@@ -55,7 +55,7 @@ class Contact(PrimaryModel):
name = models.CharField( name = models.CharField(
verbose_name=_('name'), verbose_name=_('name'),
max_length=100, max_length=100,
db_collation="natural_sort" db_collation='natural_sort',
) )
title = models.CharField( title = models.CharField(
verbose_name=_('title'), verbose_name=_('title'),

View File

@@ -19,12 +19,13 @@ class TenantGroup(NestedGroupModel):
verbose_name=_('name'), verbose_name=_('name'),
max_length=100, max_length=100,
unique=True, unique=True,
db_collation="natural_sort" db_collation='ci_natural_sort'
) )
slug = models.SlugField( slug = models.SlugField(
verbose_name=_('slug'), verbose_name=_('slug'),
max_length=100, max_length=100,
unique=True unique=True,
db_collation='case_insensitive'
) )
class Meta: class Meta:
@@ -41,11 +42,12 @@ class Tenant(ContactsMixin, PrimaryModel):
name = models.CharField( name = models.CharField(
verbose_name=_('name'), verbose_name=_('name'),
max_length=100, max_length=100,
db_collation="natural_sort" db_collation='ci_natural_sort',
) )
slug = models.SlugField( slug = models.SlugField(
verbose_name=_('slug'), verbose_name=_('slug'),
max_length=100 max_length=100,
db_collation='case_insensitive',
) )
group = models.ForeignKey( group = models.ForeignKey(
to='tenancy.TenantGroup', to='tenancy.TenantGroup',

View File

@@ -0,0 +1,92 @@
from django.db import migrations, models
PATTERN_OPS_INDEXES = [
'virtualization_clustergroup_name_4fcd26b4_like',
'virtualization_clustergroup_slug_57ca1d23_like',
'virtualization_clustertype_name_ea854d3d_like',
'virtualization_clustertype_slug_8ee4d0e0_like',
]
def remove_indexes(apps, schema_editor):
for idx in PATTERN_OPS_INDEXES:
schema_editor.execute(f'DROP INDEX IF EXISTS {idx}')
class Migration(migrations.Migration):
dependencies = [
('dcim', '0217_ci_collations'),
('extras', '0134_ci_collations'),
('ipam', '0083_ci_collations'),
('tenancy', '0021_ci_collations'),
('virtualization', '0048_populate_mac_addresses'),
]
operations = [
migrations.RunPython(
code=remove_indexes,
reverse_code=migrations.RunPython.noop,
),
migrations.RemoveConstraint(
model_name='virtualmachine',
name='virtualization_virtualmachine_unique_name_cluster_tenant',
),
migrations.RemoveConstraint(
model_name='virtualmachine',
name='virtualization_virtualmachine_unique_name_cluster',
),
migrations.AlterField(
model_name='cluster',
name='name',
field=models.CharField(db_collation='ci_natural_sort', max_length=100),
),
migrations.AlterField(
model_name='clustergroup',
name='name',
field=models.CharField(db_collation='ci_natural_sort', max_length=100, unique=True),
),
migrations.AlterField(
model_name='clustergroup',
name='slug',
field=models.SlugField(db_collation='case_insensitive', max_length=100, unique=True),
),
migrations.AlterField(
model_name='clustertype',
name='name',
field=models.CharField(db_collation='ci_natural_sort', max_length=100, unique=True),
),
migrations.AlterField(
model_name='clustertype',
name='slug',
field=models.SlugField(db_collation='case_insensitive', max_length=100, unique=True),
),
migrations.AlterField(
model_name='virtualdisk',
name='name',
field=models.CharField(db_collation='ci_natural_sort', max_length=64),
),
migrations.AlterField(
model_name='virtualmachine',
name='name',
field=models.CharField(db_collation='ci_natural_sort', max_length=64),
),
migrations.AddConstraint(
model_name='virtualmachine',
constraint=models.UniqueConstraint(
models.F('name'),
models.F('cluster'),
models.F('tenant'),
name='virtualization_virtualmachine_unique_name_cluster_tenant',
),
),
migrations.AddConstraint(
model_name='virtualmachine',
constraint=models.UniqueConstraint(
models.F('name'),
models.F('cluster'),
condition=models.Q(('tenant__isnull', True)),
name='virtualization_virtualmachine_unique_name_cluster',
violation_error_message='Virtual machine name must be unique per cluster.',
),
),
]

View File

@@ -51,7 +51,7 @@ class Cluster(ContactsMixin, CachedScopeMixin, PrimaryModel):
name = models.CharField( name = models.CharField(
verbose_name=_('name'), verbose_name=_('name'),
max_length=100, max_length=100,
db_collation="natural_sort" db_collation='ci_natural_sort',
) )
type = models.ForeignKey( type = models.ForeignKey(
verbose_name=_('type'), verbose_name=_('type'),

View File

@@ -5,7 +5,6 @@ from django.core.exceptions import ValidationError
from django.core.validators import MinValueValidator from django.core.validators import MinValueValidator
from django.db import models from django.db import models
from django.db.models import Q, Sum from django.db.models import Q, Sum
from django.db.models.functions import Lower
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from dcim.models import BaseInterface from dcim.models import BaseInterface
@@ -70,7 +69,7 @@ class VirtualMachine(ContactsMixin, ImageAttachmentsMixin, RenderConfigMixin, Co
name = models.CharField( name = models.CharField(
verbose_name=_('name'), verbose_name=_('name'),
max_length=64, max_length=64,
db_collation="natural_sort" db_collation='ci_natural_sort',
) )
status = models.CharField( status = models.CharField(
max_length=50, max_length=50,
@@ -156,11 +155,11 @@ class VirtualMachine(ContactsMixin, ImageAttachmentsMixin, RenderConfigMixin, Co
ordering = ('name', 'pk') # Name may be non-unique ordering = ('name', 'pk') # Name may be non-unique
constraints = ( constraints = (
models.UniqueConstraint( models.UniqueConstraint(
Lower('name'), 'cluster', 'tenant', 'name', 'cluster', 'tenant',
name='%(app_label)s_%(class)s_unique_name_cluster_tenant' name='%(app_label)s_%(class)s_unique_name_cluster_tenant'
), ),
models.UniqueConstraint( models.UniqueConstraint(
Lower('name'), 'cluster', 'name', 'cluster',
name='%(app_label)s_%(class)s_unique_name_cluster', name='%(app_label)s_%(class)s_unique_name_cluster',
condition=Q(tenant__isnull=True), condition=Q(tenant__isnull=True),
violation_error_message=_("Virtual machine name must be unique per cluster.") violation_error_message=_("Virtual machine name must be unique per cluster.")
@@ -275,7 +274,7 @@ class ComponentModel(NetBoxModel):
name = models.CharField( name = models.CharField(
verbose_name=_('name'), verbose_name=_('name'),
max_length=64, max_length=64,
db_collation="natural_sort" db_collation='ci_natural_sort',
) )
description = models.CharField( description = models.CharField(
verbose_name=_('description'), verbose_name=_('description'),

View File

@@ -0,0 +1,84 @@
from django.db import migrations, models
PATTERN_OPS_INDEXES = [
'vpn_ikepolicy_name_5124aa3b_like',
'vpn_ikeproposal_name_254623b7_like',
'vpn_ipsecpolicy_name_cf28a1aa_like',
'vpn_ipsecprofile_name_3ac63c72_like',
'vpn_ipsecproposal_name_2fb98e2b_like',
'vpn_l2vpn_name_8824eda5_like',
'vpn_l2vpn_slug_76b5a174_like',
'vpn_tunnel_name_f060beab_like',
'vpn_tunnelgroup_name_9f6ebf92_like',
'vpn_tunnelgroup_slug_9e614d62_like',
]
def remove_indexes(apps, schema_editor):
for idx in PATTERN_OPS_INDEXES:
schema_editor.execute(f'DROP INDEX IF EXISTS {idx}')
class Migration(migrations.Migration):
dependencies = [
('dcim', '0217_ci_collations'),
('vpn', '0009_remove_redundant_indexes'),
]
operations = [
migrations.RunPython(
code=remove_indexes,
reverse_code=migrations.RunPython.noop,
),
migrations.AlterField(
model_name='ikepolicy',
name='name',
field=models.CharField(db_collation='ci_natural_sort', max_length=100, unique=True),
),
migrations.AlterField(
model_name='ikeproposal',
name='name',
field=models.CharField(db_collation='ci_natural_sort', max_length=100, unique=True),
),
migrations.AlterField(
model_name='ipsecpolicy',
name='name',
field=models.CharField(db_collation='ci_natural_sort', max_length=100, unique=True),
),
migrations.AlterField(
model_name='ipsecprofile',
name='name',
field=models.CharField(db_collation='ci_natural_sort', max_length=100, unique=True),
),
migrations.AlterField(
model_name='ipsecproposal',
name='name',
field=models.CharField(db_collation='ci_natural_sort', max_length=100, unique=True),
),
migrations.AlterField(
model_name='l2vpn',
name='name',
field=models.CharField(db_collation='ci_natural_sort', max_length=100, unique=True),
),
migrations.AlterField(
model_name='l2vpn',
name='slug',
field=models.SlugField(db_collation='case_insensitive', max_length=100, unique=True),
),
migrations.AlterField(
model_name='tunnel',
name='name',
field=models.CharField(db_collation='ci_natural_sort', max_length=100, unique=True),
),
migrations.AlterField(
model_name='tunnelgroup',
name='name',
field=models.CharField(db_collation='ci_natural_sort', max_length=100, unique=True),
),
migrations.AlterField(
model_name='tunnelgroup',
name='slug',
field=models.SlugField(db_collation='case_insensitive', max_length=100, unique=True),
),
]

View File

@@ -23,7 +23,7 @@ class IKEProposal(PrimaryModel):
verbose_name=_('name'), verbose_name=_('name'),
max_length=100, max_length=100,
unique=True, unique=True,
db_collation="natural_sort" db_collation='ci_natural_sort',
) )
authentication_method = models.CharField( authentication_method = models.CharField(
verbose_name=('authentication method'), verbose_name=('authentication method'),
@@ -69,7 +69,7 @@ class IKEPolicy(PrimaryModel):
verbose_name=_('name'), verbose_name=_('name'),
max_length=100, max_length=100,
unique=True, unique=True,
db_collation="natural_sort" db_collation='ci_natural_sort',
) )
version = models.PositiveSmallIntegerField( version = models.PositiveSmallIntegerField(
verbose_name=_('version'), verbose_name=_('version'),
@@ -128,7 +128,7 @@ class IPSecProposal(PrimaryModel):
verbose_name=_('name'), verbose_name=_('name'),
max_length=100, max_length=100,
unique=True, unique=True,
db_collation="natural_sort" db_collation='ci_natural_sort',
) )
encryption_algorithm = models.CharField( encryption_algorithm = models.CharField(
verbose_name=_('encryption'), verbose_name=_('encryption'),
@@ -180,7 +180,7 @@ class IPSecPolicy(PrimaryModel):
verbose_name=_('name'), verbose_name=_('name'),
max_length=100, max_length=100,
unique=True, unique=True,
db_collation="natural_sort" db_collation='ci_natural_sort',
) )
proposals = models.ManyToManyField( proposals = models.ManyToManyField(
to='vpn.IPSecProposal', to='vpn.IPSecProposal',
@@ -216,7 +216,7 @@ class IPSecProfile(PrimaryModel):
verbose_name=_('name'), verbose_name=_('name'),
max_length=100, max_length=100,
unique=True, unique=True,
db_collation="natural_sort" db_collation='ci_natural_sort',
) )
mode = models.CharField( mode = models.CharField(
verbose_name=_('mode'), verbose_name=_('mode'),

View File

@@ -20,12 +20,13 @@ class L2VPN(ContactsMixin, PrimaryModel):
verbose_name=_('name'), verbose_name=_('name'),
max_length=100, max_length=100,
unique=True, unique=True,
db_collation="natural_sort" db_collation='ci_natural_sort',
) )
slug = models.SlugField( slug = models.SlugField(
verbose_name=_('slug'), verbose_name=_('slug'),
max_length=100, max_length=100,
unique=True unique=True,
db_collation='case_insensitive',
) )
type = models.CharField( type = models.CharField(
verbose_name=_('type'), verbose_name=_('type'),

View File

@@ -32,7 +32,7 @@ class Tunnel(ContactsMixin, PrimaryModel):
verbose_name=_('name'), verbose_name=_('name'),
max_length=100, max_length=100,
unique=True, unique=True,
db_collation="natural_sort" db_collation='ci_natural_sort',
) )
status = models.CharField( status = models.CharField(
verbose_name=_('status'), verbose_name=_('status'),

View File

@@ -0,0 +1,36 @@
from django.db import migrations, models
PATTERN_OPS_INDEXES = [
'wireless_wirelesslangroup_name_2ffd60c8_like',
'wireless_wirelesslangroup_slug_f5d59831_like',
]
def remove_indexes(apps, schema_editor):
for idx in PATTERN_OPS_INDEXES:
schema_editor.execute(f'DROP INDEX IF EXISTS {idx}')
class Migration(migrations.Migration):
dependencies = [
('dcim', '0217_ci_collations'),
('wireless', '0015_extend_wireless_link_abs_distance_upper_limit'),
]
operations = [
migrations.RunPython(
code=remove_indexes,
reverse_code=migrations.RunPython.noop,
),
migrations.AlterField(
model_name='wirelesslangroup',
name='name',
field=models.CharField(db_collation='ci_natural_sort', max_length=100, unique=True),
),
migrations.AlterField(
model_name='wirelesslangroup',
name='slug',
field=models.SlugField(db_collation='case_insensitive', max_length=100, unique=True),
),
]

View File

@@ -53,12 +53,13 @@ class WirelessLANGroup(NestedGroupModel):
verbose_name=_('name'), verbose_name=_('name'),
max_length=100, max_length=100,
unique=True, unique=True,
db_collation="natural_sort" db_collation='ci_natural_sort',
) )
slug = models.SlugField( slug = models.SlugField(
verbose_name=_('slug'), verbose_name=_('slug'),
max_length=100, max_length=100,
unique=True unique=True,
db_collation='case_insensitive',
) )
class Meta: class Meta: