Files
Pinry/django_images/models.py

132 lines
4.4 KiB
Python
Raw Normal View History

import hashlib
import os.path
from io import BytesIO
from django.db import models
from django.core.files.uploadedfile import InMemoryUploadedFile
from django.core.urlresolvers import reverse
from django.dispatch import receiver
import PIL
try:
from importlib import import_module
except ImportError:
from django.utils.importlib import import_module
from . import utils
from .settings import IMAGE_SIZES, IMAGE_PATH, IMAGE_AUTO_DELETE
def hashed_upload_to(instance, filename, **kwargs):
image_type = 'original' if isinstance(instance, Image) else 'thumbnail'
prefix = 'image/%s/by-md5/' % (image_type,)
hasher = hashlib.md5()
for chunk in instance.image.chunks():
hasher.update(chunk)
hash_ = hasher.hexdigest()
base, ext = os.path.splitext(filename)
return '%(prefix)s%(first)s/%(second)s/%(hash)s/%(base)s%(ext)s' % {
'prefix': prefix,
'first': hash_[0],
'second': hash_[1],
'hash': hash_,
'base': base,
'ext': ext,
}
if IMAGE_PATH is None:
upload_to = hashed_upload_to
else:
if callable(IMAGE_PATH):
upload_to = IMAGE_PATH
else:
parts = IMAGE_PATH.split('.')
module_name = '.'.join(parts[:-1])
module = import_module(module_name)
upload_to = getattr(module, parts[-1])
class Image(models.Model):
image = models.ImageField(upload_to=upload_to,
height_field='height', width_field='width',
max_length=255)
height = models.PositiveIntegerField(default=0, editable=False)
width = models.PositiveIntegerField(default=0, editable=False)
def get_by_size(self, size):
return self.thumbnail_set.get(size=size)
def get_absolute_url(self, size=None):
if not size:
return self.image.url
try:
return self.get_by_size(size).image.url
except Thumbnail.DoesNotExist:
return reverse('image-thumbnail', args=(self.id, size))
class ThumbnailManager(models.Manager):
def get_or_create_at_size(self, image, size):
if size not in IMAGE_SIZES:
raise ValueError("Received unknown size: %s" % size)
try:
thumbnail = image.get_by_size(size)
except Thumbnail.DoesNotExist:
img = utils.scale_and_crop(image.image, **IMAGE_SIZES[size])
# save to memory
buf = BytesIO()
try:
img.save(buf, img.format, **img.info)
except IOError:
if img.info.get('progression'):
orig_MAXBLOCK = PIL.ImageFile.MAXBLOCK
temp_MAXBLOCK = 1048576
if orig_MAXBLOCK >= temp_MAXBLOCK:
raise
PIL.ImageFile.MAXBLOCK = temp_MAXBLOCK
try:
img.save(buf, img.format, **img.info)
finally:
PIL.ImageFile.MAXBLOCK = orig_MAXBLOCK
else:
raise
# and save to storage
original_dir, original_file = os.path.split(image.image.name)
thumb_file = InMemoryUploadedFile(buf, "image", original_file,
None, buf.tell(), None)
thumbnail, created = image.thumbnail_set.get_or_create(
size=size, defaults={'image': thumb_file})
return thumbnail
class Thumbnail(models.Model):
original = models.ForeignKey(Image)
image = models.ImageField(upload_to=upload_to,
height_field='height', width_field='width',
max_length=255)
size = models.CharField(max_length=100)
height = models.PositiveIntegerField(default=0, editable=False)
width = models.PositiveIntegerField(default=0, editable=False)
objects = ThumbnailManager()
class Meta:
unique_together = ('original', 'size')
def get_absolute_url(self):
return self.image.url
@receiver(models.signals.post_save)
def original_changed(sender, instance, created, **kwargs):
if isinstance(instance, Image):
instance.thumbnail_set.all().delete()
@receiver(models.signals.post_delete)
def delete_image_files(sender, instance, **kwargs):
if isinstance(instance, (Image, Thumbnail)) and IMAGE_AUTO_DELETE:
if instance.image.storage.exists(instance.image.name):
instance.image.delete(save=False)