forms.py 11 KB

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