web-dev-qa-db-ja.com

このpostgres制約をDjango 2.2と一致するように書き換える方法は?

これは私がチェック制約のpostgresのために書く生のクエリです

   ALTER TABLE rea_asplinkage ADD CONSTRAINT asp_sub_project_positive_integer
        CHECK (
            jsonb_typeof(linkage-> 'root' -> 'in_sub_project') is not distinct from 'number'
        and (linkage->'root'->>'in_sub_project')::numeric % 1 = 0
        and (linkage->'root'->>'in_sub_project')::numeric > 0
        );

そして、私が移行を作成する方法はこの方法です

# Generated by Django 2.2.10 on 2020-05-16 12:59

from Django.db import connection, migrations



class Migration(migrations.Migration):

    dependencies = [("rea", "0029_asplinkage")]

    operations = [
        migrations.RunSQL(
            sql="""
            ALTER TABLE rea_asplinkage ADD CONSTRAINT asp_sub_project_positive_integer
            CHECK (
                jsonb_typeof(linkage-> 'root' -> 'in_sub_project') is not distinct from 'number'
            and (linkage->'root'->>'in_sub_project')::numeric % 1 = 0
            and (linkage->'root'->>'in_sub_project')::numeric > 0
            );
            """,
            reverse_sql="""
                ALTER TABLE rea_asplinkage DROP CONSTRAINT "asp_sub_project_positive_integer";
            """,
        )
    ]

そして、これはうまくいきます。

しかし、これは私の元のモデルがASPLinkageモデルのclass Metaに制約を示さないことを意味します

class ASPLinkage(TimeStampedModel, SoftDeletableModel, PersonStampedModel, OrganizationOwnedModel):
    linkage = JSONField(default=default_linkage_for_asp)

    objects = OrganizationOwnedSoftDeletableManager()

クラスメタ内に制約を作成するためにExpressionWrapperRawSQLを試しましたが、それでも機能しません。

参考までに、私は https://github.com/Django/django/blob/master/tests/constraints/models.py#L12 にある例を見てきました

https://realpython.com/create-Django-index-without-downtime/#when-Django-generates-a-new-migration を使用して、データベースと状態の個別の移行も確認しました

しかし、私はまだそれを機能させることができません

これは可能ですか?

更新

読みやすくするために質問の概要を書きましょう。

  1. JSONFieldに制約を書きたいのですが。
  2. 私はそれをPostgresで直接行うことができます
  3. したがって、移行ファイルで生のSQLを使用してそれを行うことができます
  4. しかし、私はDjangoモデルメタ/ CheckConstraintを使用して同等のことを行うことはできません。これは通常、誰でも行う方法です。参照してください https://docs.djangoproject.com/en/3.0/ref/モデル/制約/
  5. では、この生のSQLをどのように書き換えて、postgresのjsonfieldにDjangoの方法で)制約を生成するのですか?
3
Kim Stacks

Django 2.2でそれを達成するために 2つの新しいJSONField変換/ルックアップを登録する必要があります 以降 条件式のサポート次の3.1リリースでのみ追加されました

最初に、JSONFieldキーアクセスのルックアップを登録する必要があります

_from Django.db import models
from Django.db.models.lookups import Lookup
from Django.contrib.postgres.fields.jsonb import (
    KeyTransform, KeyTransformTextLookupMixin
)

@KeyTransform.register_lookup
class KeyTransformIsInteger(KeyTransformTextLookupMixin, Lookup):
    lookup_name = 'is_int'
    prepare_rhs = False

    def as_sql(self, compiler, connection):
        key_expr = KeyTransform(
            self.lhs.key_name, *self.lhs.source_expressions, **self.lhs.extra
        )
        key_sql, key_params = self.process_lhs(
            compiler, connection, lhs=key_expr
        )
        lhs_sql, lhs_params = self.process_lhs(compiler, connection)
        rhs_sql, rhs_params = self.process_rhs(compiler, connection)
        sql = "(jsonb_typeof(%s) = %%s AND mod(%s::numeric, %%s) = %%s) IS %s" % (
            key_sql, lhs_sql, rhs_sql
        )
        params = [
            *key_params, 'number',
            *lhs_params, 1, 0,
            *rhs_params,
        ]
        return sql, params

@KeyTransform.register_lookup
class KeyTransformIntegerGt(KeyTransformTextLookupMixin, Lookup):
    lookup_name = 'int_gt'
    prepare_rhs = False

    def as_sql(self, compiler, connection):
        lhs_sql, lhs_params = self.process_lhs(compiler, connection)
        rhs_sql, rhs_params = self.process_rhs(compiler, connection)
        sql = "%s::int > %s" % (lhs_sql, rhs_sql)
        params = [*lhs_params, *rhs_params]
        return sql, params
_

これが完了すると、次のように制約を定義できるようになります

_CheckConstraint(
    check=Q(
        linkage__root__in_sub_project__is_int=True,
        linkage__root__in_sub_project__int_gt=0,
    ),
    name='asp_sub_project_positive_integer',
)
_

Django 3.1になったら、output_field = models.BooleanField()がある限り、RawSQLを_CheckConstraint.check_に直接渡すことができます。

_RawSQL("""
   jsonb_typeof(linkage-> 'root' -> 'in_sub_project') is not distinct from 'number'
   and (linkage->'root'->>'in_sub_project')::numeric % 1 = 0
   and (linkage->'root'->>'in_sub_project')::numeric > 0
""",
    output_field=models.BooleanField()
)
_
2
Simon Charette