bestanswers.py 12 KB

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