Django ManyToMany access in save() function - python

I want to create additional objects based on user input during the .save() method of my model. the info is stored in a ManyToMany-relation .available_specs.
My code works, if one presses save twice, but not at the first time. The problem is described in the comments here Django foreign key access in save() function, too.
Here is what I do:
def save(self, *args, **kwargs):
my_save_result = super().save(*args, **kwargs) #save first to create an id
self.refresh_from_db() #just as a test, did not help at all
#the following line is the problem:
# in the first save, available_specs.all() returns []
# when I re-open the model and save again, I get the correct list
for spec in self.available_specs.all():
VehicleSpec.objects.get_or_create(vehicle=self, spec=spec)
return my_save_result
I'm using Python 3.5 and Django 1.9
Edit:
I tried to use the post_save() signal using the following code (and removed the overridden save()-method):
#receiver(post_save, sender=VehicleModel)
def create_dependent_vehicle_specs(sender, **kwargs):
vehicle = kwargs['instance']
#in the first save-process I get an empty list here,
#after I hit save again, the code works.
print(vehicle, vehicle.available_specs.all())
for spec in vehicle.available_specs.all():
VehicleSpec.objects.get_or_create(vehicle=vehicle, spec=spec)
Edit2: m2m_changed did the trick:
#receiver(m2m_changed, sender=VehicleModel)
def create_dependent_vehicle_specs(sender, **kwargs):
vehicle = kwargs['instance']
for spec in vehicle.available_specs.all():
VehicleSpec.objects.get_or_create(vehicle=vehicle, spec=spec)
m2m_changed.connect(create_dependent_vehicle_specs, sender=VehicleModel.available_specs.through) # #UndefinedVariable
BTW: A related question is here: Django accessing ManyToMany fields from post_save signal

You need to use a m2m_changed signal. You should not be calling another models' get_or_create in another models' save method

Related

Django: overriding insert, update, delete in a model

Sometimes you want to do something when there is an event for model (on create object, on update object, on delete object).
There is a method you can override in Model called save. And it even has a parameter forced_insert which I first thought would always be set to a proper value meaning whether an object will be created or updated. But the parameter is optional and you cannot expect it to be right.
Searching in source code of Model led me to methods _do_update and _do_insert, but the underscore at the beginning tells me that this method is not recommended for use. And it also have a lot of parameters which pollute code when you override it.
The only solution left that I could find is using django.db.models.signals. But I believe they are meant to be used for external purposes like when you want to create a UserProfile on every User create. But I have some internal purposes like updating fields on update. Also using signals makes code look spreaded and harder to understand.
What would be the right way deal with implementing functional on these Model events?
Look at this simplified condition from django base Model _save_table method - decision to update or insert depends on model pk and force_insert value:
def _save_table(self, raw=False, cls=None, force_insert=False,
force_update=False, using=None, update_fields=None):
updated = False
# ...
if pk_set and not force_insert:
updated = self._do_update()
if not updated:
result = self._do_insert()
# ...
return updated
And you can go that same way in your overrided save method if you want entirely custom update/insert operations:
def save(self, force_insert=False, **kwargs):
updated = False
if self.pk and not force_insert:
updated = self.custom_update()
if not updated:
self.custom_insert()
return updated

Django - NEVER update a column when saving

I am trying to use citus data (https://www.citusdata.com/) with Django.
Most everything is working so far, except trying to save a model that has already been saved:
NotSupportedError: modifying the partition value of rows is not allowed
This is because django always includes every single field in the update SQL, even if that field has not changed.
In Citus, you must pick a field to be your partitioning field, and then you cannot change it. So, when I'm saving an object, it doesn't like that the partition key is in the update statement, even if it didn't change.
I know that you can pass the update_fields keyword arg to the save method, but I'm wondering if I can somehow tell django to NEVER include a field when updating?
Django does not provide this functionality "out of the box". You could override the save method of your class to set all fields other than your partition field as the value for update_fields
def save(self, **kwargs):
kwargs.setdefault('update_fields', ['field1', 'field2'])
return super(Class, self).save(**kwargs)
A more dynamic option, if you do not want to update this method everytime you change the fields of your class, would be to use the Meta API to get all fields of the class and exclude your partition field
def save(self, **kwargs):
kwargs.setdefault(
'update_fields',
[f.name for f in self.__class__._meta.get_fields() if f.name != 'partition_field']
)
return super(Class, self).save(**kwargs)
There are several other methods by which Django will attempt to update your models. Maybe a base class that all your models inherit from that implement these methods would work

ManyToManyField is empty in post_save() function

When a new record is added to a table, I have to execute a SQL statement on an external database. This query includes the use of a ManyToManyField. So I just connected the function like this:
post_save.connect(post_save_mymodel, sender=MyModel)
And in my post_save_mymodel() function, here is what I do:
def post_save_mymodel(sender, instance, created, *args, **kwargs):
if created:
for e in instance.my_m2mfield.all():
# Query including "e".
But, too bad, instance.my_m2mfield.all() is always empty! Even though they should contain some elements! I tried to get the new element by doing
new_element = MyModel.objects.get(pk=instance.pk)
but it doesn't change anything, I've still got the same issue...
Wny help/advice?
This is because you first save your instance and after that you add m2m relations to it. This is how ManyToMany fields work in Django data models. Django needs to know the ID of the items that should be connected with m2m relations.
I think that your code looks like this:
instance = MyModel.objects.create(some_field=some_value) # post save signal triggered here
instance.my_m2mfield = my_set_of_m2m_models
You need to connect your handler to the django.db.models.signals.m2m_changed signal. See the docs. For example:
def post_save_mymodel(sender, instance, action, reverse, *args, **kwargs):
if action == 'post_add' and not reverse:
for e in instance.my_m2mfield.all():
# Query including "e"
m2m_changed.connect(post_save_mymodel, sender=MyModel.my_m2mfield.through)

django: How to save a many to many field from view

I have a model with the following save method
def save(self, *args, **kwargs):
self.url = slugify(self.name)
# Saving creative if object is saved first time
if self.reloadImages is True:
print "Reload IMages"
for img in self.creative_url.split("\n"):
content = urllib2.urlopen(img).read()
extra_image = ExtraImage()
extra_image.img.save("%s.jpg" %(self.name), ContentFile(content), save=False)
extra_image.save()
self.extra_imgs.add(extra_image)
#print self.extra_imgs.all()
#self.reloadImages = False
super(Item, self).save(*args, **kwargs)
I expect it to create an ExtraImage objects (which consists from ImageField). And add it to the ManyToMany extra_img field of the current model....
It's strange it seems that method makes its job correctly, after page refresh I don't see anything in extra_imgs field. (And ExtraImage items are actually created)
The problem is that you add images
self.extra_imgs.add(extra_image)
before self is created in your database
super(Item, self).save(*args, **kwargs)
To add on to San4ez's answer, this problem (the fact that you have to save an object before you save its dependencies, and the dependency doesn't always go the way you expect it to) frustrated me when I first started learning Django.
If you have large object graphs (or even small ones :) ) and you don't want to deal with this manually, check out Django ORM Tools which has a GraphSaver class which will automatically detect dependencies and save the graph in the right order.

Is there a better way to create this model? (Django)

I have a model that pings a REST service and saves the results.
class StoreStatus(models.Model):
store = models.OneToOneField(Store)
status = models.TextField()
def save(self, *args, **kwargs):
self.status = get_store_information(self.store.code)
self.pk = self.store.pk
super( StoreStatus, self ).save(*args, **kwargs)
I need to run it every repeatedly and figure I can just .save() it in a view, since the "Store" object is in the majority of my views.
Is there a better way to do this? I had to the set the pk manually because I was getting duplicate errors when I tried to save a second time.
Seems kind of dirty, and I'm trying to improve my coding.
Thanks
That looks quite bad.
First, associating the retrieval of the status information with the saving of the object is quite a bad idea. If 'updating' is the only action you'll ever perform on this model, then maybe a better idea would be to write a an "update()" method that automatically saves once the status is updated, instead of doing it the other way around.
def update(self):
self.status = get_store_information(self.store.code)
self.save()
Second: how are you creating the first instance of this model? You'll get duplication errors if you are trying to save a new instance every time the model gets updated. Say, if you do something like this:
# this will crap out
update = Update(mystore)
update.save()
What you should be doing is something like:
# this will work (provided we have 'update')
mystore.status.update()
Or:
# always retrieve the stored instance before saving
status, created = StoreStatus.objects.get_or_create(store=mystore)
status.update()
In case you are lazy enough, you can always add an "update_status" method to your Store model and perform the creation/update there. It's always better to be explicit about what you are doing. Remember: Django is based upon the principle of least surprise, and so should your code! :)
If I were you, I would have created a function that would:
1. Accept the Store object as a parameter,
2. Make the REST call, and
3. On receiving the response then update the status in the StoreStatus.
This would be desirable to enable loose coupling which is required for architectures involving web-based services.
Moreover, if you just want to avoid duplicate PK errors, you can check for id to safely loop the update and create conditions.
def save(self, *args, **kwargs):
if self.id:
# Update case
pass
else:
# New object
# Process for the new object
pass
# Save the changes
super(StoreStatus, self).save(*args, **kwargs)

Categories