web-dev-qa-db-ja.com

DjangoでDB制約を作成

私はDjangoこのようなモデルを持っています:

class Dummy(models.Model):
    ...
    system = models.CharField(max_length=16)

systemを空にしたり、空白文字を入れたりしないでください。

Djangoでバリデーターを使用する方法を知っています。

しかし、私はこれをデータベースレベルで適用します。

このためのDB制約を作成する最も簡単でDjangoのような方法は何ですか?

私はPostgreSQLを使用しており、他のデータベースをサポートする必要はありません。

12
guettli

最初の問題: Djangoを介したデータベース制約の作成

A) Djangoにはまだこの機能が組み込まれていません。9歳のオープン チケット がありますが、息を止めませんこの長い間続いている何かのために。

編集:リリース2.2(2019年4月)以降、Djangoはデータベースレベルをサポートします チェック制約

B)パッケージ Django-db-constraints を調べると、モデルMetaに制約を定義できます。私はこのパッケージをテストしなかったので、それが実際にどれほど役立つかわかりません。

_# example using this package
class Meta:
    db_constraints = {
        'price_above_zero': 'check (price > 0)',
    }
_

2番目の問題:フィールドsystemは空にしたり、空白を含めたりしないでください

これを実現するには、check構文でpostgres制約を作成する必要があります。私はこれらのオプションを思いつきました:

  1. 空白を削除した後、systemの長さが異なるかどうかを確認します。 この答え からのアイデアを使用して、あなたは試すことができます:

    _/* this check should only pass if `system` contains no
     * whitespaces (`\s` also detects new lines)
     */
    check ( length(system) = length(regexp_replace(system, '\s', '', 'g')) )
    _
  2. 空白カウントが0かどうかを確認します。これを行うには、_regexp_matches_を使用します。

    _/* this check should only pass if `system` contains no
     * whitespaces (`\s` also detects new lines)
     */
    check ( length(regexp_matches(system, '\s', 'g')) = 0 )
    _

    length関数できませんは_regexp_matches_と一緒に使用されます。後者は_set of text[]_(配列のセット)を返すためですが、そのセットの要素を今すぐカウントする適切な関数。


最後に、前の問題の両方をまとめると、アプローチは次のようになります。

_class Dummy(models.Model):
    # this already sets NOT NULL to the field in the database
    system = models.CharField(max_length=16)

    class Meta:
        db_constraints = {
            'system_no_spaces': 'check ( length(system) > 0 AND length(system) = length(regexp_replace(system, "\s", "", "g")) )',
        }
_

これにより、フィールドの値がチェックされます。

  1. nULLを含まない(CharFieldはデフォルトで_NOT NULL_制約を追加します)
  2. 空ではありません(checkの最初の部分:length(system) > 0
  3. 空白はありません(checkの2番目の部分:空白を置き換えても同じ長さ)

それがどのように機能するか、またはこのアプローチに問題や欠点があるかどうかをお知らせください。

12
Ralf

2019アップデート

Django 2.2は データベースレベルの制約 のサポートを追加しました。新しい CheckConstraint および niqueConstraint クラスにより、カスタムデータベース制約を追加できます。 Meta.constraintsオプション を使用して、制約をモデルに追加します。

システムの検証は次のようになります。

class Dummy(models.Model):
    ...
    system = models.CharField(max_length=16)

    class Meta:
        constraints = [
            CheckConstraint(
                check=~Q(system="") & ~Q(system__contains=" "),
                name="system_not_blank")
        ]
11
Cesar Canassa

カスタムDjango移行を介してCHECK制約を追加できます。文字列の長さを確認するには、char_length関数とpositionを使用して、空白が含まれているかどうかを確認します。

Postgres docsからの引用( https://www.postgresql.org/docs/current/static/ddl-constraints.html ):

チェック制約は、最も一般的な制約タイプです。特定の列の値がブール(真の値)式を満たす必要があることを指定できます。

Migarationで任意のsqlを実行するにはRunSQL操作を使用できます( https://docs.djangoproject.com/en/2.0/ref/migration-operations/#runsql ):

データベース上で任意のSQLを実行できます。Djangoが部分インデックスのように直接サポートしていないデータベースバックエンドのより高度な機能に役立ちます。

空の移行を作成します:

python manage.py makemigrations --empty yourappname

SQLを追加して制約を作成します:

# Generated by Django A.B on YYYY-MM-DD HH:MM
from Django.db import migrations

class Migration(migrations.Migration):

    dependencies = [
        ('yourappname', '0001_initial'),
    ]

    operations = [
         migrations.RunSQL('ALTER TABLE appname_dummy ADD CONSTRAINT syslen '
                           'CHECK (char_length(trim(system)) > 1);',
                           'ALTER TABLE appname_dummy DROP CONSTRAINT syslen;'),
         migrations.RunSQL('ALTER TABLE appname_dummy ADD CONSTRAINT syswh '
                           'CHECK (position(' ' in trim(system)) = 0);',
                           'ALTER TABLE appname_dummy DROP CONSTRAINT syswh;')


    ]

移行を実行します:

python manage.py migrate yourappname
7
ndpu

私はあなたの要件に達するために私の答えを変更します。

したがって、DB制約を実行する場合は、次のようにしてください。

import psycopg2
def your_validator():
    conn = psycopg2.connect("dbname=YOURDB user=YOURUSER")
    cursor = conn.cursor()
    query_result = cursor.execute("YOUR QUERY")
    if query_result is Null:
        # Do stuff
    else:
        # Other Stuff

次に、pre_saveシグナル。

あなたのmodels.pyファイル追加、

from Django.db.models.signals import pre_save
class Dummy(models.Model):
...
    @staticmethod
    def pre_save(sender, instance, *args, **kwargs)
        # Of course, feel free to parse args in your def.
        your_validator()
1