Ошибка при использовании CheckConstraint в Model.Meta вместе с Django GenericForeignKey - ссылки на объединенные поля не допускаются в этом запросе - PullRequest
4 голосов
/ 17 февраля 2020

Я пытаюсь ограничить 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'),
        ]

Ошибка, которую я получаю 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

Есть подсказки, как решить эту проблему? Я использовал GFK и раньше, но с новым checkconstraint это теперь может быть очень безопасным способом ошибок, если бы я мог получить это для миграции

Спасибо

1 Ответ

0 голосов
/ 22 марта 2020

Невозможно достичь этого с помощью функции CheckConstraint. Django переводит все команды ORM в низкоуровневые команды DB 101 c, и такое создание ограничений невозможно даже на уровне БД. Фактически, мы можем применить CheckConstraint только к одной строке, добавляемой / обновляемой.

В примечании к документации по PostgreSQL говорится:

PostgreSQL не поддерживает ограничения CHECK, которые ссылаются на данные таблицы, кроме проверяемой новой или обновленной строки. Хотя ограничение CHECK, которое нарушает это правило, может показаться работающим в простых тестах, оно не может гарантировать, что база данных не достигнет состояния, в котором условие ограничения ложно (из-за последующих изменений другой строки (строк)). Это может вызвать сбой базы данных и перезагрузку. Перезагрузка может завершиться неудачей, даже если состояние всей базы данных соответствует ограничению, поскольку строки не загружаются в порядке, который будет удовлетворять ограничению. Если возможно, используйте ограничения UNIQUE, EXCLUDE или FOREIGN KEY для express ограничений для нескольких строк и таблиц.

Если вы хотите выполнить однократную проверку других строк при вставке строки, а не Постоянно поддерживаемая гарантия согласованности, пользовательский триггер может быть использован для реализации этого. (Этот подход позволяет избежать проблемы дампа / перезагрузки, поскольку pg_dump не переустанавливает триггеры до перезагрузки данных, поэтому проверка не будет выполняться во время дампа / перезагрузки.)

Таким образом, единственный способ ввести желаемые ограничения использует триггеры БД. Вы можете создать пустую миграцию и добавить в нее триггер БД.

...