annotate forums/forms.py @ 955:71a671dab55d

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