bgneal@83: """ bgneal@83: Forms for the forums application. bgneal@469: bgneal@83: """ bgneal@951: from collections import OrderedDict bgneal@951: bgneal@83: from django import forms bgneal@95: from django.conf import settings bgneal@83: bgneal@110: from forums.models import Forum bgneal@83: from forums.models import Topic bgneal@83: from forums.models import Post bgneal@285: from forums.attachments import AttachmentProcessor bgneal@460: import forums.permissions as perms bgneal@469: from forums.signals import notify_new_topic, notify_new_post bgneal@83: bgneal@83: bgneal@722: FORUMS_FORM_CSS = { bgneal@722: 'all': (settings.GPP_THIRD_PARTY_CSS['markitup'] + bgneal@722: settings.GPP_THIRD_PARTY_CSS['jquery-ui']) bgneal@722: } bgneal@722: FORUMS_FORM_JS = ( bgneal@722: settings.GPP_THIRD_PARTY_JS['markitup'] + bgneal@722: settings.GPP_THIRD_PARTY_JS['jquery-ui'] + bgneal@722: ['js/jquery.form.min.js', 'js/forums.js'] bgneal@722: ) bgneal@722: bgneal@722: bgneal@106: class NewPostForm(forms.Form): bgneal@86: """Form for creating a new post.""" bgneal@286: body = forms.CharField(label='', bgneal@286: required=False, bgneal@135: widget=forms.Textarea(attrs={'class': 'markItUp smileyTarget'})) bgneal@89: topic_id = forms.IntegerField(widget=forms.HiddenInput) bgneal@89: topic = None bgneal@83: bgneal@89: class Media: bgneal@722: css = FORUMS_FORM_CSS bgneal@722: js = FORUMS_FORM_JS bgneal@89: bgneal@285: def __init__(self, *args, **kwargs): bgneal@285: super(NewPostForm, self).__init__(*args, **kwargs) bgneal@285: attachments = args[0].getlist('attachment') if len(args) else [] bgneal@285: self.attach_proc = AttachmentProcessor(attachments) bgneal@285: bgneal@286: def clean_body(self): bgneal@286: data = self.cleaned_data['body'] bgneal@286: if not data and not self.attach_proc.has_attachments(): bgneal@286: raise forms.ValidationError("This field is required.") bgneal@286: return data bgneal@286: bgneal@89: def clean_topic_id(self): bgneal@89: id = self.cleaned_data['topic_id'] bgneal@89: try: bgneal@102: self.topic = Topic.objects.select_related().get(pk=id) bgneal@89: except Topic.DoesNotExist: bgneal@89: raise forms.ValidationError('invalid topic') bgneal@286: return id bgneal@89: bgneal@89: def save(self, user, ip=None): bgneal@86: """ bgneal@86: Creates a new post from the form data and supplied arguments. bgneal@86: """ bgneal@89: post = Post(topic=self.topic, user=user, body=self.cleaned_data['body'], bgneal@89: user_ip=ip) bgneal@86: post.save() bgneal@285: self.attach_proc.save_attachments(post) bgneal@469: notify_new_post(post) bgneal@89: return post bgneal@83: bgneal@83: bgneal@83: class NewTopicForm(forms.Form): bgneal@102: """ bgneal@102: Form for creating a new topic and 1st post to that topic. bgneal@102: Superusers and moderators can also create the topic as a sticky or initially bgneal@102: locked. bgneal@102: """ bgneal@95: name = forms.CharField(label='Subject', max_length=255, bgneal@673: widget=forms.TextInput(attrs={'style': 'width:70%'})) bgneal@286: body = forms.CharField(label='', required=False, bgneal@135: widget=forms.Textarea(attrs={'class': 'markItUp smileyTarget'})) bgneal@102: user = None bgneal@102: forum = None bgneal@102: has_mod_fields = False bgneal@83: bgneal@95: class Media: bgneal@722: css = FORUMS_FORM_CSS bgneal@722: js = FORUMS_FORM_JS bgneal@95: bgneal@102: def __init__(self, user, forum, *args, **kwargs): bgneal@102: super(NewTopicForm, self).__init__(*args, **kwargs) bgneal@102: self.user = user bgneal@102: self.forum = forum bgneal@102: bgneal@460: if perms.can_moderate(forum, user): bgneal@102: self.fields['sticky'] = forms.BooleanField(required=False) bgneal@102: self.fields['locked'] = forms.BooleanField(required=False) bgneal@102: self.has_mod_fields = True bgneal@102: bgneal@285: attachments = args[0].getlist('attachment') if len(args) else [] bgneal@285: self.attach_proc = AttachmentProcessor(attachments) bgneal@285: bgneal@286: # If this form is being POSTed, and the user is trying to add bgneal@286: # attachments, create hidden fields to list the Oembed ids. In bgneal@286: # case the form isn't valid, the client-side javascript will know bgneal@286: # which Oembed media to ask for when the form is displayed with bgneal@286: # errors. bgneal@286: if self.attach_proc.has_attachments(): bgneal@286: pks = self.attach_proc.get_ids() bgneal@286: self.fields['attachment'] = forms.MultipleChoiceField(label='', bgneal@286: widget=forms.MultipleHiddenInput(), bgneal@286: choices=[(v, v) for v in pks]) bgneal@286: bgneal@286: def clean_body(self): bgneal@286: data = self.cleaned_data['body'] bgneal@286: if not data and not self.attach_proc.has_attachments(): bgneal@286: raise forms.ValidationError("This field is required.") bgneal@286: return data bgneal@286: bgneal@102: def save(self, ip=None): bgneal@83: """ bgneal@83: Creates the new Topic and first Post from the form data and supplied bgneal@86: arguments. bgneal@83: """ bgneal@102: topic = Topic(forum=self.forum, bgneal@83: name=self.cleaned_data['name'], bgneal@102: user=self.user, bgneal@102: sticky=self.has_mod_fields and self.cleaned_data['sticky'], bgneal@102: locked=self.has_mod_fields and self.cleaned_data['locked']) bgneal@83: topic.save() bgneal@83: bgneal@83: post = Post(topic=topic, bgneal@102: user=self.user, bgneal@83: body=self.cleaned_data['body'], bgneal@83: user_ip=ip) bgneal@83: post.save() bgneal@285: bgneal@285: self.attach_proc.save_attachments(post) bgneal@285: bgneal@469: notify_new_topic(topic) bgneal@469: notify_new_post(post) bgneal@469: bgneal@83: return topic bgneal@106: bgneal@106: bgneal@106: class PostForm(forms.ModelForm): bgneal@106: """ bgneal@108: Form for editing an existing post or a new, non-quick post. bgneal@106: """ bgneal@286: body = forms.CharField(label='', bgneal@286: required=False, bgneal@135: widget=forms.Textarea(attrs={'class': 'markItUp smileyTarget'})) bgneal@106: bgneal@106: class Meta: bgneal@106: model = Post bgneal@106: fields = ('body', ) bgneal@106: bgneal@106: class Media: bgneal@722: css = FORUMS_FORM_CSS bgneal@722: js = FORUMS_FORM_JS bgneal@110: bgneal@286: def __init__(self, *args, **kwargs): bgneal@295: topic_name = kwargs.pop('topic_name', None) bgneal@286: super(PostForm, self).__init__(*args, **kwargs) bgneal@286: bgneal@951: if topic_name is not None: bgneal@951: # this is a "first post", add a field for a topic name bgneal@951: new_fields = OrderedDict(name=forms.CharField( bgneal@951: label='Subject', max_length=255, bgneal@951: widget=forms.TextInput(attrs={ bgneal@951: 'class': 'title', bgneal@951: 'style': 'width: 90%', bgneal@951: }))) bgneal@951: new_fields.update(self.fields) bgneal@951: self.fields = new_fields bgneal@295: self.initial['name'] = topic_name bgneal@295: bgneal@286: attachments = args[0].getlist('attachment') if len(args) else [] bgneal@286: self.attach_proc = AttachmentProcessor(attachments) bgneal@295: bgneal@286: # If this form is being used to edit an existing post, and that post bgneal@286: # has attachments, create a hidden post_id field. The client-side bgneal@286: # AJAX will use this as a cue to retrieve the HTML for the embedded bgneal@286: # media. bgneal@286: if 'instance' in kwargs: bgneal@286: post = kwargs['instance'] bgneal@286: if post.attachments.count(): bgneal@286: self.fields['post_id'] = forms.CharField(label='', bgneal@286: widget=forms.HiddenInput(attrs={'value': post.id})) bgneal@286: bgneal@286: def clean_body(self): bgneal@286: data = self.cleaned_data['body'] bgneal@286: if not data and not self.attach_proc.has_attachments(): bgneal@286: raise forms.ValidationError('This field is required.') bgneal@286: return data bgneal@286: bgneal@295: def save(self, *args, **kwargs): bgneal@295: commit = kwargs.get('commit', False) bgneal@295: post = super(PostForm, self).save(*args, **kwargs) bgneal@295: bgneal@295: # Are we saving a "first post"? bgneal@295: if 'name' in self.cleaned_data: bgneal@295: post.topic.name = self.cleaned_data['name'] bgneal@295: if commit: bgneal@295: post.topic.save() bgneal@295: return post bgneal@295: bgneal@110: bgneal@110: class MoveTopicForm(forms.Form): bgneal@111: """ bgneal@111: Form for a moderator to move a topic to a forum. bgneal@111: """ bgneal@286: forums = forms.ModelChoiceField(label='Move to forum', bgneal@111: queryset=Forum.objects.none()) bgneal@110: bgneal@111: def __init__(self, user, *args, **kwargs): bgneal@286: hide_label = kwargs.pop('hide_label', False) bgneal@111: required = kwargs.pop('required', True) bgneal@111: super(MoveTopicForm, self).__init__(*args, **kwargs) bgneal@111: self.fields['forums'].queryset = \ bgneal@111: Forum.objects.forums_for_user(user).order_by('name') bgneal@111: if hide_label: bgneal@111: self.fields['forums'].label = '' bgneal@111: self.fields['forums'].required = required bgneal@110: bgneal@115: bgneal@115: class SplitTopicForm(forms.Form): bgneal@115: """ bgneal@115: Form for a moderator to split posts from a topic to a new topic. bgneal@115: """ bgneal@115: name = forms.CharField(label='New topic title', max_length=255, bgneal@115: widget=forms.TextInput(attrs={'size': 64})) bgneal@286: forums = forms.ModelChoiceField(label='Forum for new topic', bgneal@115: queryset=Forum.objects.none()) bgneal@115: post_ids = [] bgneal@115: split_at = False bgneal@115: bgneal@115: def __init__(self, user, *args, **kwargs): bgneal@115: super(SplitTopicForm, self).__init__(*args, **kwargs) bgneal@115: self.fields['forums'].queryset = \ bgneal@115: Forum.objects.forums_for_user(user).order_by('name') bgneal@115: bgneal@115: def clean(self): bgneal@115: self.post_ids = self.data.getlist('post_ids') bgneal@115: if len(self.post_ids) == 0: bgneal@115: raise forms.ValidationError('Please select some posts') bgneal@115: bgneal@115: self.split_at = 'split-at' in self.data bgneal@115: if self.split_at and len(self.post_ids) > 1: bgneal@115: raise forms.ValidationError('Please select only one post to split the topic at') bgneal@115: bgneal@115: return self.cleaned_data