patch_post.py 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. from rest_framework import serializers
  2. from rest_framework.response import Response
  3. from django.core.exceptions import PermissionDenied
  4. from django.utils.translation import gettext as _
  5. from misago.acl.objectacl import add_acl_to_obj
  6. from misago.conf import settings
  7. from misago.core.apipatch import ApiPatch
  8. from misago.threads.models import PostLike
  9. from misago.threads.moderation import posts as moderation
  10. from misago.threads.permissions import (
  11. allow_approve_post,
  12. allow_hide_best_answer,
  13. allow_hide_post,
  14. allow_protect_post,
  15. allow_unhide_post,
  16. )
  17. from misago.threads.permissions import exclude_invisible_posts
  18. PATCH_LIMIT = settings.MISAGO_POSTS_PER_PAGE + settings.MISAGO_POSTS_TAIL
  19. post_patch_dispatcher = ApiPatch()
  20. def patch_acl(request, post, value):
  21. """useful little op that updates post acl to current state"""
  22. if value:
  23. add_acl_to_obj(request.user_acl, post)
  24. return {"acl": post.acl}
  25. else:
  26. return {"acl": None}
  27. post_patch_dispatcher.add("acl", patch_acl)
  28. def patch_is_liked(request, post, value):
  29. if not post.acl["can_like"]:
  30. raise PermissionDenied(_("You can't like posts in this category."))
  31. # lock user to protect us from likes flood
  32. request.user.lock()
  33. # grab like state for this post and user
  34. try:
  35. user_like = post.postlike_set.get(liker=request.user)
  36. except PostLike.DoesNotExist:
  37. user_like = None
  38. # no change
  39. if (value and user_like) or (not value and not user_like):
  40. return {
  41. "likes": post.likes,
  42. "last_likes": post.last_likes or [],
  43. "is_liked": value,
  44. }
  45. # like
  46. if value:
  47. post.postlike_set.create(
  48. category=post.category,
  49. thread=post.thread,
  50. liker=request.user,
  51. liker_name=request.user.username,
  52. liker_slug=request.user.slug,
  53. )
  54. post.likes += 1
  55. # unlike
  56. if not value:
  57. user_like.delete()
  58. post.likes -= 1
  59. post.last_likes = []
  60. for like in post.postlike_set.all()[:4]:
  61. post.last_likes.append({"id": like.liker_id, "username": like.liker_name})
  62. post.save(update_fields=["likes", "last_likes"])
  63. return {"likes": post.likes, "last_likes": post.last_likes or [], "is_liked": value}
  64. post_patch_dispatcher.replace("is-liked", patch_is_liked)
  65. def patch_is_protected(request, post, value):
  66. allow_protect_post(request.user_acl, post)
  67. if value:
  68. moderation.protect_post(request.user, post)
  69. else:
  70. moderation.unprotect_post(request.user, post)
  71. return {"is_protected": post.is_protected}
  72. post_patch_dispatcher.replace("is-protected", patch_is_protected)
  73. def patch_is_unapproved(request, post, value):
  74. allow_approve_post(request.user_acl, post)
  75. if value:
  76. raise PermissionDenied(_("Content approval can't be reversed."))
  77. moderation.approve_post(request.user, post)
  78. return {"is_unapproved": post.is_unapproved}
  79. post_patch_dispatcher.replace("is-unapproved", patch_is_unapproved)
  80. def patch_is_hidden(request, post, value):
  81. if value is True:
  82. allow_hide_post(request.user_acl, post)
  83. allow_hide_best_answer(request.user_acl, post)
  84. moderation.hide_post(request.user, post)
  85. elif value is False:
  86. allow_unhide_post(request.user_acl, post)
  87. moderation.unhide_post(request.user, post)
  88. return {"is_hidden": post.is_hidden}
  89. post_patch_dispatcher.replace("is-hidden", patch_is_hidden)
  90. def post_patch_endpoint(request, post):
  91. old_is_unapproved = post.is_unapproved
  92. response = post_patch_dispatcher.dispatch(request, post)
  93. # diff posts's state against pre-patch and resync thread/category if necessarys
  94. if old_is_unapproved != post.is_unapproved:
  95. post.thread.synchronize()
  96. post.thread.save()
  97. post.category.synchronize()
  98. post.category.save()
  99. return response
  100. def bulk_patch_endpoint(request, thread):
  101. serializer = BulkPatchSerializer(data=request.data)
  102. if not serializer.is_valid():
  103. return Response(serializer.errors, status=400)
  104. posts = clean_posts_for_patch(request, thread, serializer.data["ids"])
  105. old_unapproved_posts = [p.is_unapproved for p in posts].count(True)
  106. response = post_patch_dispatcher.dispatch_bulk(request, posts)
  107. new_unapproved_posts = [p.is_unapproved for p in posts].count(True)
  108. if old_unapproved_posts != new_unapproved_posts:
  109. thread.synchronize()
  110. thread.save()
  111. thread.category.synchronize()
  112. thread.category.save()
  113. return response
  114. def clean_posts_for_patch(request, thread, posts_ids):
  115. posts_queryset = exclude_invisible_posts(
  116. request.user_acl, thread.category, thread.post_set
  117. )
  118. posts_queryset = posts_queryset.filter(id__in=posts_ids, is_event=False).order_by(
  119. "id"
  120. )
  121. posts = []
  122. for post in posts_queryset:
  123. post.category = thread.category
  124. post.thread = thread
  125. posts.append(post)
  126. if len(posts) != len(posts_ids):
  127. raise PermissionDenied(_("One or more posts to update could not be found."))
  128. return posts
  129. class BulkPatchSerializer(serializers.Serializer):
  130. ids = serializers.ListField(
  131. child=serializers.IntegerField(min_value=1),
  132. max_length=PATCH_LIMIT,
  133. min_length=1,
  134. )
  135. ops = serializers.ListField(
  136. child=serializers.DictField(), min_length=1, max_length=10
  137. )