Создание непоследовательного ID/PK для модели Django
Я нахожусь на пороге начала работы над новым webapp. Часть этого даст пользователям страницы, которые они могут настроить в отношениях от одного до многих. Эти страницы, естественно, должны иметь уникальные URL-адреса.
Оставаясь на своих устройствах, Django обычно присваивает стандартную идентификатор AUTOINCREMENT
модели. Хотя это работает фантастически, оно не выглядит великолепно, и оно также делает страницы очень предсказуемыми (что в этом случае нежелательно).
Вместо 1, 2, 3, 4 я хотел бы задать длины, произвольно сгенерированные буквенно-цифровые строки (например, h2esj4). 6 пятен возможного набора из 36 символов должны дать мне более двух миллиардов комбинаций, которые на этом этапе должны быть более чем достаточно. Конечно, если бы я мог расширить это позже, это тоже хорошо.
Но есть две проблемы:
-
Случайные строки изредка излагают плохие слова или другие оскорбительные фразы. Есть ли достойный способ обойти это? Чтобы быть справедливым, я, вероятно, мог бы согласиться на числовую строку, но у нее есть здоровенный удар по вероятности столкновений.
-
Как мне заставить Django (или базу данных) выполнять тяжелую работу над вставкой? Я бы предпочел не вставлять и затем выработать ключ (поскольку это не было бы большим количеством ключа). Я предполагаю, что есть проблемы с concurrency, которые должны быть в курсе, хотя если бы две новые страницы были сгенерированы в одно и то же время, а вторая (против всех разногласий) волшебным образом получила тот же ключ, что и первый, прежде чем было совершено первое.
Я не вижу, что это миллион миль отличается от того, как сокращают URL-адреса своих идентификаторов. Если есть достойная реализация Django, я мог бы с этим справиться.
Ответы
Ответ 1
Вот что я в итоге сделал. Я сделал абстрактную модель. Мой вариант использования для этого требует нескольких моделей, которые генерируют свои собственные, случайные слизни.
Слаг похож на AA##AA
так что 52x52x10x10x52x52 = 731,161,600
комбинаций. Возможно, в тысячу раз больше, чем мне нужно, и если это когда-нибудь станет проблемой, я могу добавить письмо в 52 раза больше комбинаций.
Использование аргумента по default
не приведет к его сокращению, поскольку абстрактная модель должна проверять наличие столкновений с порциями на дочернем элементе. Наследование было самым простым, возможно, единственным способом сделать это.
from django.db import models
from django.contrib.auth.models import User
import string, random
class SluggedModel(models.Model):
slug = models.SlugField(primary_key=True, unique=True, editable=False, blank=True)
def save(self, *args, **kwargs):
while not self.slug:
newslug = ''.join([
random.sample(string.letters, 2),
random.sample(string.digits, 2),
random.sample(string.letters, 2),
])
if not self.objects.filter(pk=newslug).exists():
self.slug = newslug
super().save(*args, **kwargs)
class Meta:
abstract = True
Ответ 2
Существует встроенный способ Django для достижения желаемого. Добавьте поле в модель "пользовательской страницы" с primary_key=True
и default=
именем функции генерации ключей, например:
class CustomPage(models.Model):
...
mykey = models.CharField(max_length=6, primary_key=True, default=pkgen)
...
Теперь для каждого экземпляра модели page
page.pk
становится псевдонимом для page.mykey
, который автоматически присваивается строкой, возвращаемой вашей функцией pkgen()
в момент создания этого экземпляра. < ш > Быстрая и эффективная реализация:
def pkgen():
from base64 import b32encode
from hashlib import sha1
from random import random
rude = ('lol',)
bad_pk = True
while bad_pk:
pk = b32encode(sha1(str(random())).digest()).lower()[:6]
bad_pk = False
for rw in rude:
if pk.find(rw) >= 0: bad_pk = True
return pk
Вероятность того, что две страницы получают одинаковые первичные ключи, очень низкая (при условии, что random()
является случайным), и проблем с concurrency нет. И, couse, этот метод легко расширяется, нарезая больше символов из закодированной строки.
Ответ 3
Возможно, вам нужно посмотреть Python UUID, он может генерировать случайные длинные символы. Но вы можете нарезать его и использовать количество символов, которое вы хотите, с небольшой проверкой, чтобы убедиться, что он уникален даже после нарезки.
фрагмент UUIDField может помочь вам, если вы не хотите причинять боль при генерации UUID самостоятельно.
Также обратите внимание на это сообщение
Ответ 4
Django теперь включает тип UUIDField, поэтому вам не нужен какой-либо пользовательский код или внешний пакет, предложенный Шрикантом Чунди. Эта реализация использует строки HEX с тире, поэтому текст довольно безопасен для детей, кроме 1337 выражений, таких как abad1d3a:)
Вы использовали бы его как псевдоним pk
в поле uuid
в качестве первичного ключа:
import uuid
from django.db import models
class MyModel(models.Model):
uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
# other fields
Обратите внимание, однако, что когда вы направляетесь к этому представлению в urls.py, вам нужно другое regex как упомянутое здесь, например:
urlpatterns = [
url(r'mymodel/(?P<pk>[^/]+)/$', MyModelDetailView.as_view(),
name='mymodel'),
]
Ответ 5
Оли: Если вы беспокоитесь о том, чтобы писать грубые слова, вы всегда можете сравнить/найти свой UUIDField для них, используя фильтр django profanity и пропустить любые UUID, которые могут быть триггерами.
Ответ 6
Вот что я в итоге использовал UUID.
import uuid
from django.db import models
from django.contrib.auth.models import User
class SluggedModel(models.Model):
slug = models.SlugField(primary_key=True, unique=True, editable=False, blank=True)
def save(self, *args, **kwargs):
if not self.slug:
uuid.uuid4().hex[:16] # can vary up to 32 chars in length
super(SluggedModel, self).save(*args, **kwargs)
class Meta:
abstract = True
Ответ 7
Глядя на приведенные выше ответы, вот что я использую сейчас.
import uuid
from django.db import models
from django.utils.http import int_to_base36
ID_LENGTH = 9
def id_gen() -> str:
"""Generates random string whose length is 'ID_LENGTH'"""
return int_to_base36(uuid.uuid4().int)[:ID_LENGTH]
class BaseModel(models.Model):
"""Django abstract model whose primary key is a random string"""
id = models.CharField(max_length=ID_LENGTH, primary_key=True, default=id_gen, editable=False)
class Meta:
abstract = True
class CustomPage(BaseModel):
...