moderation.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. from rest_framework import serializers
  2. from django.core.exceptions import PermissionDenied, ValidationError
  3. from django.http import Http404
  4. from django.utils.translation import ugettext as _, ugettext_lazy, ungettext
  5. from misago.acl import add_acl
  6. from misago.conf import settings
  7. from misago.threads.models import Thread
  8. from misago.threads.permissions import (
  9. allow_merge_post, allow_move_post, allow_split_post,
  10. can_start_thread, exclude_invisible_posts)
  11. from misago.threads.utils import get_thread_id_from_url
  12. from misago.threads.validators import validate_category, validate_title
  13. POSTS_LIMIT = settings.MISAGO_POSTS_PER_PAGE + settings.MISAGO_POSTS_TAIL
  14. __all__ = [
  15. 'MergePostsSerializer',
  16. 'MovePostsSerializer',
  17. 'NewThreadSerializer',
  18. 'SplitPostsSerializer',
  19. ]
  20. class MergePostsSerializer(serializers.Serializer):
  21. error_empty_or_required = ugettext_lazy("You have to select at least two posts to merge.")
  22. posts = serializers.ListField(
  23. child=serializers.IntegerField(
  24. error_messages={
  25. 'invalid': ugettext_lazy("One or more post ids received were invalid."),
  26. },
  27. ),
  28. error_messages={
  29. 'required': error_empty_or_required,
  30. },
  31. )
  32. def validate_posts(self, data):
  33. data = list(set(data))
  34. if len(data) < 2:
  35. raise serializers.ValidationError(self.error_empty_or_required)
  36. if len(data) > POSTS_LIMIT:
  37. message = ungettext(
  38. "No more than %(limit)s post can be merged at single time.",
  39. "No more than %(limit)s posts can be merged at single time.",
  40. POSTS_LIMIT,
  41. )
  42. raise serializers.ValidationError(message % {'limit': POSTS_LIMIT})
  43. user = self.context['user']
  44. thread = self.context['thread']
  45. posts_queryset = exclude_invisible_posts(user, thread.category, thread.post_set)
  46. posts_queryset = posts_queryset.filter(id__in=data).order_by('id')
  47. posts = []
  48. for post in posts_queryset:
  49. post.category = thread.category
  50. post.thread = thread
  51. try:
  52. allow_merge_post(user, post)
  53. except PermissionDenied as e:
  54. raise serializers.ValidationError(e)
  55. if not posts:
  56. posts.append(post)
  57. else:
  58. authorship_error = _("Posts made by different users can't be merged.")
  59. if posts[0].poster_id:
  60. if post.poster_id != posts[0].poster_id:
  61. raise serializers.ValidationError(authorship_error)
  62. else:
  63. if post.poster_id or post.poster_name != posts[0].poster_name:
  64. raise serializers.ValidationError(authorship_error)
  65. if posts[0].pk != thread.first_post_id:
  66. if (posts[0].is_hidden != post.is_hidden or
  67. posts[0].is_unapproved != post.is_unapproved):
  68. raise serializers.ValidationError(
  69. _("Posts with different visibility can't be merged.")
  70. )
  71. posts.append(post)
  72. if len(posts) != len(data):
  73. raise serializers.ValidationError(_("One or more posts to merge could not be found."))
  74. self.posts_cache = posts
  75. return data
  76. class MovePostsSerializer(serializers.Serializer):
  77. error_empty_or_required = ugettext_lazy("You have to specify at least one post to move.")
  78. thread_url = serializers.CharField(
  79. error_messages={
  80. 'required': ugettext_lazy("Enter link to new thread."),
  81. },
  82. )
  83. posts = serializers.ListField(
  84. allow_empty=False,
  85. child=serializers.IntegerField(
  86. error_messages={
  87. 'invalid': ugettext_lazy("One or more post ids received were invalid."),
  88. },
  89. ),
  90. error_messages={
  91. 'required': error_empty_or_required,
  92. 'empty': error_empty_or_required,
  93. },
  94. )
  95. def validate_thread_url(self, data):
  96. request = self.context['request']
  97. thread = self.context['thread']
  98. viewmodel = self.context['viewmodel']
  99. new_thread_id = get_thread_id_from_url(request, data)
  100. if not new_thread_id:
  101. raise serializers.ValidationError(_("This is not a valid thread link."))
  102. if new_thread_id == thread.pk:
  103. raise serializers.ValidationError(_("Thread to move posts to is same as current one."))
  104. try:
  105. new_thread = viewmodel(request, new_thread_id).unwrap()
  106. except Http404:
  107. raise serializers.ValidationError(
  108. _(
  109. "The thread you have entered link to doesn't "
  110. "exist or you don't have permission to see it."
  111. )
  112. )
  113. if not new_thread.acl['can_reply']:
  114. raise serializers.ValidationError(_("You can't move posts to threads you can't reply."))
  115. self.new_thread = new_thread
  116. return data
  117. def validate_posts(self, data):
  118. data = list(set(data))
  119. if len(data) > POSTS_LIMIT:
  120. message = ungettext(
  121. "No more than %(limit)s post can be moved at single time.",
  122. "No more than %(limit)s posts can be moved at single time.",
  123. POSTS_LIMIT,
  124. )
  125. raise serializers.ValidationError(message % {'limit': POSTS_LIMIT})
  126. request = self.context['request']
  127. thread = self.context['thread']
  128. posts_queryset = exclude_invisible_posts(request.user, thread.category, thread.post_set)
  129. posts_queryset = posts_queryset.filter(id__in=data).order_by('id')
  130. posts = []
  131. for post in posts_queryset:
  132. post.category = thread.category
  133. post.thread = thread
  134. try:
  135. allow_move_post(request.user, post)
  136. posts.append(post)
  137. except PermissionDenied as e:
  138. raise serializers.ValidationError(e)
  139. if len(posts) != len(data):
  140. raise serializers.ValidationError(_("One or more posts to move could not be found."))
  141. self.posts_cache = posts
  142. return data
  143. class NewThreadSerializer(serializers.Serializer):
  144. title = serializers.CharField()
  145. category = serializers.IntegerField()
  146. weight = serializers.IntegerField(
  147. required=False,
  148. allow_null=True,
  149. max_value=Thread.WEIGHT_GLOBAL,
  150. min_value=Thread.WEIGHT_DEFAULT,
  151. )
  152. is_hidden = serializers.NullBooleanField(required=False)
  153. is_closed = serializers.NullBooleanField(required=False)
  154. def validate_title(self, title):
  155. return validate_title(title)
  156. def validate_category(self, category_id):
  157. self.category = validate_category(self.context['user'], category_id)
  158. if not can_start_thread(self.context['user'], self.category):
  159. raise ValidationError(_("You can't create new threads in selected category."))
  160. return self.category
  161. def validate_weight(self, weight):
  162. try:
  163. add_acl(self.context['user'], self.category)
  164. except AttributeError:
  165. return weight # don't validate weight further if category failed
  166. if weight > self.category.acl.get('can_pin_threads', 0):
  167. if weight == 2:
  168. raise ValidationError(
  169. _("You don't have permission to pin threads globally in this category.")
  170. )
  171. else:
  172. raise ValidationError(
  173. _("You don't have permission to pin threads in this category.")
  174. )
  175. return weight
  176. def validate_is_hidden(self, is_hidden):
  177. try:
  178. add_acl(self.context['user'], self.category)
  179. except AttributeError:
  180. return is_hidden # don't validate hidden further if category failed
  181. if is_hidden and not self.category.acl.get('can_hide_threads'):
  182. raise ValidationError(_("You don't have permission to hide threads in this category."))
  183. return is_hidden
  184. def validate_is_closed(self, is_closed):
  185. try:
  186. add_acl(self.context['user'], self.category)
  187. except AttributeError:
  188. return is_closed # don't validate closed further if category failed
  189. if is_closed and not self.category.acl.get('can_close_threads'):
  190. raise ValidationError(
  191. _("You don't have permission to close threads in this category.")
  192. )
  193. return is_closed
  194. class SplitPostsSerializer(NewThreadSerializer):
  195. error_empty_or_required = ugettext_lazy("You have to specify at least one post to split.")
  196. posts = serializers.ListField(
  197. allow_empty=False,
  198. child=serializers.IntegerField(
  199. error_messages={
  200. 'invalid': ugettext_lazy("One or more post ids received were invalid."),
  201. },
  202. ),
  203. error_messages={
  204. 'required': error_empty_or_required,
  205. 'empty': error_empty_or_required,
  206. },
  207. )
  208. def validate_posts(self, data):
  209. if len(data) > POSTS_LIMIT:
  210. message = ungettext(
  211. "No more than %(limit)s post can be split at single time.",
  212. "No more than %(limit)s posts can be split at single time.",
  213. POSTS_LIMIT,
  214. )
  215. raise ValidationError(message % {'limit': POSTS_LIMIT})
  216. thread = self.context['thread']
  217. user = self.context['user']
  218. posts_queryset = exclude_invisible_posts(user, thread.category, thread.post_set)
  219. posts_queryset = posts_queryset.filter(id__in=data).order_by('id')
  220. posts = []
  221. for post in posts_queryset:
  222. post.category = thread.category
  223. post.thread = thread
  224. try:
  225. allow_split_post(user, post)
  226. except PermissionDenied as e:
  227. raise ValidationError(e)
  228. posts.append(post)
  229. if len(posts) != len(data):
  230. raise ValidationError(_("One or more posts to split could not be found."))
  231. self.posts_cache = posts
  232. return data