usercp.py 13 KB

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