forms.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  1. from django.utils.translation import ugettext_lazy as _
  2. import floppyforms as forms
  3. from mptt.forms import TreeNodeChoiceField
  4. from misago.forms import Form, YesNoSwitch
  5. from misago.models import Forum
  6. from misago.validators import validate_sluggable
  7. class CleanAttrsMixin(object):
  8. def clean_attrs(self):
  9. clean = []
  10. data = self.cleaned_data['attrs'].strip().split()
  11. for i in data:
  12. i = i.strip()
  13. if not i in clean:
  14. clean.append(i)
  15. return ' '.join(clean)
  16. class NewNodeForm(Form, CleanAttrsMixin):
  17. parent = False
  18. perms = False
  19. role = forms.ChoiceField(label=_("Node Type"),
  20. help_text=_("Each Node has specific role in forums tree. This role cannot be changed after node is created."),
  21. choices=(
  22. ('category', _("Category")),
  23. ('forum', _("Forum")),
  24. ('redirect', _("Redirection")),
  25. ))
  26. name = forms.CharField(label=_("Node Name"),
  27. max_length=255, validators=[validate_sluggable(
  28. _("Category name must contain alphanumeric characters."),
  29. _("Category name is too long.")
  30. )])
  31. redirect = forms.URLField(label=_("Redirect URL"),
  32. help_text=_("Redirection nodes require you to specify URL they will redirect users to upon click."),
  33. max_length=255, required=False)
  34. description = forms.CharField(label=_("Node Description"),
  35. widget=forms.Textarea, required=False)
  36. closed = forms.BooleanField(label=_("Closed Node"),
  37. widget=YesNoSwitch, required=False)
  38. attrs = forms.CharField(label=_("Node Style"),
  39. help_text=_('You can add custom CSS classess to this node, to change way it looks on board index.'),
  40. max_length=255, required=False)
  41. show_details = forms.BooleanField(label=_("Node Style"),
  42. help_text=_('You can add custom CSS classess to this node, to change way it looks on board index.'),
  43. widget=YesNoSwitch, required=False, initial=True)
  44. style = forms.CharField(label=_("Node Style"),
  45. help_text=_('You can add custom CSS classess to this node, to change way it looks on board index.'),
  46. max_length=255, required=False)
  47. layout = (
  48. (
  49. _("Basic Options"),
  50. (
  51. ('parent', {'label': _("Node Parent")}),
  52. ('perms', {'label': _("Copy Permissions from")}),
  53. ('role', {'label': _("Node Type"), 'help_text': _("Each Node has specific role in forums tree. This role cannot be changed after node is created.")}),
  54. ('name', {'label': _("Node Name")}),
  55. ('description', {'label': _("Node Description")}),
  56. ('redirect', {'label': _("Redirect URL"), 'help_text': _("Redirection nodes require you to specify URL they will redirect users to upon click.")}),
  57. ('closed', {'label': _("Closed Node")}),
  58. ),
  59. ),
  60. (
  61. _("Display Options"),
  62. (
  63. ('attrs', {'label': _("Node Attributes"), 'help_text': _('Custom templates can check nodes for predefined attributes that will change way they are rendered.')}),
  64. ('show_details', {'label': _("Show Subforums Details"), 'help_text': _('Allows you to prevent this node subforums from displaying statistics, last post data, etc. ect. on forums lists.')}),
  65. ('style', {'label': _("Node Style"), 'help_text': _('You can add custom CSS classess to this node, to change way it looks on board index.')}),
  66. ),
  67. ),
  68. )
  69. def finalize_form(self):
  70. self.fields['parent'] = TreeNodeChoiceField(label=_("Node Parent"), widget=forms.Select,
  71. queryset=Forum.objects.get(special='root').get_descendants(include_self=True), level_indicator=u'- - ')
  72. self.fields['perms'] = TreeNodeChoiceField(label=_("Copy Permissions from"), widget=forms.Select,
  73. queryset=Forum.objects.get(special='root').get_descendants(), level_indicator=u'- - ', required=False, empty_label=_("Don't copy permissions"))
  74. def clean(self):
  75. cleaned_data = super(NewNodeForm, self).clean()
  76. node_role = cleaned_data['role']
  77. if node_role != 'category' and cleaned_data['parent'].special == 'root':
  78. raise forms.ValidationError(_("Only categories can use Root Category as their parent."))
  79. if node_role == 'redirect' and not cleaned_data['redirect']:
  80. raise forms.ValidationError(_("You have to define redirection URL"))
  81. return cleaned_data
  82. class CategoryForm(Form, CleanAttrsMixin):
  83. parent = False
  84. perms = False
  85. name = forms.CharField(label=_("Category Name"),
  86. max_length=255, validators=[validate_sluggable(
  87. _("Category name must contain alphanumeric characters."),
  88. _("Category name is too long.")
  89. )])
  90. description = forms.CharField(label=_("Category Description"),
  91. widget=forms.Textarea, required=False)
  92. closed = forms.BooleanField(label=_("Closed Category"),
  93. widget=YesNoSwitch, required=False)
  94. style = forms.CharField(label=_("Category Style"),
  95. help_text=_('You can add custom CSS classess to this category, to change way it looks on board index.'),
  96. max_length=255, required=False)
  97. attrs = forms.CharField(label=_("Category Attributes"),
  98. help_text=_('Custom templates can check categories for predefined attributes that will change way they are rendered.'),
  99. max_length=255, required=False)
  100. show_details = forms.BooleanField(label=_("Show Subforums Details"),
  101. help_text=_('Allows you to prevent this category subforums from displaying statistics, last post data, etc. ect. on forums lists.'),
  102. widget=YesNoSwitch, required=False, initial=True)
  103. layout = (
  104. (
  105. _("Basic Options"),
  106. (
  107. ('parent', {'label': _("Category Parent")}),
  108. ('perms', {'label': _("Copy Permissions from")}),
  109. ('name', {'label': _("Category Name")}),
  110. ('description', {'label': _("Category Description")}),
  111. ('closed', {'label': _("Closed Category")}),
  112. ),
  113. ),
  114. (
  115. _("Display Options"),
  116. (
  117. ('attrs', {'label': _("Category Attributes"), 'help_text': _('Custom templates can check categories for predefined attributes that will change way they are rendered.')}),
  118. ('show_details', {'label': _("Show Subforums Details"), 'help_text': _('Allows you to prevent this category subforums from displaying statistics, last post data, etc. ect. on forums lists.')}),
  119. ('style', {'label': _("Category Style"), 'help_text': _('You can add custom CSS classess to this category, to change way it looks on board index.')}),
  120. ),
  121. ),
  122. )
  123. def finalize_form(self):
  124. self.fields['perms'] = TreeNodeChoiceField(label=_("Copy Permissions from"), widget=forms.Select,
  125. queryset=Forum.objects.get(special='root').get_descendants(), level_indicator=u'- - ', required=False, empty_label=_("Don't copy permissions"))
  126. class ForumForm(Form, CleanAttrsMixin):
  127. parent = False
  128. perms = False
  129. pruned_archive = False
  130. name = forms.CharField(label=_("Forum Name"),
  131. max_length=255, validators=[validate_sluggable(
  132. _("Forum name must contain alphanumeric characters."),
  133. _("Forum name is too long.")
  134. )])
  135. description = forms.CharField(label=_("Forum Description"),
  136. widget=forms.Textarea, required=False)
  137. closed = forms.BooleanField(label=_("Closed Forum"),
  138. widget=YesNoSwitch, required=False)
  139. style = forms.CharField(label=_("Forum Style"),
  140. help_text=_('You can add custom CSS classess to this forum to change way it looks on forums lists.'),
  141. max_length=255, required=False)
  142. prune_start = forms.IntegerField(label=_("Delete threads with first post older than"),
  143. help_text=_('Enter number of days since thread start after which thread will be deleted or zero to don\'t delete threads.'),
  144. min_value=0, initial=0)
  145. prune_last = forms.IntegerField(label=_("Delete threads with last post older than"),
  146. help_text=_('Enter number of days since since last reply in thread after which thread will be deleted or zero to don\'t delete threads.'),
  147. min_value=0, initial=0)
  148. attrs = forms.CharField(label=_("Forum Attributes"),
  149. help_text=_('Custom templates can check forums for predefined attributes that will change way subforums lists are rendered.'),
  150. max_length=255, required=False)
  151. show_details = forms.BooleanField(label=_("Show Subforums Details"),
  152. help_text=_("Allows you to prevent this forum's subforums from displaying statistics, last post data, etc. ect. on subforums list."),
  153. widget=YesNoSwitch, required=False, initial=True)
  154. layout = (
  155. (
  156. _("Basic Options"),
  157. (
  158. ('parent', {'label': _("Forum Parent")}),
  159. ('perms', {'label': _("Copy Permissions from")}),
  160. ('name', {'label': _("Forum Name")}),
  161. ('description', {'label': _("Forum Description")}),
  162. ('closed', {'label': _("Closed Forum")}),
  163. ),
  164. ),
  165. (
  166. _("Prune Forum"),
  167. (
  168. ('prune_start', {'label': _("Delete threads with first post older than"), 'help_text': _('Enter number of days since thread start after which thread will be deleted or zero to don\'t delete threads.')}),
  169. ('prune_last', {'label': _("Delete threads with last post older than"), 'help_text': _('Enter number of days since since last reply in thread after which thread will be deleted or zero to don\'t delete threads.')}),
  170. ('pruned_archive', {'label': _("Archive pruned threads?"), 'help_text': _('If you want, you can archive pruned threads in other forum instead of deleting them.')})
  171. ),
  172. ),
  173. (
  174. _("Display Options"),
  175. (
  176. ('attrs', {'label': _("Forum Attributes"), 'help_text': _('Custom templates can check forums for predefined attributes that will change way subforums lists are rendered.')}),
  177. ('show_details', {'label': _("Show Subforums Details"), 'help_text': _("Allows you to prevent this forum's subforums from displaying statistics, last post data, etc. ect. on subforums list.")}),
  178. ('style', {'label': _("Forum Style"), 'help_text': _('You can add custom CSS classess to this forum to change way it looks on forums lists.')}),
  179. ),
  180. ),
  181. )
  182. def finalize_form(self):
  183. self.fields['perms'] = TreeNodeChoiceField(label=_("Copy Permissions from"), widget=forms.Select,
  184. queryset=Forum.objects.get(special='root').get_descendants(), level_indicator=u'- - ', required=False, empty_label=_("Don't copy permissions"))
  185. self.fields['pruned_archive'] = TreeNodeChoiceField(label=_("Archive pruned threads?"),
  186. help_text=_('If you want, you can archive pruned threads in other forum instead of deleting them.'),
  187. widget=forms.Select, queryset=Forum.objects.get(special='root').get_descendants(), level_indicator=u'- - ', required=False, empty_label=_("Don't archive pruned threads"))
  188. def clean_pruned_archive(self):
  189. data = self.cleaned_data['pruned_archive']
  190. if data and data.pk == self.target_forum.pk:
  191. raise forms.ValidationError(_("Forum cannot be its own archive."))
  192. return data
  193. class RedirectForm(Form, CleanAttrsMixin):
  194. parent = False
  195. perms = False
  196. name = forms.CharField(max_length=255, validators=[validate_sluggable(
  197. _("Redirect name must contain alphanumeric characters."),
  198. _("Redirect name is too long.")
  199. )])
  200. description = forms.CharField(widget=forms.Textarea, required=False)
  201. redirect = forms.URLField(max_length=255)
  202. style = forms.CharField(max_length=255, required=False)
  203. layout = (
  204. (
  205. _("Basic Options"),
  206. (
  207. ('parent', {'label': _("Redirect Parent")}),
  208. ('perms', {'label': _("Copy Permissions from")}),
  209. ('name', {'label': _("Redirect Name")}),
  210. ('redirect', {'label': _("Redirect URL")}),
  211. ('description', {'label': _("Redirect Description")}),
  212. ),
  213. ),
  214. (
  215. _("Display Options"),
  216. (
  217. ('attrs', {'label': _("Forum Attributes"), 'help_text': _('Custom templates can check forums for predefined attributes that will change way subforums lists are rendered.')}),
  218. ('style', {'label': _("Redirect Style"), 'help_text': _('You can add custom CSS classess to this redirect to change way it looks on forums lists.')}),
  219. ),
  220. ),
  221. )
  222. def finalize_form(self):
  223. self.fields['perms'] = TreeNodeChoiceField(label=_("Copy Permissions from"), widget=forms.Select,
  224. queryset=Forum.objects.get(special='root').get_descendants(), level_indicator=u'- - ', required=False, empty_label=_("Don't copy permissions"))
  225. class DeleteForm(Form):
  226. def __init__(self, *args, **kwargs):
  227. self.forum = kwargs.pop('forum')
  228. super(DeleteForm, self).__init__(*args, **kwargs)
  229. def finalize_form(self):
  230. self.fields['contents'] = TreeNodeChoiceField(label=_("Move threads to"),
  231. widget=forms.Select, queryset=Forum.objects.get(special='root').get_descendants(), required=False, empty_label=_("Remove with forum"), level_indicator=u'- - ')
  232. self.fields['subforums'] = TreeNodeChoiceField(label=_("Move subforums to"), widget=forms.Select,
  233. queryset=Forum.objects.get(special='root').get_descendants(), required=False, empty_label=_("Remove with forum"), level_indicator=u'- - ')
  234. def clean_contents(self):
  235. data = self.cleaned_data['contents']
  236. if data:
  237. if data.type == 'category':
  238. raise forms.ValidationError(_("Categories cannot contain threads."))
  239. if data.type == 'redirect':
  240. raise forms.ValidationError(_("Redirects cannot contain threads."))
  241. return data
  242. def clean(self):
  243. cleaned_data = super(DeleteForm, self).clean()
  244. if self.forum.type == 'forum' and cleaned_data['contents'] and cleaned_data['contents'].lft > self.forum.lft and cleaned_data['contents'].rght < self.forum.rght and not cleaned_data['subforums']:
  245. raise forms.ValidationError(_("Destination you want to move this forum's threads to will be deleted with this forum."))
  246. return cleaned_data