Django - Query on overridden save() method cached or something

I am overriding the default save() method of my model in Django 4.
The problem is that some queries related to the model of this method (Routine) are not updated after the super().save() call.

Example:

class Routine(models.Model):

    exercises = models.ManyToManyField(Exercise)

    def save(self, *args, **kwargs):
        e = self.exercises.all()  # 3 exercises returned
        super().save(*args, **kwargs)  # Here I am deleting 1 exercise and adding a new one
        e = self.exercises.all()  # same 3 exercises returned, not updated with the previous save() call

The exercises are successfully updated but the second self.exercises.all() doesn’t return the actual exercises after the save() call, it gives the same result as before the save() call. Is this some kind of cache? Thanks in advance.

There’s nothing that would change exercises when Routine is being saved.

Any rows of Exercise that are related to an instance of Routine are still going to be related to that same Routine even after Routine is saved.

What possible differences would you expect to see?

Keep in mind that although exercises is defined as a field in Routine, it does not actually represent data in the table that is referenced by Routine. That relational information is stored in a “join table” that has foreign keys to both Routine and Exercise.

I mean, I am altering the many-to-many relationships while I am saving the Routine.
Screenshot:
image
I expect to see the new instances of the many-to-many after the Routine instance is saved.

I know about the many-to-many table with the two FKs that you are talking about, but how can I then get the changes of this table?

But the save method for Routine isn’t going to save those changes.

We need to see more of the code involved. We will at least need to see the form and the view being used to support this.

(In the common-case, the save for the related data occurs after the save method on the model itself has returned.)

There is no form since it’s just the Django admin. Is there any way to override the many to many relationship save()?

Well, there actually is a form - it’s one that the Django admin creates.

But anyway, we’d need to see your Django ModelAdmin class for this.

from django.contrib import admin
from .models import (Exercise, Routine)

class ExerciseAdmin(admin.ModelAdmin):
    pass

class RoutineAdmin(admin.ModelAdmin):
    pass

admin.site.register(Routine, RoutineAdmin)
admin.site.register(Exercise, ExerciseAdmin)

That’s it!

After you make one of these changes, if you go back to that page, do you see the updated data?

Yes! I see the updated data. I don’t get why I cannot see it in the debugger after the super().save() call though.
Maybe this info is also useful:

Both Routine and Exercise models inherit my abstract class:

class Routine(NamedModel):
    ...

And this class is:

class AuditedModel(models.Model):
    """
    Model that adds utility fields such as time created and last time
    modified.
    """

    created = models.DateTimeField(auto_now=True)
    modified = models.DateTimeField(auto_now_add=True)

    class Meta:
        abstract = True
        get_latest_by = 'created'
        ordering = ['-created', '-modified']


class NamedModel(AuditedModel):
    """
    Adds the standarized name field which is unique and saved uppercase.

    Also provides the functionality to hide.
    """

    name = models.CharField(
        max_length=300,
        unique=True,
        error_messages={'unique': 'Nombre duplicado.'}
    )
    hidden = models.BooleanField(default=False)

    def save(self, *args, **kwargs):
        try:
            self.name = self.name.upper()
        except:
            pass
        return super().save(*args, **kwargs)

    class Meta(AuditedModel.Meta):
        abstract = True
        ordering = ['name', '-created', '-modified']

    def __str__(self) -> str:
        return self.name.title()

As I pointed out earlier:

It’s not a caching or model definition issue. It’s simply the sequence of events within the normal Django operations.

When the relationship between two models is modified, that modification occurs after the save method on the base model itself has returned, and is performed as a separate step within the process.

So, at the point at which you’re looking at the code, you would not expect the many-to-many relationships to change.

(Effectively, you’re looking at results “half-way” through the save process for that form. Only part of the save has been completed.)

1 Like

I get it, it makes 100% sense to me now. Is there a way to override the admin’s form submit? So that I can make the DB updates after all the instances have been saved/updated.

I think the def save_form(self, request, form, change): could save me, thank you! You really helped me to find the proper way to make it work.

That’s a completely separate question.

You have a rather comprehensive set of ModelAdmin methods that you can override to add functionality to those views. I’m sure there are a couple of them that would be suitable candidates for what you’re looking to do there.

See the docs at ModelAdmin methods for your options.

1 Like