Генерация непоследовательного ID / PK для модели Django - PullRequest
29 голосов
/ 21 сентября 2010

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

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

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

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

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

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

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

Ответы [ 7 ]

22 голосов
/ 28 сентября 2010

Есть встроенный в 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() является достаточно случайной), и нет проблем с параллелизмом. И, конечно же, этот метод легко расширяем, выделяя больше символов из кодированной строки.

8 голосов
/ 30 сентября 2010

Вот что я в итоге сделал. Я сделал абстрактную модель. Мой вариант использования для этого требует нескольких моделей, которые генерируют свои собственные, случайные слагы.

Слизень выглядит как 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
4 голосов
/ 27 сентября 2010

Может быть, вам нужно взглянуть на Python UUID , он может генерировать случайные длинные символы.Но вы можете нарезать его и использовать нужное количество символов с небольшой проверкой, чтобы убедиться, что он уникален даже после нарезки.Фрагмент

UUIDField может помочь вам, если вы не хотите самостоятельно создавать UUID.

Также посмотрите это сообщение в блоге

3 голосов
/ 21 марта 2017

Django теперь включает UUIDField типа , поэтому вам не нужен какой-либо пользовательский код или внешний пакет, предложенный Srikanth Chundi. В этой реализации используются строки 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 вам нужно другое регулярное выражение, например , упомянутое здесь , например ::

urlpatterns = [
    url(r'mymodel/(?P<pk>[^/]+)/$', MyModelDetailView.as_view(),
        name='mymodel'),
]
1 голос
/ 27 октября 2018

Глядя на приведенные выше ответы, вот что я использую сейчас.

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):
    ...
1 голос
/ 17 июля 2016

Это то, что я в итоге использовал 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
1 голос
/ 01 октября 2010

Оли: Если вы беспокоитесь о том, чтобы произносить грубые слова, вы всегда можете сравнить / найти их в UUIDField, используя фильтр ненормативной лексики django, и пропустить все UUID, которые могут быть триггерами.

...