forms.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  1. from django.db import models
  2. from django.utils.html import conditional_escape, mark_safe
  3. from django.utils.translation import ugettext_lazy as _
  4. from mptt.forms import * # noqa
  5. from misago.core import forms
  6. from misago.core.validators import validate_sluggable
  7. from misago.forums.models import FORUMS_TREE_ID, Forum, ForumRole
  8. """
  9. Fields
  10. """
  11. class AdminForumFieldMixin(object):
  12. def __init__(self, *args, **kwargs):
  13. self.base_level = kwargs.pop('base_level', 1)
  14. kwargs['level_indicator'] = kwargs.get('level_indicator', '- - ')
  15. queryset = Forum.objects.filter(tree_id=FORUMS_TREE_ID)
  16. if not kwargs.pop('include_root', False):
  17. queryset = queryset.exclude(special_role="root_category")
  18. kwargs.setdefault('queryset',
  19. queryset)
  20. super(AdminForumFieldMixin, self).__init__(*args, **kwargs)
  21. def _get_level_indicator(self, obj):
  22. level = getattr(obj, obj._mptt_meta.level_attr) - self.base_level
  23. if level > 0:
  24. return mark_safe(conditional_escape(self.level_indicator) * level)
  25. else:
  26. return ''
  27. class AdminForumChoiceField(AdminForumFieldMixin, TreeNodeChoiceField):
  28. pass
  29. class AdminForumMultipleChoiceField(AdminForumFieldMixin,
  30. TreeNodeMultipleChoiceField):
  31. pass
  32. class MisagoForumMixin(object):
  33. def __init__(self, *args, **kwargs):
  34. self.parent = None
  35. if not 'queryset' in kwargs:
  36. kwargs['queryset'] = Forum.objects.order_by('lft')
  37. if kwargs.get('error_messages', {}):
  38. kwargs['error_messages'].update({
  39. 'invalid_choice': self.INVALID_CHOICE_ERROR
  40. })
  41. else:
  42. kwargs['error_messages'] = {
  43. 'invalid_choice': self.INVALID_CHOICE_ERROR
  44. }
  45. super(MisagoForumMixin, self).__init__(*args, **kwargs)
  46. def set_acl(self, acl=None):
  47. queryset = Forum.objects.root_category().get_descendants()
  48. if acl:
  49. allowed_ids = [0]
  50. for forum_id, perms in acl.get('forums', {}).items():
  51. if perms.get('can_see') and perms.get('can_browse'):
  52. allowed_ids.append(forum_id)
  53. queryset = queryset.filter(id__in=allowed_ids)
  54. self.queryset = queryset
  55. def _get_level_indicator(self, obj):
  56. level = obj.level - 1
  57. return mark_safe(conditional_escape('- - ') * level)
  58. class ForumChoiceField(MisagoForumMixin, TreeNodeChoiceField):
  59. INVALID_CHOICE_ERROR = _("Select valid forum.")
  60. class ForumsMultipleChoiceField(MisagoForumMixin, TreeNodeMultipleChoiceField):
  61. INVALID_CHOICE_ERROR = _("Select valid forums.")
  62. """
  63. Forms
  64. """
  65. FORUM_ROLES = (
  66. ('category', _('Category')),
  67. ('forum', _('Forum')),
  68. ('redirect', _('Redirect')),
  69. )
  70. class ForumFormBase(forms.ModelForm):
  71. role = forms.ChoiceField(label=_("Type"), choices=FORUM_ROLES)
  72. name = forms.CharField(
  73. label=_("Name"),
  74. validators=[validate_sluggable()])
  75. description = forms.CharField(
  76. label=_("Description"), max_length=2048, required=False,
  77. widget=forms.Textarea(attrs={'rows': 3}),
  78. help_text=_("Optional description explaining forum intented "
  79. "purpose."))
  80. redirect_url = forms.URLField(
  81. label=_("Redirect URL"),
  82. validators=[validate_sluggable()],
  83. help_text=_('If forum type is redirect, enter here its URL.'),
  84. required=False)
  85. css_class = forms.CharField(
  86. label=_("CSS class"), required=False,
  87. help_text=_("Optional CSS class used to customize this forum "
  88. "appearance from templates."))
  89. is_closed = forms.YesNoSwitch(
  90. label=_("Closed forum"), required=False,
  91. help_text=_("Only members with valid permissions can post in "
  92. "closed forums."))
  93. css_class = forms.CharField(
  94. label=_("CSS class"), required=False,
  95. help_text=_("Optional CSS class used to customize this forum "
  96. "appearance from templates."))
  97. prune_started_after = forms.IntegerField(
  98. label=_("Thread age"), min_value=0,
  99. help_text=_("Prune thread if number of days since its creation is "
  100. "greater than specified. Enter 0 to disable this "
  101. "pruning criteria."))
  102. prune_replied_after = forms.IntegerField(
  103. label=_("Last reply"), min_value=0,
  104. help_text=_("Prune thread if number of days since last reply is "
  105. "greater than specified. Enter 0 to disable this "
  106. "pruning criteria."))
  107. class Meta:
  108. model = Forum
  109. fields = [
  110. 'role',
  111. 'name',
  112. 'description',
  113. 'redirect_url',
  114. 'css_class',
  115. 'is_closed',
  116. 'prune_started_after',
  117. 'prune_replied_after',
  118. 'archive_pruned_in',
  119. ]
  120. def clean_copy_permissions(self):
  121. data = self.cleaned_data['copy_permissions']
  122. if data and data.pk == self.instance.pk:
  123. message = _("Permissions cannot be copied from forum into itself.")
  124. raise forms.ValidationError(message)
  125. return data
  126. def clean_archive_pruned_in(self):
  127. data = self.cleaned_data['archive_pruned_in']
  128. if data and data.pk == self.instance.pk:
  129. message = _("Forum cannot act as archive for itself.")
  130. raise forms.ValidationError(message)
  131. return data
  132. def clean(self):
  133. data = super(ForumFormBase, self).clean()
  134. self.instance.set_name(data.get('name'))
  135. if data['role'] != 'category':
  136. if not data['new_parent'].level:
  137. message = _("Only categories can have no parent category.")
  138. raise forms.ValidationError(message)
  139. if data['role'] == 'redirect':
  140. if not data.get('redirect_url'):
  141. message = _("This forum is redirect, yet you haven't "
  142. "specified URL to which it should redirect "
  143. "after click.")
  144. raise forms.ValidationError(message)
  145. return data
  146. def ForumFormFactory(instance):
  147. parent_queryset = Forum.objects.all_forums(True).order_by('lft')
  148. if instance.pk:
  149. not_siblings = models.Q(lft__lt=instance.lft)
  150. not_siblings = not_siblings | models.Q(rght__gt=instance.rght)
  151. parent_queryset = parent_queryset.filter(not_siblings)
  152. return type('ForumFormFinal', (ForumFormBase,), {
  153. 'new_parent': AdminForumChoiceField(
  154. label=_("Parent forum"),
  155. queryset=parent_queryset,
  156. initial=instance.parent,
  157. empty_label=None),
  158. 'copy_permissions': AdminForumChoiceField(
  159. label=_("Copy permissions"),
  160. help_text=_("You can replace this forum permissions with "
  161. "permissions copied from forum selected here."),
  162. queryset=Forum.objects.all_forums(),
  163. empty_label=_("Don't copy permissions"),
  164. required=False),
  165. 'archive_pruned_in': AdminForumChoiceField(
  166. label=_("Archive"),
  167. help_text=_("Instead of being deleted, pruned threads can be "
  168. "moved to designated forum."),
  169. queryset=Forum.objects.all_forums(),
  170. empty_label=_("Don't archive pruned threads"),
  171. required=False),
  172. })
  173. class DeleteForumFormBase(forms.ModelForm):
  174. class Meta:
  175. model = Forum
  176. fields = []
  177. def clean(self):
  178. data = super(DeleteForumFormBase, self).clean()
  179. if data.get('move_threads_to'):
  180. if data['move_threads_to'].pk == self.instance.pk:
  181. message = _("You are trying to move this forum threads to "
  182. "itself.")
  183. raise forms.ValidationError(message)
  184. if data['move_threads_to'].role == 'category':
  185. message = _("Threads can't be moved to category.")
  186. raise forms.ValidationError(message)
  187. if data['move_threads_to'].role == 'redirect':
  188. message = _("Threads can't be moved to redirect.")
  189. raise forms.ValidationError(message)
  190. moving_to_child = self.instance.has_child(data['move_threads_to'])
  191. if moving_to_child and not data.get('move_children_to'):
  192. message = _("You are trying to move this forum threads to a "
  193. "child forum that will be deleted together with "
  194. "this forum.")
  195. raise forms.ValidationError(message)
  196. if data.get('move_children_to'):
  197. if data['move_children_to'].special_role == 'root_category':
  198. for child in self.instance.get_children().iterator():
  199. if child.role != 'category':
  200. message = _("One or more child forums in forum are "
  201. "not categories and thus cannot be made "
  202. "root categories.")
  203. raise forms.ValidationError(message)
  204. return data
  205. def DeleteFormFactory(instance):
  206. content_queryset = Forum.objects.all_forums().order_by('lft')
  207. fields = {
  208. 'move_threads_to': AdminForumChoiceField(
  209. label=_("Move forum threads to"),
  210. queryset=content_queryset,
  211. initial=instance.parent,
  212. empty_label=_('Delete with forum'),
  213. required=False),
  214. }
  215. not_siblings = models.Q(lft__lt=instance.lft)
  216. not_siblings = not_siblings | models.Q(rght__gt=instance.rght)
  217. children_queryset = Forum.objects.all_forums(True)
  218. children_queryset = children_queryset.filter(not_siblings).order_by('lft')
  219. if children_queryset.exists():
  220. fields['move_children_to'] = AdminForumChoiceField(
  221. label=_("Move child forums to"),
  222. queryset=children_queryset,
  223. empty_label=_('Delete with forum'),
  224. required=False)
  225. return type('DeleteForumFormFinal', (DeleteForumFormBase,), fields)
  226. class ForumRoleForm(forms.ModelForm):
  227. name = forms.CharField(label=_("Role name"))
  228. class Meta:
  229. model = ForumRole
  230. fields = ['name']
  231. def RoleForumACLFormFactory(forum, forum_roles, selected_role):
  232. attrs = {
  233. 'forum': forum,
  234. 'role': forms.ModelChoiceField(
  235. label=_("Role"),
  236. required=False,
  237. queryset=forum_roles,
  238. initial=selected_role,
  239. empty_label=_("No access"))
  240. }
  241. return type('RoleForumACLForm', (forms.Form,), attrs)
  242. def ForumRolesACLFormFactory(role, forum_roles, selected_role):
  243. attrs = {
  244. 'role': role,
  245. 'forum_role': forms.ModelChoiceField(
  246. label=_("Role"),
  247. required=False,
  248. queryset=forum_roles,
  249. initial=selected_role,
  250. empty_label=_("No access"))
  251. }
  252. return type('ForumRolesACLForm', (forms.Form,), attrs)