uploaded.py 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. from hashlib import sha256
  2. from math import floor
  3. from path import Path
  4. from PIL import Image
  5. from django.core.exceptions import ValidationError
  6. from django.utils.translation import ugettext as _
  7. from misago.conf import settings
  8. from . import store
  9. ALLOWED_EXTENSIONS = ('.gif', '.png', '.jpg', '.jpeg')
  10. ALLOWED_MIME_TYPES = ('image/gif', 'image/jpeg', 'image/png', 'image/mpo')
  11. def validate_file_size(uploaded_file):
  12. upload_limit = settings.avatar_upload_limit * 1024
  13. if uploaded_file.size > upload_limit:
  14. raise ValidationError(_("Uploaded file is too big."))
  15. def validate_extension(uploaded_file):
  16. lowercased_name = uploaded_file.name.lower()
  17. for extension in ALLOWED_EXTENSIONS:
  18. if lowercased_name.endswith(extension):
  19. return True
  20. else:
  21. raise ValidationError(_("Uploaded file type is not allowed."))
  22. def validate_mime(uploaded_file):
  23. if uploaded_file.content_type not in ALLOWED_MIME_TYPES:
  24. raise ValidationError(_("Uploaded file type is not allowed."))
  25. def validate_dimensions(uploaded_file):
  26. image = Image.open(uploaded_file)
  27. min_size = max(settings.MISAGO_AVATARS_SIZES)
  28. if min(image.size) < min_size:
  29. message = _("Uploaded image should be at "
  30. "least %(size)s pixels tall and wide.")
  31. raise ValidationError(message % {'size': min_size})
  32. if image.size[0] * image.size[1] > 2000 * 3000:
  33. message = _("Uploaded image is too big.")
  34. raise ValidationError(message)
  35. image_ratio = float(min(image.size)) / float(max(image.size))
  36. if image_ratio < 0.25:
  37. message = _("Uploaded image ratio cannot be greater than 16:9.")
  38. raise ValidationError(message)
  39. return image
  40. def validate_uploaded_file(uploaded_file):
  41. try:
  42. validate_file_size(uploaded_file)
  43. validate_extension(uploaded_file)
  44. validate_mime(uploaded_file)
  45. return validate_dimensions(uploaded_file)
  46. except ValidationError as e:
  47. try:
  48. temporary_file_path = Path(uploaded_file.temporary_file_path())
  49. if temporary_file_path.exists():
  50. temporary_file_path.remove()
  51. except Exception:
  52. pass
  53. raise e
  54. def handle_uploaded_file(user, uploaded_file):
  55. image = validate_uploaded_file(uploaded_file)
  56. store.store_temporary_avatar(user, image)
  57. def clean_crop(image, crop):
  58. message = _("Crop data is invalid. Please try again.")
  59. crop_dict = {}
  60. try:
  61. crop_dict = {
  62. 'x': float(crop['offset']['x']),
  63. 'y': float(crop['offset']['y']),
  64. 'zoom': float(crop['zoom']),
  65. }
  66. except (KeyError, TypeError, ValueError):
  67. raise ValidationError(message)
  68. if crop_dict['zoom'] < 0 or crop_dict['zoom'] > 1:
  69. raise ValidationError(message)
  70. min_size = max(settings.MISAGO_AVATARS_SIZES)
  71. zoomed_size = (
  72. round(float(image.size[0]) * crop_dict['zoom'], 2),
  73. round(float(image.size[1]) * crop_dict['zoom'], 2)
  74. )
  75. if min(zoomed_size) < min_size:
  76. raise ValidationError(message)
  77. crop_square = {
  78. 'x': crop_dict['x'] * -1,
  79. 'y': crop_dict['y'] * -1,
  80. }
  81. if crop_square['x'] < 0 or crop_square['y'] < 0:
  82. raise ValidationError(message)
  83. if crop_square['x'] + min_size > zoomed_size[0]:
  84. raise ValidationError(message)
  85. if crop_square['y'] + min_size > zoomed_size[1]:
  86. raise ValidationError(message)
  87. return crop_dict
  88. def crop_source_image(user, source, crop):
  89. if source == 'tmp':
  90. image = Image.open(user.avatar_tmp)
  91. else:
  92. image = Image.open(user.avatar_src)
  93. crop = clean_crop(image, crop)
  94. min_size = max(settings.MISAGO_AVATARS_SIZES)
  95. if image.size[0] == min_size and image.size[0] == image.size[1]:
  96. cropped_image = image
  97. else:
  98. upscale = 1.0 / crop['zoom']
  99. cropped_image = image.crop((
  100. int(round(crop['x'] * upscale * -1, 0)),
  101. int(round(crop['y'] * upscale * -1, 0)),
  102. int(round((crop['x'] - min_size) * upscale * -1, 0)),
  103. int(round((crop['y'] - min_size) * upscale * -1, 0)),
  104. ))
  105. store.store_avatar(user, cropped_image)
  106. if source == 'tmp':
  107. store.store_original_avatar(user)
  108. return crop
  109. def has_temporary_avatar(user):
  110. return bool(user.avatar_tmp)
  111. def has_source_avatar(user):
  112. return bool(user.avatar_src)