Open fellipeh opened 8 years ago
You need to store image with unique filename Show me your model with ProcessesImageField and ImageSpecFields
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 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
.
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 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.
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 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.
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.
Thanks. I appreciate this because I also tried to create a test for that but without luck.
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...
It's there a reason you want to do this by hand instead of just adding another ImageSpecField and using the Transpose processor?
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.
@fellipeh we kind of abused your issue :) sorry for that. Did you managed to workaround your problem?
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)
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 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.
My comment from #229 also applies here. https://github.com/matthewwithanm/django-imagekit/issues/229#issuecomment-385145019
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?