1
votes

I have researched this and many answers out there touch on it (formsets, prefixes, etc), but none is exactly what I'm after...

I have an application that allows users to post cards to a board and each card can have zero or more comments. I have the database relationships set up so if I manually add board, card, or comment objects I can display them in the template just fine.

The card form is also working, so this is not an issue. But now I want to display a comment textarea with a click event (which I have working with jQuery). The problem is, the form is not displaying the field. Here is the form code:

class AddCommentForm(ModelForm):
    class Meta:
        model = Comment
        fields = ['message']

    message = forms.CharField(
        widget=forms.Textarea(
            attrs={
                'class': 'form-control comment-input'
                'name': 'message',
                'placeholder': 'Comment away...'
            }
        ),
        required=False,
    )

Right off the bat, there is a problem in that when Django renders a form, it uses an id. In this case, it ends up being id_message or message (depending on whether I specify in form class). Of course, in HTML, this is no good. I confirmed this in the shell:

>>> import django; import project
>>> from apps.board.forms import AddCommentForm
>>> comment_form = AddCommentForm(); print(comment_form)
<tr><th><label for="message">Message:</label></th><td><textarea class="form-control comment-input" cols="40" id="message" name="message" placeholder="Comment away..." rows="10">
</textarea></td></tr>

So I thought of using formsets but it seems not quite right, because as I understand them, they are for several forms inside one form tag. My case is different, in that each card has zero or more comments and the flow of the HTML dictates that there should be several form elements.

Another challenge is that I need to have two views: one to process the cards and one to process each comment. I'm currently using different action values to accomplish this and it seems to work.

It seems like my use case is not so strange, and I can think of several cases where this kind of situation would occur: Twitter replies, comments on blog posts, etc. I just can't get Django to work with this.

I should also note that I can manually make this work with this:

<label>
    <textarea class="form-control comment-input" name="message"></textarea>
    <button type="submit"></button>
</label>

UPDATE

Here is a view segment:

class AddCommentView(View):

    model = Comment
    form_class = AddCommentForm
    template_name = 'somedir/template.html'

    @method_decorator(login_required)
    def get(self, request):

        comment_form = self.form_class()

        return render(request, self.template_name, {
            'comment_form': comment_form
        })

If the form is working (from snippet above and shell test), I would expect this to show it on the page.

The URLConf is here:

url(r'^comment/add/$', AddCommentView.as_view(), name='add_comment_view'),

In writing this out, I see a confusing piece. My URL for the view is /comment/add/ but I want the form to be rendered at /dashboard/. The problem is that there is another view at that URL already.

If I build the form without Django forms, then post data to /comment/add/ the comment is created and saved in the database. So I guess I'm still trying to figure out how to use two views in one template with multiple forms. @Daniel Roseman points out the use of prefix and I guess this is worth exploring more.

UPDATE 2

So I got this to work using prefixes by removing the URL for that view and combining the views into one view. Two forms, one view, one template. But...

The source still shows replicate ids. I ended up not putting the id in the form attribute list. Since this is invalid HTML, what is best way to get around this?

In my view:

card_model = Card
comment_model = Comment

card_form_class = AddCardForm
comment_form_class = AddCommentForm
. . .
@method_decorator(login_required)
def get(self, request):

    card_form = self.card_form_class(prefix='card')
    comment_form = self.comment_form_class(prefix='comment')
    . . .

UPDATE 3

I have found a solution to this problem. To instantiate the form, you need to add auto_id=False to prevent multiple, replicate ids:

comment_form = self.comment_form_class(prefix='comment', auto_id=False)

2
I'm confused, you said the form is not being rendered, then you said it's rendering it with id_message. Which is it? Also, what's the problem you have with it having an id of id_message? - onyeka
Well, couple things... I didn't say it was rendered with that id. I said ends up being... I know what Django does because I use its forms elsewhere. Second, I confirmed this with testing in the shell. I will add that to the question for clarity. The problem with ids is that in HTML you can only have one id, so the form would render with the same id in multiple forms. - nicorellius
let's see your view. - onyeka

2 Answers

2
votes

You can use the prefix parameter when instantiating your forms to give them non-conflicting ids.

0
votes

While using the Django form prefix was partially correct (thanks @ Daniel Roseman), the assertion that it doesn't matter how many form elements I have is not quite correct. It does matter, because by default the prefix was meant to be used in one form (as it creates multiple replicate ids). To use it in multiple forms, you also need auto_id=False. This prevents the id from being generated by the Django form.