annotate gpp/forums/forms.py @ 286:72fd300685d5

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