annotate forums/forms.py @ 989:2908859c2fe4

Smilies now use relative links. This is for upcoming switch to SSL. Currently we do not need absolute URLs for smilies. If this changes we can add it later.
author Brian Neal <bgneal@gmail.com>
date Thu, 29 Oct 2015 20:54:34 -0500
parents 4619290d171d
children 21c592cac71c
rev   line source
bgneal@83 1 """
bgneal@83 2 Forms for the forums application.
bgneal@469 3
bgneal@83 4 """
bgneal@951 5 from collections import OrderedDict
bgneal@951 6
bgneal@83 7 from django import forms
bgneal@95 8 from django.conf import settings
bgneal@83 9
bgneal@110 10 from forums.models import Forum
bgneal@83 11 from forums.models import Topic
bgneal@83 12 from forums.models import Post
bgneal@285 13 from forums.attachments import AttachmentProcessor
bgneal@460 14 import forums.permissions as perms
bgneal@469 15 from forums.signals import notify_new_topic, notify_new_post
bgneal@963 16 from core.html import ImageCheckError
bgneal@963 17 from core.html import image_check
bgneal@963 18 from core.markup import site_markup
bgneal@83 19
bgneal@83 20
bgneal@722 21 FORUMS_FORM_CSS = {
bgneal@722 22 'all': (settings.GPP_THIRD_PARTY_CSS['markitup'] +
bgneal@722 23 settings.GPP_THIRD_PARTY_CSS['jquery-ui'])
bgneal@722 24 }
bgneal@722 25 FORUMS_FORM_JS = (
bgneal@722 26 settings.GPP_THIRD_PARTY_JS['markitup'] +
bgneal@722 27 settings.GPP_THIRD_PARTY_JS['jquery-ui'] +
bgneal@722 28 ['js/jquery.form.min.js', 'js/forums.js']
bgneal@722 29 )
bgneal@722 30
bgneal@722 31
bgneal@106 32 class NewPostForm(forms.Form):
bgneal@86 33 """Form for creating a new post."""
bgneal@286 34 body = forms.CharField(label='',
bgneal@286 35 required=False,
bgneal@135 36 widget=forms.Textarea(attrs={'class': 'markItUp smileyTarget'}))
bgneal@89 37 topic_id = forms.IntegerField(widget=forms.HiddenInput)
bgneal@89 38 topic = None
bgneal@83 39
bgneal@89 40 class Media:
bgneal@722 41 css = FORUMS_FORM_CSS
bgneal@722 42 js = FORUMS_FORM_JS
bgneal@89 43
bgneal@285 44 def __init__(self, *args, **kwargs):
bgneal@285 45 super(NewPostForm, self).__init__(*args, **kwargs)
bgneal@285 46 attachments = args[0].getlist('attachment') if len(args) else []
bgneal@285 47 self.attach_proc = AttachmentProcessor(attachments)
bgneal@285 48
bgneal@286 49 def clean_body(self):
bgneal@963 50 body = self.cleaned_data['body']
bgneal@963 51 self.body_html = None
bgneal@963 52 if not body and not self.attach_proc.has_attachments():
bgneal@963 53 raise forms.ValidationError("Please enter some text")
bgneal@963 54
bgneal@963 55 if body:
bgneal@963 56 self.body_html = site_markup(body)
bgneal@963 57 try:
bgneal@963 58 image_check(self.body_html)
bgneal@963 59 except ImageCheckError as ex:
bgneal@963 60 raise forms.ValidationError(str(ex))
bgneal@963 61
bgneal@963 62 return body
bgneal@286 63
bgneal@89 64 def clean_topic_id(self):
bgneal@89 65 id = self.cleaned_data['topic_id']
bgneal@89 66 try:
bgneal@102 67 self.topic = Topic.objects.select_related().get(pk=id)
bgneal@89 68 except Topic.DoesNotExist:
bgneal@963 69 raise forms.ValidationError('Invalid topic')
bgneal@286 70 return id
bgneal@89 71
bgneal@89 72 def save(self, user, ip=None):
bgneal@86 73 """
bgneal@86 74 Creates a new post from the form data and supplied arguments.
bgneal@86 75 """
bgneal@89 76 post = Post(topic=self.topic, user=user, body=self.cleaned_data['body'],
bgneal@89 77 user_ip=ip)
bgneal@963 78 post.save(html=self.body_html)
bgneal@285 79 self.attach_proc.save_attachments(post)
bgneal@469 80 notify_new_post(post)
bgneal@89 81 return post
bgneal@83 82
bgneal@83 83
bgneal@83 84 class NewTopicForm(forms.Form):
bgneal@102 85 """
bgneal@102 86 Form for creating a new topic and 1st post to that topic.
bgneal@102 87 Superusers and moderators can also create the topic as a sticky or initially
bgneal@102 88 locked.
bgneal@102 89 """
bgneal@95 90 name = forms.CharField(label='Subject', max_length=255,
bgneal@673 91 widget=forms.TextInput(attrs={'style': 'width:70%'}))
bgneal@286 92 body = forms.CharField(label='', required=False,
bgneal@135 93 widget=forms.Textarea(attrs={'class': 'markItUp smileyTarget'}))
bgneal@102 94 user = None
bgneal@102 95 forum = None
bgneal@102 96 has_mod_fields = False
bgneal@83 97
bgneal@95 98 class Media:
bgneal@722 99 css = FORUMS_FORM_CSS
bgneal@722 100 js = FORUMS_FORM_JS
bgneal@95 101
bgneal@102 102 def __init__(self, user, forum, *args, **kwargs):
bgneal@102 103 super(NewTopicForm, self).__init__(*args, **kwargs)
bgneal@102 104 self.user = user
bgneal@102 105 self.forum = forum
bgneal@102 106
bgneal@460 107 if perms.can_moderate(forum, user):
bgneal@102 108 self.fields['sticky'] = forms.BooleanField(required=False)
bgneal@102 109 self.fields['locked'] = forms.BooleanField(required=False)
bgneal@102 110 self.has_mod_fields = True
bgneal@102 111
bgneal@285 112 attachments = args[0].getlist('attachment') if len(args) else []
bgneal@285 113 self.attach_proc = AttachmentProcessor(attachments)
bgneal@285 114
bgneal@286 115 # If this form is being POSTed, and the user is trying to add
bgneal@286 116 # attachments, create hidden fields to list the Oembed ids. In
bgneal@286 117 # case the form isn't valid, the client-side javascript will know
bgneal@286 118 # which Oembed media to ask for when the form is displayed with
bgneal@286 119 # errors.
bgneal@286 120 if self.attach_proc.has_attachments():
bgneal@286 121 pks = self.attach_proc.get_ids()
bgneal@286 122 self.fields['attachment'] = forms.MultipleChoiceField(label='',
bgneal@286 123 widget=forms.MultipleHiddenInput(),
bgneal@286 124 choices=[(v, v) for v in pks])
bgneal@286 125
bgneal@286 126 def clean_body(self):
bgneal@963 127 body = self.cleaned_data['body']
bgneal@963 128 self.body_html = None
bgneal@963 129 if not body and not self.attach_proc.has_attachments():
bgneal@286 130 raise forms.ValidationError("This field is required.")
bgneal@963 131
bgneal@963 132 if body:
bgneal@963 133 self.body_html = site_markup(body)
bgneal@963 134 try:
bgneal@963 135 image_check(self.body_html)
bgneal@963 136 except ImageCheckError as ex:
bgneal@963 137 raise forms.ValidationError(str(ex))
bgneal@963 138
bgneal@963 139 return body
bgneal@286 140
bgneal@102 141 def save(self, ip=None):
bgneal@83 142 """
bgneal@83 143 Creates the new Topic and first Post from the form data and supplied
bgneal@86 144 arguments.
bgneal@83 145 """
bgneal@102 146 topic = Topic(forum=self.forum,
bgneal@83 147 name=self.cleaned_data['name'],
bgneal@102 148 user=self.user,
bgneal@102 149 sticky=self.has_mod_fields and self.cleaned_data['sticky'],
bgneal@102 150 locked=self.has_mod_fields and self.cleaned_data['locked'])
bgneal@83 151 topic.save()
bgneal@83 152
bgneal@83 153 post = Post(topic=topic,
bgneal@102 154 user=self.user,
bgneal@83 155 body=self.cleaned_data['body'],
bgneal@83 156 user_ip=ip)
bgneal@963 157 post.save(html=self.body_html)
bgneal@285 158
bgneal@285 159 self.attach_proc.save_attachments(post)
bgneal@285 160
bgneal@469 161 notify_new_topic(topic)
bgneal@469 162 notify_new_post(post)
bgneal@469 163
bgneal@83 164 return topic
bgneal@106 165
bgneal@106 166
bgneal@106 167 class PostForm(forms.ModelForm):
bgneal@106 168 """
bgneal@108 169 Form for editing an existing post or a new, non-quick post.
bgneal@106 170 """
bgneal@286 171 body = forms.CharField(label='',
bgneal@286 172 required=False,
bgneal@135 173 widget=forms.Textarea(attrs={'class': 'markItUp smileyTarget'}))
bgneal@106 174
bgneal@106 175 class Meta:
bgneal@106 176 model = Post
bgneal@106 177 fields = ('body', )
bgneal@106 178
bgneal@106 179 class Media:
bgneal@722 180 css = FORUMS_FORM_CSS
bgneal@722 181 js = FORUMS_FORM_JS
bgneal@110 182
bgneal@286 183 def __init__(self, *args, **kwargs):
bgneal@295 184 topic_name = kwargs.pop('topic_name', None)
bgneal@286 185 super(PostForm, self).__init__(*args, **kwargs)
bgneal@286 186
bgneal@951 187 if topic_name is not None:
bgneal@951 188 # this is a "first post", add a field for a topic name
bgneal@951 189 new_fields = OrderedDict(name=forms.CharField(
bgneal@951 190 label='Subject', max_length=255,
bgneal@951 191 widget=forms.TextInput(attrs={
bgneal@951 192 'class': 'title',
bgneal@951 193 'style': 'width: 90%',
bgneal@951 194 })))
bgneal@951 195 new_fields.update(self.fields)
bgneal@951 196 self.fields = new_fields
bgneal@295 197 self.initial['name'] = topic_name
bgneal@295 198
bgneal@286 199 attachments = args[0].getlist('attachment') if len(args) else []
bgneal@286 200 self.attach_proc = AttachmentProcessor(attachments)
bgneal@295 201
bgneal@286 202 # If this form is being used to edit an existing post, and that post
bgneal@286 203 # has attachments, create a hidden post_id field. The client-side
bgneal@286 204 # AJAX will use this as a cue to retrieve the HTML for the embedded
bgneal@286 205 # media.
bgneal@286 206 if 'instance' in kwargs:
bgneal@286 207 post = kwargs['instance']
bgneal@286 208 if post.attachments.count():
bgneal@286 209 self.fields['post_id'] = forms.CharField(label='',
bgneal@286 210 widget=forms.HiddenInput(attrs={'value': post.id}))
bgneal@286 211
bgneal@286 212 def clean_body(self):
bgneal@963 213 body = self.cleaned_data['body']
bgneal@963 214 self.body_html = None
bgneal@963 215 if not body and not self.attach_proc.has_attachments():
bgneal@286 216 raise forms.ValidationError('This field is required.')
bgneal@963 217
bgneal@963 218 if body:
bgneal@963 219 self.body_html = site_markup(body)
bgneal@963 220 try:
bgneal@963 221 image_check(self.body_html)
bgneal@963 222 except ImageCheckError as ex:
bgneal@963 223 raise forms.ValidationError(str(ex))
bgneal@963 224
bgneal@963 225 return body
bgneal@286 226
bgneal@295 227 def save(self, *args, **kwargs):
bgneal@295 228 commit = kwargs.get('commit', False)
bgneal@295 229 post = super(PostForm, self).save(*args, **kwargs)
bgneal@295 230
bgneal@295 231 # Are we saving a "first post"?
bgneal@295 232 if 'name' in self.cleaned_data:
bgneal@295 233 post.topic.name = self.cleaned_data['name']
bgneal@295 234 if commit:
bgneal@295 235 post.topic.save()
bgneal@295 236 return post
bgneal@295 237
bgneal@110 238
bgneal@110 239 class MoveTopicForm(forms.Form):
bgneal@111 240 """
bgneal@111 241 Form for a moderator to move a topic to a forum.
bgneal@111 242 """
bgneal@286 243 forums = forms.ModelChoiceField(label='Move to forum',
bgneal@111 244 queryset=Forum.objects.none())
bgneal@110 245
bgneal@111 246 def __init__(self, user, *args, **kwargs):
bgneal@286 247 hide_label = kwargs.pop('hide_label', False)
bgneal@111 248 required = kwargs.pop('required', True)
bgneal@111 249 super(MoveTopicForm, self).__init__(*args, **kwargs)
bgneal@111 250 self.fields['forums'].queryset = \
bgneal@111 251 Forum.objects.forums_for_user(user).order_by('name')
bgneal@111 252 if hide_label:
bgneal@111 253 self.fields['forums'].label = ''
bgneal@111 254 self.fields['forums'].required = required
bgneal@110 255
bgneal@115 256
bgneal@115 257 class SplitTopicForm(forms.Form):
bgneal@115 258 """
bgneal@115 259 Form for a moderator to split posts from a topic to a new topic.
bgneal@115 260 """
bgneal@115 261 name = forms.CharField(label='New topic title', max_length=255,
bgneal@115 262 widget=forms.TextInput(attrs={'size': 64}))
bgneal@286 263 forums = forms.ModelChoiceField(label='Forum for new topic',
bgneal@115 264 queryset=Forum.objects.none())
bgneal@115 265 post_ids = []
bgneal@115 266 split_at = False
bgneal@115 267
bgneal@115 268 def __init__(self, user, *args, **kwargs):
bgneal@115 269 super(SplitTopicForm, self).__init__(*args, **kwargs)
bgneal@115 270 self.fields['forums'].queryset = \
bgneal@115 271 Forum.objects.forums_for_user(user).order_by('name')
bgneal@115 272
bgneal@115 273 def clean(self):
bgneal@115 274 self.post_ids = self.data.getlist('post_ids')
bgneal@115 275 if len(self.post_ids) == 0:
bgneal@115 276 raise forms.ValidationError('Please select some posts')
bgneal@115 277
bgneal@115 278 self.split_at = 'split-at' in self.data
bgneal@115 279 if self.split_at and len(self.post_ids) > 1:
bgneal@115 280 raise forms.ValidationError('Please select only one post to split the topic at')
bgneal@115 281
bgneal@115 282 return self.cleaned_data