Business
Jobs
  • About Us
  • Solutions
    • Job Postings
      Post your job and receive qualified candidates in 48h.
    • Candidate Assessments
      500+ technical and psychological tests, plus anti-fraud.
    • Headhunting
      Tailor-made executive search from start to finish.
    • Payroll + EOR
      Payroll dispersal and EOR across 15+ LATAM countries.
  • Pricing
  • Jobs

0

331
Views
Django - Save multiple versions of an Image

My application needs to save multiple versions of an uploaded Image. One high quality image and another one just for thumbnails use (low quality). Currently this is working most of the time but sometimes the save method simply fails and all of my Thumbnail images are getting deleted, especially then if I use the remove_cover checkbox at my form

raise ValueError("The '%s' attribute has no file associated with it." % self.field.name) app | ValueError: The 'postcover_tn' attribute has no file associated with it.

-> See full trace here: https://pastebin.com/hgieMGet

models.py

class Post(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    author = models.ForeignKey(User, on_delete=models.CASCADE)
    title = models.CharField()
    content = models.TextField(blank=False)
    postcover = models.ImageField(
        verbose_name="Post Cover",
        blank=True,
        null=True,
        upload_to=image_uploads,
    )
    postcover_tn = models.ImageField(
        verbose_name="Post Cover Thumbnail",
        blank=True,
        null=True,
        upload_to=image_uploads,
    )
    published_date = models.DateTimeField(auto_now_add=True, null=True)

    def save(self, *args, **kwargs):
        super(Post, self).save(*args, **kwargs)
        if self.postcover:
            if not (self.postcover_tn and os.path.exists(self.postcover_tn.path)):
                image = Image.open(self.postcover)
                outputIoStream = BytesIO()
                baseheight = 500
                hpercent = baseheight / image.size[1]
                wsize = int(image.size[0] * hpercent)
                imageTemproaryResized = image.resize((wsize, baseheight))
                imageTemproaryResized.save(outputIoStream, format='PNG')
                outputIoStream.seek(0)
                self.postcover = InMemoryUploadedFile(outputIoStream, 'ImageField',
                                                      "%s.png" % self.postcover.name.split('.')[0], 'image/png',
                                                      sys.getsizeof(outputIoStream), None)
                image = Image.open(self.postcover)
                outputIoStream = BytesIO()
                baseheight = 175
                hpercent = baseheight / image.size[1]
                wsize = int(image.size[0] * hpercent)
                imageTemproaryResized = image.resize((wsize, baseheight))
                imageTemproaryResized.save(outputIoStream, format='PNG')
                outputIoStream.seek(0)
                self.postcover_tn = InMemoryUploadedFile(outputIoStream, 'ImageField',
                                                      "%s.png" % self.postcover.name.split('.')[0], 'image/png',
                                                      sys.getsizeof(outputIoStream), None)
        elif self.postcover_tn:
            self.postcover_tn.delete()

        super(Post, self).save(*args, **kwargs)

It also seems that I'm not able to properly resolve:

  • self.postcover_tn.delete() -> Unresolved attribute reference 'delete' for class 'InMemoryUploadedFile'
  • self.postcover_tn.path -> Unresolved attribute reference 'path' for class 'InMemoryUploadedFile'

forms.py:

def save(self, commit=True):
    instance = super(PostForm, self).save(commit=False)
    if self.cleaned_data.get('remove_cover'):
        try:
            os.unlink(instance.postcover.path)
        except OSError:
            pass
        instance.postcover = None
    if commit:
        instance.save()
    return instance
over 4 years ago · Santiago Trujillo
1 answers
Answer question

0

maybe if we look at the problem from another angle, we could solve it otherwise, out of the box. signals are very handy when it comes to handle images (add, update and delete) and below how i managed to solve your issue:

in models.py:

# from django.template.defaultfilters import slugify


class Post(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    author = models.ForeignKey(User, on_delete=models.CASCADE)
    title = models.CharField()
    # slug = models.SlugField('slug', max_length=255,
        # unique=True, null=True, blank=True,
        # help_text='If blank, the slug will be generated automatically from the given title.'
    # )
    content = models.TextField(blank=False)

    # ------------------------------------------------------------------------------------

    # rename images with the current post id/pk (which is UUID) and keep the extension
    # for cover thumbnail we append "_thumbnail" to the name 

    # e.g: 
    # img/posts/77b122a3d241461b80c51adc41d719fb.jpg
    # img/posts/77b122a3d241461b80c51adc41d719fb_thumbnail.jpg

    def upload_cover(instance, filename):
        ext = filename.split('.')[-1]
        filename = '{}.{}'.format(instance.id, ext)
        path = 'img/posts/'
        return '{}{}'.format(path, filename)

    postcover = models.ImageField('Post Cover',
        upload_to=upload_cover,  # callback function
        null=True, blank=True,
        help_text=_('Upload Post Cover.')
    )

    def upload_thumbnail(instance, filename):
        ext = filename.split('.')[-1]
        filename = '{}_thumbnail.{}'.format(instance.id, ext)
        path = 'img/posts/'
        return '{}{}'.format(path, filename)

    postcover_tn = models.ImageField('Post Cover Thumbnail',
        upload_to=upload_thumbnail,  # callback function
        null=True, blank=True,
        help_text=_('Upload Post Cover Thumbnail.')
    )

    # ------------------------------------------------------------------------------------

    published_date = models.DateTimeField(auto_now_add=True, null=True)

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

        # i moved the logic to signals

        # if not self.slug:
            # self.slug = slugify(self.title)
        super(Post, self).save(*args, **kwargs)

create new file and rename it signals.py (near to models.py):

import io
import sys

from PIL import Image

from django.core.files.uploadedfile import InMemoryUploadedFile
from django.dispatch import receiver
from django.db.models.signals import pre_save, pre_delete

from .models import Post

#  DRY
def image_resized(image, h):
    name = image.name
    _image = Image.open(image)
    content_type = Image.MIME[_image.format]
    r = h / _image.size[1]  # ratio
    w = int(_image.size[0] * r)
    imageTemproaryResized = _image.resize((w, h))
    file = io.BytesIO()
    imageTemproaryResized.save(file, _image.format)
    file.seek(0)
    size = sys.getsizeof(file)
    return file, name, content_type, size


@receiver(pre_save, sender=Post, dispatch_uid='post.save_image')
def save_image(sender, instance, **kwargs):

    # add image (cover | thumbnail)
    if instance._state.adding:

        #  postcover
        file, name, content_type, size = image_resized(instance.postcover, 500)
        instance.postcover = InMemoryUploadedFile(file, 'ImageField', name, content_type, size, None)

        #  postcover_tn
        file, name, content_type, size = image_resized(instance.postcover_tn, 175)
        instance.postcover_tn = InMemoryUploadedFile(file, 'ImageField', name, content_type, size, None)


    # update image (cover | thumbnail)
    if not instance._state.adding:
        # we have 2 cases:
        # - replace old with new
        # - delete old (when 'clear' checkbox is checked)

        #  postcover
        old = sender.objects.get(pk=instance.pk).postcover
        new = instance.postcover
        if (old and not new) or (old and new and old.url != new.url):
            old.delete(save=False)

        #  postcover_tn
        old = sender.objects.get(pk=instance.pk).postcover_tn
        new = instance.postcover_tn
        if (old and not new) or (old and new and old.url != new.url):
            old.delete(save=False)


@receiver(pre_delete, sender=Post, dispatch_uid='post.delete_image')
def delete_image(sender, instance, **kwargs):
    s = sender.objects.get(pk=instance.pk)

    if (not s.postcover or s.postcover is not None) and (not s.postcover_tn or s.postcover_tn is not None):
        s.postcover.delete(False)
        s.postcover_tn.delete(False)

in apps.py:

we need to register signals in apps.py since we use decorators @receiver:

from django.apps import AppConfig
from django.utils.translation import ugettext_lazy as _


class BlogConfig(AppConfig):  # change to the name of your app
    name = 'blog'  # and here
    verbose_name = _('Blog Entries')

    def ready(self):
        from . import signals

and this is the first screen shot of post admin area

enter image description here

since the thumbnail is generated from post cover, as UI/UX good practices there's no need to show a second input file for post cover thumbnail (i kept the second image field read only in admin.py).

below is the second screenshot after i uploaded the image

PS: the screenshot is taken from another app that i'm working on, so there's little changes, in your case you should see

  • Post Cover instead Featured Image
  • Currently: img/posts/8b0be417db564c53ad06cb493029e2ca.jpg (see upload_cover() in models.py) instead Currently: img/blog/posts/featured/8b0be417db564c53ad06cb493029e2ca.jpg

enter image description here

in admin.py

# "img/posts/default.jpg" and "img/posts/default_thumbnail.jpg" are placeholders
# grab to 2 image placeholders from internet and put them under "/static" folder

def get_post_cover(obj):
    src = obj.postcover.url if obj.postcover and \
    hasattr(obj.postcover, 'url') else os.path.join(
        settings.STATIC_URL, 'img/posts/default.jpg')
    return mark_safe('<img src="{}" height="500" style="border:1px solid #ccc">'.format(src))
get_post_cover.short_description = ''
get_post_cover.allow_tags = True

def get_post_cover_thumbnail(obj):
    src = obj.postcover_tn.url if obj.postcover_tn and \
    hasattr(obj.postcover_tn, 'url') else os.path.join(
        settings.STATIC_URL, 'img/posts/default_thumbnail.jpg')
    return mark_safe('<img src="{}" height="175" style="border:1px solid #ccc">'.format(src))
get_post_cover_thumbnail.short_description = ''
get_post_cover_thumbnail.allow_tags = True


class PostAdmin(admin.ModelAdmin):
    list_display = ('title', .. )
    fields = (
        'author', 'title', 'content',
        get_post_cover, get_post_cover_thumbnail, 'postcover',
    )
    readonly_fields = (get_post_cover, get_post_cover_thumbnail)

[..]

and finally you don't need any delete logic in save() function in forms.py

over 4 years ago · Santiago Trujillo Report
Answer question
Find remote jobs

Discover the new way to find a job!

Top jobs
Top job categories
Business
Post vacancy Pricing Sales
Legal
Terms and conditions Privacy policy
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Show me some job opportunities
There's an error!