uploaded.py 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. from hashlib import sha256
  2. from path import path
  3. from PIL import Image
  4. from django.core.exceptions import ValidationError
  5. from django.utils.translation import ugettext as _
  6. from misago.conf import settings
  7. from misago.users.avatars import store
  8. ALLOWED_EXTENSIONS = ('.gif', '.png', '.jpg', '.jpeg')
  9. ALLOWED_MIME_TYPES = ('image/gif', 'image/jpeg', 'image/png')
  10. def validate_file_size(uploaded_file):
  11. upload_limit = settings.avatar_upload_limit * 1024
  12. if uploaded_file.size > upload_limit:
  13. raise ValidationError(_("Uploaded file is too big."))
  14. def validate_extension(uploaded_file):
  15. lowercased_name = uploaded_file.name.lower()
  16. for extension in ALLOWED_EXTENSIONS:
  17. if lowercased_name.endswith(extension):
  18. return True
  19. else:
  20. raise ValidationError(_("Uploaded file type is not allowed."))
  21. def validate_mime(uploaded_file):
  22. if uploaded_file.content_type not in ALLOWED_MIME_TYPES:
  23. raise ValidationError(_("Uploaded file type is not allowed."))
  24. def validate_dimensions(uploaded_file):
  25. image = Image.open(uploaded_file)
  26. if min(image.size) < 100:
  27. message = _("Uploaded image should be at "
  28. "least 100 pixels tall and wide.")
  29. raise ValidationError(message)
  30. image = Image.open(uploaded_file)
  31. if image.size[0] * image.size[1] > 2000 * 3000:
  32. message = _("Uploaded image is too big.")
  33. raise ValidationError(message)
  34. image_ratio = float(min(image.size)) / float(max(image.size))
  35. if image_ratio < 0.25:
  36. message = _("Uploaded image ratio cannot be greater than 16:9.")
  37. raise ValidationError(message)
  38. return image
  39. def validate_uploaded_file(uploaded_file):
  40. try:
  41. validate_file_size(uploaded_file)
  42. validate_extension(uploaded_file)
  43. validate_mime(uploaded_file)
  44. return validate_dimensions(uploaded_file)
  45. except ValidationError as e:
  46. try:
  47. temporary_file_path = path(uploaded_file.temporary_file_path())
  48. if temporary_file_path.exists():
  49. temporary_file_path.remove()
  50. except Exception:
  51. pass
  52. raise e
  53. def handle_uploaded_file(user, uploaded_file):
  54. image = validate_uploaded_file(uploaded_file)
  55. store.store_temporary_avatar(user, image)
  56. def crop_string_to_dict(image, crop):
  57. message = _("Crop is invalid. Please try again.")
  58. crop_dict = {}
  59. try:
  60. crop_list = [int(x) for x in crop.split(',')]
  61. if len(crop_list) != 8:
  62. raise ValidationError(message)
  63. except (TypeError, ValueError):
  64. raise ValidationError(message)
  65. cropped_size = (crop_list[0], crop_list[1])
  66. if cropped_size[0] < 10:
  67. raise ValidationError(message)
  68. if cropped_size[1] < 10:
  69. raise ValidationError(message)
  70. if crop_list[2] != crop_list[3]:
  71. # We only allow cropping to squares
  72. raise ValidationError(message)
  73. crop_dict['width'] = crop_list[2]
  74. crop_dict['height'] = crop_list[3]
  75. if crop_dict['width'] != crop_dict['height']:
  76. raise ValidationError(message)
  77. if crop_dict['width'] > cropped_size[0]:
  78. raise ValidationError(message)
  79. if crop_dict['height'] > cropped_size[1]:
  80. raise ValidationError(message)
  81. crop_dict['source'] = (crop_list[4], crop_list[6],
  82. crop_list[5], crop_list[7])
  83. if crop_dict['source'][0] < 0 or crop_dict['source'][2] > cropped_size[0]:
  84. raise ValidationError(message)
  85. if crop_dict['source'][1] < 0 or crop_dict['source'][3] > cropped_size[1]:
  86. raise ValidationError(message)
  87. source_w = crop_dict['source'][2] - crop_dict['source'][0]
  88. source_h = crop_dict['source'][3] - crop_dict['source'][1]
  89. if source_w != source_h:
  90. raise ValidationError(message)
  91. crop_dict['ratio'] = float(image.size[0]) / float(cropped_size[0])
  92. return crop_dict
  93. def crop_source_image(user, source, crop):
  94. image = Image.open(store.avatar_file_path(user, source))
  95. crop = crop_string_to_dict(image, crop)
  96. crop_dimensions = [int(d * crop['ratio']) for d in crop['source']]
  97. cropped_image = image.crop(crop_dimensions)
  98. store.store_avatar(user, cropped_image)
  99. if source == 'tmp':
  100. store.store_original_avatar(user)
  101. return crop
  102. def avatar_source_token(user, source):
  103. token_seed = (
  104. unicode(user.pk),
  105. user.username,
  106. user.email,
  107. source,
  108. unicode(store.avatar_file_path(user, source)),
  109. settings.SECRET_KEY
  110. )
  111. return sha256('+'.join(token_seed)).hexdigest()[:10]
  112. def has_temporary_avatar(user):
  113. return store.avatar_file_exists(user, 'tmp')
  114. def has_original_avatar(user):
  115. return store.avatar_file_exists(user, 'org')