patch.py 9.3 KB

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