Question

I am wondering how I can make sure that all model instances in a loop over a queryset are up to date in every step of the loop.

In a situation where I have a model with a name attribute and I update the name attributes in the loop, the changes made in the loop itself are not picked up by the queryset. The queryset is probably cached at the beginning of the loop and not updated during the loop.

How can I update the main loop queryset such that I get the desired result in the example below?

myapp/models.py

class mymodel(models.Model):
  name = models.TextField()

Loop in shell

from myapp.models import mymodel

mymodel.objects.create(name='A')
mymodel.objects.create(name='BAB')
mymodel.objects.create(name='CBB')

for mod1 in mymodel.objects.all():
    # Loop over all mod2
    for mod2 in mymodel.objects.filter(name__contains=mod1.name).exclude(id=mod1.id):

        # Remove mod1.name from mod2.name
        mod2.name = ''.join(mod2.name.split(mod1.name))

        # Save mod2
        mod2.save()

[mod1.name for mod1 in mymodel.objects.all()]

Result

[u'A', u'BB', u'CBB']

Desired result (the second item becomes BB after the first loop iteration, and this can be filtered out of CBB in the second step of the iteration, leating to C).

Out: [u'A', u'BB', u'C']

I tried using iterator() for the queryset, but that did not change the result.

Was it helpful?

Solution

The reason why your code doesn't work is because the query in the outer loop is executed once at the start, but each object can be updated in the inner loop, which causes them to be stale.

As an example, after you create all your objects, you have ["A", "BAB", "CBB"]. When mod1 is "A", it changes "BAB" to "BB", but the second object in your outer loop has already been loaded, so when the second iteration of the outer loop occurs it queries the database for name__contains="BAB" instead of name__contains"BB", as you expect.

You can fix this by mutating all your objects in memory (assuming your table is small enough):

objects = list(mymodel.objects.all())
for mod1 in objects:
    for mod2 in objects:
        if mod1.name in mod2.name and mod1.id != mod2.id:
            mod2.name = ''.join(mod2.name.split(mod1.name))

for mod1 in objects:
    mod1.save()
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top