forms.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. from django.utils.translation import ugettext_lazy as _
  2. from django import 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(choices=(
  20. ('category', _("Category")),
  21. ('forum', _("Forum")),
  22. ('redirect', _("Redirection")),
  23. ))
  24. name = forms.CharField(max_length=255, validators=[validate_sluggable(
  25. _("Category name must contain alphanumeric characters."),
  26. _("Category name is too long.")
  27. )])
  28. redirect = forms.URLField(max_length=255, required=False)
  29. description = forms.CharField(widget=forms.Textarea, required=False)
  30. closed = forms.BooleanField(widget=YesNoSwitch, required=False)
  31. attrs = forms.CharField(max_length=255, required=False)
  32. show_details = forms.BooleanField(widget=YesNoSwitch, required=False, initial=True)
  33. style = forms.CharField(max_length=255, required=False)
  34. layout = (
  35. (
  36. _("Basic Options"),
  37. (
  38. ('parent', {'label': _("Node Parent")}),
  39. ('perms', {'label': _("Copy Permissions from")}),
  40. ('role', {'label': _("Node Type"), 'help_text': _("Each Node has specific role in forums tree. This role cannot be changed after node is created.")}),
  41. ('name', {'label': _("Node Name")}),
  42. ('description', {'label': _("Node Description")}),
  43. ('redirect', {'label': _("Redirect URL"), 'help_text': _("Redirection nodes require you to specify URL they will redirect users to upon click.")}),
  44. ('closed', {'label': _("Closed Node")}),
  45. ),
  46. ),
  47. (
  48. _("Display Options"),
  49. (
  50. ('attrs', {'label': _("Node Attributes"), 'help_text': _('Custom templates can check nodes for predefined attributes that will change way they are rendered.')}),
  51. ('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.')}),
  52. ('style', {'label': _("Node Style"), 'help_text': _('You can add custom CSS classess to this node, to change way it looks on board index.')}),
  53. ),
  54. ),
  55. )
  56. def finalize_form(self):
  57. self.fields['parent'] = TreeNodeChoiceField(queryset=Forum.objects.get(special='root').get_descendants(include_self=True), level_indicator=u'- - ')
  58. self.fields['perms'] = TreeNodeChoiceField(queryset=Forum.objects.get(special='root').get_descendants(), level_indicator=u'- - ', required=False, empty_label=_("Don't copy permissions"))
  59. def clean(self):
  60. cleaned_data = super(NewNodeForm, self).clean()
  61. node_role = cleaned_data['role']
  62. if node_role != 'category' and cleaned_data['parent'].special == 'root':
  63. raise forms.ValidationError(_("Only categories can use Root Category as their parent."))
  64. if node_role == 'redirect' and not cleaned_data['redirect']:
  65. raise forms.ValidationError(_("You have to define redirection URL"))
  66. return cleaned_data
  67. class CategoryForm(Form, CleanAttrsMixin):
  68. parent = False
  69. perms = False
  70. name = forms.CharField(max_length=255, validators=[validate_sluggable(
  71. _("Category name must contain alphanumeric characters."),
  72. _("Category name is too long.")
  73. )])
  74. description = forms.CharField(widget=forms.Textarea, required=False)
  75. closed = forms.BooleanField(widget=YesNoSwitch, required=False)
  76. style = forms.CharField(max_length=255, required=False)
  77. attrs = forms.CharField(max_length=255, required=False)
  78. show_details = forms.BooleanField(widget=YesNoSwitch, required=False, initial=True)
  79. layout = (
  80. (
  81. _("Basic Options"),
  82. (
  83. ('parent', {'label': _("Category Parent")}),
  84. ('perms', {'label': _("Copy Permissions from")}),
  85. ('name', {'label': _("Category Name")}),
  86. ('description', {'label': _("Category Description")}),
  87. ('closed', {'label': _("Closed Category")}),
  88. ),
  89. ),
  90. (
  91. _("Display Options"),
  92. (
  93. ('attrs', {'label': _("Category Attributes"), 'help_text': _('Custom templates can check categories for predefined attributes that will change way they are rendered.')}),
  94. ('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.')}),
  95. ('style', {'label': _("Category Style"), 'help_text': _('You can add custom CSS classess to this category, to change way it looks on board index.')}),
  96. ),
  97. ),
  98. )
  99. def finalize_form(self):
  100. self.fields['perms'] = TreeNodeChoiceField(queryset=Forum.objects.get(special='root').get_descendants(), level_indicator=u'- - ', required=False, empty_label=_("Don't copy permissions"))
  101. class ForumForm(Form, CleanAttrsMixin):
  102. parent = False
  103. perms = False
  104. pruned_archive = False
  105. name = forms.CharField(max_length=255, validators=[validate_sluggable(
  106. _("Forum name must contain alphanumeric characters."),
  107. _("Forum name is too long.")
  108. )])
  109. description = forms.CharField(widget=forms.Textarea, required=False)
  110. closed = forms.BooleanField(widget=YesNoSwitch, required=False)
  111. style = forms.CharField(max_length=255, required=False)
  112. prune_start = forms.IntegerField(min_value=0, initial=0)
  113. prune_last = forms.IntegerField(min_value=0, initial=0)
  114. attrs = forms.CharField(max_length=255, required=False)
  115. show_details = forms.BooleanField(widget=YesNoSwitch, required=False, initial=True)
  116. layout = (
  117. (
  118. _("Basic Options"),
  119. (
  120. ('parent', {'label': _("Forum Parent")}),
  121. ('perms', {'label': _("Copy Permissions from")}),
  122. ('name', {'label': _("Forum Name")}),
  123. ('description', {'label': _("Forum Description")}),
  124. ('closed', {'label': _("Closed Forum")}),
  125. ),
  126. ),
  127. (
  128. _("Prune Forum"),
  129. (
  130. ('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.')}),
  131. ('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.')}),
  132. ('pruned_archive', {'label': _("Archive pruned forums?"), 'help_text': _('If you want, you can archive pruned threads in other forum instead of deleting them.')})
  133. ),
  134. ),
  135. (
  136. _("Display Options"),
  137. (
  138. ('attrs', {'label': _("Forum Attributes"), 'help_text': _('Custom templates can check forums for predefined attributes that will change way subforums lists are rendered.')}),
  139. ('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.")}),
  140. ('style', {'label': _("Forum Style"), 'help_text': _('You can add custom CSS classess to this forum to change way it looks on forums lists.')}),
  141. ),
  142. ),
  143. )
  144. def finalize_form(self):
  145. self.fields['perms'] = TreeNodeChoiceField(queryset=Forum.objects.get(special='root').get_descendants(), level_indicator=u'- - ', required=False, empty_label=_("Don't copy permissions"))
  146. self.fields['pruned_archive'] = TreeNodeChoiceField(queryset=Forum.objects.get(special='root').get_descendants(), level_indicator=u'- - ', required=False, empty_label=_("Don't archive pruned threads"))
  147. def clean_pruned_archive(self):
  148. data = self.cleaned_data['pruned_archive']
  149. if data and data.pk == self.target_forum.pk:
  150. raise forms.ValidationError(_("Forum cannot be its own archive."))
  151. return data
  152. class RedirectForm(Form, CleanAttrsMixin):
  153. parent = False
  154. perms = False
  155. name = forms.CharField(max_length=255, validators=[validate_sluggable(
  156. _("Redirect name must contain alphanumeric characters."),
  157. _("Redirect name is too long.")
  158. )])
  159. description = forms.CharField(widget=forms.Textarea, required=False)
  160. redirect = forms.URLField(max_length=255)
  161. style = forms.CharField(max_length=255, required=False)
  162. layout = (
  163. (
  164. _("Basic Options"),
  165. (
  166. ('parent', {'label': _("Redirect Parent")}),
  167. ('perms', {'label': _("Copy Permissions from")}),
  168. ('name', {'label': _("Redirect Name")}),
  169. ('redirect', {'label': _("Redirect URL")}),
  170. ('description', {'label': _("Redirect Description")}),
  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. ('style', {'label': _("Redirect Style"), 'help_text': _('You can add custom CSS classess to this redirect to change way it looks on forums lists.')}),
  178. ),
  179. ),
  180. )
  181. def finalize_form(self):
  182. self.fields['perms'] = TreeNodeChoiceField(queryset=Forum.objects.get(special='root').get_descendants(), level_indicator=u'- - ', required=False, empty_label=_("Don't copy permissions"))
  183. class DeleteForm(Form):
  184. layout = (
  185. (
  186. _("Delete Options"),
  187. (
  188. ('contents', {'label': _("Move threads to")}),
  189. ('subforums', {'label': _("Move subforums to")}),
  190. ),
  191. ),
  192. )
  193. def __init__(self, *args, **kwargs):
  194. self.forum = kwargs.pop('forum')
  195. super(DeleteForm, self).__init__(*args, **kwargs)
  196. def finalize_form(self):
  197. self.fields['contents'] = TreeNodeChoiceField(queryset=Forum.objects.get(special='root').get_descendants(), required=False, empty_label=_("Remove with forum"), level_indicator=u'- - ')
  198. self.fields['subforums'] = TreeNodeChoiceField(queryset=Forum.objects.get(special='root').get_descendants(), required=False, empty_label=_("Remove with forum"), level_indicator=u'- - ')
  199. def clean_contents(self):
  200. data = self.cleaned_data['contents']
  201. if data:
  202. if data.type == 'category':
  203. raise forms.ValidationError(_("Categories cannot contain threads."))
  204. if data.type == 'redirect':
  205. raise forms.ValidationError(_("Redirects cannot contain threads."))
  206. return data
  207. def clean(self):
  208. cleaned_data = super(DeleteForm, self).clean()
  209. 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']:
  210. raise forms.ValidationError(_("Destination you want to move this forum's threads to will be deleted with this forum."))
  211. return cleaned_data