django-imagekit icon indicating copy to clipboard operation
django-imagekit copied to clipboard

CACHE (thumbs) doesn't 'recreate' after change the image.

Open fellipeh opened this issue 8 years ago • 17 comments

I have one image field in my model, but when I try to change the image, in my model change goes ok! But the link generated by imagekit (with CACHE file) doesn't reflect the changes... still show the last image.

Any idea the reason?

fellipeh avatar Dec 02 '15 11:12 fellipeh

You need to store image with unique filename Show me your model with ProcessesImageField and ImageSpecFields

nex2hex avatar Dec 03 '15 14:12 nex2hex

I'm having this same issue. I'm rotating an image using PIL and saving it with a new name, but the thumbnail doesn't match up with the original (which is, itself, a ProcessedImageField). Interestingly, the thumbnail is being rotated, it just doesn't match up with the original. Here's my code:

class InvItemImage(models.Model):
    image_full = ProcessedImageField(upload_to='invitem_images',
                                     processors=[ResizeToFit(1000, 1000)],
                                     format='JPEG',
                                     options={'quality': 75})
    image_thumbnail = ImageSpecField(source='image_full',
                                     processors=[ResizeToFit(100, 100)],
                                     format='JPEG',
                                     options={'quality': 60})

    def _rotate(self, degrees):
        rotated = PIL.Image.open(self.image_full).rotate(degrees, expand=1)
        # save to the same path but with a different name
        dirname, fname = os.path.split(self.image_full.path)
        fbase, fext = os.path.splitext(fname)
        fname = str(uuid.uuid4()) + fext
        fullpath = os.path.join(dirname, fname)
        upload_to_path = os.path.join('invitem_images', fname)
        rotated.save(fullpath)
        self.image_full.name = upload_to_path
        self.save()

    def rotate_left(self):
        self._rotate(90)

    def rotate_right(self):
        self._rotate(-90)

mgalgs avatar Apr 17 '16 05:04 mgalgs

@mgalgs you are doing it wrong. This way imagekit can't understand that you are changing the source.

When you deal with files in Django you need to use Django's abstractions and not rely on filesystem path, because your DEFAULT_FILE_STORAGE can be some remote storage (like S3 or something), and not a FileSystemStorage.

In order change the image you need to simulate upload. The easiest way to do it is to assign instance of django.core.files.TemporaryUploadedFile or django.core.files.SimpleUploadedFile. Difference between the two is that one is stored on disk and other is stored on memory. If you deal with large images and don't have enough RAM you can use TemporaryUploadedFile. If your images are not so big and/or you have enough RAM, you can use SimpleUploadedFile.

With PIL you can save the image to a file object instead of a filesystem path, and by that way you don't care where the file is located and if this is a real file in the filesystem or fake in memory file (like SimpleUploadedFile

If you have problems with this I can try to make some example code sample for your case, but first I want from you to try to do it in order to understand how Django handles files in FileFiled.

vstoykov avatar Apr 17 '16 10:04 vstoykov

Thanks for the tips, @vstoykov. I came up with something that seems to work and doesn't rely on using a filesystem storage backend:

    def _rotate(self, degrees):
        import StringIO
        # Without the following open I get:
        # ValueError: I/O operation on closed file
        # Apprently it's a bug? http://stackoverflow.com/a/3033986/209050
        self.image_full.open()
        rotated = PIL.Image.open(self.image_full).rotate(degrees, expand=1)
        sio = StringIO.StringIO()
        rotated.save(sio, format='JPEG')
        imuf = InMemoryUploadedFile(sio, None, 'rotated.jpg', 'image/jpeg',
                                    sio.len, None)
        self.image_full = imuf
        self.save()

Let me know if you see any glaring errors :)

mgalgs avatar Apr 19 '16 07:04 mgalgs

@mgalgs Looks good. The only thing that I have as suggestion is to use io.BytesIO instead of StringIO.StringIO when you are writing binary data. It will be easier to port to Python 3.

@fellipeh We do not know how you are using the field in your logic. If it is something similar to @mgalgs you can try his solution and see if it works for you.

If it works I think that we can close this issue.

vstoykov avatar Apr 19 '16 07:04 vstoykov

One more thing: looks like I should actually have a self.image_full.close() after the self.save() at the very end of the function, otherwise I end up with a ValueError: The file cannot be reopened. when I try to access the URL of the just-rotated image file:

  File "/home/mgalgs/sites/str-prod/env/lib/python2.7/site-packages/imagekit/cachefiles/__init__.py", line 84, in url
    return self._storage_attr('url')
  File "/home/mgalgs/sites/str-prod/env/lib/python2.7/site-packages/imagekit/cachefiles/__init__.py", line 74, in _storage_attr
    existence_required.send(sender=self, file=self)
  File "/home/mgalgs/sites/str-prod/env/lib/python2.7/site-packages/django/dispatch/dispatcher.py", line 192, in send
    response = receiver(signal=self, sender=sender, **named)
  File "/home/mgalgs/sites/str-prod/env/lib/python2.7/site-packages/imagekit/registry.py", line 53, in existence_required_receiver
    self._receive(file, 'on_existence_required')
  File "/home/mgalgs/sites/str-prod/env/lib/python2.7/site-packages/imagekit/registry.py", line 61, in _receive
    call_strategy_method(file, callback)
  File "/home/mgalgs/sites/str-prod/env/lib/python2.7/site-packages/imagekit/utils.py", line 151, in call_strategy_method
    fn(file)
  File "/home/mgalgs/sites/str-prod/env/lib/python2.7/site-packages/imagekit/cachefiles/strategies.py", line 15, in on_existence_required
    file.generate()
  File "/home/mgalgs/sites/str-prod/env/lib/python2.7/site-packages/imagekit/cachefiles/__init__.py", line 93, in generate
    self.cachefile_backend.generate(self, force)
  File "/home/mgalgs/sites/str-prod/env/lib/python2.7/site-packages/imagekit/cachefiles/backends.py", line 109, in generate
    self.generate_now(file, force=force)
  File "/home/mgalgs/sites/str-prod/env/lib/python2.7/site-packages/imagekit/cachefiles/backends.py", line 96, in generate_now
    file._generate()
  File "/home/mgalgs/sites/str-prod/env/lib/python2.7/site-packages/imagekit/cachefiles/__init__.py", line 97, in _generate
    content = generate(self.generator)
  File "/home/mgalgs/sites/str-prod/env/lib/python2.7/site-packages/imagekit/utils.py", line 134, in generate
    content = generator.generate()
  File "/home/mgalgs/sites/str-prod/env/lib/python2.7/site-packages/imagekit/specs/__init__.py", line 153, in generate
    self.source.open()
  File "/home/mgalgs/sites/str-prod/env/lib/python2.7/site-packages/django/db/models/fields/files.py", line 81, in open
    self.file.open(mode)
  File "/home/mgalgs/sites/str-prod/env/lib/python2.7/site-packages/django/core/files/base.py", line 141, in open
    raise ValueError("The file cannot be reopened.")
ValueError: The file cannot be reopened.

mgalgs avatar Jun 29 '16 07:06 mgalgs

@mgalgs You say that if you close the file after save then the ValueError will not be raised. Hmmm this value error probably is caused by #350 or probably solving of it will need your approach.

I haven't had enough time to look close for it fix it.

vstoykov avatar Jun 29 '16 08:06 vstoykov

Sorry, I should have been a bit more clear. That was my proposed fix, but it sounds like it might not be correct. Haven't actually tried it yet because it only happens rarely in production and I didn't have time this morning to put together a proper test case. I'll take a closer look tonight.

mgalgs avatar Jun 29 '16 19:06 mgalgs

Thanks. I appreciate this because I also tried to create a test for that but without luck.

vstoykov avatar Jun 30 '16 12:06 vstoykov

So I've been playing around with this some more and I have no idea what's going on... It seems clear that my rotate function above doesn't work well when using an s3 storage backend (and possibly other backends?). It actually oscillates between working and not working... Works one request, doesn't work the next... Back and forth from one request to the next. So confused...

mgalgs avatar Jul 06 '16 06:07 mgalgs

It's there a reason you want to do this by hand instead of just adding another ImageSpecField and using the Transpose processor?

matthewwithanm avatar Jul 06 '16 13:07 matthewwithanm

Yes, the reason is that this is a user-driven operation. I already have the Transpose processor on my ImageField which takes care of correcting most bogus rotation, but somehow things still slip through rotated incorrectly so my users need to be able to rotate things manually after the fact.

mgalgs avatar Jul 06 '16 15:07 mgalgs

@fellipeh we kind of abused your issue :) sorry for that. Did you managed to workaround your problem?

vstoykov avatar Aug 01 '16 23:08 vstoykov

I have the same problem. The source image is not valid anymore, and is recreated. It is (re)saved with the id of the object (easy to lookup image in the storage, prevents unnecessary duplicates). I have no way now to properly create a new thumbnail.

I tried deleting the file in storage, and generating again:

obj.image_thumbnail._storage_attr('delete')
obj.image_thumbnail.generate(force=True)

But that results in a warning that the file is saved with a different name.

Is there any reason that generate(force=True) doesn't just forcefully regenerates the file? (As I would expect)

tino avatar Oct 11 '16 08:10 tino

Ah wait, I see that it does if I use a storage backend that overwrite. Hmm... Not sure what part of this belongs to imagekit, but I do think this ought to be simpler.

tino avatar Oct 11 '16 12:10 tino

@tino I think that your situation is very different than described issue. With Django storage system you basically never use the same file again when you write to the storage. In your case django-imagekit is warning you that you already has a file with the supposed name but because Django never overwrite files, new file is created.

vstoykov avatar Oct 14 '16 17:10 vstoykov

My comment from #229 also applies here. https://github.com/matthewwithanm/django-imagekit/issues/229#issuecomment-385145019

jochengcd avatar Apr 28 '18 06:04 jochengcd