attachment.py 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. import os
  2. from hashlib import md5
  3. from io import BytesIO
  4. from django.conf import settings
  5. from django.core.files import File
  6. from django.core.files.base import ContentFile
  7. from django.core.urlresolvers import reverse
  8. from django.db import models
  9. from django.utils import timezone
  10. from django.utils.crypto import get_random_string
  11. from PIL import Image
  12. from misago.core.utils import slugify
  13. def upload_to(instance, filename):
  14. spread_path = md5(str(instance.secret[:16]).encode()).hexdigest()
  15. secret = Attachment.generate_new_secret()
  16. filename_lowered = filename.lower().strip()
  17. for extension in instance.filetype.extensions_list:
  18. if filename_lowered.endswith(extension):
  19. break
  20. filename_clean = u'.'.join((
  21. slugify(filename[:(len(extension) + 1) * -1])[:16],
  22. extension
  23. ))
  24. return os.path.join('attachments', spread_path[:2], spread_path[2:4], secret, filename_clean)
  25. class Attachment(models.Model):
  26. secret = models.CharField(max_length=64)
  27. filetype = models.ForeignKey('AttachmentType')
  28. post = models.ForeignKey(
  29. 'Post',
  30. blank=True,
  31. null=True,
  32. on_delete=models.SET_NULL
  33. )
  34. uploaded_on = models.DateTimeField(default=timezone.now)
  35. uploader = models.ForeignKey(
  36. settings.AUTH_USER_MODEL,
  37. blank=True,
  38. null=True,
  39. on_delete=models.SET_NULL
  40. )
  41. uploader_name = models.CharField(max_length=255)
  42. uploader_slug = models.CharField(max_length=255)
  43. uploader_ip = models.GenericIPAddressField()
  44. filename = models.CharField(max_length=255)
  45. size = models.PositiveIntegerField(default=0)
  46. thumbnail = models.ImageField(blank=True, null=True, upload_to=upload_to)
  47. image = models.ImageField(blank=True, null=True, upload_to=upload_to)
  48. file = models.FileField(blank=True, null=True, upload_to=upload_to)
  49. @classmethod
  50. def generate_new_secret(cls):
  51. return get_random_string(settings.MISAGO_ATTACHMENT_SECRET_LENGTH)
  52. @property
  53. def is_image(self):
  54. return bool(self.image)
  55. @property
  56. def is_file(self):
  57. return not self.is_image
  58. def get_absolute_url(self):
  59. return reverse('misago:attachment', kwargs={
  60. 'pk': self.pk,
  61. 'secret': self.secret,
  62. })
  63. def get_thumbnail_url(self):
  64. if self.thumbnail:
  65. return reverse('misago:attachment-thumbnail', kwargs={
  66. 'pk': self.pk,
  67. 'secret': self.secret,
  68. })
  69. else:
  70. return None
  71. def set_file(self, upload):
  72. self.file = File(upload, upload.name)
  73. def set_image(self, upload):
  74. fileformat = self.filetype.extensions_list[0]
  75. self.image = File(upload, upload.name)
  76. thumbnail = Image.open(upload)
  77. downscale_image = (
  78. thumbnail.size[0] > settings.MISAGO_ATTACHMENT_IMAGE_SIZE_LIMIT[0] or
  79. thumbnail.size[1] > settings.MISAGO_ATTACHMENT_IMAGE_SIZE_LIMIT[1]
  80. )
  81. strip_animation = fileformat == 'gif'
  82. thumb_stream = BytesIO()
  83. if downscale_image:
  84. if fileformat == 'jpg':
  85. # normalize jpg to jpeg for Pillow
  86. thumbnail.save(thumb_stream, 'jpeg')
  87. else:
  88. thumbnail.save(thumb_stream, fileformat)
  89. elif strip_animation:
  90. thumbnail.save(thumb_stream, fileformat)
  91. if downscale_image or strip_animation:
  92. self.thumbnail = ContentFile(thumb_stream.getvalue(), upload.name)