patch.py 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  1. from django.contrib.auth import get_user_model
  2. from django.core.exceptions import PermissionDenied, ValidationError
  3. from django.shortcuts import get_object_or_404
  4. from django.utils import six
  5. from django.utils.translation import ugettext as _
  6. from misago.acl import add_acl
  7. from misago.categories.models import Category
  8. from misago.categories.permissions import allow_browse_category, allow_see_category
  9. from misago.categories.serializers import CategorySerializer
  10. from misago.core.apipatch import ApiPatch
  11. from misago.core.shortcuts import get_int_or_404
  12. from misago.threads.moderation import threads as moderation
  13. from misago.threads.participants import (
  14. add_participant, change_owner, make_participants_aware, remove_participant)
  15. from misago.threads.permissions import (
  16. allow_add_participant, allow_add_participants, allow_approve_thread, allow_change_owner, allow_edit_thread,
  17. allow_pin_thread, allow_hide_thread, allow_move_thread, allow_remove_participant, allow_start_thread,
  18. allow_unhide_thread)
  19. from misago.threads.serializers import ThreadParticipantSerializer
  20. from misago.threads.validators import validate_title
  21. UserModel = get_user_model()
  22. thread_patch_dispatcher = ApiPatch()
  23. def patch_acl(request, thread, value):
  24. """useful little op that updates thread acl to current state"""
  25. if value:
  26. add_acl(request.user, thread)
  27. return {'acl': thread.acl}
  28. else:
  29. return {'acl': None}
  30. thread_patch_dispatcher.add('acl', patch_acl)
  31. def patch_title(request, thread, value):
  32. try:
  33. value_cleaned = six.text_type(value).strip()
  34. except (TypeError, ValueError):
  35. raise PermissionDenied(_("Invalid thread title."))
  36. try:
  37. validate_title(value_cleaned)
  38. except ValidationError as e:
  39. raise PermissionDenied(e.args[0])
  40. allow_edit_thread(request.user, thread)
  41. moderation.change_thread_title(request, thread, value_cleaned)
  42. return {'title': thread.title}
  43. thread_patch_dispatcher.replace('title', patch_title)
  44. def patch_weight(request, thread, value):
  45. allow_pin_thread(request.user, thread)
  46. if not thread.acl.get('can_pin_globally') and thread.weight == 2:
  47. raise PermissionDenied(_("You can't change globally pinned threads weights in this category."))
  48. if value == 2:
  49. if thread.acl.get('can_pin_globally'):
  50. moderation.pin_thread_globally(request, thread)
  51. else:
  52. raise PermissionDenied(_("You can't pin threads globally in this category."))
  53. elif value == 1:
  54. moderation.pin_thread_locally(request, thread)
  55. elif value == 0:
  56. moderation.unpin_thread(request, thread)
  57. return {'weight': thread.weight}
  58. thread_patch_dispatcher.replace('weight', patch_weight)
  59. def patch_move(request, thread, value):
  60. allow_move_thread(request.user, thread)
  61. category_pk = get_int_or_404(value)
  62. new_category = get_object_or_404(
  63. Category.objects.all_categories().select_related('parent'), pk=category_pk
  64. )
  65. add_acl(request.user, new_category)
  66. allow_see_category(request.user, new_category)
  67. allow_browse_category(request.user, new_category)
  68. allow_start_thread(request.user, new_category)
  69. if new_category == thread.category:
  70. raise PermissionDenied(_("You can't move thread to the category it's already in."))
  71. moderation.move_thread(request, thread, new_category)
  72. return {'category': CategorySerializer(new_category).data}
  73. thread_patch_dispatcher.replace('category', patch_move)
  74. def patch_flatten_categories(request, thread, value):
  75. try:
  76. return {'category': thread.category_id}
  77. except AttributeError:
  78. return {'category': thread.category_id}
  79. thread_patch_dispatcher.replace('flatten-categories', patch_flatten_categories)
  80. def patch_is_unapproved(request, thread, value):
  81. allow_approve_thread(request.user, thread)
  82. if value:
  83. raise PermissionDenied(_("Content approval can't be reversed."))
  84. moderation.approve_thread(request, thread)
  85. return {
  86. 'is_unapproved': thread.is_unapproved,
  87. 'has_unapproved_posts': thread.has_unapproved_posts,
  88. }
  89. thread_patch_dispatcher.replace('is-unapproved', patch_is_unapproved)
  90. def patch_is_closed(request, thread, value):
  91. if thread.acl.get('can_close'):
  92. if value:
  93. moderation.close_thread(request, thread)
  94. else:
  95. moderation.open_thread(request, thread)
  96. return {'is_closed': thread.is_closed}
  97. else:
  98. if value:
  99. raise PermissionDenied(_("You don't have permission to close this thread."))
  100. else:
  101. raise PermissionDenied(_("You don't have permission to open this thread."))
  102. thread_patch_dispatcher.replace('is-closed', patch_is_closed)
  103. def patch_is_hidden(request, thread, value):
  104. if value:
  105. allow_hide_thread(request.user, thread)
  106. moderation.hide_thread(request, thread)
  107. else:
  108. allow_unhide_thread(request.user, thread)
  109. moderation.unhide_thread(request, thread)
  110. return {'is_hidden': thread.is_hidden}
  111. thread_patch_dispatcher.replace('is-hidden', patch_is_hidden)
  112. def patch_subscription(request, thread, value):
  113. request.user.subscription_set.filter(thread=thread).delete()
  114. if value == 'notify':
  115. thread.subscription = request.user.subscription_set.create(
  116. thread=thread,
  117. category=thread.category,
  118. last_read_on=thread.last_post_on,
  119. send_email=False,
  120. )
  121. return {'subscription': False}
  122. elif value == 'email':
  123. thread.subscription = request.user.subscription_set.create(
  124. thread=thread,
  125. category=thread.category,
  126. last_read_on=thread.last_post_on,
  127. send_email=True,
  128. )
  129. return {'subscription': True}
  130. else:
  131. return {'subscription': None}
  132. thread_patch_dispatcher.replace('subscription', patch_subscription)
  133. def patch_add_participant(request, thread, value):
  134. allow_add_participants(request.user, thread)
  135. try:
  136. username = six.text_type(value).strip().lower()
  137. if not username:
  138. raise PermissionDenied(_("You have to enter new participant's username."))
  139. participant = UserModel.objects.get(slug=username)
  140. except UserModel.DoesNotExist:
  141. raise PermissionDenied(_("No user with such name exists."))
  142. if participant in [p.user for p in thread.participants_list]:
  143. raise PermissionDenied(_("This user is already thread participant."))
  144. allow_add_participant(request.user, participant)
  145. add_participant(request, thread, participant)
  146. make_participants_aware(request.user, thread)
  147. participants = ThreadParticipantSerializer(thread.participants_list, many=True)
  148. return {'participants': participants.data}
  149. thread_patch_dispatcher.add('participants', patch_add_participant)
  150. def patch_remove_participant(request, thread, value):
  151. try:
  152. user_id = int(value)
  153. except (ValueError, TypeError):
  154. user_id = 0
  155. for participant in thread.participants_list:
  156. if participant.user_id == user_id:
  157. break
  158. else:
  159. raise PermissionDenied(_("Participant doesn't exist."))
  160. allow_remove_participant(request.user, thread, participant.user)
  161. remove_participant(request, thread, participant.user)
  162. if len(thread.participants_list) == 1:
  163. return {'deleted': True}
  164. else:
  165. make_participants_aware(request.user, thread)
  166. participants = ThreadParticipantSerializer(thread.participants_list, many=True)
  167. return {
  168. 'deleted': False,
  169. 'participants': participants.data,
  170. }
  171. thread_patch_dispatcher.remove('participants', patch_remove_participant)
  172. def patch_replace_owner(request, thread, value):
  173. try:
  174. user_id = int(value)
  175. except (ValueError, TypeError):
  176. user_id = 0
  177. for participant in thread.participants_list:
  178. if participant.user_id == user_id:
  179. if participant.is_owner:
  180. raise PermissionDenied(_("This user already is thread owner."))
  181. else:
  182. break
  183. else:
  184. raise PermissionDenied(_("Participant doesn't exist."))
  185. allow_change_owner(request.user, thread)
  186. change_owner(request, thread, participant.user)
  187. make_participants_aware(request.user, thread)
  188. participants = ThreadParticipantSerializer(thread.participants_list, many=True)
  189. return {'participants': participants.data}
  190. thread_patch_dispatcher.replace('owner', patch_replace_owner)
  191. def thread_patch_endpoint(request, thread):
  192. old_title = thread.title
  193. old_is_hidden = thread.is_hidden
  194. old_is_unapproved = thread.is_unapproved
  195. old_category = thread.category
  196. response = thread_patch_dispatcher.dispatch(request, thread)
  197. # diff thread's state against pre-patch and resync category if necessary
  198. hidden_changed = old_is_hidden != thread.is_hidden
  199. unapproved_changed = old_is_unapproved != thread.is_unapproved
  200. category_changed = old_category != thread.category
  201. title_changed = old_title != thread.title
  202. if thread.category.last_thread_id != thread.pk:
  203. title_changed = False # don't trigger resync on simple title change
  204. if hidden_changed or unapproved_changed or category_changed:
  205. thread.category.synchronize()
  206. thread.category.save()
  207. if category_changed:
  208. old_category.synchronize()
  209. old_category.save()
  210. elif title_changed:
  211. thread.category.last_thread_title = thread.title
  212. thread.category.last_thread_slug = thread.slug
  213. thread.category.save(update_fields=['last_thread_title', 'last_thread_slug'])
  214. return response