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)
Related
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
I have been at this for Two days and I'm now hoping someone can point me in the right direction. All I am trying to do is duplicate an entry in a table/model into another model with mirrored fields, essentially creating an archived version. I want this to happen when the user calls the update view.
What I have tried so far is setting pk to None and then trying to find a way to move the previous version to the mirrored/archive model. After a couple of hours of research I gave up on this path. Next I thought the answer would lie with the pre_save receiver but I can't find a way to access the model instance to then save that to the archive model.
#receiver(pre_save, sender=InstrumentAnnual)
def archive_calc_instance(sender, instance, **kwargs):
stored_id = getattr(instance, 'id', None)
e = InstrumentAnnual.objects.filter(id = stored_id)
archive = InstrumentAnnualArchive(e.field_name, e.another_field_name...)
archive.save()
As far as I can tell this should work however e only contains the First field from the model.
Is there something that can be done with this code to achieve my goal or is there a more 'Django' way to solve this? I.e. some sort of official archive feature?
Thanks in advance.
With the help of #Igor's comment I amended my solution to this:
def archive_calc(self, object_id):
annual = InstrumentAnnual.objects.get(id = object_id)
annual_archive = InstrumentAnnualArchive()
for field in annual._meta.fields:
setattr(annual_archive, field.name, getattr(annual, field.name))
annual_archive.pk = None
annual_archive.save()
It occured to me that using pre_save wouldn't work as it is listening/linked to a model, not a view as I originally thought. So I placed the above code in my Update View and called it passing the id in object_id.
Thanks again for the help.
You should be using named arguments in your constructor, otherwise the first argument will be interpreted as the id, so try:
# omitted code
e = InstrumentAnnual.objects.filter(id=stored_id)
archive = InstrumentalAnnualArchive(field_name=e.field_name, another_name=e.another_field_name, …)
archive.save()
But you could also use Django's create function, so:
# omitted code
e = InstrumentAnnual.objects.filter(id=stored_id)
archive = InstrumentalAnnualArchive.objects.create(field_name=e.field_name, another_name=e.another_field_name, …)
This way handles the save for you, so you don't need to explicitly save your object.
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.
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.
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