[Django] #21584: prefetch_related child queryset does not update on create

84 views
Skip to first unread message

Django

unread,
Dec 9, 2013, 1:31:20 PM12/9/13
to django-...@googlegroups.com
#21584: prefetch_related child queryset does not update on create
----------------------------------------------+--------------------
Reporter: lucaswiman | Owner: nobody
Type: Uncategorized | Status: new
Component: Database layer (models, ORM) | Version: 1.6
Severity: Normal | Keywords:
Triage Stage: Unreviewed | Has patch: 0
Easy pickings: 0 | UI/UX: 0
----------------------------------------------+--------------------
When a child foreign key relationship has been prefetched, calling
the .create method on the queryset does not update the queryset.
I've reproduced this bug in Django 1.5.4 and Django 1.6.

How to reproduce:

models.py
{{{
#!python
from django.db import models

class Parent(models.Model):
pass


class Child(models.Model):
parent = models.ForeignKey(Parent)
}}}

In the shell:

{{{
>>> p = Parent.objects.create()
>>> list(p.child_set.all())
[]
>>> child = p.child_set.create()
>>> list(p.child_set.all())
[<Child: Child object>]
>>>
>>> p2 = Parent.objects.create()
>>> parents =
Parent.objects.filter(pk=p2.id).prefetch_related('child_set')
>>> [p2_prefetched] = parents
>>> list(p2_prefetched.child_set.all())
[]
>>> p2_prefetched.child_set.create()
<Child: Child object>
>>> list(p2_prefetched.child_set.all())
[]
}}}

The last expression should return a list with one child in it, but returns
an empty list instead.

--
Ticket URL: <https://code.djangoproject.com/ticket/21584>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

Django

unread,
Jan 17, 2014, 9:05:44 AM1/17/14
to django-...@googlegroups.com
#21584: prefetch_related child queryset does not update on create
-------------------------------------+-------------------------------------
Reporter: lucaswiman | Owner: nobody
Type: Uncategorized | Status: closed
Component: Database layer | Version: 1.6
(models, ORM) | Resolution: invalid
Severity: Normal | Triage Stage:
Keywords: | Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0

Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by lukeplant):

* status: new => closed
* needs_better_patch: => 0
* resolution: => invalid
* needs_tests: => 0
* needs_docs: => 0


Comment:

This is not a bug, I'm afraid. The results of DB queries that have already
been run are never updated automatically by the Django ORM, and that is by
design. (There may be some small exceptions, such as when you update a FK
object, and the FK ID on that object may be updated as well, but only
those that can be done with no dependency tracking). When you specify
'prefetch_related', you are specifying this exact behaviour i.e. the
'child_set.all()' is not lazy, but prefetched in a single query.

Changing this would really require an identity mapper, and a very
fundamental change to the way the Django ORM works.

--
Ticket URL: <https://code.djangoproject.com/ticket/21584#comment:1>

Django

unread,
May 16, 2014, 1:44:21 PM5/16/14
to django-...@googlegroups.com
#21584: prefetch_related child queryset does not update on create
-------------------------------------+-------------------------------------
Reporter: lucaswiman | Owner: nobody

Type: Uncategorized | Status: closed
Component: Database layer | Version: 1.6
(models, ORM) | Resolution: invalid
Severity: Normal | Triage Stage:
Keywords: | Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0

Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------

Comment (by lucaswiman):

> This is not a bug, I'm afraid.


It's very unintuitive that the child_set won't keep track of what
elements are in it when it is mutated by its own methods. This seems to
break the "iterable" abstraction.


>There may be some small exceptions, such as when you update a FK object,
and the FK ID on that object may be updated as well, but only those that
can be done with no dependency tracking


Isn't that precisely the behavior that's being described here? The
foreign key relationship should know that a new entry was created, because
the create method was called on the child_set itself. Create should
invalidate the cache, because there's no way the cache can remain valid
after create is called. Couldn't create set self._results_cache to None,
and self.prefetch_done to False when create is called?

--
Ticket URL: <https://code.djangoproject.com/ticket/21584#comment:2>

Django

unread,
Jun 13, 2014, 9:50:45 AM6/13/14
to django-...@googlegroups.com
#21584: prefetch_related child queryset does not update on create
-------------------------------------+-------------------------------------
Reporter: lucaswiman | Owner: nobody

Type: Uncategorized | Status: closed
Component: Database layer | Version: 1.6
(models, ORM) | Resolution: invalid
Severity: Normal | Triage Stage:
Keywords: | Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0

Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------

Comment (by lukeplant):

Sorry, that's just how the ORM works. Objects that represent collections
do not keep track of their elements, because they represent querysets i.e.
queries that may or may not yet have been evaluated, not actual
collections of objects. If you have:

{{{
#!python
my_objects = Foo.objects.all().filter(bar=1)
list(my_objects) # evaluate query
my_objects.update(bar=2)
}}}

then you will find that the 'update' has not affected anything in
`my_objects` - either by changing the instances, or by removing them from
the collection (since they no longer match the requirement `bar=1`).

In the same way, `p.child_set` does not keep track of elements that are
referred to. When you call `all()`, it executes a query every time,
(rather than tracking creates/adds/deletes etc.). If you have used
`prefetch_related`, however, it never executes a query when you just do
`all()` because it has been prefetched. This is exactly what
`prefetch_related` is supposed to do - the `all()` will not return data to
reflect what is in the DB at that moment in time, but what was in the DB
when the query was first evaluated.

--
Ticket URL: <https://code.djangoproject.com/ticket/21584#comment:3>

Django

unread,
Mar 31, 2017, 5:17:57 PM3/31/17
to django-...@googlegroups.com
#21584: prefetch_related child queryset does not update on create
-------------------------------------+-------------------------------------
Reporter: Lucas Wiman | Owner: nobody

Type: Uncategorized | Status: closed
Component: Database layer | Version: 1.6
(models, ORM) |
Severity: Normal | Resolution: invalid
Keywords: | Triage Stage:
| Unreviewed

Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0

Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------

Comment (by Nick Retallack):

But how do I bust the cache?

--
Ticket URL: <https://code.djangoproject.com/ticket/21584#comment:4>

Reply all
Reply to author
Forward
0 new messages