forms.py 11 KB

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