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

Oli*_*Oli 29 django url primary-key django-models

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

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

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

但有两个问题:

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

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

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

ato*_*zer 22

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

class CustomPage(models.Model):
    ...
    mykey = models.CharField(max_length=6, primary_key=True, default=pkgen)
    ...
Run Code Online (Sandbox Code Playgroud)

现在,对于每个模型实例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
Run Code Online (Sandbox Code Playgroud)

两个页面获得相同主键的概率非常低(假设random()足够随机),并且没有并发问题.而且,通过从编码字符串中切割更多字符,这种方法可以轻松扩展.

  • 我不明白这个概念中b32encode和sha1的意义.难道一个简单的随机选择的字符列表不会产生随机的结果,而且开销(和代码)要少得多吗? (3认同)
  • `random_key = lambda: '{k:032X}'.format(k=random.getrandbits(128))` (2认同)

Oli*_*Oli 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:
            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
Run Code Online (Sandbox Code Playgroud)


met*_*mit 7

Django 现在包含一个UUIDField 类型,因此您不需要任何自定义代码或 Srikanth Chundi 建议的外部包。这个实现使用带破折号的十六进制字符串,所以除了像 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
Run Code Online (Sandbox Code Playgroud)

但是请注意,当您在urls.py 中路由到此视图时,您需要此处提到的不同正则表达式,例如:

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

  • 你是这么问的:`B00B5`.. 但是,是的,HEX 肯定更好。庞大的 36 字符长度是这里的真正问题。 (2认同)