At what point does django write an object to the database? - django

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.

Related

Create postgresql command based on DELETE REST call within Django

So I have roughly the following:
class exampleSerializer(serializers.ModelSerializer):
def delete(self, request, *args, **kwargs):
//Custom code
The goal is to access the data of the row that the delete function is gonna delete. Based on this information I need to make some extra PostgreSQL commands before the functions finishes. How can I access this data?
Are you using a ModelViewset?
It’s best to execute your code there.
Something like this:
class YourViewSet(ModelViewSet):
def perform_destroy(self, instance):
# your code here
super().perform_destroy(instance)

Unable to override model save()

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()

Is save() method commiting changes asynchronously?

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.

Overridden save() method behavior not using super().save() method

I have a model with a customized save() method that creates intermediate models if the conditions match:
class Person(models.Model):
integervalue = models.PositiveIntegerField(...)
some_field = models.CharField(...)
related_objects = models.ManyToManyField('OtherModel', through='IntermediaryModel')
...
def save(self, *args, **kwargs):
if self.pk is None: # if a new object is being created - then
super(Person, self).save(*args, **kwargs) # save instance first to obtain PK for later
if self.some_field == 'Foo':
for otherModelInstance in OtherModel.objects.all(): # creates instances of intermediate model objects for all OtherModels
new_Intermediary_Model_instance = IntermediaryModel.objects.create(person = self, other = otherModelInstance)
super(Person, self).save(*args, **kwargs) #should be called upon exiting the cycle
However, if editing an existing Person both through shell and through admin interface - if I alter integervalue of some existing Person - the changes are not saved. As if for some reason last super(...).save() is not called.
However, if I were to add else block to the outer if, like:
if self.pk is None:
...
else:
super(Person, self).save(*args, **kwargs)
the save() would work as expected for existing objects - changed integervalue is saved in database.
Am I missing something, or this the correct behavior? Is "self.pk is None" indeed a valid indicator that object is just being created in Django?
P.S. I am currently rewriting this into signals, though this behavior still puzzles me.
If your pk is None, super's save() is called twice, which I think is not you expect. Try these changes:
class Person(models.Model):
def save(self, *args, **kwargs):
is_created = True if not self.pk else False
super(Person, self).save(*args, **kwargs)
if is_created and self.some_field == 'Foo':
for otherModelInstance in OtherModel.objects.all():
new_Intermediary_Model_instance = IntermediaryModel.objects.create(person = self, other = otherModelInstance)
It's not such a good idea to override save() method. Django is doing a lot of stuff behind the scene to make sure that model objects are saved as they expected. If you do it in incorrectly it would yield bizarre behavior and hard to debug.
Please check django signals, it's convenient way to access your model object information and status. They provide useful parameters like instance, created and updated_fields to fit specially your need to check the object.
Thanks everyone for your answers - after careful examination I may safely conclude that I tripped over my own two feet.
After careful examination and even a trip with pdb, I found that the original code had mixed indentation - \t instead of \s{4} before the last super().save().

django: ModelForm access instance of new object

I wanted to ask if there is a way to access instance id in ModelForm save method. (Need an object in order to add some extra data).
def save(self, *args, **kwargs):
instance = super(MyForm, self).save(*args, **kwargs)
print instance
return instance
And in all cases I am getting instance before it's saved in database (so it does not have an id and I can't attach objects to it)
It isn't necessary to override the ModelForm save() function. Instead, it's better to call save with commit=False. The Django docs explain this in depth, but here's a quick example:
new_object = form.save(commit=False)
new_object.name = 'Whatever'
new_object.save()
By calling save with commit=False, you get an object back. You can do whatever you want with this object, but make sure to save it once you make your changes!