Reputation: 1812
Background
I have model C inherits B
In C, it has just normal charField, nullBooleanField and foreign keys.
class C(B):
uid = UUIDField(unique=True)
x = models.ForeignKey(X, to_field='uid')
y = models.ForeignKey(Y, to_field='uid', null=True)
z = models.TextField()
u = models.ForeignKey(U, null=True)
foo = models.NullBooleanField(default=False)
In B, it has bunch of dateField like below
class B(models.Model):
date_added = models.DateTimeField(auto_now_add=True)
date_modified = models.DateTimeField(auto_now=True)
date_deactivated = models.DateTimeField(null=True, blank=True)
active = models.BooleanField(default=True, db_index=True,
help_text='Set to false if the instance is deleted.')
class Meta:
abstract = True
None of those classes has meta order specified.
The problem
When I was in a shell_plus, I assigned C.objects.all()
to a variable called cs
, then I can see cs[0]
, cs[1]
, cs[2]
and cs[3]
. There are only 4 records. Now I want to change one nullBooleanField to false/true. I did cs[1].foo = true
and cs[1].save()
I would expect 4 records are in the same order as when I fetched them and the second record's foo field is updated.
The actual result is, for some reasons, the second record is moved to the back of the list, and it doesn't remember my changes.
For example, in the beginning, 4 records are R1, R2, R3, R4 and R2.foo is false. After the assigning foo to true for the second record as I describe above, the result is R1, R3, R4, R2 and R2.foo is still false.
The workaround seems to be assigning cs[1] to a variable first then change the variable. It would be nice the know why.
Is there anything in Django that can cause queryset result to be rearranged?
Edit does each queryset[index] cause a database hit?
Upvotes: 1
Views: 155
Reputation: 477190
The problem: qs
is a QuerySet
. If you write qs[i]
, you actually make another query (one that looks like <OLD QUERY> LIMIT 1 OFFSET i
), so you fetch the i-th element from the old queryset into memory.
The point is that if you write qs[i]
twice, you make two independent fetches. If we thus write two qs[1]
s, we obtain two different objects. If you thus perform a qs[1].save()
, you make an independent fetch, load the object from the database, and immediately save it. But previous modifications that have not been saved will have no effect.
If you thus want to make a change to an object, you need to use a reference, so write:
# will change the second element
cs1 = cs[1] # we fetch the object, and store it in cs1
cs1.foo = True # we change the object
cs1.save() # we save the object
instead of:
# will *NOT* change the second element
cs[1].foo = True # load object, change it, but throws the object away
cs[1].save() # loads the object again, and save it, with no change
The fact that the order of the objects changes between two queries is common in many database engines (well usually given some row is updated, or even if a new row is created). It depends on the indexes a database has how the rows are obtained, and often rows that are changed will occur as last (or first) row in the result set, although if you do not specify the order, usually there are no strong guarantees how the result will be ordered.
Upvotes: 1