Пользовательское упорядочивание Django вызывает ошибку для связанной модели?

У меня есть две следующие модели:

class A(models.Model):
    # more fields here
    finished_at = models.DateTimeField(db_index=True, null=True, blank=True)

    class Meta:
        ordering = [
            models.F("finished_at").desc(nulls_first=True),
            "other_field",
        ]


class B(models.Model):
    related = models.ForeignKey(A, on_delete=models.CASCADE)

    class Meta:
        ordering = [
            "a"
        ]

Затем, когда я пытаюсь удалить A экземпляры через админку сайта, или при доступе к B экземплярам либо через админку, либо через командную строку, он выдает ошибку: Cannot resolve keyword into field finished_at.

Но если я удалю упорядочивание на модели A, она работает нормально.

Я смог сузить проблему, удалив либо "a" из B упорядочивания, либо не используя models.F выражение из A упорядочивания решит проблему.

Полная трассировка стека:

Вместо того, чтобы поместить имя связанной модели, вы должны обратиться к id связанной модели.

class B(models.Model):
    a = models.ForeignKey(A, on_delete=models.CASCADE)

    class Meta:
        ordering = [
            "a_id"
        ]

Это известная ошибка: #29538 (Query Expression в упорядочивании связанного объекта не работает) - Django

Это происходит потому, что:

  1. B имеет on_delete=models.CASCADE на A.
  2. B имеет ordering на A.
  3. A имеет ordering на F объект.
  4. SQLCompiler find_ordering_name не преобразует должным образом F объекты отношений.

Существует устаревший PR (закрыт из-за неактивности), который пытался исправить это: django/django#10146

Вот патч, который работает для простых случаев, таких как в вопросе и сообщении об ошибке:

from django.db.models.constants import LOOKUP_SEP
from django.db.models.expressions import F
from django.db.models.sql.datastructures import Join
from django.db.models.sql.query import get_field_names_from_opts

def resolve_expression(self, query=None, allow_joins=True, reuse=None, summarize=False, for_save=False):
    name = self.name
    if self.name not in get_field_names_from_opts(query.get_meta()):
        for datastructure in query.alias_map.values():
            if (
                    isinstance(datastructure, Join) and
                    datastructure.join_field.opts == query.get_meta() and
                    self.name in get_field_names_from_opts(datastructure.join_field.related_model._meta)
            ):
                name = datastructure.join_field.name + LOOKUP_SEP + self.name
                break
    return query.resolve_ref(name, allow_joins, reuse, summarize)

F.resolve_expression = resolve_expression

Пример случая, когда это не работает: В дополнение к вышесказанному, A имеет ordering на другой модели C, которая имеет ordering на объекте F.

Правильное исправление (определение полного пути) должно быть сделано в SQLCompiler find_ordering_name.

Вернуться на верх