moderation.py 12 KB

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