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.2 KiB

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