Opened 73 minutes ago
#36751 new Bug
Django Admin facets broken in 6.0
| Reported by: | Rafael Urben | Owned by: | |
|---|---|---|---|
| Component: | contrib.admin | Version: | 6.0 |
| Severity: | Normal | Keywords: | |
| Cc: | Rafael Urben | Triage Stage: | Unreviewed |
| Has patch: | no | Needs documentation: | no |
| Needs tests: | no | Patch needs improvement: | no |
| Easy pickings: | no | UI/UX: | no |
Description
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'