Django 自定义排序会导致相关模型出错?

Django custom ordering causes error for related model?

我有两个模型如下:

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):
    a = models.ForeignKey(A, on_delete=models.CASCADE)

    class Meta:
        ordering = [
            "a"
        ]

然后当我尝试通过管理站点删除 A 个实例时,或者当通过管理站点或命令行访问 B 个实例时,它给出错误:Cannot resolve keyword into field finished_at.

但是,如果我删除模型 A 上的顺序,它就可以正常工作。

我可以缩小问题范围,从 B 排序中删除“a”或不使用 A 排序中的 models.F 表达式将解决问题.

完整堆栈跟踪:


Django Version: 3.2.11
Python Version: 3.8.12
Installed Applications:
['django.contrib.admin',
 'django.contrib.auth',
 'django.contrib.contenttypes',
 'django.contrib.sessions',
 'django.contrib.messages',
 'django.contrib.staticfiles',
 'rest_framework',
 'corsheaders',
 'my-apps',
]
Installed Middleware:
['django.middleware.security.SecurityMiddleware',
 'django.contrib.sessions.middleware.SessionMiddleware',
 'corsheaders.middleware.CorsMiddleware',
 'django.middleware.common.CommonMiddleware',
 'django.middleware.csrf.CsrfViewMiddleware',
 'django.contrib.auth.middleware.AuthenticationMiddleware',
 'django.contrib.messages.middleware.MessageMiddleware',
 'django.middleware.clickjacking.XFrameOptionsMiddleware']



Traceback (most recent call last):
  File "BASE_DIR/.venv/lib/python3.8/site-packages/django/core/handlers/exception.py", line 47, in inner
    response = get_response(request)
  File "BASE_DIR/.venv/lib/python3.8/site-packages/django/core/handlers/base.py", line 181, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "BASE_DIR/.venv/lib/python3.8/site-packages/django/contrib/admin/options.py", line 616, in wrapper
    return self.admin_site.admin_view(view)(*args, **kwargs)
  File "BASE_DIR/.venv/lib/python3.8/site-packages/django/utils/decorators.py", line 130, in _wrapped_view
    response = view_func(request, *args, **kwargs)
  File "BASE_DIR/.venv/lib/python3.8/site-packages/django/views/decorators/cache.py", line 44, in _wrapped_view_func
    response = view_func(request, *args, **kwargs)
  File "BASE_DIR/.venv/lib/python3.8/site-packages/django/contrib/admin/sites.py", line 232, in inner
    return view(request, *args, **kwargs)
  File "BASE_DIR/.venv/lib/python3.8/site-packages/django/utils/decorators.py", line 43, in _wrapper
    return bound_method(*args, **kwargs)
  File "BASE_DIR/.venv/lib/python3.8/site-packages/django/utils/decorators.py", line 130, in _wrapped_view
    response = view_func(request, *args, **kwargs)
  File "BASE_DIR/.venv/lib/python3.8/site-packages/django/contrib/admin/options.py", line 1815, in changelist_view
    'selection_note': _('0 of %(cnt)s selected') % {'cnt': len(cl.result_list)},
  File "BASE_DIR/.venv/lib/python3.8/site-packages/django/db/models/query.py", line 262, in __len__
    self._fetch_all()
  File "BASE_DIR/.venv/lib/python3.8/site-packages/django/db/models/query.py", line 1324, in _fetch_all
    self._result_cache = list(self._iterable_class(self))
  File "BASE_DIR/.venv/lib/python3.8/site-packages/django/db/models/query.py", line 51, in __iter__
    results = compiler.execute_sql(chunked_fetch=self.chunked_fetch, chunk_size=self.chunk_size)
  File "BASE_DIR/.venv/lib/python3.8/site-packages/django/db/models/sql/compiler.py", line 1162, in execute_sql
    sql, params = self.as_sql()
  File "BASE_DIR/.venv/lib/python3.8/site-packages/django/db/models/sql/compiler.py", line 513, in as_sql
    extra_select, order_by, group_by = self.pre_sql_setup()
  File "BASE_DIR/.venv/lib/python3.8/site-packages/django/db/models/sql/compiler.py", line 56, in pre_sql_setup
    order_by = self.get_order_by()
  File "BASE_DIR/.venv/lib/python3.8/site-packages/django/db/models/sql/compiler.py", line 372, in get_order_by
    resolved = expr.resolve_expression(self.query, allow_joins=True, reuse=None)
  File "BASE_DIR/.venv/lib/python3.8/site-packages/django/db/models/expressions.py", line 247, in resolve_expression
    c.set_source_expressions([
  File "BASE_DIR/.venv/lib/python3.8/site-packages/django/db/models/expressions.py", line 248, in <listcomp>
    expr.resolve_expression(query, allow_joins, reuse, summarize)
  File "BASE_DIR/.venv/lib/python3.8/site-packages/django/db/models/expressions.py", line 578, in resolve_expression
    return query.resolve_ref(self.name, allow_joins, reuse, summarize)
  File "BASE_DIR/.venv/lib/python3.8/site-packages/django/db/models/sql/query.py", line 1756, in resolve_ref
    join_info = self.setup_joins(field_list, self.get_meta(), self.get_initial_alias(), can_reuse=reuse)
  File "BASE_DIR/.venv/lib/python3.8/site-packages/django/db/models/sql/query.py", line 1625, in setup_joins
    path, final_field, targets, rest = self.names_to_path(
  File "BASE_DIR/.venv/lib/python3.8/site-packages/django/db/models/sql/query.py", line 1539, in names_to_path
    raise FieldError("Cannot resolve keyword '%s' into field. "

Exception Type: FieldError at /admin/some/path/
Exception Value: Cannot resolve keyword 'finished_at' into field. Choices are

不是输入相关模型的名称,而是必须解决相关模型的id。

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

    class Meta:
        ordering = [
            "a_id"
        ]

这是一个已知错误:#29538 (Query Expression in ordering of a related object fails) – Django

发生这种情况是因为:

  1. BA 上有 on_delete=models.CASCADE
  2. BA 上有 ordering
  3. AF 对象上有 ordering
  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 在另一个模型 C 上有 orderingorderingF对象。

需要在 SQLCompiler find_ordering_name.

中完成正确的修复(确定完整路径)