Отфильтровать несуществующие объекты GenericForeignKey в наборе запросов Django

У меня есть простая модель с общим внешним ключом:

class Generic(models.Model):
    content_type = models.ForeignKey(ContentType)
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey('content_type', 'object_id')

Я хотел бы отфильтровать все записи в этой таблице, которые имеют ненулевые content_object, т. е. отфильтровать все экземпляры Generic, чьи объекты контента больше не существуют:

Generic.objects.filter(~Q(content_object=None))

Это не работает, за исключением:

django.core.exceptions.FieldError: Поле «content_object» не генерирует автоматическое обратное отношение и поэтому не может использоваться для обратного запроса. Если это GenericForeignKey, рассмотрите возможность добавления GenericRelation.

Добавление GenericRelation к указанным моделям типов контента не имеет значения.

Любая помощь в том, как этого достичь, будет оценена по достоинству, большое спасибо.

РЕДАКТИРОВАТЬ: я понимаю, что могу каскадировать удаление, однако в моей ситуации это не вариант (я хочу сохранить данные).


person Ben    schedule 26.01.2016    source источник


Ответы (1)


Если вы хотите отфильтровать некоторые записи out, часто лучше использовать exclude() метод:

Generic.objects.exclude(object_id__isnull=True)

Однако обратите внимание, что ваша модель теперь не допускает пустых полей content_object. Чтобы изменить это поведение, используйте аргумент null=True как для object_id, так и для content_type. поля.

Обновлять

Хорошо, поскольку вопрос перешел от фильтрации нулевых записей к определению неработающих ссылок на РСУБД без помощи самой РСУБД, я бы предложил (довольно медленный и требующий памяти) обходной путь:

broken_items = []
for ct in ContentType.objects.all():        
    broken_items.extend(
        Generic.objects
        .filter(content_type=ct)
        .exclude(object_id__in=ct.model_class().objects.all())
        .values_list('pk', flat=True))

Это будет работать как одноразовый сценарий, но не как надежное решение. Если вы абсолютно хотите сохранить данные, единственный быстрый способ, который я мог бы придумать, - это иметь логический флаг is_deleted в вашей модели Generic и установить его в сигнале (post|pre)_delete.

person Alex Morozov    schedule 26.01.2016
comment
Другой парень прокомментировал это же решение, а затем удалил его, потому что оно не работает, за исключением того, что ваше недействительно, так как это проверит, имеет ли поле object_id значение null, а не связанный объект, на который ссылается этот идентификатор. В любом случае, если вы исправите это, оно по-прежнему дает то же исключение, что и в исходном вопросе. Фильтровать или исключать здесь не имеет значения. Проверка FWIW = None аналогична isnull=True. - person Ben; 26.01.2016
comment
Бен, но что тогда означает ненулевое content_object? Это просто (достаточно тонкая) оболочка вокруг двух фактических полей в вашей базе данных. Если вы хотите исключить, что Generics не ссылаются на какую-либо другую модель, запрос, который я дал выше, является тем, который работает. Или, пожалуйста, уточните свое определение нулевого объекта в ответе. - person Alex Morozov; 26.01.2016
comment
Привет Алекс, я уточнил вопрос. Ненулевой означает фактический объект, который вы можете получить из таблицы, определенной content_type_id, используя идентификатор, определенный object_id. Например, если у вас было общее отношение к объекту, который сейчас удален. - person Ben; 26.01.2016
comment
Хорошо, теперь я вижу. Ознакомьтесь с моими обновленными мыслями по этому поводу. - person Alex Morozov; 26.01.2016
comment
Спасибо Алекс, это не плохой обходной путь. Я планирую удалить флаги в будущем, так что, вероятно, это будет окончательное решение. Оцените ответ! - person Ben; 26.01.2016
comment
Во второй версии разве исключение не должно быть на object_id, а не на pk: .exclude(object_id__in=ct.model_class().objects.all())? (Поскольку pk будет относиться к собственному идентификатору Generic, а не к идентификатору связанной модели, которую вы хотите проверить.) - person medmunds; 17.04.2018
comment
Исправьте @medmunds. Однако, чтобы быть честным с вами, я бы посоветовал вообще избегать общих FK в Django, поскольку с ними приходится сталкиваться разными способами; лучше реорганизовать вашу схему, чтобы они не нуждались в них, если вы можете. - person Ben; 18.04.2018