patch.py 7.5 KB

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