patch_post.py 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. from django.core.exceptions import PermissionDenied
  2. from django.utils.translation import gettext as _, ngettext
  3. from rest_framework import serializers
  4. from rest_framework.response import Response
  5. from ....acl.objectacl import add_acl_to_obj
  6. from ....conf import settings
  7. from ....core.apipatch import ApiPatch
  8. from ...models import PostLike
  9. from ...moderation import posts as moderation
  10. from ...permissions import (
  11. allow_approve_post,
  12. allow_hide_best_answer,
  13. allow_hide_post,
  14. allow_protect_post,
  15. allow_unhide_post,
  16. exclude_invisible_posts,
  17. )
  18. post_patch_dispatcher = ApiPatch()
  19. def patch_acl(request, post, value):
  20. """useful little op that updates post acl to current state"""
  21. if value:
  22. add_acl_to_obj(request.user_acl, post)
  23. return {"acl": post.acl}
  24. return {"acl": None}
  25. post_patch_dispatcher.add("acl", patch_acl)
  26. def patch_is_liked(request, post, value):
  27. if not post.acl["can_like"]:
  28. raise PermissionDenied(_("You can't like posts in this category."))
  29. # lock user to protect us from likes flood
  30. request.user.lock()
  31. # grab like state for this post and user
  32. try:
  33. user_like = post.postlike_set.get(liker=request.user)
  34. except PostLike.DoesNotExist:
  35. user_like = None
  36. # no change
  37. if (value and user_like) or (not value and not user_like):
  38. return {
  39. "likes": post.likes,
  40. "last_likes": post.last_likes or [],
  41. "is_liked": value,
  42. }
  43. # like
  44. if value:
  45. post.postlike_set.create(
  46. category=post.category,
  47. thread=post.thread,
  48. liker=request.user,
  49. liker_name=request.user.username,
  50. liker_slug=request.user.slug,
  51. )
  52. post.likes += 1
  53. # unlike
  54. if not value:
  55. user_like.delete()
  56. post.likes -= 1
  57. post.last_likes = []
  58. for like in post.postlike_set.all()[:4]:
  59. post.last_likes.append({"id": like.liker_id, "username": like.liker_name})
  60. post.save(update_fields=["likes", "last_likes"])
  61. return {"likes": post.likes, "last_likes": post.last_likes or [], "is_liked": value}
  62. post_patch_dispatcher.replace("is-liked", patch_is_liked)
  63. def patch_is_protected(request, post, value):
  64. allow_protect_post(request.user_acl, post)
  65. if value:
  66. moderation.protect_post(request.user, post)
  67. else:
  68. moderation.unprotect_post(request.user, post)
  69. return {"is_protected": post.is_protected}
  70. post_patch_dispatcher.replace("is-protected", patch_is_protected)
  71. def patch_is_unapproved(request, post, value):
  72. allow_approve_post(request.user_acl, post)
  73. if value:
  74. raise PermissionDenied(_("Content approval can't be reversed."))
  75. moderation.approve_post(request.user, post)
  76. return {"is_unapproved": post.is_unapproved}
  77. post_patch_dispatcher.replace("is-unapproved", patch_is_unapproved)
  78. def patch_is_hidden(request, post, value):
  79. if value is True:
  80. allow_hide_post(request.user_acl, post)
  81. allow_hide_best_answer(request.user_acl, post)
  82. moderation.hide_post(request.user, post)
  83. elif value is False:
  84. allow_unhide_post(request.user_acl, post)
  85. moderation.unhide_post(request.user, post)
  86. return {"is_hidden": post.is_hidden}
  87. post_patch_dispatcher.replace("is-hidden", patch_is_hidden)
  88. def post_patch_endpoint(request, post):
  89. old_is_unapproved = post.is_unapproved
  90. response = post_patch_dispatcher.dispatch(request, post)
  91. # diff posts's state against pre-patch and resync thread/category if necessarys
  92. if old_is_unapproved != post.is_unapproved:
  93. post.thread.synchronize()
  94. post.thread.save()
  95. post.category.synchronize()
  96. post.category.save()
  97. return response
  98. def bulk_patch_endpoint(request, thread):
  99. serializer = BulkPatchSerializer(
  100. data=request.data, context={"settings": request.settings}
  101. )
  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), min_length=1
  132. )
  133. ops = serializers.ListField(
  134. child=serializers.DictField(), min_length=1, max_length=10
  135. )
  136. def validate_ids(self, data):
  137. settings = self.context["settings"]
  138. limit = settings.posts_per_page + settings.posts_per_page_orphans
  139. if len(data) > limit:
  140. message = ngettext(
  141. "No more than %(limit)s post can be updated at a single time.",
  142. "No more than %(limit)s posts can be updated at a single time.",
  143. limit,
  144. )
  145. raise serializers.ValidationError(message % {"limit": limit})
  146. return data