Use case-insensitive collations on fields considered for uniqueness

This commit is contained in:
Jeremy Stretch 2025-10-24 14:12:59 -04:00
parent dac0a06f4f
commit 06052f8eaa
29 changed files with 123 additions and 81 deletions

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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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: