matthewwithanm / django-imagekit

Automated image processing for Django. Currently v4.0
http://django-imagekit.rtfd.org/
BSD 3-Clause "New" or "Revised" License
2.28k stars 275 forks source link

ProcessedImageField.save() - imagekit.exceptions.MissingSource: The spec '<imagekit.specs.DynamicSpec object at 0x00000270D3590748>' has no source file associated with it. #449

Open ceefour opened 6 years ago

ceefour commented 6 years ago

Model:

photo = ProcessedImageField(max_length=1000, blank=True, upload_to='profile',
                            processors=[ResizeToFit(4000, 4000)], format='JPEG')

Code:

# Fetch photo if we're missing and Firebase has it
if not profile.photo and claims.get('picture'):
    logger.info('Fetching photo for %s: %s ...', profile, claims['picture'])
    r = requests.get(claims['picture'])
    logger.debug('Fetched %s', claims['picture'])
    f = io.BytesIO(r.content)
    photo_name = '%s-%s-%s.jpg' % (profile.id, slugify(profile.name), random.randint(1000, 9999))
    logger.debug('Saving %s into S3 %s ...', claims['picture'], photo_name)
    profile.photo.save(photo_name, File(f))
    logger.debug('Saved %s into S3 %s', claims['picture'], profile.photo.name)

Error:

INFO 2017-12-10 09:15:47,717 views_api: Fetching photo for 218: Hendy Irawan: https://lh3.googleusercontent.com/-vH3W10UA68A/AAAAAAAAAAI/AAAAAAAAAAA/85MwSRebal4/s96-c/photo.jpg ...
DEBUG 2017-12-10 09:15:48,007 views_api: Fetched https://lh3.googleusercontent.com/-vH3W10UA68A/AAAAAAAAAAI/AAAAAAAAAAA/85MwSRebal4/s96-c/photo.jpg
DEBUG 2017-12-10 09:15:48,008 views_api: Saving https://lh3.googleusercontent.com/-vH3W10UA68A/AAAAAAAAAAI/AAAAAAAAAAA/85MwSRebal4/s96-c/photo.jpg into S3 218-hendy-irawan-5462.jpg ...
ERROR 2017-12-10 09:15:48,011 views_api: Cannot prepare user profile
Traceback (most recent call last):
  File "C:\Users\ceefour\git\samara\samaraweb\edu\views_api.py", line 179, in userprofile_prepare
    profile.photo.save(photo_name, File(f))
  File "C:\Users\ceefour\git\samara\venv\lib\site-packages\imagekit\models\fields\files.py", line 12, in save
    content = generate(spec)
  File "C:\Users\ceefour\git\samara\venv\lib\site-packages\imagekit\utils.py", line 152, in generate
    content = generator.generate()
  File "C:\Users\ceefour\git\samara\venv\lib\site-packages\imagekit\specs\__init__.py", line 144, in generate
    " with it." % self)
imagekit.exceptions.MissingSource: The spec '<imagekit.specs.DynamicSpec object at 0x00000270D3590748>' has no source file associated with it.

This seems to be similar to #339 however I'm not calling .url at all. This happens during save()

ceefour commented 6 years ago

Workaround: Instead of using BytesIO I am now using ContentFile and it works:

# Fetch photo if we're missing and Firebase has it
if not profile.photo and claims.get('picture'):
    logger.info('Fetching photo for %s: %s ...', profile, claims['picture'])
    r = requests.get(claims['picture'])
    logger.debug('Fetched %s (%s bytes)', claims['picture'], len(r.content))
    photo_name = '%s-%s-%s.jpg' % (profile.id, slugify(profile.name), random.randint(1000, 9999))
    logger.debug('Saving %s into S3 %s ...', claims['picture'], photo_name)
    profile.photo.save(photo_name, ContentFile(r.content))
    logger.debug('Saved %s into S3 %s', claims['picture'], profile.photo.name)

My question is:

  1. was my original usage correct?
  2. if it was correct: then this would be a bug if it was incorrect: then the exception should better express what's wrong
vstoykov commented 6 years ago

From my experience with working with Django's storage system your original usage was incorrect. You need to use the right file class from Django for every particular case.

The problem is not that you are using BytesIO (internally ContentFile will also use BytesIO when you pass binary data) but actually the problem is using of File to wrap BytesIO. By default File represent a file from the file system. In some cases it can works with BytesIO but is not designed to work in all cases with it. For that reason in Django there are other classes which subclass File in order to make it work correctly. One of them is ContentFile and other (I'm not sure if it is documented) is InMemoryUploadedFile (located in 'django.core.files.uploadedfile').

I'm not sure if some changes can be made in ImageKit in order to raise appropriate exception or even to make it work even if used as you make it in the first place.

I'll leave the issue for now open if someone want to try to investigate if we can improve ImageKit behaviour somehow in this direction.