我正在开始研究新的webapp。部分内容将为用户提供可以在一对多关系中自定义的页面。这些页面自然需要有唯一的URL。
Django留给自己的设备,通常会为模型分配一个标准的AUTOINCREMENT
ID。虽然这很有效,但它看起来并不好看,它也使得页面非常容易预测(在这种情况下这是不可取的)。
而不是1,2,3,4我想要设定长度,随机生成的字母数字字符串(例如h2esj4)。可能的36个字符的6个点应该给我超过20亿个组合,在这个阶段应该绰绰有余。当然,如果我可以在以后扩展它,那也会很好。
但有两个问题:
随机字符串偶尔会拼出坏词或其他令人反感的词组。有没有一个体面的方式回避这个?为了公平起见,我可能会接受一个数字字符串,但它确实会对冲突的可能性产生重大影响。
如何让Django(或数据库)在插入时进行繁重的工作?我宁愿不插入而然后计算出密钥(因为这不是一个关键)。我假设有并发问题也要注意,如果同时生成两个新页面,而第二个(克服所有可能性)神奇地获得与第一个提交之前的第一个相同的密钥。
我不认为这与URL缩短器生成ID的方式有一百万英里的差异。如果有一个体面的Django实现,我可以捎带它。
答案 0 :(得分:22)
有内置的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()
足够随机),并且没有并发问题。而且,通过从编码字符串中切割更多字符,这种方法很容易扩展。
答案 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