Django Crispy Forms、Twitter Bootstrap 和 Formsets with form-inline

Posted

技术标签:

【中文标题】Django Crispy Forms、Twitter Bootstrap 和 Formsets with form-inline【英文标题】:Django Crispy Forms, Twitter Bootstrap, and Formsets with form-inline 【发布时间】:2013-08-01 06:49:49 【问题描述】:

我正在试验django-crispy-forms 和 Twitter Bootstrap,这是 django-crispy-forms 用作其默认模板包的。我正在使用 Django 1.4.5、django-crispy-forms 1.2.3 和 Twitter Bootstrap 2.3.2。

相关代码

urls.py

from django.conf.urls import patterns, url
from core import views

urlpatterns = patterns('',
    url(r'^$', views.Survey.as_view(), name='survey'),
)

views.py

from django.views.generic.edit import FormView
from .forms import SurveyFormset


class Survey(FormView):
    form_class = SurveyFormset
    template_name = 'survey.html'

forms.py

from django import forms
from django.forms.formsets import formset_factory
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Submit


class SurveyForm(forms.Form):
    def __init__(self, *args, **kwargs):
        self.helper = FormHelper()
        self.helper.form_action = '.'
        self.helper.form_class = 'form-inline'

        self.helper.add_input(Submit('submit', 'Submit'))
        super(SurveyForm, self).__init__(*args, **kwargs)

    name = forms.CharField(
        widget=forms.TextInput(attrs='placeholder': 'Name'),
        label='',
        max_length=50
    )

    favorite_food = forms.CharField(
        widget=forms.TextInput(attrs='placeholder': 'Favorite food'),
        label='',
        max_length=50
    )

    favorite_game = forms.CharField(
        widget=forms.TextInput(attrs='placeholder': 'Favorite game'),
        label='',
        max_length=50
    )

SurveyFormset = formset_factory(SurveyForm, extra=2)

survey.html

% load crispy_forms_tags %

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <title>Survey</title>
        <link href="http://netdna.bootstrapcdn.com/twitter-bootstrap/2.3.2/css/bootstrap-combined.min.css" rel="stylesheet">
    </head>
    <body>
        % crispy form form.form.helper %
    </body>
</html>

实际结果

尽管我将表单的类设置为form-inline,但表单字段都是堆叠的。这也使您无法分辨出有两种不同的形式;它看起来就像一个带有重复字段的长表单。

这是正在生成的 HTML:

<form action="." class="form-inline" method="post">
    <div style='display:none'><input type='hidden' name='csrfmiddlewaretoken' value='foo' /></div>

    <div>
        <input type="hidden" name="form-TOTAL_FORMS" value="2" id="id_form-TOTAL_FORMS" />
        <input type="hidden" name="form-INITIAL_FORMS" value="0" id="id_form-INITIAL_FORMS" />
        <input type="hidden" name="form-MAX_NUM_FORMS" value="1000" id="id_form-MAX_NUM_FORMS" />
    </div>

    <div id="div_id_form-0-name" class="control-group">
        <div class="controls">
            <input name="form-0-name" maxlength="50" placeholder="Name" type="text" class="textinput textInput" id="id_form-0-name" />
        </div>
    </div>

    <div id="div_id_form-0-favorite_food" class="control-group">
        <div class="controls">
            <input name="form-0-favorite_food" maxlength="50" placeholder="Favorite food" type="text" class="textinput textInput" id="id_form-0-favorite_food" />
        </div>
    </div>

    <div id="div_id_form-0-favorite_game" class="control-group">
        <div class="controls">
            <input name="form-0-favorite_game" maxlength="50" placeholder="Favorite game" type="text" class="textinput textInput" id="id_form-0-favorite_game" />
        </div>
    </div>

    <div id="div_id_form-1-name" class="control-group">
        <div class="controls">
            <input name="form-1-name" maxlength="50" placeholder="Name" type="text" class="textinput textInput" id="id_form-1-name" />
        </div>
    </div>

    <div id="div_id_form-1-favorite_food" class="control-group">
        <div class="controls">
            <input name="form-1-favorite_food" maxlength="50" placeholder="Favorite food" type="text" class="textinput textInput" id="id_form-1-favorite_food" />
        </div>
    </div>

    <div id="div_id_form-1-favorite_game" class="control-group">
        <div class="controls">
            <input name="form-1-favorite_game" maxlength="50" placeholder="Favorite game" type="text" class="textinput textInput" id="id_form-1-favorite_game" />
        </div>
    </div>

    <div class="form-actions">
        <input type="submit" name="submit" value="Submit" class="btn btn-primary" id="submit-id-submit" />
    </div>
</form>

预期结果

由于 Twitter Bootstrap 是 django-crispy-forms 使用的默认模板包,我希望它默认能够很好地处理这个问题。这些字段应该并排,而不是堆叠,并且应该很明显有两种不同的形式。

我希望 HTML 看起来像这样:

<form action="." class="form-inline" method="post">
    <div style='display:none'><input type='hidden' name='csrfmiddlewaretoken' value='foo' /></div>

    <div>
        <input type="hidden" name="form-TOTAL_FORMS" value="2" id="id_form-TOTAL_FORMS" />
        <input type="hidden" name="form-INITIAL_FORMS" value="0" id="id_form-INITIAL_FORMS" />
        <input type="hidden" name="form-MAX_NUM_FORMS" value="1000" id="id_form-MAX_NUM_FORMS" />
    </div>

    <div id="id_form-0" class="control-group">
        <input type="text" id="id_form-0-name" class="textinput textInput" placeholder="Name" maxlength="50" name="form-0-name">
        <input type="text" id="id_form-0-favorite_food" class="textinput textInput" placeholder="Favorite food" maxlength="50" name="form-0-favorite_food">
        <input type="text" id="id_form-0-favorite_game" class="textinput textInput" placeholder="Favorite game" maxlength="50" name="form-0-favorite_game">
    </div>

    <div id="id_form-1" class="control-group">
        <input type="text" id="id_form-1-name" class="textinput textInput" placeholder="Name" maxlength="50" name="form-1-name">
        <input type="text" id="id_form-1-favorite_food" class="textinput textInput" placeholder="Favorite food" maxlength="50" name="form-1-favorite_food">
        <input type="text" id="id_form-1-favorite_game" class="textinput textInput" placeholder="Favorite game" maxlength="50" name="form-1-favorite_game">
    </div>

    <div class="form-actions">
        <input type="submit" name="submit" value="Submit" class="btn btn-primary" id="submit-id-submit" />
    </div>
</form>

我做错了吗?我应该怎么做才能获得预期的结果?

【问题讨论】:

【参考方案1】:

我也能够复制这个问题。虽然我不能 100% 确定这种行为,但我能够在 django-crispy-forms documentation 中找到以下内容:

    #forms.py
    def __init__(self, *args, **kwargs):
        self.helper = FormHelper()
        self.helper.form_action = '.'
        self.helper.form_class = 'form-inline'
        self.helper.template = 'bootstrap/table_inline_formset.html'

        self.helper.add_input(Submit('submit', 'Submit'))
        super(SurveyForm, self).__init__(*args, **kwargs)

    # views.py
    from django.views.generic.edit import FormView
    from .forms import SurveyFormset, SurveyForm


    class Survey(FormView):
        form_class = SurveyForm
        template_name = 'survey.html'

        def get(self, request, *args, **kwargs):
            self.object = None
            form_class = self.get_form_class()
            form = self.get_form(form_class)
            formset = SurveyFormset()
            return self.render_to_response(
                self.get_context_data(
                    form=form,
                    formset=formset,
                )
            )

        def post(self, request, *args, **kwargs):
            """
            Handles POST requests, instantiating a form instance and its inline
            formsets with the passed POST variables and then checking them for
            validity.
            """
            self.object = None
            form_class = self.get_form_class()
            form = self.get_form(form_class)
            formset = SurveyFormset(self.request.POST)
            if (form.is_valid() and formset.is_valid()):
                return self.form_valid(form, formset)
            else:
                return self.form_invalid(form, formset)

        def form_valid(self, form, formset):
            """
            Called if all forms are valid. Creates a Recipe instance along with
            associated Ingredients and Instructions and then redirects to a
            success page.
            """
            self.object = form.save()
            formset.instance = self.object
            formset.save()
            return HttpResponseRedirect(self.get_success_url())

        def form_invalid(self, form, formset):
            """
            Called if a form is invalid. Re-renders the context data with the
            data-filled forms and errors.
            """
            return self.render_to_response(
                self.get_context_data(
                    form=form,
                    formset=formset,
                )
            )

    # survey.html
    % load crispy_forms_tags %

    <!DOCTYPE html>
    <html lang="en">
        <head>
            <meta charset="utf-8">
            <title>Survey</title>
            <link href="http://netdna.bootstrapcdn.com/twitter-bootstrap/2.3.2/css/bootstrap-combined.min.css" rel="stylesheet">
        </head>
        <body>
            <div class="container">
                <div class="row">
                    <div class="span12">
                        % crispy formset formset.form.helper 'bootstrap' %
                    </div>
                </div>
            </div>
        </body>
    </html>

虽然我对使用 div 的解决方案感到好奇,因为 django-crispy-forms 文档指出它应该可以工作,但我可以确认该解决方案在我的机器上有效。上面的部分代码归功于Kevin Dias。

【讨论】:

以上是关于Django Crispy Forms、Twitter Bootstrap 和 Formsets with form-inline的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Ajax 渲染 django-crispy-forms 的验证错误?

Django-crispy-forms:为 AppendedText 父 div 设置 CSS 类

python测试开发django-41.crispy-forms设计标签式导航菜单(TabHolder)

使用内联表单在 django-crispy-forms 中呈现字段错误

使用Ajax验证并提交Django表单(django-crispy-forms)

在 python 中通过 django-crispy-forms 渲染表单