view forums/forms.py @ 693:ad69236e8501

For issue #52, update many 3rd party Javascript libraries. Updated to jquery 1.10.2, jquery ui 1.10.3. This broke a lot of stuff. - Found a newer version of the jquery cycle all plugin (3.0.3). - Updated JPlayer to 2.4.0. - Updated to MarkItUp 1.1.14. This also required me to add multiline attributes set to true on various buttons in the markdown set. - As per a stackoverflow post, added some code to get multiline titles in a jQuery UI dialog. They removed that functionality but allow you to put it back. Tweaked the MarkItUp preview CSS to show blockquotes in italic. Did not update TinyMCE at this time. I'm not using the JQuery version and this version appears to work ok for now. What I should do is make a repo for MarkItUp and do a vendor branch thing so I don't have to futz around diffing directories to figure out if I'll lose changes when I update.
author Brian Neal <bgneal@gmail.com>
date Wed, 04 Sep 2013 19:55:20 -0500
parents 92101013d5ac
children 71d17d267e27
line wrap: on
line source
"""
Forms for the forums application.

"""
from django import forms
from django.conf import settings

from forums.models import Forum
from forums.models import Topic
from forums.models import Post
from forums.attachments import AttachmentProcessor
import forums.permissions as perms
from forums.signals import notify_new_topic, notify_new_post


class NewPostForm(forms.Form):
    """Form for creating a new post."""
    body = forms.CharField(label='',
            required=False,
            widget=forms.Textarea(attrs={'class': 'markItUp smileyTarget'}))
    topic_id = forms.IntegerField(widget=forms.HiddenInput)
    topic = None

    class Media:
        css = {
            'all': (settings.GPP_THIRD_PARTY_CSS['markitup'] +
                settings.GPP_THIRD_PARTY_CSS['jquery-ui']),
        }
        js = (settings.GPP_THIRD_PARTY_JS['markitup'] +
                settings.GPP_THIRD_PARTY_JS['jquery-ui'] +
                ['js/forums.js'])

    def __init__(self, *args, **kwargs):
        super(NewPostForm, self).__init__(*args, **kwargs)
        attachments = args[0].getlist('attachment') if len(args) else []
        self.attach_proc = AttachmentProcessor(attachments)

    def clean_body(self):
        data = self.cleaned_data['body']
        if not data and not self.attach_proc.has_attachments():
            raise forms.ValidationError("This field is required.")
        return data

    def clean_topic_id(self):
        id = self.cleaned_data['topic_id']
        try:
            self.topic = Topic.objects.select_related().get(pk=id)
        except Topic.DoesNotExist:
            raise forms.ValidationError('invalid topic')
        return id

    def save(self, user, ip=None):
        """
        Creates a new post from the form data and supplied arguments.
        """
        post = Post(topic=self.topic, user=user, body=self.cleaned_data['body'],
                user_ip=ip)
        post.save()
        self.attach_proc.save_attachments(post)
        notify_new_post(post)
        return post


class NewTopicForm(forms.Form):
    """
    Form for creating a new topic and 1st post to that topic.
    Superusers and moderators can also create the topic as a sticky or initially
    locked.
    """
    name = forms.CharField(label='Subject', max_length=255,
            widget=forms.TextInput(attrs={'style': 'width:70%'}))
    body = forms.CharField(label='', required=False,
            widget=forms.Textarea(attrs={'class': 'markItUp smileyTarget'}))
    user = None
    forum = None
    has_mod_fields = False

    class Media:
        css = {
            'all': (settings.GPP_THIRD_PARTY_CSS['markitup'] +
                settings.GPP_THIRD_PARTY_CSS['jquery-ui']),
        }
        js = (settings.GPP_THIRD_PARTY_JS['markitup'] +
                settings.GPP_THIRD_PARTY_JS['jquery-ui'] +
                ['js/forums.js'])

    def __init__(self, user, forum, *args, **kwargs):
        super(NewTopicForm, self).__init__(*args, **kwargs)
        self.user = user
        self.forum = forum

        if perms.can_moderate(forum, user):
            self.fields['sticky'] = forms.BooleanField(required=False)
            self.fields['locked'] = forms.BooleanField(required=False)
            self.has_mod_fields = True

        attachments = args[0].getlist('attachment') if len(args) else []
        self.attach_proc = AttachmentProcessor(attachments)

        # If this form is being POSTed, and the user is trying to add 
        # attachments, create hidden fields to list the Oembed ids. In
        # case the form isn't valid, the client-side javascript will know
        # which Oembed media to ask for when the form is displayed with
        # errors.
        if self.attach_proc.has_attachments():
            pks = self.attach_proc.get_ids()
            self.fields['attachment'] = forms.MultipleChoiceField(label='',
                    widget=forms.MultipleHiddenInput(),
                    choices=[(v, v) for v in pks])

    def clean_body(self):
        data = self.cleaned_data['body']
        if not data and not self.attach_proc.has_attachments():
            raise forms.ValidationError("This field is required.")
        return data

    def save(self, ip=None):
        """
        Creates the new Topic and first Post from the form data and supplied
        arguments.
        """
        topic = Topic(forum=self.forum,
                name=self.cleaned_data['name'],
                user=self.user,
                sticky=self.has_mod_fields and self.cleaned_data['sticky'],
                locked=self.has_mod_fields and self.cleaned_data['locked'])
        topic.save()

        post = Post(topic=topic,
                user=self.user,
                body=self.cleaned_data['body'],
                user_ip=ip)
        post.save()

        self.attach_proc.save_attachments(post)

        notify_new_topic(topic)
        notify_new_post(post)

        return topic


class PostForm(forms.ModelForm):
    """
    Form for editing an existing post or a new, non-quick post.
    """
    body = forms.CharField(label='',
            required=False,
            widget=forms.Textarea(attrs={'class': 'markItUp smileyTarget'}))

    class Meta:
        model = Post
        fields = ('body', )

    class Media:
        css = {
            'all': (settings.GPP_THIRD_PARTY_CSS['markitup'] +
                settings.GPP_THIRD_PARTY_CSS['jquery-ui']),
        }
        js = (settings.GPP_THIRD_PARTY_JS['markitup'] +
                settings.GPP_THIRD_PARTY_JS['jquery-ui'] +
                ['js/forums.js'])

    def __init__(self, *args, **kwargs):
        topic_name = kwargs.pop('topic_name', None)
        super(PostForm, self).__init__(*args, **kwargs)

        if topic_name is not None:  # this is a "first post"
            self.fields.insert(0, 'name', forms.CharField(label='Subject',
                    max_length=255,
                    widget=forms.TextInput(attrs={'size': 64})))
            self.initial['name'] = topic_name

        attachments = args[0].getlist('attachment') if len(args) else []
        self.attach_proc = AttachmentProcessor(attachments)

        # If this form is being used to edit an existing post, and that post
        # has attachments, create a hidden post_id field. The client-side
        # AJAX will use this as a cue to retrieve the HTML for the embedded
        # media.
        if 'instance' in kwargs:
            post = kwargs['instance']
            if post.attachments.count():
                self.fields['post_id'] = forms.CharField(label='',
                        widget=forms.HiddenInput(attrs={'value': post.id}))

    def clean_body(self):
        data = self.cleaned_data['body']
        if not data and not self.attach_proc.has_attachments():
            raise forms.ValidationError('This field is required.')
        return data

    def save(self, *args, **kwargs):
        commit = kwargs.get('commit', False)
        post = super(PostForm, self).save(*args, **kwargs)

        # Are we saving a "first post"?
        if 'name' in self.cleaned_data:
            post.topic.name = self.cleaned_data['name']
            if commit:
                post.topic.save()
        return post


class MoveTopicForm(forms.Form):
    """
    Form for a moderator to move a topic to a forum.
    """
    forums = forms.ModelChoiceField(label='Move to forum',
          queryset=Forum.objects.none())

    def __init__(self, user, *args, **kwargs):
        hide_label = kwargs.pop('hide_label', False)
        required = kwargs.pop('required', True)
        super(MoveTopicForm, self).__init__(*args, **kwargs)
        self.fields['forums'].queryset = \
            Forum.objects.forums_for_user(user).order_by('name')
        if hide_label:
            self.fields['forums'].label = ''
        self.fields['forums'].required = required


class SplitTopicForm(forms.Form):
    """
    Form for a moderator to split posts from a topic to a new topic.
    """
    name = forms.CharField(label='New topic title', max_length=255,
            widget=forms.TextInput(attrs={'size': 64}))
    forums = forms.ModelChoiceField(label='Forum for new topic',
          queryset=Forum.objects.none())
    post_ids = []
    split_at = False

    def __init__(self, user, *args, **kwargs):
        super(SplitTopicForm, self).__init__(*args, **kwargs)
        self.fields['forums'].queryset = \
            Forum.objects.forums_for_user(user).order_by('name')

    def clean(self):
        self.post_ids = self.data.getlist('post_ids')
        if len(self.post_ids) == 0:
            raise forms.ValidationError('Please select some posts')

        self.split_at = 'split-at' in self.data
        if self.split_at and len(self.post_ids) > 1:
            raise forms.ValidationError('Please select only one post to split the topic at')

        return self.cleaned_data