patch_post.py 5.3 KB

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