#34481 closed Bug (fixed)

Admin check for reversed foreign key used in "list_display"

Reported by: Natalia Bidart Owned by: Baha Sdtbekov
Component: contrib.admin Version: dev
Severity: Normal Keywords:
Cc: Baha Sdtbekov Triage Stage: Ready for checkin
Has patch: yes Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

Currently the admin site checks and reports an admin.E109 system check error when a ManyToManyField is declared within the list_display values.
But, when a reversed foreign key is used there is no system error reported:

System check identified no issues (0 silenced).
April 10, 2023 - 19:04:29
Django version 5.0.dev20230410064954, using settings 'projectfromrepo.settings'
Starting development server at http://0.0.0.0:8000/
Quit the server with CONTROL-C.

and visiting the admin site for the relevant model results in the following error:

TypeError: create_reverse_many_to_one_manager.<locals>.RelatedManager.__call__() missing 1 required keyword-only argument: 'manager'
[10/Apr/2023 19:30:40] "GET /admin/testapp/question/ HTTP/1.1" 500 415926

Ideally, using a reversed foreign key would also result in a system check error instead of a 500 response.

To reproduce, follow the Question and Choice models from the Django Tutorial and add the following to the QuestionAdmin:

list_display = ["question_text", "choice_set"]

Then, visit /admin/testapp/question/ and the following traceback is returned:

Internal Server Error: /admin/testapp/question/
Traceback (most recent call last):
  File "/some/path/django/django/db/models/options.py", line 681, in get_field
    return self.fields_map[field_name]
           ~~~~~~~~~~~~~~~^^^^^^^^^^^^
KeyError: 'choice_set'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/some/path/django/django/contrib/admin/utils.py", line 288, in lookup_field
    f = _get_non_gfk_field(opts, name)
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/some/path/django/django/contrib/admin/utils.py", line 319, in _get_non_gfk_field
    field = opts.get_field(name)
            ^^^^^^^^^^^^^^^^^^^^
  File "/some/path/django/django/db/models/options.py", line 683, in get_field
    raise FieldDoesNotExist(
django.core.exceptions.FieldDoesNotExist: Question has no field named 'choice_set'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/some/path/django/django/core/handlers/exception.py", line 55, in inner
    response = get_response(request)
               ^^^^^^^^^^^^^^^^^^^^^
  File "/some/path/django/django/core/handlers/base.py", line 220, in _get_response
    response = response.render()
               ^^^^^^^^^^^^^^^^^
  File "/some/path/django/django/template/response.py", line 111, in render
    self.content = self.rendered_content
                   ^^^^^^^^^^^^^^^^^^^^^
  File "/some/path/django/django/template/response.py", line 89, in rendered_content
    return template.render(context, self._request)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/some/path/django/django/template/backends/django.py", line 61, in render
    return self.template.render(context)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/some/path/django/django/template/base.py", line 171, in render
    return self._render(context)
           ^^^^^^^^^^^^^^^^^^^^^
  File "/some/path/django/django/template/base.py", line 163, in _render
    return self.nodelist.render(context)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/some/path/django/django/template/base.py", line 1001, in render
    return SafeString("".join([node.render_annotated(context) for node in self]))
                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/some/path/django/django/template/base.py", line 1001, in <listcomp>
    return SafeString("".join([node.render_annotated(context) for node in self]))
                               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/some/path/django/django/template/base.py", line 962, in render_annotated
    return self.render(context)
           ^^^^^^^^^^^^^^^^^^^^
  File "/some/path/django/django/template/loader_tags.py", line 157, in render
    return compiled_parent._render(context)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/some/path/django/django/template/base.py", line 163, in _render
    return self.nodelist.render(context)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/some/path/django/django/template/base.py", line 1001, in render
    return SafeString("".join([node.render_annotated(context) for node in self]))
                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/some/path/django/django/template/base.py", line 1001, in <listcomp>
    return SafeString("".join([node.render_annotated(context) for node in self]))
                               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/some/path/django/django/template/base.py", line 962, in render_annotated
    return self.render(context)
           ^^^^^^^^^^^^^^^^^^^^
  File "/some/path/django/django/template/loader_tags.py", line 157, in render
    return compiled_parent._render(context)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/some/path/django/django/template/base.py", line 163, in _render
    return self.nodelist.render(context)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/some/path/django/django/template/base.py", line 1001, in render
    return SafeString("".join([node.render_annotated(context) for node in self]))
                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/some/path/django/django/template/base.py", line 1001, in <listcomp>
    return SafeString("".join([node.render_annotated(context) for node in self]))
                               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/some/path/django/django/template/base.py", line 962, in render_annotated
    return self.render(context)
           ^^^^^^^^^^^^^^^^^^^^
  File "/some/path/django/django/template/loader_tags.py", line 63, in render
    result = block.nodelist.render(context)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/some/path/django/django/template/base.py", line 1001, in render
    return SafeString("".join([node.render_annotated(context) for node in self]))
                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/some/path/django/django/template/base.py", line 1001, in <listcomp>
    return SafeString("".join([node.render_annotated(context) for node in self]))
                               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/some/path/django/django/template/base.py", line 962, in render_annotated
    return self.render(context)
           ^^^^^^^^^^^^^^^^^^^^
  File "/some/path/django/django/template/loader_tags.py", line 63, in render
    result = block.nodelist.render(context)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/some/path/django/django/template/base.py", line 1001, in render
    return SafeString("".join([node.render_annotated(context) for node in self]))
                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/some/path/django/django/template/base.py", line 1001, in <listcomp>
    return SafeString("".join([node.render_annotated(context) for node in self]))
                               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/some/path/django/django/template/base.py", line 962, in render_annotated
    return self.render(context)
           ^^^^^^^^^^^^^^^^^^^^
  File "/some/path/django/django/contrib/admin/templatetags/base.py", line 45, in render
    return super().render(context)
           ^^^^^^^^^^^^^^^^^^^^^^^
  File "/some/path/django/django/template/library.py", line 258, in render
    _dict = self.func(*resolved_args, **resolved_kwargs)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/some/path/django/django/contrib/admin/templatetags/admin_list.py", line 340, in result_list
    "results": list(results(cl)),
               ^^^^^^^^^^^^^^^^^
  File "/some/path/django/django/contrib/admin/templatetags/admin_list.py", line 316, in results
    yield ResultList(None, items_for_result(cl, res, None))
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/some/path/django/django/contrib/admin/templatetags/admin_list.py", line 307, in __init__
    super().__init__(*items)
  File "/some/path/django/django/contrib/admin/templatetags/admin_list.py", line 217, in items_for_result
    f, attr, value = lookup_field(field_name, result, cl.model_admin)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/some/path/django/django/contrib/admin/utils.py", line 301, in lookup_field
    value = attr()
            ^^^^^^
TypeError: create_reverse_many_to_one_manager.<locals>.RelatedManager.__call__() missing 1 required keyword-only argument: 'manager'
[10/Apr/2023 19:30:40] "GET /admin/testapp/question/ HTTP/1.1" 500 415926

Change History (8)

comment:1 by Mariusz Felisiak, 13 months ago

Component: Uncategorizedcontrib.admin
Triage Stage: UnreviewedAccepted
Type: UncategorizedBug

Thanks for the report.

comment:2 by Baha Sdtbekov, 13 months ago

Cc: Baha Sdtbekov added

comment:3 by Baha Sdtbekov, 13 months ago

Owner: changed from nobody to Baha Sdtbekov
Status: newassigned

comment:4 by Baha Sdtbekov, 13 months ago

Last edited 13 months ago by Baha Sdtbekov (previous) (diff)

comment:5 by Mariusz Felisiak, 13 months ago

Has patch: set

comment:6 by Natalia Bidart, 13 months ago

Needs documentation: set
Patch needs improvement: set

comment:7 by Mariusz Felisiak, 12 months ago

Needs documentation: unset
Patch needs improvement: unset
Triage Stage: AcceptedReady for checkin

comment:8 by GitHub <noreply@…>, 12 months ago

Resolution: fixed
Status: assignedclosed

In c813fb3:

Fixed #34481 -- Added system check for reverse related fields in ModelAdmin.list_display.

Note: See TracTickets for help on using tickets.
Back to Top