bestanswers.py 12 KB

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