forms.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. from django import forms
  2. from django.conf import settings
  3. from django.utils.translation import ungettext, ugettext_lazy as _
  4. from mptt.forms import TreeNodeChoiceField
  5. from misago.acl.utils import ACLError403, ACLError404
  6. from misago.forms import Form
  7. from misago.forums.models import Forum
  8. from misago.threads.models import Thread
  9. from misago.utils import slugify
  10. from misago.utils.validators import validate_sluggable
  11. class ThreadNameMixin(object):
  12. def clean_thread_name(self):
  13. data = self.cleaned_data['thread_name']
  14. slug = slugify(data)
  15. if len(slug) < self.request.settings['thread_name_min']:
  16. raise forms.ValidationError(ungettext(
  17. "Thread name must contain at least one alpha-numeric character.",
  18. "Thread name must contain at least %(count)d alpha-numeric characters.",
  19. self.request.settings['thread_name_min']
  20. ) % {'count': self.request.settings['thread_name_min']})
  21. if len(data) > self.request.settings['thread_name_max']:
  22. raise forms.ValidationError(ungettext(
  23. "Thread name cannot be longer than %(count)d character.",
  24. "Thread name cannot be longer than %(count)d characters.",
  25. self.request.settings['thread_name_max']
  26. ) % {'count': self.request.settings['thread_name_max']})
  27. return data
  28. class PostForm(Form, ThreadNameMixin):
  29. post = forms.CharField(widget=forms.Textarea)
  30. def __init__(self, data=None, file=None, request=None, mode=None, *args, **kwargs):
  31. self.mode = mode
  32. super(PostForm, self).__init__(data, file, request=request, *args, **kwargs)
  33. def finalize_form(self):
  34. self.layout = [
  35. [
  36. None,
  37. [
  38. ('thread_name', {'label': _("Thread Name")}),
  39. ('edit_reason', {'label': _("Edit Reason")}),
  40. ('post', {'label': _("Post Content")}),
  41. ],
  42. ],
  43. ]
  44. if self.mode in ['edit_thread', 'edit_post']:
  45. self.fields['edit_reason'] = forms.CharField(max_length=255,required=False,help_text=_("Optional reason for changing this post."))
  46. else:
  47. del self.layout[0][1][1]
  48. if self.mode not in ['edit_thread', 'new_thread']:
  49. del self.layout[0][1][0]
  50. else:
  51. self.fields['thread_name'] = forms.CharField(
  52. max_length=self.request.settings['thread_name_max'],
  53. validators=[validate_sluggable(
  54. _("Thread name must contain at least one alpha-numeric character."),
  55. _("Thread name is too long. Try shorter name.")
  56. )])
  57. def clean_post(self):
  58. data = self.cleaned_data['post']
  59. if len(data) < self.request.settings['post_length_min']:
  60. raise forms.ValidationError(ungettext(
  61. "Post content cannot be empty.",
  62. "Post content cannot be shorter than %(count)d characters.",
  63. self.request.settings['post_length_min']
  64. ) % {'count': self.request.settings['post_length_min']})
  65. return data
  66. class SplitThreadForm(Form, ThreadNameMixin):
  67. def finalize_form(self):
  68. self.layout = [
  69. [
  70. None,
  71. [
  72. ('thread_name', {'label': _("New Thread Name")}),
  73. ('thread_forum', {'label': _("New Thread Forum")}),
  74. ],
  75. ],
  76. ]
  77. self.fields['thread_name'] = forms.CharField(
  78. max_length=self.request.settings['thread_name_max'],
  79. validators=[validate_sluggable(
  80. _("Thread name must contain at least one alpha-numeric character."),
  81. _("Thread name is too long. Try shorter name.")
  82. )])
  83. self.fields['thread_forum'] = TreeNodeChoiceField(queryset=Forum.tree.get(token='root').get_descendants().filter(pk__in=self.request.acl.forums.acl['can_browse']),level_indicator=u'- - ')
  84. def clean_thread_forum(self):
  85. new_forum = self.cleaned_data['thread_forum']
  86. # Assert its forum and its not current forum
  87. if new_forum.type != 'forum':
  88. raise forms.ValidationError(_("This is not a forum."))
  89. return new_forum
  90. class MovePostsForm(Form, ThreadNameMixin):
  91. error_source = 'thread_url'
  92. def __init__(self, data=None, request=None, thread=None, *args, **kwargs):
  93. self.thread = thread
  94. super(MovePostsForm, self).__init__(data, request=request, *args, **kwargs)
  95. def finalize_form(self):
  96. self.layout = [
  97. [
  98. None,
  99. [
  100. ('thread_url', {'label': _("New Thread Link"), 'help_text': _("To select new thread, simply copy and paste here its link.")}),
  101. ],
  102. ],
  103. ]
  104. self.fields['thread_url'] = forms.CharField()
  105. def clean_thread_url(self):
  106. from django.core.urlresolvers import resolve
  107. from django.http import Http404
  108. thread_url = self.cleaned_data['thread_url']
  109. try:
  110. thread_url = thread_url[len(settings.BOARD_ADDRESS):]
  111. match = resolve(thread_url)
  112. thread = Thread.objects.get(pk=match.kwargs['thread'])
  113. self.request.acl.threads.allow_thread_view(self.request.user, thread)
  114. if thread.pk == self.thread.pk:
  115. raise forms.ValidationError(_("New thread is same as current one."))
  116. return thread
  117. except (Http404, KeyError):
  118. raise forms.ValidationError(_("This is not a correct thread URL."))
  119. except (Thread.DoesNotExist, ACLError403, ACLError404):
  120. raise forms.ValidationError(_("Thread could not be found."))
  121. class QuickReplyForm(Form):
  122. post = forms.CharField(widget=forms.Textarea)
  123. class MoveThreadsForm(Form):
  124. error_source = 'new_forum'
  125. def __init__(self, data=None, request=None, forum=None, *args, **kwargs):
  126. self.forum = forum
  127. super(MoveThreadsForm, self).__init__(data, request=request, *args, **kwargs)
  128. def finalize_form(self):
  129. self.fields['new_forum'] = TreeNodeChoiceField(queryset=Forum.tree.get(token='root').get_descendants().filter(pk__in=self.request.acl.forums.acl['can_browse']),level_indicator=u'- - ')
  130. self.layout = [
  131. [
  132. _("Thread Options"),
  133. [
  134. ('new_forum', {'label': _("Move Thread to"), 'help_text': _("Select forum you want to move threads to.")}),
  135. ],
  136. ],
  137. ]
  138. def clean_new_forum(self):
  139. new_forum = self.cleaned_data['new_forum']
  140. # Assert its forum and its not current forum
  141. if new_forum.type != 'forum':
  142. raise forms.ValidationError(_("This is not forum."))
  143. if new_forum.pk == self.forum.pk:
  144. raise forms.ValidationError(_("New forum is same as current one."))
  145. return new_forum
  146. class MergeThreadsForm(Form, ThreadNameMixin):
  147. def __init__(self, data=None, request=None, threads=[], *args, **kwargs):
  148. self.threads = threads
  149. super(MergeThreadsForm, self).__init__(data, request=request, *args, **kwargs)
  150. def finalize_form(self):
  151. self.fields['thread_name'] = forms.CharField(
  152. max_length=self.request.settings['thread_name_max'],
  153. initial=self.threads[0].name,
  154. validators=[validate_sluggable(
  155. _("Thread name must contain at least one alpha-numeric character."),
  156. _("Thread name is too long. Try shorter name.")
  157. )])
  158. self.layout = [
  159. [
  160. _("Thread Options"),
  161. [
  162. ('thread_name', {'label': _("Thread Name"), 'help_text': _("Name of new thread that will be created as result of merge.")}),
  163. ],
  164. ],
  165. [
  166. _("Merge Order"),
  167. [
  168. ],
  169. ],
  170. ]
  171. choices = []
  172. for i, thread in enumerate(self.threads):
  173. choices.append((str(i), i + 1))
  174. for i, thread in enumerate(self.threads):
  175. self.fields['thread_%s' % thread.pk] = forms.ChoiceField(choices=choices,initial=str(i))
  176. self.layout[1][1].append(('thread_%s' % thread.pk, {'label': thread.name}))
  177. def clean(self):
  178. cleaned_data = super(MergeThreadsForm, self).clean()
  179. self.merge_order = {}
  180. lookback = []
  181. for thread in self.threads:
  182. order = int(cleaned_data['thread_%s' % thread.pk])
  183. if order in lookback:
  184. raise forms.ValidationError(_("One or more threads have same position in merge order."))
  185. lookback.append(order)
  186. self.merge_order[order] = thread
  187. return cleaned_data