moderation.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  1. from django.core.urlresolvers import reverse
  2. from django.contrib import messages
  3. from django.contrib.auth import get_user_model
  4. from django.db import IntegrityError, transaction
  5. from django.shortcuts import redirect, render
  6. from django.utils.translation import ugettext as _
  7. from misago.acl import add_acl
  8. from misago.core.decorators import require_POST
  9. from misago.core.shortcuts import get_object_or_404, validate_slug
  10. from misago.core.utils import clean_return_path
  11. from misago.markup import Editor
  12. from misago.notifications import notify_user
  13. from misago.users.avatars.dynamic import set_avatar as set_dynamic_avatar
  14. from misago.users import warnings
  15. from misago.users.bans import get_user_ban
  16. from misago.users.decorators import deny_guests
  17. from misago.users.forms.rename import ChangeUsernameForm
  18. from misago.users.forms.modusers import (BanForm, ModerateAvatarForm,
  19. ModerateSignatureForm, WarnUserForm)
  20. from misago.users.models import Ban
  21. from misago.users.pages import user_profile
  22. from misago.users.permissions.moderation import (allow_rename_user,
  23. allow_moderate_avatar,
  24. allow_moderate_signature,
  25. allow_ban_user,
  26. allow_lift_ban)
  27. from misago.users.permissions.warnings import (allow_warn_user,
  28. allow_see_warnings,
  29. allow_cancel_warning,
  30. allow_delete_warning)
  31. from misago.users.permissions.delete import allow_delete_user
  32. from misago.users.signatures import set_user_signature
  33. def user_moderation_view(required_permission=None):
  34. def wrap(f):
  35. @deny_guests
  36. @transaction.atomic
  37. def decorator(request, *args, **kwargs):
  38. queryset = get_user_model().objects.select_for_update()
  39. user_id = kwargs.pop('user_id')
  40. kwargs['user'] = get_object_or_404(queryset, id=user_id)
  41. validate_slug(kwargs['user'], kwargs.pop('user_slug'))
  42. add_acl(request.user, kwargs['user'])
  43. if required_permission:
  44. required_permission(request.user, kwargs['user'])
  45. return f(request, *args, **kwargs)
  46. return decorator
  47. return wrap
  48. def moderation_return_path(request, user):
  49. return_path = clean_return_path(request)
  50. if not return_path:
  51. return reverse(user_profile.get_default_link(),
  52. kwargs={'user_slug': user.slug, 'user_id': user.pk})
  53. return return_path
  54. @user_moderation_view(allow_warn_user)
  55. def warn(request, user, reason=None):
  56. return_path = moderation_return_path(request, user)
  57. if warnings.is_user_warning_level_max(user):
  58. message = _("%(user)s has maximum warning "
  59. "level and can't be warned.")
  60. message = message % {'user': user.username}
  61. messages.info(request, message)
  62. return redirect(return_path)
  63. form = WarnUserForm(initial={'reason': reason})
  64. if request.method == 'POST':
  65. form = WarnUserForm(request.POST)
  66. if form.is_valid():
  67. warnings.warn_user(request.user, user, form.cleaned_data['reason'])
  68. notify_user(user,
  69. _("%(user)s has given you an warning."),
  70. reverse('misago:user_warnings', kwargs={
  71. 'user_slug': user.slug, 'user_id': user.pk
  72. }),
  73. "warnings_%s" % user.pk,
  74. formats={'user': request.user.username},
  75. sender=request.user)
  76. message = _("%(user)s has been warned.")
  77. message = message % {'user': user.username}
  78. messages.success(request, message)
  79. return redirect(return_path)
  80. warning_levels = warnings.get_warning_levels()
  81. current_level = warning_levels[user.warning_level]
  82. next_level = warning_levels[user.warning_level + 1]
  83. return render(request, 'misago/modusers/warn.html', {
  84. 'profile': user,
  85. 'form': form,
  86. 'return_path': return_path,
  87. 'current_level': current_level,
  88. 'next_level': next_level
  89. })
  90. def warning_moderation_view(required_permission=None):
  91. def wrap(f):
  92. @deny_guests
  93. @transaction.atomic
  94. def decorator(request, *args, **kwargs):
  95. queryset = kwargs['user'].warnings
  96. warning_id = kwargs.pop('warning_id')
  97. kwargs['warning'] = get_object_or_404(queryset, id=warning_id)
  98. add_acl(request.user, kwargs['warning'])
  99. required_permission(request.user, kwargs['warning'])
  100. response = f(request, *args, **kwargs)
  101. if response:
  102. return response
  103. else:
  104. return_path = moderation_return_path(request, kwargs['user'])
  105. return redirect(return_path)
  106. return decorator
  107. return wrap
  108. @user_moderation_view(allow_see_warnings)
  109. @warning_moderation_view(allow_cancel_warning)
  110. def cancel_warning(request, user, warning):
  111. warnings.cancel_warning(request.user, user, warning)
  112. message = _("%(user)s's warning has been canceled.")
  113. message = message % {'user': user.username}
  114. messages.success(request, message)
  115. notify_user(user,
  116. _("%(user)s has canceled your warning."),
  117. reverse('misago:user_warnings', kwargs={
  118. 'user_slug': user.slug, 'user_id': user.pk
  119. }),
  120. "warnings_%s" % user.pk,
  121. formats={'user': request.user.username},
  122. sender=request.user)
  123. @user_moderation_view(allow_see_warnings)
  124. @warning_moderation_view(allow_delete_warning)
  125. def delete_warning(request, user, warning):
  126. warnings.delete_warning(request.user, user, warning)
  127. message = _("%(user)s's warning has been deleted.")
  128. message = message % {'user': user.username}
  129. messages.success(request, message)
  130. notify_user(user,
  131. _("%(user)s has deleted your warning."),
  132. reverse('misago:user_warnings', kwargs={
  133. 'user_slug': user.slug, 'user_id': user.pk
  134. }),
  135. "warnings_%s" % user.pk,
  136. formats={'user': request.user.username},
  137. sender=request.user)
  138. @user_moderation_view(allow_rename_user)
  139. def rename(request, user):
  140. return_path = moderation_return_path(request, user)
  141. form = ChangeUsernameForm(user=user)
  142. if request.method == 'POST':
  143. old_username = user.username
  144. form = ChangeUsernameForm(request.POST, user=user)
  145. if form.is_valid():
  146. try:
  147. form.change_username(changed_by=request.user)
  148. notify_user(user,
  149. _("%(user)s has changed your name to %(newname)s."),
  150. reverse('misago:user_warnings', kwargs={
  151. 'user_slug': user.slug, 'user_id': user.pk
  152. }),
  153. "name_history_%s" % user.pk,
  154. formats={
  155. 'user': request.user.username,
  156. 'newname': user.username,
  157. },
  158. sender=request.user)
  159. message = _("%(old_username)s's username has been changed.")
  160. message = message % {'old_username': old_username}
  161. messages.success(request, message)
  162. return redirect(return_path)
  163. except IntegrityError:
  164. message = _("Error changing username. Please try again.")
  165. messages.error(request, message)
  166. return render(request, 'misago/modusers/rename.html',
  167. {'profile': user, 'form': form, 'return_path': return_path})
  168. @user_moderation_view(allow_moderate_avatar)
  169. def moderate_avatar(request, user):
  170. return_path = moderation_return_path(request, user)
  171. avatar_locked = user.is_avatar_locked
  172. form = ModerateAvatarForm(instance=user)
  173. if request.method == 'POST':
  174. form = ModerateAvatarForm(request.POST, instance=user)
  175. if form.is_valid():
  176. if not avatar_locked and form.cleaned_data['is_avatar_locked']:
  177. set_dynamic_avatar(user)
  178. user.save(update_fields=(
  179. 'is_avatar_locked',
  180. 'avatar_lock_user_message',
  181. 'avatar_lock_staff_message'
  182. ))
  183. if avatar_locked != user.is_avatar_locked:
  184. if user.is_avatar_locked:
  185. message = _("%(user)s has locked your avatar.")
  186. else:
  187. message = _("%(user)s has unlocked your avatar.")
  188. notify_user(user,
  189. message,
  190. reverse('misago:usercp_change_avatar'),
  191. "usercp_avatar_%s" % user.pk,
  192. formats={'user': request.user.username},
  193. sender=request.user)
  194. message = _("%(user)s's avatar has been moderated.")
  195. message = message % {'user': user.username}
  196. messages.success(request, message)
  197. if 'stay' not in request.POST:
  198. return redirect(return_path)
  199. return render(request, 'misago/modusers/avatar.html',
  200. {'profile': user, 'form': form, 'return_path': return_path})
  201. @user_moderation_view(allow_moderate_signature)
  202. def moderate_signature(request, user):
  203. return_path = moderation_return_path(request, user)
  204. form = ModerateSignatureForm(instance=user)
  205. if request.method == 'POST':
  206. form = ModerateSignatureForm(request.POST, instance=user)
  207. if form.is_valid():
  208. set_user_signature(request, user, form.cleaned_data['signature'])
  209. user.save(update_fields=(
  210. 'signature',
  211. 'signature_parsed',
  212. 'signature_checksum',
  213. 'is_signature_locked',
  214. 'signature_lock_user_message',
  215. 'signature_lock_staff_message'
  216. ))
  217. message = _("%(user)s's signature has been moderated.")
  218. message = message % {'user': user.username}
  219. messages.success(request, message)
  220. notify_user(user,
  221. _("%(user)s has moderated your signature."),
  222. reverse('misago:usercp_edit_signature'),
  223. "usercp_signature_%s" % user.pk,
  224. formats={'user': request.user.username},
  225. sender=request.user)
  226. if 'stay' not in request.POST:
  227. return redirect(return_path)
  228. acl = user.acl
  229. editor = Editor(form['signature'],
  230. allow_blocks=acl['allow_signature_blocks'],
  231. allow_links=acl['allow_signature_links'],
  232. allow_images=acl['allow_signature_images'])
  233. return render(request, 'misago/modusers/signature.html', {
  234. 'profile': user,
  235. 'form': form,
  236. 'editor': editor,
  237. 'return_path': return_path
  238. })
  239. @user_moderation_view(allow_ban_user)
  240. def ban_user(request, user):
  241. return_path = moderation_return_path(request, user)
  242. form = BanForm(user=user)
  243. if request.method == 'POST':
  244. form = BanForm(request.POST, user=user)
  245. if form.is_valid():
  246. form.ban_user()
  247. message = _("%(user)s has been banned.")
  248. messages.success(request, message % {'user': user.username})
  249. return redirect(return_path)
  250. return render(request, 'misago/modusers/ban.html',
  251. {'profile': user, 'form': form, 'return_path': return_path})
  252. @require_POST
  253. @user_moderation_view(allow_lift_ban)
  254. def lift_user_ban(request, user):
  255. return_path = moderation_return_path(request, user)
  256. user_ban = get_user_ban(user).ban
  257. user_ban.lift()
  258. user_ban.save()
  259. Ban.objects.invalidate_cache()
  260. message = _("%(user)s's ban has been lifted.")
  261. messages.success(request, message % {'user': user.username})
  262. return redirect(return_path)
  263. @require_POST
  264. @user_moderation_view(allow_delete_user)
  265. def delete(request, user):
  266. user.delete(delete_content=True)
  267. message = _("User %(user)s has been deleted.")
  268. messages.success(request, message % {'user': user.username})
  269. return redirect('misago:index')