#36751: Django Admin facets broken in 6.0
------------------------------+-----------------------------------------
Reporter: Rafael Urben | Type: Bug
Status: new | Component: contrib.admin
Version: 6.0 | Severity: Normal
Keywords: | Triage Stage: Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
------------------------------+-----------------------------------------
In Django 6.0, activating facets leads to an internal server error when
the admin queryset contains certain annotations.
As an example, the following annotations lead to an error:
{{{
def get_queryset(self, request):
return (
super()
.get_queryset(request)
.annotate(
annotation_has_usable_password=Case(
When(
Q(password__isnull=False) &
~Q(password__startswith="!"), then=Value(True)
),
default=Value(False),
output_field=BooleanField(),
),
)
)
}}}
The error observed seems to depend on the database backend:
- With sqlite3, I get 'NoneType' object has no attribute 'as_sql'
(triggered in template rendering at site-
packages\django\contrib\admin\templates\admin\change_list.html, error at
line 72)
- With mysql, I get When() supports a Q object, a boolean expression, or
lookups as a condition.
(triggered in template rendering at site-
packages\django\contrib\admin\templates\admin\change_list.html, error at
line 72)
I have created a small gist with a minimal reproducer admin.py that
triggers the error:
[https://gist.github.com/rafaelurben/670658ffe1a9cc0cfee45380e8f148a0]
The example works without an issue in Django 5.2.8 but fails on Django
6.0rc1 (tested in a new project with a new venv with sqlite and in an
existing project with mysql).
I'm not sure if I have missed something in the Django 6.0 release notes,
but this looks like a bug.
Traceback (sqlite):
{{{
Environment:
Request Method: GET
Request URL: http://localhost:8000/admin/auth/user/?_facets=True
Django Version: 6.0rc1
Python Version: 3.13.5
Installed Applications:
['django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'testapp']
Installed Middleware:
['django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware']
Template error:
In template C:\Users\rafae\Coding\Django\reproducer_dj60_facets\.venv\Lib
\site-packages\django\contrib\admin\templates\admin\change_list.html,
error at line 72
'NoneType' object has no attribute 'as_sql'
62 : <h2 id="changelist-filter-header">{% translate
'Filter' %}</h2>
63 : {% if cl.is_facets_optional or cl.has_active_filters
%}<div id="changelist-filter-extra-actions">
64 : {% if cl.is_facets_optional %}<h3>
65 : {% if cl.add_facets %}<a href="{{
cl.remove_facet_link }}" class="hidelink">{% translate "Hide counts"
%}</a>
66 : {% else %}<a href="{{ cl.add_facet_link }}"
class="viewlink">{% translate "Show counts" %}</a>{% endif %}
67 : </h3>{% endif %}
68 : {% if cl.has_active_filters %}<h3>
69 : <a href="{{ cl.clear_all_filters_qs }}">✖
{% translate "Clear all filters" %}</a>
70 : </h3>{% endif %}
71 : </div>{% endif %}
72 : {% for spec in cl.filter_specs %} {% admin_list_filter
cl spec %} {% endfor %}
73 : </search>
74 : {% endif %}
75 : {% endblock %}
76 : <div>
77 : {% block search %}{% search_form cl %}{% endblock %}
78 : {% block date_hierarchy %}{% if cl.date_hierarchy %}{%
date_hierarchy cl %}{% endif %}{% endblock %}
79 :
80 : <form id="changelist-form" method="post"{% if cl.formset
and cl.formset.is_multipart %} enctype="multipart/form-data"{% endif %}
novalidate>{% csrf_token %}
81 : {% if cl.formset %}
82 : <div>{{ cl.formset.management_form }}</div>
Traceback (most recent call last):
File "C:\Users\rafae\Coding\Django\reproducer_dj60_facets\.venv\Lib
\site-packages\django\core\handlers\exception.py", line 55, in inner
response = get_response(request)
^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\rafae\Coding\Django\reproducer_dj60_facets\.venv\Lib
\site-packages\django\core\handlers\base.py", line 221, in _get_response
response = response.render()
^^^^^^^^^^^^^^^^^
File "C:\Users\rafae\Coding\Django\reproducer_dj60_facets\.venv\Lib
\site-packages\django\template\response.py", line 114, in render
self.content = self.rendered_content
^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\rafae\Coding\Django\reproducer_dj60_facets\.venv\Lib
\site-packages\django\template\response.py", line 92, in rendered_content
return template.render(context, self._request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\rafae\Coding\Django\reproducer_dj60_facets\.venv\Lib
\site-packages\django\template\backends\django.py", line 107, in render
return self.template.render(context)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\rafae\Coding\Django\reproducer_dj60_facets\.venv\Lib
\site-packages\django\template\base.py", line 173, in render
return self._render(context)
^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\rafae\Coding\Django\reproducer_dj60_facets\.venv\Lib
\site-packages\django\template\base.py", line 165, in _render
return self.nodelist.render(context)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\rafae\Coding\Django\reproducer_dj60_facets\.venv\Lib
\site-packages\django\template\base.py", line 1089, in render
return SafeString("".join([node.render_annotated(context) for node in
self]))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\rafae\Coding\Django\reproducer_dj60_facets\.venv\Lib
\site-packages\django\template\base.py", line 1050, in render_annotated
return self.render(context)
^^^^^^^^^^^^^^^^^^^^
File "C:\Users\rafae\Coding\Django\reproducer_dj60_facets\.venv\Lib
\site-packages\django\template\loader_tags.py", line 160, in render
return compiled_parent._render(context)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\rafae\Coding\Django\reproducer_dj60_facets\.venv\Lib
\site-packages\django\template\base.py", line 165, in _render
return self.nodelist.render(context)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\rafae\Coding\Django\reproducer_dj60_facets\.venv\Lib
\site-packages\django\template\base.py", line 1089, in render
return SafeString("".join([node.render_annotated(context) for node in
self]))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\rafae\Coding\Django\reproducer_dj60_facets\.venv\Lib
\site-packages\django\template\base.py", line 1050, in render_annotated
return self.render(context)
^^^^^^^^^^^^^^^^^^^^
File "C:\Users\rafae\Coding\Django\reproducer_dj60_facets\.venv\Lib
\site-packages\django\template\loader_tags.py", line 160, in render
return compiled_parent._render(context)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\rafae\Coding\Django\reproducer_dj60_facets\.venv\Lib
\site-packages\django\template\base.py", line 165, in _render
return self.nodelist.render(context)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\rafae\Coding\Django\reproducer_dj60_facets\.venv\Lib
\site-packages\django\template\base.py", line 1089, in render
return SafeString("".join([node.render_annotated(context) for node in
self]))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\rafae\Coding\Django\reproducer_dj60_facets\.venv\Lib
\site-packages\django\template\base.py", line 1050, in render_annotated
return self.render(context)
^^^^^^^^^^^^^^^^^^^^
File "C:\Users\rafae\Coding\Django\reproducer_dj60_facets\.venv\Lib
\site-packages\django\template\loader_tags.py", line 66, in render
result = block.nodelist.render(context)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\rafae\Coding\Django\reproducer_dj60_facets\.venv\Lib
\site-packages\django\template\base.py", line 1089, in render
return SafeString("".join([node.render_annotated(context) for node in
self]))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\rafae\Coding\Django\reproducer_dj60_facets\.venv\Lib
\site-packages\django\template\base.py", line 1050, in render_annotated
return self.render(context)
^^^^^^^^^^^^^^^^^^^^
File "C:\Users\rafae\Coding\Django\reproducer_dj60_facets\.venv\Lib
\site-packages\django\template\loader_tags.py", line 66, in render
result = block.nodelist.render(context)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\rafae\Coding\Django\reproducer_dj60_facets\.venv\Lib
\site-packages\django\template\base.py", line 1089, in render
return SafeString("".join([node.render_annotated(context) for node in
self]))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\rafae\Coding\Django\reproducer_dj60_facets\.venv\Lib
\site-packages\django\template\base.py", line 1050, in render_annotated
return self.render(context)
^^^^^^^^^^^^^^^^^^^^
File "C:\Users\rafae\Coding\Django\reproducer_dj60_facets\.venv\Lib
\site-packages\django\template\defaulttags.py", line 333, in render
return nodelist.render(context)
^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\rafae\Coding\Django\reproducer_dj60_facets\.venv\Lib
\site-packages\django\template\base.py", line 1089, in render
return SafeString("".join([node.render_annotated(context) for node in
self]))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\rafae\Coding\Django\reproducer_dj60_facets\.venv\Lib
\site-packages\django\template\base.py", line 1050, in render_annotated
return self.render(context)
^^^^^^^^^^^^^^^^^^^^
File "C:\Users\rafae\Coding\Django\reproducer_dj60_facets\.venv\Lib
\site-packages\django\template\defaulttags.py", line 249, in render
nodelist.append(node.render_annotated(context))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\rafae\Coding\Django\reproducer_dj60_facets\.venv\Lib
\site-packages\django\template\base.py", line 1050, in render_annotated
return self.render(context)
^^^^^^^^^^^^^^^^^^^^
File "C:\Users\rafae\Coding\Django\reproducer_dj60_facets\.venv\Lib
\site-packages\django\template\library.py", line 322, in render
output = self.func(*resolved_args, **resolved_kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\rafae\Coding\Django\reproducer_dj60_facets\.venv\Lib
\site-packages\django\contrib\admin\templatetags\admin_list.py", line 517,
in admin_list_filter
"choices": list(spec.choices(cl)),
^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\rafae\Coding\Django\reproducer_dj60_facets\.venv\Lib
\site-packages\django\contrib\admin\filters.py", line 543, in choices
facet_counts = self.get_facet_queryset(changelist) if add_facets else
None
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\rafae\Coding\Django\reproducer_dj60_facets\.venv\Lib
\site-packages\django\contrib\admin\filters.py", line 87, in
get_facet_queryset
return filtered_qs.aggregate(
File "C:\Users\rafae\Coding\Django\reproducer_dj60_facets\.venv\Lib
\site-packages\django\db\models\query.py", line 594, in aggregate
return self.query.chain().get_aggregation(self.db, kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\rafae\Coding\Django\reproducer_dj60_facets\.venv\Lib
\site-packages\django\db\models\sql\query.py", line 633, in
get_aggregation
result = compiler.execute_sql(SINGLE)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\rafae\Coding\Django\reproducer_dj60_facets\.venv\Lib
\site-packages\django\db\models\sql\compiler.py", line 1611, in
execute_sql
sql, params = self.as_sql()
^^^^^^^^^^^^^
File "C:\Users\rafae\Coding\Django\reproducer_dj60_facets\.venv\Lib
\site-packages\django\db\models\sql\compiler.py", line 767, in as_sql
extra_select, order_by, group_by = self.pre_sql_setup(
File "C:\Users\rafae\Coding\Django\reproducer_dj60_facets\.venv\Lib
\site-packages\django\db\models\sql\compiler.py", line 86, in
pre_sql_setup
self.setup_query(with_col_aliases=with_col_aliases)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\rafae\Coding\Django\reproducer_dj60_facets\.venv\Lib
\site-packages\django\db\models\sql\compiler.py", line 75, in setup_query
self.select, self.klass_info, self.annotation_col_map =
self.get_select(
File "C:\Users\rafae\Coding\Django\reproducer_dj60_facets\.venv\Lib
\site-packages\django\db\models\sql\compiler.py", line 317, in get_select
sql, params = self.compile(col)
^^^^^^^^^^^^^^^^^
File "C:\Users\rafae\Coding\Django\reproducer_dj60_facets\.venv\Lib
\site-packages\django\db\models\sql\compiler.py", line 576, in compile
sql, params = vendor_impl(self, self.connection)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\rafae\Coding\Django\reproducer_dj60_facets\.venv\Lib
\site-packages\django\db\models\expressions.py", line 29, in as_sqlite
sql, params = self.as_sql(compiler, connection, **extra_context)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\rafae\Coding\Django\reproducer_dj60_facets\.venv\Lib
\site-packages\django\db\models\aggregates.py", line 193, in as_sql
filter_sql, filter_params = compiler.compile(self.filter)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\rafae\Coding\Django\reproducer_dj60_facets\.venv\Lib
\site-packages\django\db\models\sql\compiler.py", line 576, in compile
sql, params = vendor_impl(self, self.connection)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\rafae\Coding\Django\reproducer_dj60_facets\.venv\Lib
\site-packages\django\db\models\expressions.py", line 29, in as_sqlite
sql, params = self.as_sql(compiler, connection, **extra_context)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\rafae\Coding\Django\reproducer_dj60_facets\.venv\Lib
\site-packages\django\db\models\aggregates.py", line 47, in as_sql
return super().as_sql(compiler, connection, **extra_context)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\rafae\Coding\Django\reproducer_dj60_facets\.venv\Lib
\site-packages\django\db\models\expressions.py", line 1107, in as_sql
arg_sql, arg_params = compiler.compile(arg)
^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\rafae\Coding\Django\reproducer_dj60_facets\.venv\Lib
\site-packages\django\db\models\sql\compiler.py", line 578, in compile
sql, params = node.as_sql(self, self.connection)
^^^^^^^^^^^
Exception Type: AttributeError at /admin/auth/user/
Exception Value: 'NoneType' object has no attribute 'as_sql'
}}}
--
Ticket URL: <https://code.djangoproject.com/ticket/36751>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.
--
You received this message because you are subscribed to the Google Groups
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To view this discussion visit
https://groups.google.com/d/msgid/django-updates/0107019aa8e38be3-c6799f5d-9dc7-465c-8519-3de1e2ce1cee-000000%40eu-central-1.amazonses.com.