usercp.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  1. from django.contrib import messages
  2. from django.contrib.auth import update_session_auth_hash
  3. from django.core.exceptions import ValidationError
  4. from django.core.urlresolvers import reverse
  5. from django.db import IntegrityError, transaction
  6. from django.http import Http404, JsonResponse
  7. from django.shortcuts import redirect, render as django_render
  8. from django.utils.translation import ugettext as _
  9. from django.views.decorators.debug import sensitive_post_parameters
  10. from misago.conf import settings
  11. from misago.core.decorators import ajax_only, require_POST
  12. from misago.core.exceptions import AjaxError
  13. from misago.core.mail import mail_user
  14. from misago.markup import Editor
  15. from misago.users import avatars
  16. from misago.users.decorators import deny_guests
  17. from misago.users.forms.rename import ChangeUsernameForm
  18. from misago.users.forms.usercp import (ChangeForumOptionsForm,
  19. EditSignatureForm,
  20. ChangeEmailPasswordForm)
  21. from misago.users.signatures import set_user_signature
  22. from misago.users.sites import usercp
  23. from misago.users.changedcredentials import (cache_new_credentials,
  24. get_new_credentials)
  25. from misago.users.namechanges import UsernameChanges
  26. def render(request, template, context=None):
  27. context = context or {}
  28. context['pages'] = usercp.get_pages(request)
  29. for page in context['pages']:
  30. if page['is_active']:
  31. context['active_page'] = page
  32. break
  33. return django_render(request, template, context)
  34. @deny_guests
  35. def change_forum_options(request):
  36. form = ChangeForumOptionsForm(instance=request.user)
  37. if request.method == 'POST':
  38. form = ChangeForumOptionsForm(request.POST, instance=request.user)
  39. if form.is_valid():
  40. form.save()
  41. message = _("Your forum options have been changed.")
  42. messages.success(request, message)
  43. return redirect('misago:usercp_change_forum_options')
  44. return render(request, 'misago/usercp/change_forum_options.html',
  45. {'form': form})
  46. @deny_guests
  47. def change_avatar(request):
  48. avatar_size = max(settings.MISAGO_AVATARS_SIZES)
  49. if not request.user.is_avatar_banned and request.method == 'POST':
  50. if 'dl-gravatar' in request.POST and settings.allow_custom_avatars:
  51. try:
  52. avatars.gravatar.set_avatar(request.user)
  53. message = _("Gravatar was downloaded and set as new avatar.")
  54. messages.success(request, message)
  55. except avatars.gravatar.GravatarError:
  56. message = _("Failed to connect to Gravatar servers.")
  57. messages.info(request, message)
  58. except avatars.gravatar.NoGravatarAvailable:
  59. message = _("No Gravatar is associated "
  60. "with your e-mail address.")
  61. messages.info(request, message)
  62. elif 'set-dynamic' in request.POST:
  63. avatars.dynamic.set_avatar(request.user)
  64. message = _("New avatar based on your account was set.")
  65. messages.success(request, message)
  66. return redirect('misago:usercp_change_avatar')
  67. return render(request, 'misago/usercp/change_avatar.html', {
  68. 'avatar_size': avatar_size,
  69. 'galleries_exist': avatars.gallery.galleries_exist(),
  70. 'has_source_image': avatars.uploaded.has_original_avatar(request.user)
  71. })
  72. def avatar_not_banned(f):
  73. def decorator(request, *args, **kwargs):
  74. if request.user.is_avatar_banned:
  75. message = _("You don't have permission to change your avatar.")
  76. messages.info(request, message)
  77. return redirect('misago:usercp_change_avatar')
  78. else:
  79. return f(request, *args, **kwargs)
  80. return decorator
  81. @deny_guests
  82. @avatar_not_banned
  83. def upload_avatar(request):
  84. if not settings.allow_custom_avatars:
  85. messages.info(request, _("Avatar uploads are currently disabled."))
  86. return redirect('misago:usercp_change_avatar')
  87. return render(request, 'misago/usercp/upload_avatar.html', {
  88. 'upload_limit': settings.avatar_upload_limit * 1024,
  89. 'upload_limit_mb': settings.avatar_upload_limit / 1024.0,
  90. 'allowed_extensions': avatars.uploaded.ALLOWED_EXTENSIONS,
  91. 'allowed_mime_types': avatars.uploaded.ALLOWED_MIME_TYPES,
  92. })
  93. @ajax_only
  94. @deny_guests
  95. @require_POST
  96. @avatar_not_banned
  97. def upload_avatar_handler(request):
  98. if not settings.allow_custom_avatars:
  99. raise AjaxError(_("Avatar uploads are currently disabled."))
  100. new_avatar = request.FILES.get('new-avatar');
  101. if not new_avatar:
  102. raise AjaxError(_("No file was sent."))
  103. try:
  104. avatars.uploaded.handle_uploaded_file(request.user, new_avatar)
  105. except ValidationError as e:
  106. raise AjaxError(e.args[0])
  107. return JsonResponse({'is_error': 0, 'message': 'Image has been uploaded.'})
  108. @deny_guests
  109. @avatar_not_banned
  110. def crop_avatar(request, use_tmp_avatar):
  111. if use_tmp_avatar:
  112. if not avatars.uploaded.has_temporary_avatar(request.user):
  113. messages.error(request, _("Upload image that you want to crop."))
  114. return redirect('misago:usercp_change_avatar')
  115. else:
  116. if not avatars.uploaded.has_original_avatar(request.user):
  117. messages.error(request, _("You don't have uploaded image to crop."))
  118. return redirect('misago:usercp_change_avatar')
  119. if use_tmp_avatar:
  120. token = avatars.uploaded.avatar_source_token(request.user, 'tmp')
  121. avatar_url = reverse('misago:user_avatar_tmp', kwargs={
  122. 'user_id': request.user.pk, 'token': token
  123. })
  124. else:
  125. token = avatars.uploaded.avatar_source_token(request.user, 'org')
  126. avatar_url = reverse('misago:user_avatar_org', kwargs={
  127. 'user_id': request.user.pk, 'token': token
  128. })
  129. if request.method == 'POST':
  130. crop = request.POST.get('crop')
  131. try:
  132. if use_tmp_avatar:
  133. avatars.uploaded.crop_source_image(request.user, 'tmp', crop)
  134. else:
  135. avatars.uploaded.crop_source_image(request.user, 'org', crop)
  136. request.user.avatar_crop = crop
  137. request.user.save(update_fields=['avatar_crop'])
  138. if use_tmp_avatar:
  139. messages.success(request, _("Uploaded avatar was set."))
  140. else:
  141. messages.success(request, _("Avatar was cropped."))
  142. return redirect('misago:usercp_change_avatar')
  143. except ValidationError as e:
  144. messages.error(request, e.args[0])
  145. if not use_tmp_avatar and request.user.avatar_crop:
  146. user_crop = request.user.avatar_crop.split(',')
  147. current_crop = {
  148. 'image_width': user_crop[0],
  149. 'selection_len': user_crop[3],
  150. 'start_x': user_crop[4],
  151. 'start_y': user_crop[6],
  152. }
  153. else:
  154. current_crop = None
  155. return render(request, 'misago/usercp/crop_avatar.html', {
  156. 'avatar_url': avatar_url,
  157. 'crop': current_crop
  158. })
  159. @deny_guests
  160. @avatar_not_banned
  161. def avatar_galleries(request):
  162. if not avatars.gallery.galleries_exist():
  163. messages.info(request, _("No avatars galleries exist."))
  164. return redirect('misago:usercp_change_avatar')
  165. if request.method == 'POST':
  166. new_image = request.POST.get('new-image')
  167. if new_image:
  168. if avatars.gallery.is_avatar_from_gallery(new_image):
  169. avatars.gallery.set_avatar(request.user, new_image)
  170. messages.success(request, _("Avatar from gallery was set."))
  171. return redirect('misago:usercp_change_avatar')
  172. else:
  173. messages.error(request, _("Incorrect image."))
  174. return render(request, 'misago/usercp/avatar_galleries.html', {
  175. 'galleries': avatars.gallery.get_available_galleries()
  176. })
  177. @deny_guests
  178. def edit_signature(request):
  179. if not request.user.acl['can_have_signature']:
  180. raise Http404()
  181. form = EditSignatureForm(instance=request.user)
  182. if not request.user.is_signature_banned and request.method == 'POST':
  183. form = EditSignatureForm(request.POST, instance=request.user)
  184. if form.is_valid():
  185. set_user_signature(request.user, form.cleaned_data['signature'])
  186. request.user.save(update_fields=['signature', 'signature_parsed',
  187. 'signature_checksum'])
  188. if form.cleaned_data['signature']:
  189. messages.success(request, _("Your signature has been edited."))
  190. else:
  191. message = _("Your signature has been cleared.")
  192. messages.success(request, message)
  193. return redirect('misago:usercp_edit_signature')
  194. acl = request.user.acl
  195. editor = Editor(form['signature'],
  196. allow_blocks=acl['allow_signature_blocks'],
  197. allow_links=acl['allow_signature_links'],
  198. allow_images=acl['allow_signature_images'])
  199. return render(request, 'misago/usercp/edit_signature.html',
  200. {'form': form, 'editor': editor})
  201. @deny_guests
  202. @transaction.atomic()
  203. def change_username(request):
  204. namechanges = UsernameChanges(request.user)
  205. form = ChangeUsernameForm()
  206. if request.method == 'POST' and namechanges.left:
  207. form = ChangeUsernameForm(request.POST, user=request.user)
  208. if form.is_valid():
  209. try:
  210. form.change_username(changed_by=request.user)
  211. message = _("Your username has been changed.")
  212. messages.success(request, message)
  213. return redirect('misago:usercp_change_username')
  214. except IntegrityError:
  215. message = _("Error changing username. Please try again.")
  216. messages.error(request, message)
  217. return render(request, 'misago/usercp/change_username.html', {
  218. 'form': form,
  219. 'changes_left': namechanges.left,
  220. 'next_change_on': namechanges.next_on
  221. })
  222. @sensitive_post_parameters()
  223. @deny_guests
  224. def change_email_password(request):
  225. form = ChangeEmailPasswordForm()
  226. if request.method == 'POST':
  227. form = ChangeEmailPasswordForm(request.POST, user=request.user)
  228. if form.is_valid():
  229. new_email = ''
  230. new_password = ''
  231. # Store original data
  232. old_email = request.user.email
  233. old_password = request.user.password
  234. # Assign new creds to user temporarily
  235. if form.cleaned_data['new_email']:
  236. request.user.set_email(form.cleaned_data['new_email'])
  237. new_email = request.user.email
  238. if form.cleaned_data['new_password']:
  239. request.user.set_password(form.cleaned_data['new_password'])
  240. new_password = request.user.password
  241. request.user.email = old_email
  242. request.user.password = old_password
  243. credentials_token = cache_new_credentials(
  244. request.user, new_email, new_password)
  245. mail_subject = _("Confirm changes to %(username)s account "
  246. "on %(forum_title)s forums")
  247. subject_formats = {'username': request.user.username,
  248. 'forum_title': settings.forum_name}
  249. mail_subject = mail_subject % subject_formats
  250. if new_email:
  251. # finally override email before sending message
  252. request.user.email = new_email
  253. mail_user(request, request.user, mail_subject,
  254. 'misago/emails/change_email_password',
  255. {'credentials_token': credentials_token})
  256. message = _("E-mail was sent to %(email)s with a link that "
  257. "you have to click to confirm changes.")
  258. messages.info(request, message % {'email': request.user.email})
  259. return redirect('misago:usercp_change_email_password')
  260. return render(request, 'misago/usercp/change_email_password.html',
  261. {'form': form})
  262. @deny_guests
  263. def confirm_email_password_change(request, token):
  264. new_credentials = get_new_credentials(request.user, token)
  265. if not new_credentials:
  266. messages.error(request, _("Confirmation link is invalid."))
  267. else:
  268. changes_made = []
  269. if new_credentials['email']:
  270. request.user.set_email(new_credentials['email'])
  271. changes_made.extend(['email', 'email_hash'])
  272. if new_credentials['password']:
  273. request.user.password = new_credentials['password']
  274. update_session_auth_hash(request, request.user)
  275. changes_made.append('password')
  276. try:
  277. request.user.save(update_fields=changes_made)
  278. message = _("Changes in e-mail and password have been saved.")
  279. messages.success(request, message)
  280. except IntegrityError:
  281. messages.error(request, _("Confirmation link is invalid."))
  282. return redirect('misago:usercp_change_email_password')