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.
 
 
 
 
 
 

128 lines
4.2 KiB

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