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

Template ValueError - The file cannot be reopened.

Open fabiocaccamo opened this issue 9 years ago • 11 comments

I just tried to use imagekit on a project which stores media on s3, but I got the following error:

Template ValueError - The file cannot be reopened.

boto==2.39.0
Django==1.8.11
django-imagekit==3.3
django-s3-storage==0.9.8

Model:

image_thumbnail_300x200 = ImageSpecField(source='image',
                                      processors=[ResizeToFill(300, 200)],
                                      format='JPEG',
                                      options={'quality':90})

Template: {{ obj.image_thumbnail_300x200.url }}

fabiocaccamo avatar Mar 31 '16 17:03 fabiocaccamo

This is probably a duplicate of #350

vstoykov avatar Apr 01 '16 06:04 vstoykov

Probably it's related, but I'm not using imagekit template tags and I'm not trying to access width/height.

fabiocaccamo avatar Apr 01 '16 09:04 fabiocaccamo

Ok then the issue is different, or the other issue is related to yours not the vise versa.

I'm kinda busy right now and don't have enough free time to investigate the issue and make tests for it.

I will appreciate any Pull Request which will have a solution for this.

vstoykov avatar Apr 01 '16 12:04 vstoykov

On Django/1.9.5 and django-imagekit/3.3

I had the same issue with multiple ProcessedImageField fields in the same Model (Thus reusing the same original uploaded file stream).

https://github.com/matthewwithanm/django-imagekit/commit/13c92db760bc65f2436b93d764ee5ca2d3946e08 fix wasn't working since self.source.open() caused a seek(0) on a closed=True django.core.files.uploadedfile.InMemoryUploadedFile; which is prohibited.

Hackish/Dirty workaround for anyone in my situation... The fix was to override ProcessedImageFieldFile's save() this way:

class HackedProcessedImageFieldFile(ProcessedImageFieldFile):
    """
    Rendering multiple image from the same source resulted in I/O error due to an attempt from imagekit to
      rewind (seek(0)) the stream while it was closed.

    Looks like it's impossible to reopen a closed InMemoryFile (BytesIO Stream).
    """

    def save(self, name, content, save=True):

        # Copy original file/stream's content to a clone.
        initial = io.BytesIO()
        shutil.copyfileobj(self.file, initial)
        initial.seek(0)

        # Run preprocessor
        super(HackedProcessedImageFieldFile, self).save(name, content, save)

        # Copy clone over real file for reuse in next call.
        self.file.file = initial


class HackedProcessedImageField(ProcessedImageField):
    attr_class = HackedProcessedImageFieldFile


class MyModel(...):
    thumbnail = HackedProcessedImageField(...)

Stream is copied before being closed by whoever closes it and then re-applied over the original file.

raphael-riel avatar May 02 '16 15:05 raphael-riel

@fabiocaccamo sorry for taking so long. Can you test the code in #380. I know that it does not sounds similar but I think that the problem you are facing can be related to the usage of NamedTemporaryFile which I removed in order to work with Django 1.10.

@raphael-riel I think that your issue is different despite looking similar. Probably another issue need to be opened but first I want to check if the problem that reported the original author of this issue is still present.

vstoykov avatar Jul 10 '16 23:07 vstoykov

@fabiocaccamo forget about #380 I think that the fix that I made for #350 will work for you. Can you check #384 ?

vstoykov avatar Jul 17 '16 02:07 vstoykov

I'm having this same problem and have finally managed to come up with full repro instructions. It does seem to be specific to s3 (and possibly django-s3-storage). I couldn't get it to repro using local storage. I didn't try another s3 backend (like django-storages).

This still repros on the develop branch (which includes #384 and #380).

I've put together a full test project for this, over here. See the README.md for full instructions, which I'm also pasting below.


S3 prep

First, create a new s3 bucket (ours is named iktest-media in this example). We'll need your access keys and bucket name available in your environment later, so go ahead and just export them now:

$ export AWS_ACCESS_KEY_ID=...
$ export AWS_SECRET_ACCESS_KEY=...
$ export AWS_S3_BUCKET_NAME=iktest-media  # replace with your bucket name

Now clone the iktest repo:

$ git clone https://github.com/mgalgs/iktest.git
$ cd iktest

and upload the IMG_20160609_183718.jpg file from this repo to your new bucket:

$ aws s3 cp IMG_20160609_183718.jpg s3://iktest-media/media/test_images/

Your bucket should now look like this:

$ aws s3 ls --recursive s3://iktest-media
2016-08-11 20:03:26      49093 media/test_images/IMG_20160609_183718.jpg

Django prep

Install everything in a fresh virtualenv and bootstrap some data:

$ virtualenv env
$ . env/bin/activate
$ pip install Django==1.9 django-s3-storage Pillow git+git://github.com/matthewwithanm/django-imagekit.git@develop
$ python manage.py migrate
$ python manage.py testdata

Running the app

You're now ready to run the dev server:

$ python manage runserver

Load http://localhost:8000 in your web browser. You'll notice that the first time you load the page it will complain with:

Internal Server Error: /
Traceback (most recent call last):
  File "/home/mgalgs/sites/iktest2/env/lib/python2.7/site-packages/django/core/handlers/base.py", line 149, in get_response
    response = self.process_exception_by_middleware(e, request)
  File "/home/mgalgs/sites/iktest2/env/lib/python2.7/site-packages/django/core/handlers/base.py", line 147, in get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/home/mgalgs/sites/iktest2/iktest/testapp/views.py", line 10, in index_view
    'widgets': TestWidget.objects.all(),
  File "/home/mgalgs/sites/iktest2/env/lib/python2.7/site-packages/django/shortcuts.py", line 67, in render
    template_name, context, request=request, using=using)
  File "/home/mgalgs/sites/iktest2/env/lib/python2.7/site-packages/django/template/loader.py", line 97, in render_to_string
    return template.render(context, request)
  File "/home/mgalgs/sites/iktest2/env/lib/python2.7/site-packages/django/template/backends/django.py", line 95, in render
    return self.template.render(context)
  File "/home/mgalgs/sites/iktest2/env/lib/python2.7/site-packages/django/template/base.py", line 206, in render
    return self._render(context)
  File "/home/mgalgs/sites/iktest2/env/lib/python2.7/site-packages/django/template/base.py", line 197, in _render
    return self.nodelist.render(context)
  File "/home/mgalgs/sites/iktest2/env/lib/python2.7/site-packages/django/template/base.py", line 988, in render
    bit = node.render_annotated(context)
  File "/home/mgalgs/sites/iktest2/env/lib/python2.7/site-packages/django/template/base.py", line 955, in render_annotated
    return self.render(context)
  File "/home/mgalgs/sites/iktest2/env/lib/python2.7/site-packages/django/template/defaulttags.py", line 220, in render
    nodelist.append(node.render_annotated(context))
  File "/home/mgalgs/sites/iktest2/env/lib/python2.7/site-packages/django/template/base.py", line 955, in render_annotated
    return self.render(context)
  File "/home/mgalgs/sites/iktest2/env/lib/python2.7/site-packages/django/template/base.py", line 1039, in render
    output = self.filter_expression.resolve(context)
  File "/home/mgalgs/sites/iktest2/env/lib/python2.7/site-packages/django/template/base.py", line 705, in resolve
    obj = self.var.resolve(context)
  File "/home/mgalgs/sites/iktest2/env/lib/python2.7/site-packages/django/template/base.py", line 846, in resolve
    value = self._resolve_lookup(context)
  File "/home/mgalgs/sites/iktest2/env/lib/python2.7/site-packages/django/template/base.py", line 887, in _resolve_lookup
    current = getattr(current, bit)
  File "/home/mgalgs/sites/iktest2/env/lib/python2.7/site-packages/imagekit/cachefiles/__init__.py", line 84, in url
    return self._storage_attr('url')
  File "/home/mgalgs/sites/iktest2/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/iktest2/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/iktest2/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/iktest2/env/lib/python2.7/site-packages/imagekit/registry.py", line 61, in _receive
    call_strategy_method(file, callback)
  File "/home/mgalgs/sites/iktest2/env/lib/python2.7/site-packages/imagekit/utils.py", line 148, in call_strategy_method
    fn(file)
  File "/home/mgalgs/sites/iktest2/env/lib/python2.7/site-packages/imagekit/cachefiles/strategies.py", line 15, in on_existence_required
    file.generate()
  File "/home/mgalgs/sites/iktest2/env/lib/python2.7/site-packages/imagekit/cachefiles/__init__.py", line 93, in generate
    self.cachefile_backend.generate(self, force)
  File "/home/mgalgs/sites/iktest2/env/lib/python2.7/site-packages/imagekit/cachefiles/backends.py", line 108, in generate
    self.generate_now(file, force=force)
  File "/home/mgalgs/sites/iktest2/env/lib/python2.7/site-packages/imagekit/cachefiles/backends.py", line 95, in generate_now
    file._generate()
  File "/home/mgalgs/sites/iktest2/env/lib/python2.7/site-packages/imagekit/cachefiles/__init__.py", line 97, in _generate
    content = generate(self.generator)
  File "/home/mgalgs/sites/iktest2/env/lib/python2.7/site-packages/imagekit/utils.py", line 134, in generate
    content = generator.generate()
  File "/home/mgalgs/sites/iktest2/env/lib/python2.7/site-packages/imagekit/specs/__init__.py", line 153, in generate
    self.source.open()
  File "/home/mgalgs/sites/iktest2/env/lib/python2.7/site-packages/django/db/models/fields/files.py", line 81, in open
    self.file.open(mode)
  File "/home/mgalgs/sites/iktest2/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.

If you reload your page it will work(!). However, the thumbnails that got generated won't actually be valid. Compare the urls from your web browser to:

$ aws s3 ls --recursive s3://iktest-media

mgalgs avatar Aug 12 '16 04:08 mgalgs

FWIW, the issue I was experiencing (which I had postulated is the same issue being reported here) turned out to be due to the fact that django-s3-storage didn't support re-opening files. @etianen has since added support for that and the issue has gone away for me.

@fabiocaccamo, I noticed that you're using djangoe-s3-storage as well. You might want to try the 0.9.11 release to see if the problem goes away for you.

mgalgs avatar Nov 02 '16 04:11 mgalgs

Bear in mind that there are plenty of file storage backends that don't allow files to be reopened. It's better to track down where the file is being closed and stop that happening.

etianen avatar Nov 02 '16 10:11 etianen

@etianen actually if the custom storage does not allow files to be reopened then this is the problem of the custom storage because by default all files opened with Django's default storage can be reopened.

This is because Django implement only filesystem storage and django.core.files.File asume that it is opened from the filesystem and it's open method will work even if the underlying file was closed.

In order this to work in the open() method or more precisely in the _open() method of the custom storage need to returned instance of some subclass of File which need to know how to reopen itself if it was already closed.

https://github.com/django/django/blob/stable/1.10.x/django/core/files/storage.py#L299-L300 https://github.com/django/django/blob/stable/1.10.x/django/core/files/base.py#L135-L141

Also one more thing why files need to be closed https://github.com/matthewwithanm/django-imagekit/pull/335

P.S. @etianen Now I see that you are the owner of django-s3-storage and the fix you already provided is exaclty what I said above.

@mgalgs @etianen sorry that I was unable to help with resolving the issue in django-s3-storage but I was/am very bussy and does not have enough time to work on django-imagekit.

Now when the problem is discovered I need to find some time to release new version of django-imagekit with all fixes that are in master.

@mgalgs thanks for investigation and starting discussion on django-s3-starge issue tracker @etianen thanks for the fix in django-s3-storage

vstoykov avatar Nov 03 '16 22:11 vstoykov

@mgalgs @etianen sorry that I was unable to help with resolving the issue in django-s3-storage but I was/am very bussy and does not have enough time to work on django-imagekit.

No need to apologize. Thanks for all your hard work.

mgalgs avatar Nov 03 '16 22:11 mgalgs