代码之家  ›  专栏  ›  技术社区  ›  Oli

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

  •  29
  • Oli  · 技术社区  · 14 年前

    我正在着手开发一个新的网络应用程序。其中一部分将为用户提供他们可以在一对多关系中定制的页面。这些页面自然需要有唯一的url。

    Django通常会自行指定一个标准 AUTOINCREMENT 模型的ID。虽然这工作得非常好,但它看起来不太好,而且它还使页面非常可预测(在本例中这是不需要的)。

    而不是1,2,3,4我想设置长度,随机生成的字母数字字符串(如h2esj4)。一组36个字符中的6个点可以给我20多亿个组合,这在现阶段已经足够了。当然,如果我能在以后扩展这个,那也很好。

    但有两个问题:

    1. 随机的字符串偶尔会拼写出不好的单词或其他冒犯性的短语。有没有合适的方法来回避这个问题?公平地说,我可能会满足于一个数字字符串,但它确实对冲突的可能性有很大的影响。

    2. 如何让Django(或数据库)在insert上执行繁重的操作?我宁愿不插入 找出钥匙(因为那不是什么钥匙)。我假设也有并发问题需要注意,尽管如果同时生成两个新页面,并且第二个页面(尽管有可能)在提交第一个页面之前神奇地获得了与第一个页面相同的密钥。

    7 回复  |  直到 14 年前
        1
  •  22
  •   atomizer    14 年前

    有一个内置的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() 在创建该实例时。
    快速实施(&D):

    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() 足够随机),并且没有并发问题。当然,通过从编码字符串中分割更多的字符,这种方法很容易扩展。

        2
  •  10
  •   Oli    5 年前

    鼻涕虫看起来像 AA##AA 所以这就是 52x52x10x10x52x52 = 731,161,600

    使用 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
    
        3
  •  6
  •   Community CDub    7 年前

    Django现在包括 UUIDField type ,因此您不需要任何自定义代码或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
    

    但是,请注意,当您在中路由到此视图时 网址.py ,您需要一个不同的正则表达式作为 mentioned here

    urlpatterns = [
        url(r'mymodel/(?P<pk>[^/]+)/$', MyModelDetailView.as_view(),
            name='mymodel'),
    ]
    
        4
  •  4
  •   Srikanth Chundi    14 年前

    也许你需要看看 Python UUID ,它可以生成随机的长字符。但是你可以对它进行切片,并使用你想要的字符数,而不需要做任何检查,以确保即使在切片之后它也是唯一的。

    UUIDField 如果您不想自己痛苦地生成UUID,代码片段可能会对您有所帮助。

    blog post

        5
  •  2
  •   Elf Sternberg    14 年前

    奥利:如果你担心拼写出粗鲁的单词,你可以使用django亵渎过滤器,在uuid字段中比较/搜索它们,并跳过任何可能会触发的uuid。

        6
  •  1
  •   All Іѕ Vаиітy durdenk    8 年前

    这就是我最终使用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
    
        7
  •  1
  •   Nour Wolf    6 年前

    看看上面的答案,这里是我现在使用的。

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