Trying to execute some code when the Profile model gets updated but it appears the save method never gets called as the print statement never shows.
Profile.objects.filter(user__id=1).update(field_a='test')
models.py
class Profile(models.Model):
def save(self, *args, **kwargs):
print("Test")
super(Profile, self).save(*args, **kwargs)
No, the save method is not called. See the docs on update():
Finally, realize that update() does an update at the SQL level and, thus, does not call any save() methods on your models, nor does it emit the pre_save or post_save signals (which are a consequence of calling Model.save()). If you want to update a bunch of records for a model that has a custom save() method, loop over them and call save(), like this:
for e in Entry.objects.filter(pub_date__year=2010):
e.comments_on = False
e.save()
Related
Model:
class Tester:
test = models.ForeignKey(Platform)
count = models.Integer()
status = models.CharField(max_length=1, default='A')
I need to change the status to 'D' every time I insert a new record for the same test.
I tried using Signals pre_save, but that function went into a loop. I would really appreciate any help.
Signal fucntion probably goes into an infinite loop because you save the same model's instances in that function, in turn each one triggering the signal function itself. With a little care you can prevent this from happenning:
from django.db.models.signals import pre_save
#receiver(pre_save, sender=Tester)
def tester_pre_save(sender, instance, **kwargs):
if not instance.pk:
# This means that a new record is being created. We need this check as you want to do the operation when a new entry is **inserted** into table
Tester.objects.filter(test=instance.test).update(status='D')
or, with the post_save signal:
from django.db.models.signals import post_save
#receiver(post_save, sender=Tester)
def tester_post_save(sender, instance, created, **kwargs):
if created:
# This means that a new record has been created. We need this check as you want to do the operation when a new entry is **inserted** into table
Tester.objects.filter(test=instance.test).update(status='D')
Important points here is. as we are using update method of a query set to update existing entries, those won't trigger signals, because they don't use the model's save method, so that signal method won't be triggered for other instances we update here. And even if it were to be triggered, as we are doing the update operation under a condition check (if a new instance is being created), signal methods for those other saves wouldn't do anything, hence they would not cause an infinite loop.
Override the save method of our Tester class:
def save(self, *args, **kwargs):
if Tester.objects.filter(test=self.test).count()>0:
self.status="D"
else:
self.status="A"
super(Model, self).save(*args, **kwargs)
Put this into the class definition of Tester.
I have a simple class model with Django Admin (v. 1.9.2) like this:
from django.contrib.auth.models import User
class Foo(models.Model):
...
users = models.ManyToManyField(User)
bar = None
I have also overloaded save() method like this:
def save(self, *args, **kwargs):
self.bar = 1
async_method.delay(...)
super(Foo, self).save(*args, **kwargs)
Here async_method is an asynchronous call to a task that will run on Celery, which takes the users field and will add some values to it.
At the same time, whenever a user is added to the ManyToManyField, I want to do an action depending on the value of the bar field. For that, I have defined a m2m_changed signal:
def process_new_users(sender, instance, **kwargs):
if kwargs['action'] == 'post_add':
# Do some stuff
print instance.bar
m2m_changed.connect(process_new_users, sender=Foo.users.through)
And there's the problem. Although I'm changing the value of bar inside the save() method and before I call the asynchronous method, when the process_new_users() method is triggered, instance.bar is still None (initial value).
I'm not sure if this is because the save() method commits changes asynchronously and when the process_new_users() is triggered it has not yet commited changes and is retrieving the old value, or if I'm missing something else.
Is my assumption correct? If so, is there a way to force the values in save() be commited synchronously so I can then call the asynchronous method?
Note: Any alternative way of achieving this is also welcome.
UPDATE 1: As of #Gert's answer, I implemented a transaction.on_change() trigger so whenever the Foo instance is saved, I can safely call the asynchronous function afterwards. To do that I implemented this:
bar = BooleanField(default=False) # bar has became a BooleanField
def call_async(self):
async_method.delay(...)
def save(self, *args, **kwargs):
self.bar = True
super(Foo, self).save(*args, **kwargs)
transaction.on_commit(lambda: self.call_async())
Unfortunately, this changes nothing. Instead of None I'm now getting False when I should be getting True in the m2m_changed signal.
You want to make sure that your database is up to date. In Django 1.9, there is a new transaction.on_commit which can trigger celery tasks.
I am overriding the save method on one of my Models like so:
class Foo(models.Model):
...
def save(self, *args, **kwargs):
request = kwargs.pop('request', None)
super(Foo, self).save(*args, **kwargs)
some_stuff()
some_stuff() performs some queries which expect that the new/modified Foo object has been saved to the database (which I why I've put it after the super() call). However, I'm finding when some_stuff() runs the newly created/modified object is not present in the DB.
Is my understanding of when things are written to the DB incorrect? How else might I do this (I've considered signals, but all of this is within the same app, so that seems overkill)?
UPDATE: I've tried putting a signal receiver to see if that makes any difference; in fact it gets called before the super() call finishes, so the DB state is no different whether I override save() use a signal.
maybe you need call some_stuff() like this: self.some_stuff(). after Super(...), the self would be the object present in DB. And you can do some with self in your method.
I want to override a ModelForm's save() function so that it updates a field on the model if the user pressed a particular submit button. I also want to check through some other fields and update their values, and I've done this on the ModelAdmin's save_model() function. However, the save_model() function is being passed None for the object. If I comment out the form's save() function, then the save_model() function works as expected.
Is there an issue with overriding both, or have I made a mistake somewhere?
Here's a minimal example:
admin.py:
class TestAdmin(admin.ModelAdmin):
form = TestForm
def save_model(self, request, obj, form, change):
print 'test'
super(PostAdmin, self).save_model(request, obj, form, change)
admin.site.register(TestModel, TestAdmin)
forms.py:
class TestForm(forms.ModelForm):
class Meta:
model = TestModel
def save(self, force_insert=False, force_update=False, commit=True):
print 'test'
super(TestForm, self).save(commit=True)
Your ModelForm needs to return the instance.
As far as I remember, just prior to save_model, the admin does a save(commit=False) and passes the unsaved instance to save_model. If you don't return anything, save() == None.
return super(CategoryForm, self).save(commit=True)
If you are overriding the ModelAdmin.save_model, and not calling the super().save_model (in your example you are, I can see.), you should explicitly call the form.save().
If you are calling the ModelForm.save somehow, through super or explicit calling, I don't see why it wouldn't work; but I can tell you that if I were to override the save_model, my preference would be to limit myself to overriding the Model.save().
I have a situation where when one of my models is saved MyModel I want to check a field, and trigger the same change in any other Model with the same some_key.
The code works fine, but its recursively calling the signals. As a result I am wasting CPU/DB/API calls. I basically want to bypass the signals during the .save(). Any suggestions?
class MyModel(models.Model):
#bah
some_field = #
some_key = #
#in package code __init__.py
#receiver(models_.post_save_for, sender=MyModel)
def my_model_post_processing(sender, **kwargs):
# do some unrelated logic...
logic = 'fun! '
#if something has changed... update any other field with the same id
cascade_update = MyModel.exclude(id=sender.id).filter(some_key=sender.some_key)
for c in cascade_update:
c.some_field = sender.some_field
c.save()
Disconnect the signal before calling save and then reconnect it afterwards:
post_save.disconnect(my_receiver_function, sender=MyModel)
instance.save()
post_save.connect(my_receiver_function, sender=MyModel)
Disconnecting a signal is not a DRY and consistent solution, such as using update() instead of save().
To bypass signal firing on your model, a simple way to go is to set an attribute on the current instance to prevent upcoming signals firing.
This can be done using a simple decorator that checks if the given instance has the 'skip_signal' attribute, and if so prevents the method from being called:
from functools import wraps
def skip_signal(signal_func):
#wraps(signal_func)
def _decorator(sender, instance, **kwargs):
if hasattr(instance, 'skip_signal'):
return None
return signal_func(sender, instance, **kwargs)
return _decorator
Based on your example, that gives us:
from django.db.models.signals import post_save
from django.dispatch import receiver
#receiver(post_save, sender=MyModel)
#skip_signal()
def my_model_post_save(sender, instance, **kwargs):
instance.some_field = my_value
# Here we flag the instance with 'skip_signal'
# and my_model_post_save won't be called again
# thanks to our decorator, avoiding any signal recursion
instance.skip_signal = True
instance.save()
Hope This helps.
A solution may be use update() method to bypass signal:
cascade_update = MyModel.exclude(
id=sender.id).filter(
some_key=sender.some_key).update(
some_field = sender.some_field )
"Be aware that the update() method is converted directly to an SQL statement. It is a bulk operation for direct updates. It doesn't run any save() methods on your models, or emit the pre_save or post_save signals"
You could move related objects update code into MyModel.save method. No playing with signal is needed then:
class MyModel(models.Model):
some_field = #
some_key = #
def save(self, *args, **kwargs):
super(MyModel, self).save(*args, **kwargs)
for c in MyModel.objects.exclude(id=self.id).filter(some_key=self.some_key):
c.some_field = self.some_field
c.save()