#23001: Annotation breaks with deferred fields and select_related
----------------------------------------------+----------------------
     Reporter:  smeatonj                      |      Owner:  smeatonj
         Type:  Uncategorized                 |     Status:  new
    Component:  Database layer (models, ORM)  |    Version:  master
     Severity:  Normal                        |   Keywords:
 Triage Stage:  Unreviewed                    |  Has patch:  0
Easy pickings:  0                             |      UI/UX:  0
----------------------------------------------+----------------------
 I've discovered a bug that I think was introduced by:
 
https://github.com/django/django/commit/0b6f05ede648ce62a5c91c7c38a0a362711f0656

 The problem manifests as annotations being assigned an incorrect value
 when there are deferred fields and select_related is in use.

 Passing Test:

 {{{
 def test_annotate_defer(self):
         qs = Book.objects.annotate(
             page_sum=Sum("pages")).defer('name').filter(pk=1)

         rows = [
             (1, "159059725", 447, "The Definitive Guide to Django: Web
 Development Done Right")
         ]
         self.assertQuerysetEqual(
             qs.order_by('pk'), rows,
             lambda r: (r.id, r.isbn, r.page_sum, r.name)
         )
 }}}

 Failing Test:

 {{{
 def test_annotate_defer_select_related(self):
         qs = Book.objects.select_related('contact').annotate(
             page_sum=Sum("pages")).defer('name').filter(pk=1)

         rows = [
             (1, "159059725", 447, "Adrian Holovaty",
             "The Definitive Guide to Django: Web Development Done Right")
         ]
         self.assertQuerysetEqual(
             qs.order_by('pk'), rows,
             lambda r: (r.id, r.isbn, r.page_sum, r.contact.name, r.name)
         )
 }}}

 The problem is in the iterator method of django.db.models.query, which
 results in the aggregate_start variable being off by len(deferred):

 {{{
 if load_fields and not fill_cache:
             # Some fields have been deferred, so we have to initialize
             # via keyword arguments.
             skip = set()
             init_list = []
             for field in fields:
                 if field.name not in load_fields:
                     skip.add(field.attname)
                 else:
                     init_list.append(field.attname)
             model_cls = deferred_class_factory(self.model, skip)
         else:
             model_cls = self.model
             init_list = [f.attname for f in fields]
 }}}

 fill_cache is set to true when select_related is in use, which skips to
 the else block. The else block doesn't take deferred fields into account
 like the if block does. I'll attempt to patch this by including similar
 logic in the else block (skipping deferred fields), but I'm not yet sure
 what else that might affect.

-- 
Ticket URL: <https://code.djangoproject.com/ticket/23001>
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 post to this group, send email to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/django-updates/051.42cb7d4a0b497b2de81992321163152a%40djangoproject.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to