#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.

Reply via email to