I am using django-crispy-forms and Bootstrap 3 in my template rendering.
Here is how my form looks like:
As you can notice, the fields are not aligned correctly. I want them to be inline. please provide suggestions on how to fix this:
My crispy form code is below:
class SortFieldsForm(forms.Form):
latest_year=forms.BooleanField(label="latest year")
newest_entry=forms.BooleanField(label="newest post")
price_order=forms.ChoiceField(
widget=forms.RadioSelect,
label="price order",
choices=(('lowest_price','lowest '),('highest_price','highest'),),
initial="lowest_price",
)
def __init__(self, *args, **kwargs):
self.helper = FormHelper()
self.helper.form_class = 'form-inline'
self.helper.field_template='bootstrap3/layout/inline_field.html'
self.helper.form_method = 'post'
self.helper.form_action = '.'
super(SortFieldsForm, self).__init__(*args, **kwargs)
self.helper.layout = Layout(
InlineRadios('price_order'),
'newest_entry',
'latest_year',
Submit('submit', 'Submit', css_class='button white'),
)
And the generated HTML code:
<form action="." id="id-exampleForm" class="form-inline" method="post" >
<div id="div_id_price_order" class="form-group">
<label for="id_price_order" class="control-label requiredField">
price order
<span class="asteriskField">*</span>
</label>
<div class="controls ">
<label class="radio-inline">
<input type="radio" checked="checked" name="price_order" id="id_price_order_1" value="lowest_price" >lowest</label>
<label class="radio-inline">
<input type="radio" name="price_order" id="id_price_order_2" value="highest_price" >highest</label>
</div>
</div>
<div id="div_id_newest_entry" class="checkbox">
<label for="id_newest_entry" class=" requiredField">
<input class="checkboxinput checkbox" id="id_newest_entry" name="newest_entry" type="checkbox" />
newest post
</label>
</div>
<div id="div_id_latest_year" class="checkbox">
<label for="id_latest_year" class=" requiredField">
<input class="checkboxinput checkbox" id="id_latest_year" name="latest_year" type="checkbox" />
latest year
</label>
</div>
<input type="submit" name="submit" value="Submit" class="btn btn-primary button white" id="submit-id-submit"/>
</form>
This has nothing to do with crispy-forms itself, but rather that you have a label on the radio inputs that is pushing the actual inputs down. You need to add a margin-top style to div_id_newest_entry, submit-id-submit, and div_id_latest_year. For example (your use case may vary a bit), in your CSS file:
#div_id_newest_entry,
#div_id_latest_year,
#submit-id-submit {
margin-top: 25px;
}
Related
I'm using a FormSet with crispy, and have but a submit button on each row, however hitting submit does not update records currently.
I've searched and found some similar answers which suggest the submit isn't inside the form, but mine are. Also that a form action is missing, but none of my other crispy forms have actions and they work without issue.
Are there any other reasons seen from the code below that would cause the records to not save?
forms.py
class ChangeGroupForm(FormHelper):
def __init__(self, *args, **kwargs):
super(ChangeGroupForm, self).__init__(*args, **kwargs)
self.form_method = 'post'
self.css_class = 'form-inline'
self.form_id = 'changegroup_form'
self.form_show_labels = False
self.layout = Layout(
Div(
Div(
Div(
Field('group', placeholder='Group', css_class="form-control mb-2 mr-sm-2"),
css_class='col-lg-3'
),
Div(
Field('gps', placeholder='gps coords', css_class="form-control mb-2 mr-sm-2"),
css_class='col-lg-8'
),
Div(
HTML("""<input type="submit" name="submit" value="Save" class="btn btn-primary mt-1"/>"""),
css_class='col-lg-1'
),
css_class='row'
),
)
)
self.render_required_fields = True
views.py
#login_required
def db_change_groups(request):
change_form = modelformset_factory(ChangeGroup, fields=('group','gps'))
change_form_helper = ChangeGroupForm()
return render(request, 'home/db_change_groups.html', {
"change_form": change_form,
"change_form_helper": change_form_helper,
})
template.html
{% crispy change_form change_form_helper %}
rendered html
<form id="changegroup_form" method="post"> <input type="hidden" name="csrfmiddlewaretoken"
value="7v0000CPl3G70M6HLfF2FAiwefdfsdgdfwewdf7Gp4nay1hFqZ1Y34SBUA000mHBZQ54">
<div> <input type="hidden" name="form-TOTAL_FORMS" value="10" id="id_form-TOTAL_FORMS"> <input type="hidden"
name="form-INITIAL_FORMS" value="9" id="id_form-INITIAL_FORMS"> <input type="hidden"
name="form-MIN_NUM_FORMS" value="0" id="id_form-MIN_NUM_FORMS"> <input type="hidden"
name="form-MAX_NUM_FORMS" value="1000" id="id_form-MAX_NUM_FORMS"> </div>
<div>
<div class="row">
<div class="col-lg-3">
<div id="div_id_form-0-group" class="form-group">
<div class="controls "> <input type="text" name="form-0-group" value="A" maxlength="50"
class="form-control mb-2 mr-sm-2 textinput textInput form-control" placeholder="Group"
id="id_form-0-group"> </div>
</div>
</div>
<div class="col-lg-8">
<div id="div_id_form-0-gps" class="form-group">
<div class="controls "> <input type="text" name="form-0-gps" maxlength="255"
class="form-control mb-2 mr-sm-2 textinput textInput form-control" placeholder="gps coords"
id="id_form-0-gps"> </div>
</div>
</div>
<div class="col-lg-1"> <input type="submit" name="submit" value="Save" class="btn btn-primary mt-1" />
</div>
</div>
</div> <input type="hidden" name="form-0-id" value="1" id="id_form-0-id">
<div>
<div class="row">
<div class="col-lg-3">
<div id="div_id_form-1-group" class="form-group">
<div class="controls "> <input type="text" name="form-1-group" value="B" maxlength="50"
class="form-control mb-2 mr-sm-2 textinput textInput form-control" placeholder="Group"
id="id_form-1-group"> </div>
</div>
</div>
<div class="col-lg-8">
<div id="div_id_form-1-gps" class="form-group">
<div class="controls "> <input type="text" name="form-1-gps" maxlength="255"
class="form-control mb-2 mr-sm-2 textinput textInput form-control" placeholder="gps coords"
id="id_form-1-gps"> </div>
</div>
</div>
<div class="col-lg-1"> <input type="submit" name="submit" value="Save" class="btn btn-primary mt-1" />
</div>
</div>
</div>
</div> <input type="hidden" name="form-9-id" id="id_form-9-id">
</form>
You need to attach somthing like this to your view
def db_change_groups(request):
....
if request.method == "POST":
form = ChangeGroupForm(request.POST)
if form.is_valid():
# Access cleaned data with
group = form.cleaned_data['group']
# Then you can save this to a model.
# return success template or something
else:
# Check for errors.
If you created the form with ModelForm instead of FormHelper you could use form.save() which you automatically save it to the model.
First, I want to generate a page that looks like this:
There are three things that makes it difficult.
1. Label + Input field set should line up horizontally
2. Some of the fields have checkbox
3. Date Field has a special Bootstrap plugin.
Here are the html source code for each types:
generic type:
<div class="col-lg-2 col-md-2 col-sm-4 col-xs-6" style="margin-bottom: 5px">
<label class="input-upper-title">Drill String Name</label>
<input type="text" id="" class="form-control input-field-height-vertical" name="" required="">
</div>
has checkbox:
<div class="col-lg-2 col-md-2 col-sm-4 col-xs-6" style="margin-bottom: 5px">
<label class="input-upper-title">String Length (ft)</label>
<div class="has-checkbox">
<input type="checkbox"><input disabled="disabled" type="text" id="" class="form-control input-field-height-vertical input-calculated" name="" data-parsley-trigger="" required="">
</div>
</div>
has date plugin:
<div class="col-lg-2 col-md-2 col-sm-4 col-xs-6" style="margin-bottom: 5px">
<label class="input-upper-title">Date Run</label>
<div class="form-group">
<div class="input-group date" id="datetimepicker7" data-target-input="nearest">
<input type="text" class="form-control datetimepicker-input input-field-height-vertical" data-target="#datetimepicker7"/>
<div class="input-group-append" data-target="#datetimepicker7" data-toggle="datetimepicker">
<div class="input-group-text"><i class="fa fa-calendar"></i></div>
</div>
</div>
</div>
</div>
And here is my forms.py:
class BHA_overall_Form(forms.ModelForm):
prefix = 'bha_overall'
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.helper = FormHelper()
class Meta():
model = BHA_overall
fields = '__all__'
How should I modify my forms.py, using crispy_forms to get the same effect as my html source code does?
When an error is rendered for a non-radio button, I see this:
<div id="div_id_field_a" class="control-group error">
<label for="id_field_a" class="control-label ">Field A</label>
<div class="controls">
<select class="select" id="id_field_a" name="field_a">
<option value="" selected="selected"></option>
<option value="0">0</option>
<option value="1">1</option>
</select>
<span id="error_1_id_field_a" class="help-inline"><strong>Select one</strong></span>
</div>
</div>
When an error is rendered for a radio button, I see this:
<div id="div_id_field_b" class="control-group error">
<label for="id_field_b_0" class="control-label ">Field b</label>
<div class="controls">
<p id="error_1_id_field_b" class="help-block"><strong>Enter a value</strong></p>
<label class="radio"><input type="radio" name="field_b" id="id_field_b_1" value="True">Yes</label>
<label class="radio"> <input type="radio" name="field_b" id="id_field_b_2" value="False">No</label>
</div>
</div>
How can I get the error message to appear after the radio button controls, which is what happens when a combo box used? Or at least not render the error inside a p tag and instead a span tag?
Here's how I render the form:
{% load crispy_forms_tags %}
<form id='main-form'>
{% csrf_token %}
{% crispy form form.helper %}
</form>
and the form:
class MyForm(ReoBaseForm):
field_b = forms.ChoiceField(required=False, choices=((True, 'Yes'), (False, 'No')),
label='Field b', widget=forms.RadioSelect())
def __init__(self, *args, **kwargs):
super(MyForm, self).__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_tag = False
It seems the FormHelper simply doesn't anything. Here's my Form:
class PerguntarForm(forms.Form):
title = forms.CharField(label='Título', max_length=200)
categoria = forms.ModelChoiceField(queryset=Category.objects.all(), empty_label=None)
orcamento = forms.FloatField(label='Preço máximo')
def __init__(self, *args, **kwargs):
super(PerguntarForm, self).__init__(*args, **kwargs)
self.helper = FormHelper(self)
self.helper.layout.append(Submit('save', 'save'))
self.helper.layout = Layout(
PrependedText('orcamento', ',00', active=True),
)
However, PrependedText isn't applied to the 'orcamento' field when rendered. The layout append doesn't either, which I placed just to see if something happened.
Here's the output:
<div id="div_id_title" class="form-group"><label for="id_title" class="control-label requiredField">
Título<span class="asteriskField">*</span></label><div class="controls "><input class="textinput textInput form-control" id="id_title" maxlength="200" name="title" type="text" /> </div></div><div id="div_id_categoria" class="form-group"><label for="id_categoria" class="control-label requiredField">
Categoria<span class="asteriskField">*</span></label><div class="controls "><select class="select form-control" id="id_categoria" name="categoria"><option value="4">Celular</option><option value="5">TV</option><option value="6">Computador</option></select></div></div><div id="div_id_orcamento" class="form-group"><label for="id_orcamento" class="control-label requiredField">
Preço máximo<span class="asteriskField">*</span></label><div class="controls "><input class="numberinput form-control" id="id_orcamento" name="orcamento" step="any" type="number" /> </div></div>
<input type="submit" value="Enviar" />
</form>
</div>
Make sure you are using the crispy form tag, not the crispy form filter.
{% load crispy_forms_tags %}
{% crispy form %}
Where form is the name of the form in the template.
I'm experimenting with django-crispy-forms and Twitter Bootstrap, which is what django-crispy-forms uses as its default template pack. I'm using Django 1.4.5, django-crispy-forms 1.2.3, and Twitter Bootstrap 2.3.2.
Relevant code
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>
Actual results
The form fields are all stacked even though I set the form's class to form-inline. This also makes it so that you can't tell that there are two distinct forms; it just looks like one long form with duplicate fields.
Here is the HTML being produced:
<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>
Expected results
Since Twitter Bootstrap is what django-crispy-forms uses as its default template pack, I would expect it to handle this nicely by default. The fields should side by side, not stacked, and it should be obvious that there are two distinct forms.
I would expect the HTML to look something like this:
<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>
Am I doing something wrong? What should I do to get the expected results?
I was able to replicate this issue as well. While I am not 100% certain as to the behavior, I was able to find the following in the 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>
While I am curious of a solution using the divs as the django-crispy-forms documentation states it should work, I can confirm this solution works on my machine. Part of the code above is credited to Kevin Dias.