#17664: Smart `if` tag silencing exceptions plus `QuerySet` caching equals buggy
behaviour.
-------------------------+-------------------------------------------------
Reporter: | Owner: nobody
mrmachine | Status: new
Type: Bug | Version: SVN
Component: | Keywords: smart if tag queryset exception
Uncategorized | silenced
Severity: Normal | Has patch: 0
Triage Stage: | UI/UX: 0
Unreviewed |
Easy pickings: 0 |
-------------------------+-------------------------------------------------
This buggy behaviour took me a while to track down. I hope I can explain
it clearly.
Given a `QuerySet` with invalid ordering (and possibly other conditions)
that should raise an exception when evaluated, `{% if qs %}` will raise
the exception while `{% if not qs %}` will silence it and leave `qs` as if
it were simply empty on subsequent access in the template.
First, the exception is silenced and the queryset becomes empty:
{{{
>>> from django.contrib.auth.models import User
>>> from django.template import Template, Context
>>> Template('count: {{ qs.count }}, empty: {% if not qs %}yes{% else
%}no{% endif %}, qs: {{ qs }}, count: {{ qs.count
}}').render(Context({'qs': User.objects.order_by('invalid_field')}))
u'count: 98, empty: no, qs: [], count: 0'
}}}
And now if we swap the `{% if %}` around a bit, we get an exception:
{{{
>>> Template('count: {{ qs.count }}, empty: {% if qs %}no{% else %}yes{%
endif %}, qs: {{ qs }}, count: {{ qs.count }}').render(Context({'qs':
User.objects.order_by('invalid_field')}))
Traceback (most recent call last):
File "<console>", line 1, in <module>
File "/Users/mrmachine/django/template/base.py", line 139, in render
return self._render(context)
File "/Users/mrmachine/django/template/base.py", line 133, in _render
return self.nodelist.render(context)
File "/Users/mrmachine/django/template/base.py", line 819, in render
bits = []
File "/Users/mrmachine/django/template/debug.py", line 73, in
render_node
return node.render(context)
File "/Users/mrmachine/django/template/defaulttags.py", line 273, in
render
if var:
File "/Users/mrmachine/django/db/models/query.py", line 129, in
__nonzero__
iter(self).next()
File "/Users/mrmachine/django/db/models/query.py", line 117, in
_result_iter
self._fill_cache()
File "/Users/mrmachine/django/db/models/query.py", line 855, in
_fill_cache
self._result_cache.append(self._iter.next())
File "/Users/mrmachine/django/db/models/query.py", line 288, in iterator
for row in compiler.results_iter():
File "/Users/mrmachine/django/db/models/sql/compiler.py", line 704, in
results_iter
for rows in self.execute_sql(MULTI):
File "/Users/mrmachine/django/db/models/sql/compiler.py", line 750, in
execute_sql
sql, params = self.as_sql()
File "/Users/mrmachine/django/db/models/sql/compiler.py", line 64, in
as_sql
ordering, ordering_group_by = self.get_ordering()
File "/Users/mrmachine/django/db/models/sql/compiler.py", line 364, in
get_ordering
self.query.model._meta, default_order=asc):
File "/Users/mrmachine/django/db/models/sql/compiler.py", line 393, in
find_ordering_name
opts, alias, False)
File "/Users/mrmachine/django/db/models/sql/query.py", line 1289, in
setup_joins
"Choices are: %s" % (name, ", ".join(names)))
FieldError: Cannot resolve keyword 'invalid_field' into field. Choices
are: date_joined, email, first_name, groups, id, is_active, is_staff,
is_superuser, last_login, last_name, logentry, password, user_permissions,
username
}}}
I think the `{% if %}` tag needs to be a little less quick to silence
exceptions here, but there is also a problem with the way a `QuerySet`
will appear to be empty after an exception is raised.
First, we get an exception:
{{{
>>> qs = User.objects.order_by('abc')
>>> list(qs)
Traceback (most recent call last):
File "<console>", line 1, in <module>
File "/Users/mrmachine/django/db/models/query.py", line 86, in __len__
self._result_cache.extend(self._iter)
File "/Users/mrmachine/django/db/models/query.py", line 288, in iterator
for row in compiler.results_iter():
File "/Users/mrmachine/django/db/models/sql/compiler.py", line 704, in
results_iter
for rows in self.execute_sql(MULTI):
File "/Users/mrmachine/django/db/models/sql/compiler.py", line 750, in
execute_sql
sql, params = self.as_sql()
File "/Users/mrmachine/django/db/models/sql/compiler.py", line 64, in
as_sql
ordering, ordering_group_by = self.get_ordering()
File "/Users/mrmachine/django/db/models/sql/compiler.py", line 364, in
get_ordering
self.query.model._meta, default_order=asc):
File "/Users/mrmachine/django/db/models/sql/compiler.py", line 393, in
find_ordering_name
opts, alias, False)
File "/Users/mrmachine/django/db/models/sql/query.py", line 1289, in
setup_joins
"Choices are: %s" % (name, ", ".join(names)))
FieldError: Cannot resolve keyword 'abc' into field. Choices are:
date_joined, email, first_name, groups, id, is_active, is_staff,
is_superuser, last_login, last_name, logentry, password, user_permissions,
username
}}}
Then, we appear to have an empty queryset:
{{{
>>> list(qs)
[]
}}}
Even though the `Query` is still invalid:
{{{
>>> print qs.query
Traceback (most recent call last):
File "<console>", line 1, in <module>
File "/Users/mrmachine/django/db/models/sql/query.py", line 167, in
__str__
sql, params = self.sql_with_params()
File "/Users/mrmachine/django/db/models/sql/query.py", line 175, in
sql_with_params
return self.get_compiler(DEFAULT_DB_ALIAS).as_sql()
File "/Users/mrmachine/django/db/models/sql/compiler.py", line 64, in
as_sql
ordering, ordering_group_by = self.get_ordering()
File "/Users/mrmachine/django/db/models/sql/compiler.py", line 364, in
get_ordering
self.query.model._meta, default_order=asc):
File "/Users/mrmachine/django/db/models/sql/compiler.py", line 393, in
find_ordering_name
opts, alias, False)
File "/Users/mrmachine/django/db/models/sql/query.py", line 1289, in
setup_joins
"Choices are: %s" % (name, ", ".join(names)))
FieldError: Cannot resolve keyword 'abc' into field. Choices are:
date_joined, email, first_name, groups, id, is_active, is_staff,
is_superuser, last_login, last_name, logentry, password, user_permissions,
username
}}}
I think that this only becomes a problem when the exception is mistakenly
silenced or not handled correctly, as in the example with the `{% if %}`
tag. In most circumstances, the exception would be raised and further
processing would not occur.
However, I think we should still look at the possibility of NOT caching
results when a queryset fails to evaluate in this way. I do not think it
is appropriate to equate an invalid queryset with an empty one. If an
exception is raised once when trying to evaluate a queryset, the exception
should be cached or the queryset should be re-evaluated when it is
accessed again.
--
Ticket URL: <https://code.djangoproject.com/ticket/17664>
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 post to this group, send email to [email protected].
To unsubscribe from this group, send email to
[email protected].
For more options, visit this group at
http://groups.google.com/group/django-updates?hl=en.