uploaded.py 4.3 KB

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