为Django模型生成非顺序ID / PK

时间:2010-09-21 09:37:16

标签: django url django-models primary-key

我正在开始研究新的webapp。部分内容将为用户提供可以在一对多关系中自定义的页面。这些页面自然需要有唯一的URL。

Django留给自己的设备,通常会为模型分配一个标准的AUTOINCREMENT ID。虽然这很有效,但它看起来并不好看,它也使得页面非常容易预测(在这种情况下这是不可取的)。

而不是1,2,3,4我想要设定长度,随机生成的字母数字字符串(例如h2esj4)。可能的36个字符的6个点应该给我超过20亿个组合,在这个阶段应该绰绰有余。当然,如果我可以在以后扩展它,那也会很好。

但有两个问题:

  1. 随机字符串偶尔会拼出坏词或其他令人反感的词组。有没有一个体面的方式回避这个?为了公平起见,我可能会接受一个数字字符串,但它确实会对冲突的可能性产生重大影响。

  2. 如何让Django(或数据库)在插入时进行繁重的工作?我宁愿不插入而然后计算出密钥(因为这不是一个关键)。我假设有并发问题也要注意,如果同时生成两个新页面,而第二个(克服所有可能性)神奇地获得与第一个提交之前的第一个相同的密钥。

  3. 我不认为这与URL缩短器生成ID的方式有一百万英里的差异。如果有一个体面的Django实现,我可以捎带它。

7 个答案:

答案 0 :(得分:22)

有内置的Django方式来实现你想要的。使用primary_key=Truedefault=密钥生成函数名称向“自定义页面”模型添加字段,如下所示:

class CustomPage(models.Model):
    ...
    mykey = models.CharField(max_length=6, primary_key=True, default=pkgen)
    ...

现在,对于每个模型实例pagepage.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()足够随机),并且没有并发问题。而且,通过从编码字符串中切割更多字符,这种方法很容易扩展。

答案 1 :(得分:9)

这就是我最终做的事情。我做了一个抽象的模型。我的用例是需要几个模型来生成自己的随机slu ..

一个slug看起来像AA##AA,因此52x52x10x10x52x52 = 731,161,600组合。可能是我需要的一千倍以上,如果这是一个问题,我可以添加一个52倍以上组合的字母。

使用default参数不会削减它,因为抽象模型需要检查孩子上的slug碰撞。继承是最简单的,可能只是这样做的方式。

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:
            ret = []
            ret.extend(random.sample(string.letters, 2))
            ret.extend(random.sample(string.digits, 2))
            ret.extend(random.sample(string.letters, 2))

            newslug = ''.join(ret)
            if self.objects.filter(pk=newslug).count():
                self.slug = newslug

        super(SluggedModel, self).save(*args, **kwargs)

    class Meta:
        abstract = True

答案 2 :(得分:4)

可能需要查看Python UUID,它可以生成随机冗长的字符。但是你可以将它切片并使用你需要的字符数量进行少量检查,以确保即使在切片之后它也是唯一的。

如果您不想自己生成UUID,那么

UUIDField代码段可能会对您有所帮助。

另请查看此blog post

答案 3 :(得分:3)

Django现在包含UUIDField type,因此您不需要任何自定义代码或Srikanth Chundi建议的外部包。这个实现使用带有破折号的HEX字符串,因此除了像abad1d3a这样的1337表达式之外,文本还是儿童安全的:)

您可以像这样使用它将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 中路由到此视图时,您需要一个不同的正则表达式mentioned here,例如:

urlpatterns = [
    url(r'mymodel/(?P<pk>[^/]+)/$', MyModelDetailView.as_view(),
        name='mymodel'),
]

答案 4 :(得分:1)

Oli:如果你担心拼写粗话,你可以随时使用django亵渎过滤器比较/搜索你的UUIDField,并跳过任何可能触发的UUID。

答案 5 :(得分:1)

看看上面的答案,这就是我现在正在使用的东西。

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):
    ...

答案 6 :(得分:0)

这就是我最终使用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