为Django模型生成非顺序ID/PK
我即将开始开发一个新的网络应用程序。其中一部分将为用户提供他们可以在一对多关系中定制的页面。这些页面自然需要有唯一的URL 离开自己的设备,Django通常会为模型分配一个标准的为Django模型生成非顺序ID/PK,django,url,django-models,primary-key,Django,Url,Django Models,Primary Key,我即将开始开发一个新的网络应用程序。其中一部分将为用户提供他们可以在一对多关系中定制的页面。这些页面自然需要有唯一的URL 离开自己的设备,Django通常会为模型分配一个标准的AUTOINCREMENTID。虽然这工作得非常好,但它看起来并不好,而且它也使页面非常可预测(在本例中这是不需要的) 而不是1,2,3,4,我想设置长度,随机生成的字母数字字符串(如h2esj4)。一组36个字符中的6个点应该给我20多亿个组合,这在现阶段已经足够了。当然,如果我能在以后扩展这个,那也会很好 但有两个问
AUTOINCREMENT
ID。虽然这工作得非常好,但它看起来并不好,而且它也使页面非常可预测(在本例中这是不需要的)
而不是1,2,3,4,我想设置长度,随机生成的字母数字字符串(如h2esj4)。一组36个字符中的6个点应该给我20多亿个组合,这在现阶段已经足够了。当然,如果我能在以后扩展这个,那也会很好
但有两个问题:
我认为这与URL缩短器生成ID的方式没有什么不同。如果有一个像样的Django实现,我可以利用它。也许你需要看看,它可以生成随机的长字符。但是,您可以对其进行切片,并使用所需的字符数,而无需进行任何检查,以确保即使在切片之后它也是唯一的 如果您不想自己痛苦地生成UUID,这个代码片段可能会对您有所帮助
还可以看看这个有一个内置的Django方法来实现您想要的。在“自定义页面”模型中添加一个字段,其中包含键生成函数的
primary\u 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()
足够随机),并且不存在并发问题。当然,通过从编码字符串中分割更多的字符,这个方法很容易扩展。下面是我最后要做的。我做了一个抽象模型。我的用例是需要几个模型来生成它们自己的随机段塞
一个slug看起来像AA##AA
,这就是52x52x10x10x10x52x52=731161600
组合。可能比我需要的多1000倍,如果这是个问题,我可以添加一封信,增加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
奥利:如果你担心拼写粗俗的单词,你可以使用django亵渎过滤器比较/搜索你的UUID字段,并跳过任何可能触发的UUID。这就是我最终使用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
Django现在包含一个,因此您不需要任何自定义代码或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
但是,请注意,在URL.py中路由到此视图时,需要使用不同的正则表达式,例如:
urlpatterns=[
url(r'mymodel/(?P[^/]+)/$”,MyModelDetailView.as_view(),
name='mymodel'),
]
看看上面的答案,下面是我现在使用的
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):
...
这并没有真正绕过我在问题中强调的两个问题中的任何一个。当然,
UUIDField
有助于将一些代码从我的模型中抽象出来,但它仍然在数据库之外(我非常喜欢它),并且仍然能够很好地拼写出粗鲁的单词。我不理解b32encode和sha1在这个概念中的意义。一个简单的随机字符列表不会产生同样的随机结果,并且开销(和代码)会少很多吗?@Oli您可以生成任何您想要的字符串,关键是将回调函数设置为默认值是将字符串指定为PK的方式。对我来说似乎是正确的解决方案+1 UpvoteIn在可重用的设置中,它不能进行冲突检查。同一段代码的模型实例不能超过一次。这是default
参数中的一个缺陷,该参数无法获取附加信息(将类传递给生成器)。random\u key=lambda:{k:032X}.format(k=random.getrandbits(128))
id=django.utils.http.int\u to_base36(uuid.uuid4().int)[:length]
。我最近决定采取一些UK ID的方法来处理一些PK,但是我也会考虑这个问题。我认为你的片段在任何情况下都是一样的。只需将您生成的4行“ret”替换为类似“ret=uuid.uuid1()”的内容,我正在尝试使用您的方法,但我发现无法通过ClassName实例访问管理器错误。你是如何克服这一点的?这是一个古老的线索,但对于任何偶然发现这一点并使用MySQL的人来说,有一件事需要警惕,那就是MySQL在默认情况下对字符串匹配不区分大小写,因此“AB12AB”和“AB12AB”的ID都会被找到,除非你明确告诉MySQL使用区分大小写的匹配:@Oli:
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):
...