djangodjango-imagekit

"The file cannot be reopened." error from django-imagekit after moving a file


I'm using django-imagekit to generate a thumbnail on a Django model:

class Book(models.Model):
    title = models.CharField(null=False, blank=False, max_length=255)
    thumbnail = models.ImageField(
        upload_to=upload_path, null=False, blank=True, default=""
    )

    list_thumbnail = ImageSpecField(processors=[ResizeToFit(80, 160)],
                                    source="thumbnail",
                                    format="JPEG")

That works fine. However I'm trying to move the original thumbnail file after upload. Here's a simplified version of my save() method, that just moves the file into a "new" directory and re-saves the object (it's more complicated than that really):

    def save(self, *args, **kwargs):
        super().save(*args, **kwargs)

        if self.thumbnail and "/new/" not in self.thumbnail.path:
            # Move the thumbnail to correct location.
            initial_name = self.thumbnail.name
            initial_path = self.thumbnail.path
            new_name = os.path.join(os.path.dirname(initial_name),
                                    "new",
                                    os.path.basename(initial_name))
            new_path = os.path.join(settings.MEDIA_ROOT, new_name)

            if not os.path.exists(os.path.dirname(new_path)):
                os.makedirs(os.path.dirname(new_path))

            os.rename(initial_path, new_path)

            self.thumbnail.name = new_name
            kwargs["force_insert"] = False
            super().save(*args, **kwargs)

This works fine by default.

But if I have this in settings.py:

IMAGEKIT_DEFAULT_CACHEFILE_STRATEGY = "imagekit.cachefiles.strategies.Optimistic"

then I get errors resulting from imagekit signals, presumably confused that the file has moved while trying to generate the list_thumbnail. Here's some of the traceback:

...
  File "/venv-path/python3.8/site-packages/imagekit/specs/sourcegroups.py", line 33, in receiver
    fn(self, sender=sender, **kwargs)
  File "/venv-path/python3.8/site-packages/imagekit/specs/sourcegroups.py", line 101, in post_save_receiver
    self.dispatch_signal(source_saved, file, sender, instance,
  File "/venv-path/python3.8/site-packages/imagekit/specs/sourcegroups.py", line 124, in dispatch_signal
    signal.send(sender=source_group, source=file)
  File "/venv-path/python3.8/site-packages/django/dispatch/dispatcher.py", line 180, in send
    return [
  File "/venv-path/python3.8/site-packages/django/dispatch/dispatcher.py", line 181, in <listcomp>
    (receiver, receiver(signal=self, sender=sender, **named))
  File "/venv-path/python3.8/site-packages/imagekit/registry.py", line 116, in source_group_receiver
    call_strategy_method(file, callback_name)
  File "/venv-path/python3.8/site-packages/imagekit/utils.py", line 166, in call_strategy_method
    fn(file)
  File "/venv-path/python3.8/site-packages/imagekit/cachefiles/strategies.py", line 30, in on_source_saved
    file.generate()
  File "/venv-path/python3.8/site-packages/imagekit/cachefiles/__init__.py", line 94, in generate
    self.cachefile_backend.generate(self, force)
  File "/venv-path/python3.8/site-packages/imagekit/cachefiles/backends.py", line 109, in generate
    self.generate_now(file, force=force)
  File "/venv-path/python3.8/site-packages/imagekit/cachefiles/backends.py", line 96, in generate_now
    file._generate()
  File "/venv-path/python3.8/site-packages/imagekit/cachefiles/__init__.py", line 98, in _generate
    content = generate(self.generator)
  File "/venv-path/python3.8/site-packages/imagekit/utils.py", line 152, in generate
    content = generator.generate()
  File "/venv-path/python3.8/site-packages/imagekit/specs/__init__.py", line 153, in generate
    self.source.open()
  File "/venv-path/python3.8/site-packages/django/db/models/fields/files.py", line 77, in open
    self.file.open(mode)
  File "/venv-path/python3.8/site-packages/django/core/files/base.py", line 114, in open
    raise ValueError("The file cannot be reopened.")
ValueError: The file cannot be reopened.

I could change the cachefile strategy, but I'd like to keep it. So I'm not sure how to tell the imagekit fields about the file's new location.


Solution

  • It turns out that as well as updating the ImageField's name attribute, I had to update its File object's path.

    So I added this:

                self.thumbnail.file.path = new_path
    

    to the save method:

        def save(self, *args, **kwargs):
            super().save(*args, **kwargs)
    
            if self.thumbnail and "/new/" not in self.thumbnail.path:
                # Move the thumbnail to correct location.
                initial_name = self.thumbnail.name
                initial_path = self.thumbnail.path
                new_name = os.path.join(os.path.dirname(initial_name),
                                        "new",
                                        os.path.basename(initial_name))
                new_path = os.path.join(settings.MEDIA_ROOT, new_name)
    
                if not os.path.exists(os.path.dirname(new_path)):
                    os.makedirs(os.path.dirname(new_path))
    
                os.rename(initial_path, new_path)
    
                self.thumbnail.name = new_name
                self.thumbnail.file.path = new_path  # THE NEW LINE
    
                kwargs["force_insert"] = False
                super().save(*args, **kwargs)