bestanswers.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372
  1. from django import forms
  2. from django.core.exceptions import PermissionDenied
  3. from django.utils import timezone
  4. from django.utils.translation import gettext_lazy as _
  5. from django.utils.translation import ngettext
  6. from ...acl import algebra
  7. from ...acl.decorators import return_boolean
  8. from ...categories.models import Category, CategoryRole
  9. from ...categories.permissions import get_categories_roles
  10. from ..models import Post, Thread
  11. __all__nope = [
  12. "allow_mark_best_answer",
  13. "can_mark_best_answer",
  14. "allow_mark_as_best_answer",
  15. "can_mark_as_best_answer",
  16. "allow_unmark_best_answer",
  17. "can_unmark_best_answer",
  18. "allow_hide_best_answer",
  19. "can_hide_best_answer",
  20. "allow_delete_best_answer",
  21. "can_delete_best_answer",
  22. ]
  23. class CategoryPermissionsForm(forms.Form):
  24. legend = _("Best answers")
  25. can_mark_best_answers = forms.TypedChoiceField(
  26. label=_("Can mark posts as best answers"),
  27. coerce=int,
  28. initial=0,
  29. choices=[(0, _("No")), (1, _("Own threads")), (2, _("All threads"))],
  30. )
  31. can_change_marked_answers = forms.TypedChoiceField(
  32. label=_("Can change marked answers"),
  33. coerce=int,
  34. initial=0,
  35. choices=[(0, _("No")), (1, _("Own threads")), (2, _("All threads"))],
  36. )
  37. best_answer_change_time = forms.IntegerField(
  38. label=_(
  39. "Time limit for changing marked best answer in owned thread, in minutes"
  40. ),
  41. help_text=_(
  42. "Enter 0 to don't limit time for changing marked best answer in "
  43. "owned thread."
  44. ),
  45. initial=0,
  46. min_value=0,
  47. )
  48. def change_permissions_form(role):
  49. if isinstance(role, CategoryRole):
  50. return CategoryPermissionsForm
  51. def build_acl(acl, roles, key_name):
  52. categories_roles = get_categories_roles(roles)
  53. categories = list(Category.objects.all_categories(include_root=True))
  54. for category in categories:
  55. category_acl = acl["categories"].get(category.pk, {"can_browse": 0})
  56. if category_acl["can_browse"]:
  57. acl["categories"][category.pk] = build_category_acl(
  58. category_acl, category, categories_roles, key_name
  59. )
  60. private_category = Category.objects.private_threads()
  61. private_threads_acl = acl["categories"].get(private_category.pk)
  62. if private_threads_acl:
  63. private_threads_acl.update(
  64. {
  65. "can_mark_best_answers": 0,
  66. "can_change_marked_answers": 0,
  67. "best_answer_change_time": 0,
  68. }
  69. )
  70. return acl
  71. def build_category_acl(acl, category, categories_roles, key_name):
  72. category_roles = categories_roles.get(category.pk, [])
  73. final_acl = {
  74. "can_mark_best_answers": 0,
  75. "can_change_marked_answers": 0,
  76. "best_answer_change_time": 0,
  77. }
  78. final_acl.update(acl)
  79. algebra.sum_acls(
  80. final_acl,
  81. roles=category_roles,
  82. key=key_name,
  83. can_mark_best_answers=algebra.greater,
  84. can_change_marked_answers=algebra.greater,
  85. best_answer_change_time=algebra.greater_or_zero,
  86. )
  87. return final_acl
  88. def add_acl_to_thread(user_acl, thread):
  89. thread.acl.update(
  90. {
  91. "can_mark_best_answer": can_mark_best_answer(user_acl, thread),
  92. "can_change_best_answer": can_change_best_answer(user_acl, thread),
  93. "can_unmark_best_answer": can_unmark_best_answer(user_acl, thread),
  94. }
  95. )
  96. def add_acl_to_post(user_acl, post):
  97. post.acl.update(
  98. {
  99. "can_mark_as_best_answer": can_mark_as_best_answer(user_acl, post),
  100. "can_hide_best_answer": can_hide_best_answer(user_acl, post),
  101. "can_delete_best_answer": can_delete_best_answer(user_acl, post),
  102. }
  103. )
  104. def register_with(registry):
  105. registry.acl_annotator(Thread, add_acl_to_thread)
  106. registry.acl_annotator(Post, add_acl_to_post)
  107. def allow_mark_best_answer(user_acl, target):
  108. if user_acl["is_anonymous"]:
  109. raise PermissionDenied(_("You have to sign in to mark best answers."))
  110. category_acl = user_acl["categories"].get(target.category_id, {})
  111. if not category_acl.get("can_mark_best_answers"):
  112. raise PermissionDenied(
  113. _(
  114. "You don't have permission to mark best answers in the "
  115. '"%(category)s" category.'
  116. )
  117. % {"category": target.category}
  118. )
  119. if (
  120. category_acl["can_mark_best_answers"] == 1
  121. and user_acl["user_id"] != target.starter_id
  122. ):
  123. raise PermissionDenied(
  124. _(
  125. "You don't have permission to mark best answer in this thread "
  126. "because you didn't start it."
  127. )
  128. )
  129. if not category_acl["can_close_threads"]:
  130. if target.category.is_closed:
  131. raise PermissionDenied(
  132. _(
  133. "You don't have permission to mark best answer in this thread "
  134. 'because its category "%(category)s" is closed.'
  135. )
  136. % {"category": target.category}
  137. )
  138. if target.is_closed:
  139. raise PermissionDenied(
  140. _(
  141. "You can't mark best answer in this thread because it's closed "
  142. "and you don't have permission to open it."
  143. )
  144. )
  145. can_mark_best_answer = return_boolean(allow_mark_best_answer)
  146. def allow_change_best_answer(user_acl, target):
  147. if not target.has_best_answer:
  148. return # shortcircut permission test
  149. category_acl = user_acl["categories"].get(target.category_id, {})
  150. if not category_acl.get("can_change_marked_answers"):
  151. raise PermissionDenied(
  152. _(
  153. "You don't have permission to change this thread's marked answer "
  154. 'because it\'s in the "%(category)s" category.'
  155. )
  156. % {"category": target.category}
  157. )
  158. if category_acl["can_change_marked_answers"] == 1:
  159. if user_acl["user_id"] != target.starter_id:
  160. raise PermissionDenied(
  161. _(
  162. "You don't have permission to change this thread's marked answer "
  163. "because you are not a thread starter."
  164. )
  165. )
  166. if not has_time_to_change_answer(user_acl, target):
  167. # pylint: disable=line-too-long
  168. message = ngettext(
  169. "You don't have permission to change best answer that was marked for more than %(minutes)s minute.",
  170. "You don't have permission to change best answer that was marked for more than %(minutes)s minutes.",
  171. category_acl["best_answer_change_time"],
  172. )
  173. raise PermissionDenied(
  174. message % {"minutes": category_acl["best_answer_change_time"]}
  175. )
  176. if target.best_answer_is_protected and not category_acl["can_protect_posts"]:
  177. raise PermissionDenied(
  178. _(
  179. "You don't have permission to change this thread's best answer "
  180. "because a moderator has protected it."
  181. )
  182. )
  183. can_change_best_answer = return_boolean(allow_change_best_answer)
  184. def allow_unmark_best_answer(user_acl, target):
  185. if user_acl["is_anonymous"]:
  186. raise PermissionDenied(_("You have to sign in to unmark best answers."))
  187. if not target.has_best_answer:
  188. return # shortcircut test
  189. category_acl = user_acl["categories"].get(target.category_id, {})
  190. if not category_acl.get("can_change_marked_answers"):
  191. raise PermissionDenied(
  192. _(
  193. "You don't have permission to unmark threads answers in "
  194. 'the "%(category)s" category.'
  195. )
  196. % {"category": target.category}
  197. )
  198. if category_acl["can_change_marked_answers"] == 1:
  199. if user_acl["user_id"] != target.starter_id:
  200. raise PermissionDenied(
  201. _(
  202. "You don't have permission to unmark this best answer "
  203. "because you are not a thread starter."
  204. )
  205. )
  206. if not has_time_to_change_answer(user_acl, target):
  207. # pylint: disable=line-too-long
  208. message = ngettext(
  209. "You don't have permission to unmark best answer that was marked for more than %(minutes)s minute.",
  210. "You don't have permission to unmark best answer that was marked for more than %(minutes)s minutes.",
  211. category_acl["best_answer_change_time"],
  212. )
  213. raise PermissionDenied(
  214. message % {"minutes": category_acl["best_answer_change_time"]}
  215. )
  216. if not category_acl["can_close_threads"]:
  217. if target.category.is_closed:
  218. raise PermissionDenied(
  219. _(
  220. "You don't have permission to unmark this best answer "
  221. 'because its category "%(category)s" is closed.'
  222. )
  223. % {"category": target.category}
  224. )
  225. if target.is_closed:
  226. raise PermissionDenied(
  227. _(
  228. "You can't unmark this thread's best answer "
  229. "because it's closed and you don't have permission to open it."
  230. )
  231. )
  232. if target.best_answer_is_protected and not category_acl["can_protect_posts"]:
  233. raise PermissionDenied(
  234. _(
  235. "You don't have permission to unmark this thread's best answer "
  236. "because a moderator has protected it."
  237. )
  238. )
  239. can_unmark_best_answer = return_boolean(allow_unmark_best_answer)
  240. def allow_mark_as_best_answer(user_acl, target):
  241. if user_acl["is_anonymous"]:
  242. raise PermissionDenied(_("You have to sign in to mark best answers."))
  243. if target.is_event:
  244. raise PermissionDenied(_("Events can't be marked as best answers."))
  245. category_acl = user_acl["categories"].get(target.category_id, {})
  246. if not category_acl.get("can_mark_best_answers"):
  247. raise PermissionDenied(
  248. _(
  249. "You don't have permission to mark best answers "
  250. 'in the "%(category)s" category.'
  251. )
  252. % {"category": target.category}
  253. )
  254. if (
  255. category_acl["can_mark_best_answers"] == 1
  256. and user_acl["user_id"] != target.thread.starter_id
  257. ):
  258. raise PermissionDenied(
  259. _(
  260. "You don't have permission to mark best answer in this thread "
  261. "because you didn't start it."
  262. )
  263. )
  264. if target.is_first_post:
  265. raise PermissionDenied(
  266. _("First post in a thread can't be marked as best answer.")
  267. )
  268. if target.is_hidden:
  269. raise PermissionDenied(_("Hidden posts can't be marked as best answers."))
  270. if target.is_unapproved:
  271. raise PermissionDenied(_("Unapproved posts can't be marked as best answers."))
  272. if target.is_protected and not category_acl["can_protect_posts"]:
  273. raise PermissionDenied(
  274. _(
  275. "You don't have permission to mark this post as best answer "
  276. "because a moderator has protected it."
  277. )
  278. )
  279. can_mark_as_best_answer = return_boolean(allow_mark_as_best_answer)
  280. def allow_hide_best_answer(user_acl, target):
  281. if target.is_best_answer:
  282. raise PermissionDenied(
  283. _("You can't hide this post because its marked as best answer.")
  284. )
  285. can_hide_best_answer = return_boolean(allow_hide_best_answer)
  286. def allow_delete_best_answer(user_acl, target):
  287. if target.is_best_answer:
  288. raise PermissionDenied(
  289. _("You can't delete this post because its marked as best answer.")
  290. )
  291. can_delete_best_answer = return_boolean(allow_delete_best_answer)
  292. def has_time_to_change_answer(user_acl, target):
  293. category_acl = user_acl["categories"].get(target.category_id, {})
  294. change_time = category_acl.get("best_answer_change_time", 0)
  295. if change_time:
  296. diff = timezone.now() - target.best_answer_marked_on
  297. diff_minutes = int(diff.total_seconds() / 60)
  298. return diff_minutes < change_time
  299. return True