You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

models.py 4.4 KiB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. import hashlib
  2. import os.path
  3. from io import BytesIO
  4. from django.db import models
  5. from django.core.files.uploadedfile import InMemoryUploadedFile
  6. from django.core.urlresolvers import reverse
  7. from django.dispatch import receiver
  8. import PIL
  9. try:
  10. from importlib import import_module
  11. except ImportError:
  12. from django.utils.importlib import import_module
  13. from . import utils
  14. from .settings import IMAGE_SIZES, IMAGE_PATH, IMAGE_AUTO_DELETE
  15. def hashed_upload_to(instance, filename, **kwargs):
  16. image_type = 'original' if isinstance(instance, Image) else 'thumbnail'
  17. prefix = 'image/%s/by-md5/' % (image_type,)
  18. hasher = hashlib.md5()
  19. for chunk in instance.image.chunks():
  20. hasher.update(chunk)
  21. hash_ = hasher.hexdigest()
  22. base, ext = os.path.splitext(filename)
  23. return '%(prefix)s%(first)s/%(second)s/%(hash)s/%(base)s%(ext)s' % {
  24. 'prefix': prefix,
  25. 'first': hash_[0],
  26. 'second': hash_[1],
  27. 'hash': hash_,
  28. 'base': base,
  29. 'ext': ext,
  30. }
  31. if IMAGE_PATH is None:
  32. upload_to = hashed_upload_to
  33. else:
  34. if callable(IMAGE_PATH):
  35. upload_to = IMAGE_PATH
  36. else:
  37. parts = IMAGE_PATH.split('.')
  38. module_name = '.'.join(parts[:-1])
  39. module = import_module(module_name)
  40. upload_to = getattr(module, parts[-1])
  41. class Image(models.Model):
  42. image = models.ImageField(upload_to=upload_to,
  43. height_field='height', width_field='width',
  44. max_length=255)
  45. height = models.PositiveIntegerField(default=0, editable=False)
  46. width = models.PositiveIntegerField(default=0, editable=False)
  47. def get_by_size(self, size):
  48. return self.thumbnail_set.get(size=size)
  49. def get_absolute_url(self, size=None):
  50. if not size:
  51. return self.image.url
  52. try:
  53. return self.get_by_size(size).image.url
  54. except Thumbnail.DoesNotExist:
  55. return reverse('image-thumbnail', args=(self.id, size))
  56. class ThumbnailManager(models.Manager):
  57. def get_or_create_at_size(self, image_id, size):
  58. image = Image.objects.get(id=image_id)
  59. if size not in IMAGE_SIZES:
  60. raise ValueError("Received unknown size: %s" % size)
  61. try:
  62. thumbnail = image.get_by_size(size)
  63. except Thumbnail.DoesNotExist:
  64. img = utils.scale_and_crop(image.image, **IMAGE_SIZES[size])
  65. # save to memory
  66. buf = BytesIO()
  67. try:
  68. img.save(buf, img.format, **img.info)
  69. except IOError:
  70. if img.info.get('progression'):
  71. orig_MAXBLOCK = PIL.ImageFile.MAXBLOCK
  72. temp_MAXBLOCK = 1048576
  73. if orig_MAXBLOCK >= temp_MAXBLOCK:
  74. raise
  75. PIL.ImageFile.MAXBLOCK = temp_MAXBLOCK
  76. try:
  77. img.save(buf, img.format, **img.info)
  78. finally:
  79. PIL.ImageFile.MAXBLOCK = orig_MAXBLOCK
  80. else:
  81. raise
  82. # and save to storage
  83. original_dir, original_file = os.path.split(image.image.name)
  84. thumb_file = InMemoryUploadedFile(buf, "image", original_file,
  85. None, buf.tell(), None)
  86. thumbnail, created = image.thumbnail_set.get_or_create(
  87. size=size, defaults={'image': thumb_file})
  88. return thumbnail
  89. class Thumbnail(models.Model):
  90. original = models.ForeignKey(Image)
  91. image = models.ImageField(upload_to=upload_to,
  92. height_field='height', width_field='width',
  93. max_length=255)
  94. size = models.CharField(max_length=100)
  95. height = models.PositiveIntegerField(default=0, editable=False)
  96. width = models.PositiveIntegerField(default=0, editable=False)
  97. objects = ThumbnailManager()
  98. class Meta:
  99. unique_together = ('original', 'size')
  100. def get_absolute_url(self):
  101. return self.image.url
  102. @receiver(models.signals.post_save)
  103. def original_changed(sender, instance, created, **kwargs):
  104. if isinstance(instance, Image):
  105. instance.thumbnail_set.all().delete()
  106. @receiver(models.signals.post_delete)
  107. def delete_image_files(sender, instance, **kwargs):
  108. if isinstance(instance, (Image, Thumbnail)) and IMAGE_AUTO_DELETE:
  109. if instance.image.storage.exists(instance.image.name):
  110. instance.image.delete(save=False)