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.
Related
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
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
How I can replace one image uploaded by an ImageField (in my models) with the new one selected by the same ImageField?
And, can I delete all images uploaded by an ImageField when I delete the model object (bulk delete)?
After hours of searching I found this code.
It worked for me in Django 3
class Image(models.Model):
image = models.ImageField()
def save(self, *args, **kwargs):
try:
this = Image.objects.get(id=self.id)
if this.image != self.image:
this.image.delete(save=False)
except:
pass # when new photo then we do nothing, normal case
super().save(*args, **kwargs)
This might get tricky,
And a lot of times depends on what are your constraints.
1. Write your own save method for the model and then delete the old image a and replace with a new one.
os.popen("rm %s" % str(info.photo.path))
Write a cron job to purge all the unreferenced files. But then again if disk space is a issue, you might want to do the 1st one, that will get you some delay in page load.
After the image was deleted, I needed to remove it from filesystem.
I used signal for this.
It works well until I found some strange behaviour.
If in django admin I select another file (so, I change image file) and simultaneously choose delete image then image is not removed from filesystem/ It looks like django tries to delete the file that is in POST data.
Is there any way to delete old file?
If I am understanding your question correctly, you need to delete the old image when you replace an image in Django admin.
For this a delete signal will not help since the model instance is never deleted but only a single attribute is changed. For this you need a way to figure out if the model instance is dirty (any fields have changed) when saving.
You can use django-dirtyfields to do this.
class FooModel(DirtyFieldsMixin, models.Model):
image = models.ImageField(upload_to='...')
...
def changeremove(sender, instance, *args, **kwargs):
if instance.is_dirty():
name = 'image'
new = getattr(instance, name)
old = instance.get_dirty_fields().get(name, None)
if old:
old.delete(save=False)
setattr(instance, name, new)
post_save.connect(changeremove, sender=FooModel)
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)