web-dev-qa-db-ja.com

Djangoモデルの非シーケンシャルID / PKの生成

私は新しいウェブアプリの作業を始めようとしています。この一部は、ユーザーが1対多の関係でカスタマイズできるページを提供します。これらのページには当然、一意のURLが必要です。

独自のデバイスに任せると、Djangoは通常、標準のAUTOINCREMENT IDをモデルに割り当てます。これは素晴らしく機能しますが、見栄えが悪く、ページを非常に予測可能にします。 (この場合、望ましくないもの)。

1、2、3、4ではなく、セット長のランダムに生成された英数字の文字列(h2esj4など)が必要です。 36文字の可能なセットの6つのスポットは、この段階では十分すぎるはずの20億以上の組み合わせを私に与えるはずです。もちろん、後でこれを拡張できれば、それも良いことです。

しかし、2つの問題があります。

  1. ランダムな文字列は、悪い単語やその他の不快なフレーズを綴ることがあります。それを回避するための適切な方法はありますか?公平を期すために、私はおそらく数値文字列で解決することができますが、衝突の可能性に大きな打撃を与えます。

  2. Django(またはデータベース)に挿入の手間のかかる作業を行わせるにはどうすればよいですか?挿入せずにその後キーを計算します(そうなると2つの新しいページが同時に生成され、2番目のページ(すべての確率に対して)が最初のページの前の最初のページと魔法のように同じキーを取得した場合でも、注意すべき並行性の問題があると思います。コミットされました。

これは、URL短縮サービスがIDを生成する方法と100万マイルも異なるとは思いません。まともなDjangoの実装があれば、それを便乗させることができます。

29
Oli

これが私がやったことです。抽象モデルを作りました。このための私のユースケースは、独自のランダムなスラッグを生成するいくつかのモデルを必要としています。

ナメクジはAA##AAのように見えるので、それは52x52x10x10x52x52 = 731,161,600の組み合わせです。おそらく私が必要とするよりも1000倍多く、それが問題になる場合は、52倍以上の組み合わせの手紙を追加することができます。

抽象モデルは子のスラッグ衝突をチェックする必要があるため、default引数を使用してもそれはカットされません。継承は最も簡単で、おそらくそれを行う唯一の方法でした。

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
9
Oli

組み込みの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
_

2つのページが同一の主キーを取得する確率は非常に低く(random()が十分にランダムであると仮定)、同時実行の問題はありません。そして、当然のことながら、このメソッドは、エンコードされた文字列からより多くの文字をスライスすることで簡単に拡張できます。

22
atomizer

Python UUID を確認する必要があるかもしれませんが、ランダムに長い文字を生成する可能性があります。ただし、スライスして必要な文字数を使用することで、スライスした後でも一意であることを確認できます。

IDField スニペットは、UUIDを自分で生成する手間をかけたくない場合に役立つことがあります。

こちらもご覧ください ブログ投稿

4
Srikanth Chundi

Djangoに IDField type が含まれるようになったため、カスタムコードやSrikanthChundiが提案した外部パッケージは必要ありません。この実装ではダッシュ付きの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でこのビューにルーティングする場合は、 ここで説明 とは異なる正規表現が必要になることに注意してください。 =、例:

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

Oli:失礼な単語のスペルが心配な場合は、Django冒とく的な表現フィルターを使用して、UUIDFieldでそれらを比較/検索し、トリガーとなる可能性のあるUUIDをスキップできます。

2
Elf Sternberg

上記の答えを見て、これが私が今使っているものです。

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):
    ...
1
Nour Wolf

これが私が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
1