avatar.py 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. import json
  2. from django.core.exceptions import ValidationError
  3. from django.utils.translation import gettext as _
  4. from rest_framework import status
  5. from rest_framework.response import Response
  6. from misago.conf import settings
  7. from misago.core.decorators import require_dict_data
  8. from misago.core.utils import format_plaintext_for_html
  9. from misago.users import avatars
  10. from misago.users.models import AvatarGallery
  11. from misago.users.serializers import ModerateAvatarSerializer
  12. @require_dict_data
  13. def avatar_endpoint(request, pk=None):
  14. if request.user.is_avatar_locked:
  15. if request.user.avatar_lock_user_message:
  16. reason = format_plaintext_for_html(request.user.avatar_lock_user_message)
  17. else:
  18. reason = None
  19. return Response(
  20. {
  21. "detail": _("Your avatar is locked. You can't change it."),
  22. "reason": reason,
  23. },
  24. status=status.HTTP_403_FORBIDDEN,
  25. )
  26. avatar_options = get_avatar_options(request, request.user)
  27. if request.method == "POST":
  28. return avatar_post(request, avatar_options)
  29. else:
  30. return Response(avatar_options)
  31. def get_avatar_options(request, user):
  32. options = {
  33. "avatars": user.avatars,
  34. "generated": True,
  35. "gravatar": False,
  36. "crop_src": False,
  37. "crop_tmp": False,
  38. "upload": False,
  39. "galleries": False,
  40. }
  41. # Allow existing galleries
  42. if avatars.gallery.galleries_exist():
  43. options["galleries"] = []
  44. for gallery in avatars.gallery.get_available_galleries():
  45. gallery_images = []
  46. for image in gallery["images"]:
  47. gallery_images.append({"id": image.id, "url": image.url})
  48. options["galleries"].append(
  49. {"name": gallery["name"], "images": gallery_images}
  50. )
  51. # Can't have custom avatar?
  52. if not request.settings.allow_custom_avatars:
  53. return options
  54. # Allow Gravatar download
  55. options["gravatar"] = True
  56. # Allow crop if we have uploaded temporary avatar
  57. if avatars.uploaded.has_source_avatar(user):
  58. try:
  59. options["crop_src"] = {
  60. "url": user.avatar_src.url,
  61. "crop": json.loads(user.avatar_crop),
  62. "size": max(settings.MISAGO_AVATARS_SIZES),
  63. }
  64. except (TypeError, ValueError):
  65. pass
  66. # Allow crop of uploaded avatar
  67. if avatars.uploaded.has_temporary_avatar(user):
  68. options["crop_tmp"] = {
  69. "url": user.avatar_tmp.url,
  70. "size": max(settings.MISAGO_AVATARS_SIZES),
  71. }
  72. # Allow upload conditions
  73. options["upload"] = {
  74. "limit": request.settings.avatar_upload_limit * 1024,
  75. "allowed_extensions": avatars.uploaded.ALLOWED_EXTENSIONS,
  76. "allowed_mime_types": avatars.uploaded.ALLOWED_MIME_TYPES,
  77. }
  78. return options
  79. class AvatarError(Exception):
  80. pass
  81. def avatar_post(request, options):
  82. user = request.user
  83. data = request.data
  84. avatar_type = data.get("avatar", "nope")
  85. try:
  86. type_options = options[avatar_type]
  87. if not type_options:
  88. return Response(
  89. {"detail": _("This avatar type is not allowed.")},
  90. status=status.HTTP_400_BAD_REQUEST,
  91. )
  92. avatar_strategy = AVATAR_TYPES[avatar_type]
  93. except KeyError:
  94. return Response(
  95. {"detail": _("Unknown avatar type.")}, status=status.HTTP_400_BAD_REQUEST
  96. )
  97. try:
  98. if avatar_type == "upload":
  99. # avatar_upload strategy requires access to request.settings
  100. response_dict = {"detail": avatar_upload(request, user, data)}
  101. else:
  102. response_dict = {"detail": avatar_strategy(user, data)}
  103. except AvatarError as e:
  104. return Response({"detail": e.args[0]}, status=status.HTTP_400_BAD_REQUEST)
  105. user.save()
  106. updated_options = get_avatar_options(request, user)
  107. response_dict.update(updated_options)
  108. return Response(response_dict)
  109. def avatar_generate(user, data):
  110. avatars.dynamic.set_avatar(user)
  111. return _("New avatar based on your account was set.")
  112. def avatar_gravatar(user, data):
  113. try:
  114. avatars.gravatar.set_avatar(user)
  115. return _("Gravatar was downloaded and set as new avatar.")
  116. except avatars.gravatar.NoGravatarAvailable:
  117. raise AvatarError(_("No Gravatar is associated with your e-mail address."))
  118. except avatars.gravatar.GravatarError:
  119. raise AvatarError(_("Failed to connect to Gravatar servers."))
  120. def avatar_gallery(user, data):
  121. try:
  122. image_pk = int(data.get("image"))
  123. image = AvatarGallery.objects.get(pk=image_pk)
  124. if image.gallery == "__default__":
  125. raise ValueError()
  126. avatars.gallery.set_avatar(user, image)
  127. return _("Avatar from gallery was set.")
  128. except (TypeError, ValueError, AvatarGallery.DoesNotExist):
  129. raise AvatarError(_("Incorrect image."))
  130. def avatar_upload(request, user, data):
  131. new_avatar = data.get("image")
  132. if not new_avatar:
  133. raise AvatarError(_("No file was sent."))
  134. try:
  135. avatars.uploaded.handle_uploaded_file(request, user, new_avatar)
  136. except ValidationError as e:
  137. raise AvatarError(e.args[0])
  138. # send back url for temp image
  139. return user.avatar_tmp.url
  140. def avatar_crop_src(user, data):
  141. avatar_crop(user, data, "src")
  142. return _("Avatar was re-cropped.")
  143. def avatar_crop_tmp(user, data):
  144. avatar_crop(user, data, "tmp")
  145. return _("Uploaded avatar was set.")
  146. def avatar_crop(user, data, suffix):
  147. try:
  148. crop = avatars.uploaded.crop_source_image(user, suffix, data.get("crop", {}))
  149. user.avatar_crop = json.dumps(crop)
  150. except ValidationError as e:
  151. raise AvatarError(e.args[0])
  152. AVATAR_TYPES = {
  153. "generated": avatar_generate,
  154. "gravatar": avatar_gravatar,
  155. "galleries": avatar_gallery,
  156. "upload": avatar_upload,
  157. "crop_src": avatar_crop_src,
  158. "crop_tmp": avatar_crop_tmp,
  159. }
  160. def moderate_avatar_endpoint(request, profile):
  161. if request.method == "POST":
  162. is_avatar_locked = profile.is_avatar_locked
  163. serializer = ModerateAvatarSerializer(profile, data=request.data)
  164. if serializer.is_valid():
  165. if serializer.validated_data["is_avatar_locked"] and not is_avatar_locked:
  166. avatars.dynamic.set_avatar(profile)
  167. serializer.save()
  168. return Response(
  169. {
  170. "avatars": profile.avatars,
  171. "is_avatar_locked": int(profile.is_avatar_locked),
  172. "avatar_lock_user_message": profile.avatar_lock_user_message,
  173. "avatar_lock_staff_message": profile.avatar_lock_staff_message,
  174. }
  175. )
  176. else:
  177. return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
  178. else:
  179. return Response(
  180. {
  181. "is_avatar_locked": int(profile.is_avatar_locked),
  182. "avatar_lock_user_message": profile.avatar_lock_user_message,
  183. "avatar_lock_staff_message": profile.avatar_lock_staff_message,
  184. }
  185. )