annotate forums/forms.py @ 887:9a15f7c27526

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