如何重写此 postgres 约束以更符合 django 2.2?
How to rewrite this postgres constraint to be more in line with 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()
我已经尝试 ExpressionWrapper
和 RawSQL
在 class Meta 中创建约束,但它仍然不起作用。
作为参考,我查看了 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
查看了单独的数据库和状态迁移
但我仍然无法让它工作
这可能吗?
更新
为了提高可读性,让我总结一下我的问题。
- 我想在 JSONField 上写约束。
- 我可以直接在 Postgres 上这样做
- 因此我可以在迁移文件中使用原始 sql
- 但是我不能使用 Django 模型元/CheckConstraint 做等效的事情,这通常是任何人做的。参见 https://docs.djangoproject.com/en/3.0/ref/models/constraints/
- 那么我如何重写这个原始 sql 以在 postgres 中但以 Django 方式对 jsonfield 产生约束?
为了在 Django 2.2 上实现 you'll need to register two new JSONField
transforms/lookups since support for conditional expressions was only added in the upcoming 3.1 release。
您首先要为 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 上,你应该能够将 RawSQL
直接传递给 CheckConstraint.check
只要它有 output_field = models.BooleanField()
.
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()
)
这是我为检查约束为 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()
我已经尝试 ExpressionWrapper
和 RawSQL
在 class Meta 中创建约束,但它仍然不起作用。
作为参考,我查看了 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
查看了单独的数据库和状态迁移但我仍然无法让它工作
这可能吗?
更新
为了提高可读性,让我总结一下我的问题。
- 我想在 JSONField 上写约束。
- 我可以直接在 Postgres 上这样做
- 因此我可以在迁移文件中使用原始 sql
- 但是我不能使用 Django 模型元/CheckConstraint 做等效的事情,这通常是任何人做的。参见 https://docs.djangoproject.com/en/3.0/ref/models/constraints/
- 那么我如何重写这个原始 sql 以在 postgres 中但以 Django 方式对 jsonfield 产生约束?
为了在 Django 2.2 上实现 you'll need to register two new JSONField
transforms/lookups since support for conditional expressions was only added in the upcoming 3.1 release。
您首先要为 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 上,你应该能够将 RawSQL
直接传递给 CheckConstraint.check
只要它有 output_field = models.BooleanField()
.
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()
)