2

In my current project I have my images stored on a s3 bucket. I have a pre_save signal receiver to delete the actually image from the s3 bucket on the Image class.

class Image(models.Model):
    name = models.CharField(max_length = 255)
    caption = models.CharField(max_length = 255)
    image = models.ImageField(upload_to='uploads/',blank=True,null=True)
    rent_property = models.ForeignKey(RentProperty, related_name='Images')
    is_main_image = models.BooleanField(default=False)

@receiver(models.signals.pre_save, sender=Image)
def auto_delete_file_on_change(sender, instance, **kwargs):
    """Deletes file from filesystem
    when corresponding `MediaFile` object is changed.
    """
    if not instance.pk:
        return False

    try:
        old_file = Image.objects.get(pk=instance.pk).image
    except Image.DoesNotExist:
        return False

    new_file = instance.image
    if not old_file == new_file:
        old_file.delete(save=False)

My problem is, I am using django-rest-framework, and I want to get the PATCH to work. but if I try to patch an Image description for example, it would delete the image itself. My question is, how do I write an IF that would differentiate weather or not there is a new image in the patch that needs changing , and if not, do nothing?

Yoav Schwartz
  • 2,017
  • 2
  • 23
  • 43
  • 2
    I usually save an SHA-1 hash in a model field for data files and check if it changes. Would that work in your case? – Fiver May 20 '14 at 16:31
  • You are quite the genius. It worked perfectly. Since it get a new random unique name every time it updates, i just check if thats changed, if not, I keep the old Image. Can you please write your comment in the form of an answer so I may accept it and it might help other people in the future? – Yoav Schwartz May 22 '14 at 08:16
  • Great, glad it worked for you. I've added an answer with a bit more detail. – Fiver May 22 '14 at 13:48

1 Answers1

2

For models with an ImageField or FileField, I include an additional field to store the SHA-1 hash string. I've found this useful to have for many reasons:

  • Reducing unneeded transfers for the same file for updates (your case)
  • Preventing users from uploading duplicate files as new instances
  • Providing users with an SHA-1 hash when downloading files so they can verify the download
  • Doing data integrity checks on the back-end file system to verify the files have not changed

I also save the original file name in order to reproduce it for user facing views/downloads. In this way the back-end names do not matter to the users.

Here's a basic implementation based on your model:

import hashlib
from django.core.exceptions import ValidationError
from django.core.files import File
from django.db import models

class Image(models.Model):
    name = models.CharField(max_length = 255)
    caption = models.CharField(max_length = 255)
    image = models.ImageField(upload_to='uploads/',blank=True,null=True)
    original_filename = models.CharField(
        unique=False,
        null=False,
        blank=False,
        editable=False,
        max_length=256)
    sha1 = models.CharField(
        unique=True,
        null=False,
        blank=False,
        editable=False,
        max_length=40)
    rent_property = models.ForeignKey(RentProperty, related_name='Images')
    is_main_image = models.BooleanField(default=False)

    def clean(self):
        """
        Overriding clean to do the following:
            - Save  original file name, since it may already exist on our side.
            - Save SHA-1 hash and check for duplicate files.
        """

        self.original_filename = self.image.name.split('/')[-1]
        # get the hash
        file_hash = hashlib.sha1(self.image.read())
        self.sha1 = file_hash.hexdigest()

        # Check if this file has already been uploaded,
        # if so delete the temp file and raise ValidationError
        duplicate_hashes = Image.objects.all().exclude(
                id=self.id).values_list('sha1', flat=True)
        if self.sha1 in duplicate_hashes:
            if hasattr(self.image.file, 'temporary_file_path'):
                temp_file_path = self.image.file.temporary_file_path()
                os.unlink(temp_file_path)

            raise ValidationError(
                "This image already exists."
            )
Fiver
  • 9,909
  • 9
  • 43
  • 63