Opened 8 years ago

Closed 8 years ago

#26364 closed Bug (wontfix)

Updating an object in a queryset with save() leads to bug using that queryset as a lookup filter

Reported by: jonathan-golorry Owned by: nobody
Component: Database layer (models, ORM) Version: 1.9
Severity: Normal Keywords:
Cc: Triage Stage: Unreviewed
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

If an object in a queryset is updated using the save() method, it is updated for future accesses to that queryset, implying the queryset is storing the object properly. However, if the queryset is used as an object set in a filter for a future lookup, the queryset does not represent the updated objects properly.

Partial example:

class Foo(models.Model):
    state = models.CharField(max_length=30)

class Bar(models.Model):
    parent = models.OneToOneField(Foo)
foos = Foo.objects.all()
for foo in foos:
    foo.state = "Updated"
    foo.save()
bars = Bar.objects.filter(parent__in=foos)

Because the objects are updated before they are used in the Bar filter, they aren't recognized and the bars queryset will be empty.

Change History (5)

comment:1 by jonathan-golorry, 8 years ago

Component: UncategorizedDatabase layer (models, ORM)

comment:2 by jonathan-golorry, 8 years ago

Type: UncategorizedBug

comment:3 by Tim Graham, 8 years ago

The following test case passes for me. Did I do something wrong in trying to reproduce the issue?

f1 = Foo.objects.create(state='A')
f2 = Foo.objects.create(state='B')
Bar.objects.create(parent=f1)
Bar.objects.create(parent=f2)

foos = Foo.objects.all()
for foo in foos:
    foo.state = "Updated"
    foo.save()
bars = Bar.objects.filter(parent__in=foos)
self.assertEqual(len(bars), 2)

comment:4 by Anssi Kääriäinen, 8 years ago

I believe the foos queryset should have a exclude() on state='Updated' so that the foos are in the original queryset but not in the queryset after update.

I'm afraid there isn't much we can do to fix this. The parent__in=foos filter creates a subquery on foos (not using the existing objects), and the behavior has been like this forever. Note that if you instead do Bar.objects.filter(parent__in=list(foos)) then you get the expected results.

[In retrospect it might have been better if there was an .execute() method on querysets. This way it would be much clearer what is happening. Consider

foos = Foo.objects.all()
for foo in foos.execute():
    foo.state = "Updated"
    foo.save()
bars = Bar.objects.filter(parent__in=foos)

vs

foos = Foo.objects.all().execute()
for foo in foos:
    foo.state = "Updated"
    foo.save()
bars = Bar.objects.filter(parent__in=foos)

The latter is clearly using the executed queryset, while the former executes the queryset just for the for loop. I believe changing this is way too abusive for the gained clarity.]

comment:5 by Tim Graham, 8 years ago

Resolution: wontfix
Status: newclosed
Note: See TracTickets for help on using tickets.
Back to Top