Vous êtes sur la page 1sur 70

django-crispy-forms

Miguel Araujo
@maraujop

Django forms

Django forms
form.as_ul
form.as_p
form.as_table

Django forms
class ExampleForm(forms.Form):
username = forms.CharField()
email = forms.CharField()

Django forms
{{ example_form.as_ul }}
<li>
<label for="id_username">Username:</label>
<input type="text" name="username" id="id_username">
</li>
<li>
<label for="id_email">Email:</label>
<input type="text" name="email" id="id_email">
</li>

How about divs?

Reorder fields?

Django forms
class ExampleForm(forms.Form):
email = forms.CharField()
username = forms.CharField()

Moving chunks of code

Django forms
class ExampleForm(forms.Form):

class ExampleForm(forms.Form):

email = forms.CharField()

username = forms.CharField()

username = forms.CharField()

email = forms.CharField()

Moving chunks of code

Django forms
class ExampleForm(forms.Form):
email = forms.CharField()
username = forms.CharField()
class ExtraFieldForm(ExampleForm):
comment = forms.CharField()

Make comment the first field?

Django forms
class ExtraFieldForm(ExampleForm):
comment = forms.CharField()
def __init__(self, *args, **kwargs):
super(ExtraFieldForm, self).__init__(*args, **kwargs)
self.fields.keyOrder = ['comment', 'email', 'username']

self.fields is a SortedDict
self.fields.keyOrder is a list ['username', 'email', 'comment']

Django forms
What if I have 100 fields?

class ExtraFieldForm(ExampleForm):
comment = forms.CharField()
def __init__(self, *args, **kwargs):
super(ExtraFieldForm, self).__init__(*args, **kwargs)
self.fields.keyOrder.remove('comment')
self.fields.keyOrder.insert(0, 'comment')

Django ModelForms
class ExampleForm(forms.ModelForm):
class Meta:
model = ExampleModel
fields = ('username', 'email')

ModelForms are different, why?

Customize output?

Asteriks for required fields


{% for field in form %}
{{ field }} {% if field.field.required %}(*){% endif %}
{% endfor %}

Asteriks for required fields


{% for field in form %}
{{ field }} {% if field.field.required %}(*){% endif %}
{% endfor %}

What about field.errors ?


What about form.non_field_errors ?
etc.

Something more
complex?

django-crispy-forms

Formerly known as django-uni-form, created by Daniel Greenfeld


@pydanny in 2008

I joined the project in the middle of 2010 and became lead developer
38 contributors
Tested and thoroughly used
Two template packs: bootstrap & uni_form CRISPY_TEMPLATE_PACK

django-crispy-forms

A filter |crispy
A tag {% crispy %}
They work on forms, modelforms and formsets

|crispy filter

Easy div format


No need to change form code at all
{% load crispy_forms_tags %}
{{ example_form|crispy }}

Django forms
{{ example_form|crispy }}
<div id="div_id_username" class="clearfix control-group">
<label for="id_username" class="control-label requiredField">Username<span class="asteriskField">*</span></label>
<div class="controls">
<input id="id_username" type="text" class="textinput textInput" name="username">
</div>
</div>
<div id="div_id_email" class="clearfix control-group">
<label for="id_email" class="control-label requiredField">Email<span class="asteriskField">*</span></label>
[...]

{% crispy %} tag

I don't like writing HTML for forms


I need customization power
They need to be as DRY as possible

{% crispy %} tag
{% crispy form [helper] %}
{% crispy example_form %}
<form class="" method="post">
<div style="display:none"><input type="hidden" name="csrfmiddlewaretoken" ...></div>
<div id="div_id_username" class="clearfix control-group">
<label for="id_username" class="control-label requiredField">Username<span class="asteriskField">*</span>
</label>
<div class="controls">
<input id="id_username" type="text" class="textinput textInput" name="username">
</div>
</div>
[...]
</form>

{% crispy %} tag
{% crispy form [helper] %}
{% crispy example_form %}

How do we customize this output?

<form class="" method="post">


<div style="display:none"><input type="hidden" name="csrfmiddlewaretoken" ...></div>
<div id="div_id_username" class="clearfix control-group">
<label for="id_username" class="control-label requiredField">Username<span class="asteriskField">*</span>
</label>
<div class="controls">
<input id="id_username" type="text" class="textinput textInput" name="username">
</div>
</div>
[...]
</form>

FormHelper

They control global form rendering behaviour


They are form decoupled

FormHelper attributes

form_method: helper.form_method = 'post'


form_action: helper.form_action = 'addItem'
form_id
form_class
form_tag: helper.form_tag = False

FormHelper
helpers.py

class ExampleFormHelper(FormHelper):
form_method = 'post'
form_id = 'example-form-id'
Template

{% crispy example_form example_form_helper %}

Attaching FormHelper
helpers.py
class ExampleFormHelper(FormHelper):
layout = Layout('comment', 'username', 'email')

forms.py
class ExampleForm(forms.Form):

helper = ExampleFormHelper()

Template

{% crispy example_form %}

Coupled FormHelper
forms.py

class ExampleForm(forms.Form):
helper = FormHelper()
helper.form_method = 'post'
helper.form_id = 'example-form-id'
Template

{% crispy example_form %}

Custom FormHelper attributes


forms.py

class ExampleForm(forms.Form):
helper = FormHelper()
helper.help_text_as_placeholder = True
Crispy-forms Templates

{{ help_text_as_placeholder }}

Programmatic
layouts

Special attribute layout and Layout class


forms.py

class ExtraFieldForm(forms.Form):
helper = FormHelper()
helper.layout = Layout('comment', 'username', 'email')
Template

{% crispy example_form %}

Layouts
Basic Layout for ExtraFieldForm

Layout(
'comment',
'email',
'username',
)

Layouts

Custom output is defined by a Python layout


Flexible and highly reusable

Layouts
A bit more complex Layout

Layout(

Div(
'comment',
'username',
css_id="div-wrapping-comment-and-username"
),
)

Layouts
A bit more complex Layout

Layout(

Div(

Beware that layouts are rendered strict

'comment',
'username',
css_id="div-wrapping-comment-and-username"
),
)

Layouts
Layout power comes from layout objects:

They are Python classes


Every layout object has an associated template

Layout objects
Div(
'comment',
'username',
css_id="div-wrapping-fields",
css_class="crispy-divs",
data-markup="crispy rocks"
)

Layout objects
Fieldset(
"This is the legend of the fieldset",
'comment',
'username',
css_class="fieldsets",
)

Layout objects
HTML(
"""<p>HTML code embedded. You can access the context from
where form is rendered. Hi {{ user.username }}</p>"""
)
HTML(
"{% include 'template.html' %}"
)

Layout objects
Submit('name', 'value')
FormActions(
Submit('save_changes', 'Save changes', css_class="btn-primary"),
Submit('cancel', 'Cancel'),
)

Layout objects
Setting attributes to fields

In Django
class ExampleForm(forms.Form):
username = forms.CharField(
widget = forms.TextInput(attrs={'class': 'whatever', 'autocomplete': 'off'})
)

Layout objects
Setting attributes to fields

In Django
class ExampleForm(forms.Form):
username = forms.CharField(
widget = forms.TextInput(attrs={'class': 'whatever', 'autocomplete': 'off'})
)

In Crispy-forms
Field('username', css_class='whatever', autocomplete='off')

Nested Layout objects


Div(
Div(
'name',
'value'
),
HTML("Hero?")
)

Crispy-forms internals

Layout has a fields attribute


All layout objects have a fields attribute

Layout decoupled
class GlobalLayout(Layout):
def __init__(self, *args, **kwargs):
self.fields = ['username', 'email']

GlobalLayout('comment')

self.fields.append(args)
class LayoutChunk(Layout):
def __init__(self):
self.fields = [
HTML("Hero?"),
Div('name')
]

LayoutChunk()

Layout composition
class ComplexForm(forms.ModelForm):
helper = FormHelper()
helper.layout = Layout(
GlobalLayout(),
'extra_field'
)

Dynamic Layouts
views.py

def view(request):
form = ExampleForm()
form.helper.layout.fields.append(HTML("<p>Added extra HTML</p>"))
[...]
return render(request, 'template.html', {'form': form})

Dynamic Layouts
! If you manipulate a helper, use an instance variable
class ExampleForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.helper = FormHelper()
self.helper.layout = Layout(
'comment',
'username'
)

Custom Layout object


TR('username')
<tr><td>{{ field }}</td></tr>

Custom Layout object


class TR(object):

template = 'myTemplate.html' # <tr><td>{{ field|safe }}</td></tr>


def __init__(self, field):
self.field = field
def render(self, form, form_style, context):
field = render_field(field, form, form_style, context)
return render_to_string(self.template, Context({'field': field}))

Examples

Crispy-forms
templates

Templates

Can be easily overriden at various levels


There are layout templates and global templates

Overriding Layout objects templates


Globally

from crispy_forms.layout import Div


Div.template = 'mydiv.html'

Individually

Div('field_name', template='mydiv.html')

field.html template
crispy_forms/templates/{bootstrap|uni_form}/field.html
<div id="div_{{ field.auto_id }}" class="...>
<label ...>
{% if field|css_class == 'radioselect' %}
{% include 'bootstrap/layout/radioselect.html' %}
{% endif %}
[...]
{% if field|css_class != "radioselect" and ... %}
{% crispy_field field %}
{% include 'bootstrap/layout/help_text_and_errors.html' %}
{% endif %}
</div>

field.html template
crispy_forms/templates/{bootstrap|uni_form}/field.html
<div id="div_{{ field.auto_id }}" class="...>
<label ...>
{% if field|css_class == 'radioselect' %}
{% include 'bootstrap/layout/radioselect.html' %}
{% endif %}
[...]
{% if field|css_class != "radioselect" and ... %}
{% crispy_field field %}

{% crispy_field field %} != {{ field }}

{% include 'bootstrap/layout/help_text_and_errors.html' %}
{% endif %}
</div>

Examples

All selects using chosen


class AnimalForm(forms.form):
animal = forms.ChoiceField(choices=[
('cat', 'cat'),
('dog', 'dog'),
])
food = forms.ChoiceField(choices=[
('meat', 'meat'),
('fish', 'fish'),
])
helper = FormHelper()
helper.form_class = 'form-horizontal'

$(document).ready(function () {
$(".chzn-select").chosen();
});

All selects using chosen


field.html
{% if field|css_class == "select" %}
<div class="controls">
{% crispy_field field 'class' 'chzn-select' %}
</div>
{% endif %}
{% if field|css_class != "checkboxselectmultiple" and
field|css_class != "radioselect" and
field|css_class != "select" %}
[...]

Field labels as holders

.holder {
color: #999;
font-size: 19px;
position: relative;
left: -210px;
top: 4px;
}

Field labels as holders


field.html (Using a custom attribute)
{% if not label_as_holder and field.label and not field|is_checkbox %}
<label for="{{ field.id_for_label }}" class="control-label" ...> {{ field.label|safe }} </label>
{% endif %}
{% crispy_field field %}
{% if label_as_holder %}
<span class="holder {% if field.value %}hidden{% endif %}">{{ field.label }}</span>
{% endif %}

Scratching the surface

There is more power, filters, layout objects, attributes, etc.


Javascript validation
Visit the docs django-crispy-forms.rtfd.org
There is more coming

Thanks,
questions?
@maraujop

Vous aimerez peut-être aussi