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:
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