#22382: ManyRelatedManager's get_prefetch_queryset doesn't validate the prefetch
types
----------------------------------------------+--------------------
     Reporter:  Keryn Knight <django@…>       |      Owner:  nobody
         Type:  Bug                           |     Status:  new
    Component:  Database layer (models, ORM)  |    Version:  master
     Severity:  Normal                        |   Keywords:
 Triage Stage:  Unreviewed                    |  Has patch:  0
Easy pickings:  0                             |      UI/UX:  0
----------------------------------------------+--------------------
 
[https://github.com/django/django/blob/a2407c9577c400bf9931ff1db1d7757afa378162/django/db/models/fields/related.py#L898
 the return value] for `get_prefetch_queryset` includes two lambda
 functions, one for
 
[https://github.com/django/django/blob/a2407c9577c400bf9931ff1db1d7757afa378162/django/db/models/query.py#L1909
 setting the resulting tuple] as a key in the `rel_obj_cache` and another
 for
 
[https://github.com/django/django/blob/a2407c9577c400bf9931ff1db1d7757afa378162/django/db/models/query.py#L1913
 getting it back later].

 The
 
[https://github.com/django/django/blob/a2407c9577c400bf9931ff1db1d7757afa378162/django/db/models/fields/related.py#L900
 one which is used for retrieving] the data uses `getattr` while the other
 pulls the data from the `_prefetch_related_val_...` attribute returned as
 part of the `QuerySet.extra` call. The prefetched values do not
 necessarily correlate to the intended types, however, and so values can
 end up mismatched.

 By way of example, and how I discovered this, [https://github.com/dcramer
 /django-uuidfield django-uuidfield] returns a `StringUUID` in it's
 [https://github.com/dcramer/django-
 uuidfield/blob/master/uuidfield/fields.py#L138 to_python] method, which is
 what ends up being returned
 
[https://github.com/django/django/blob/a2407c9577c400bf9931ff1db1d7757afa378162/django/db/models/fields/related.py#L900
 as instance_attr] - something like
 `(UUID('7d917781c54e4fdfa551a693c3782380'),)` but the
 
[https://github.com/django/django/blob/a2407c9577c400bf9931ff1db1d7757afa378162/django/db/models/fields/related.py#L899
 rel_obj_attr] doesn't take into account the `to_python` for the relation,
 and so returns `(u'7d917781c54e4fdfa551a693c3782380',)` - the naive
 strings from the database.

 Thus, the key `(u'7d917781c54e4fdfa551a693c3782380',)` never exists in the
 `rel_obj_cache`, and Django assumes everything went well (because it just
 sets a default of `[]`). Future queries to the relation
 (`x.relation.all()`) will yield nothing - they won't trigger a query
 because they've been prefetched, but they won't be populated because of
 the type mismatch in the dictionary keys.

 Arguably the problem exists downstream in `django-uuidfield`, but the
 problem *also* exists in Django, and may exist as an edge-case in other
 third party fields - I've marked it as master, but the type-mismatch issue
 exists from 1.4 (attrgetter version) to 1.6 (list comprehension version)
 and looks to still be there in master (generator expression version)

 The fix that seems to work for me is transforming:
 {{{
 lambda result: tuple(getattr(result, '_prefetch_related_val_%s' %
 f.attname) for f in fk.local_related_fields),
 }}}
 into:
 {{{
 lambda result: tuple(f.rel.get_related_field().to_python(getattr(result,
 '_prefetch_related_val_%s' % f.attname)) for f in
 fk.local_related_fields),
 }}}

 I'm not sure if any of the equivalent methods on other classes would be
 affected.

-- 
Ticket URL: <https://code.djangoproject.com/ticket/22382>
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/080.e139f0603281d8a1eba8c34beec2c014%40djangoproject.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to