usercp.py 13 KB

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