Подтвердить что ты не робот

Создание непоследовательного ID/PK для модели Django

Я нахожусь на пороге начала работы над новым webapp. Часть этого даст пользователям страницы, которые они могут настроить в отношениях от одного до многих. Эти страницы, естественно, должны иметь уникальные URL-адреса.

Оставаясь на своих устройствах, Django обычно присваивает стандартную идентификатор AUTOINCREMENT модели. Хотя это работает фантастически, оно не выглядит великолепно, и оно также делает страницы очень предсказуемыми (что в этом случае нежелательно).

Вместо 1, 2, 3, 4 я хотел бы задать длины, произвольно сгенерированные буквенно-цифровые строки (например, h2esj4). 6 пятен возможного набора из 36 символов должны дать мне более двух миллиардов комбинаций, которые на этом этапе должны быть более чем достаточно. Конечно, если бы я мог расширить это позже, это тоже хорошо.

Но есть две проблемы:

  • Случайные строки изредка излагают плохие слова или другие оскорбительные фразы. Есть ли достойный способ обойти это? Чтобы быть справедливым, я, вероятно, мог бы согласиться на числовую строку, но у нее есть здоровенный удар по вероятности столкновений.

  • Как мне заставить Django (или базу данных) выполнять тяжелую работу над вставкой? Я бы предпочел не вставлять и затем выработать ключ (поскольку это не было бы большим количеством ключа). Я предполагаю, что есть проблемы с concurrency, которые должны быть в курсе, хотя если бы две новые страницы были сгенерированы в одно и то же время, а вторая (против всех разногласий) волшебным образом получила тот же ключ, что и первый, прежде чем было совершено первое.

Я не вижу, что это миллион миль отличается от того, как сокращают URL-адреса своих идентификаторов. Если есть достойная реализация Django, я мог бы с этим справиться.

4b9b3361

Ответ 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):
    ...