django replace old image uploaded by ImageField - python

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.

Related

Django ManyToMany access in save() function

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

Django admin on delete image (bug or error)

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)

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.

Copy the contents of an ImageField to a new file path in Django

I have a Django model object that has a few normal attributes, and it has a ImageField for the logo. I want to write a method that'll copy this object to a new object. It's easy enough to instanciate the new object, then loop over all the attributes and copy from the old to new (e.g. new_object.name = old_object.name). However if I do that with the logo field (i.e. new_object.logo = old_object.logo), both new_object and old_object point to the same file on the harddisk. If you edit the old file, the logo for the new object will change.
What's the best way to have a full/deep copy of the ImageField? I want it to have a different file name and to point to a different file on the disk, but it should have exactly the same content.
Why not create a function that is fun on the save signal which copies the image file to whereever you want, then creates a new object with a new image field?
models.py
class ObjectOne(models.Model):
logo = models.ImageField(...)
class ObjectTwo(models.Model):
logo = models.ImageField(...)
from django.db.models.signals import post_save
from signals import my_signal
post_save.connect(my_signal, dispatch_uid="001")
signals.py
from models. import ObjectOne, ObjectTwo
def my_signal(sender, instance, *args, **kwargs):
if sender is ObjectOne:
new_obj = ObjectTwo()
new_obj.logo.save("custom/path/new_filename.jpg",File(open(instance.image.url,"w"))
new_obj.save()
I haven't tested the image copying code but this is the general idea. There is more here:
Programmatically saving image to Django ImageField
Django: add image in an ImageField from image url
how to manually assign imagefield in Django

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