简体   繁体   English

在 Model.Meta 中使用 CheckConstraint 和 Django GenericForeignKey 时出错 - 此查询中不允许加入的字段引用

[英]Error Using CheckConstraint in Model.Meta along with Django GenericForeignKey - Joined field references are not permitted in this query

I am trying to restrict GFK to be pointed to objects of a few models only, and I thought CheckConstraint will be a great way to do this, however I get this error我试图限制 GFK 只指向几个模型的对象,我认为 CheckConstraint 将是一个很好的方法来做到这一点,但是我得到了这个错误

class ManualAdjustment(Model):
    content_type = models.ForeignKey(ContentType, null=True, on_delete=models.CASCADE)
    object_id = models.PositiveIntegerField(null=True)

    booking_obj = GenericForeignKey('content_type', 'object_id')  
    # should point to a app1.Booking1 or app2.Booking2 or app3.Booking3 only - trying to enforce this via CheckConstraint


    class Meta:
        constraints = [
            models.CheckConstraint(
                check=
                Q(content_type__app_label='app1', content_type__model='booking1') |
                Q(content_type__app_label='app2', content_type__model='booking2') |
                Q(content_type__app_label='app3', content_type__model='booking3'),
                name='myconstraint_only_certain_models'),
        ]

Error I get on migrate我在迁移时出错

    execute_from_command_line(sys.argv)
  File "/Users/myuser/.virtualenvs/xenia371/lib/python3.7/site-packages/django/core/management/__init__.py", line 381, in execute_from_command_line
    utility.execute()
  File "/Users/myuser/.virtualenvs/xenia371/lib/python3.7/site-packages/django/core/management/__init__.py", line 375, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "/Users/myuser/.virtualenvs/xenia371/lib/python3.7/site-packages/django/core/management/base.py", line 323, in run_from_argv
    self.execute(*args, **cmd_options)
  File "/Users/myuser/.virtualenvs/xenia371/lib/python3.7/site-packages/django/core/management/commands/sqlmigrate.py", line 30, in execute
    return super().execute(*args, **options)
  File "/Users/myuser/.virtualenvs/xenia371/lib/python3.7/site-packages/django/core/management/base.py", line 364, in execute
    output = self.handle(*args, **options)
  File "/Users/myuser/.virtualenvs/xenia371/lib/python3.7/site-packages/django/core/management/commands/sqlmigrate.py", line 64, in handle
    sql_statements = executor.collect_sql(plan)
  File "/Users/myuser/.virtualenvs/xenia371/lib/python3.7/site-packages/django/db/migrations/executor.py", line 225, in collect_sql
    state = migration.apply(state, schema_editor, collect_sql=True)
  File "/Users/myuser/.virtualenvs/xenia371/lib/python3.7/site-packages/django/db/migrations/migration.py", line 124, in apply
    operation.database_forwards(self.app_label, schema_editor, old_state, project_state)
  File "/Users/myuser/.virtualenvs/xenia371/lib/python3.7/site-packages/django/db/migrations/operations/models.py", line 827, in database_forwards
    schema_editor.add_constraint(model, self.constraint)
  File "/Users/myuser/.virtualenvs/xenia371/lib/python3.7/site-packages/django/db/backends/base/schema.py", line 343, in add_constraint
    sql = constraint.create_sql(model, self)
  File "/Users/myuser/.virtualenvs/xenia371/lib/python3.7/site-packages/django/db/models/constraints.py", line 47, in create_sql
    check = self._get_check_sql(model, schema_editor)
  File "/Users/myuser/.virtualenvs/xenia371/lib/python3.7/site-packages/django/db/models/constraints.py", line 37, in _get_check_sql
    where = query.build_where(self.check)
  File "/Users/myuser/.virtualenvs/xenia371/lib/python3.7/site-packages/django/db/models/sql/query.py", line 1296, in build_where
    return self._add_q(q_object, used_aliases=set(), allow_joins=False, simple_col=True)[0]
  File "/Users/myuser/.virtualenvs/xenia371/lib/python3.7/site-packages/django/db/models/sql/query.py", line 1312, in _add_q
    current_negated, allow_joins, split_subq, simple_col)
  File "/Users/myuser/.virtualenvs/xenia371/lib/python3.7/site-packages/django/db/models/sql/query.py", line 1318, in _add_q
    split_subq=split_subq, simple_col=simple_col,
  File "/Users/myuser/.virtualenvs/xenia371/lib/python3.7/site-packages/django/db/models/sql/query.py", line 1199, in build_filter
    raise FieldError("Joined field references are not permitted in this query")
django.core.exceptions.FieldError: Joined field references are not permitted in this query

Any clue on how to solve this?关于如何解决这个问题的任何线索? I have used GFK before but with new checkconstraint it actually now can be a nicely error-safe way, if I could get this to migrate我以前使用过 GFK,但有了新的检查约束,它实际上现在可以成为一种很好的错误安全方式,如果我可以让它迁移

Thanks谢谢

It is not possible to achieve this using the CheckConstraint functionality.使用CheckConstraint功能无法实现这一点。 Django translates all ORM commands to the low level DB specific commands, and such constraint creation isn't possible even on the DB level. Django 将所有 ORM 命令转换为低级 DB 特定命令,即使在 DB 级别也无法创建此类约束。 In fact, we can apply CheckConstraint to a single row beeing added/updated only.事实上,我们可以将CheckConstraint应用于仅添加/更新的单行。

The note in documentation on PostgreSQL says: PostgreSQL 文档中的注释说:

PostgreSQL does not support CHECK constraints that reference table data other than the new or updated row being checked.除了要检查的新行或更新行之外,PostgreSQL 不支持引用表数据的 CHECK 约束。 While a CHECK constraint that violates this rule may appear to work in simple tests, it cannot guarantee that the database will not reach a state in which the constraint condition is false (due to subsequent changes of the other row(s) involved).虽然违反此规则的 CHECK 约束在简单测试中可能看起来有效,但它不能保证数据库不会达到约束条件为假的状态(由于涉及的其他行的后续更改)。 This would cause a database dump and reload to fail.这将导致数据库转储和重新加载失败。 The reload could fail even when the complete database state is consistent with the constraint, due to rows not being loaded in an order that will satisfy the constraint.即使完整的数据库状态与约束一致,重新加载也可能失败,因为行未按满足约束的顺序加载。 If possible, use UNIQUE, EXCLUDE, or FOREIGN KEY constraints to express cross-row and cross-table restrictions.如果可能,请使用 UNIQUE、EXCLUDE 或 FOREIGN KEY 约束来表达跨行和跨表限制。

If what you desire is a one-time check against other rows at row insertion, rather than a continuously-maintained consistency guarantee, a custom trigger can be used to implement that.如果您想要的是在行插入时对其他行进行一次性检查,而不是持续维护的一致性保证,则可以使用自定义触发器来实现这一点。 (This approach avoids the dump/reload problem because pg_dump does not reinstall triggers until after reloading data, so that the check will not be enforced during a dump/reload.) (这种方法避免了转储/重新加载问题,因为 pg_dump 在重新加载数据之后才重新安装触发器,因此在转储/重新加载期间不会强制执行检查。)

So, the only way to introduce desired constrains is using db triggers.因此,引入所需约束的唯一方法是使用 db 触发器。 You can create empty migration and add DB trigger into it.您可以创建空迁移并将数据库触发器添加到其中。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM