Django Crispy Formsでのフォームレンダリング

Python,TIPSDjango

この投稿ではカスタムフォームのレンダリングを行うためのDjango Crispy Formsを使用してみます。
以下のようなフォームを実装します。


下準備

ライブラリのインストール

まずはライブラリをインストールします。

pip install django-crispy-forms

次にインストールしたライブラリをsettingsに登録します。

settings.py

INSTALLED_APPS = [
    ...

    'crispy_forms',
]

CRISPY_TEMPLATE_PACK = 'bootstrap4'

Bootstrap4 のセットアップ

こちらから最新のコンパイル済みのCSSとJavaScriptがダウンロード可能です。

もしくはCDNにホストされたBootstrapも使用可能です。

<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous"></script>

今回はCDNバージョンを使用します、以下がこの投稿で使用するbase.htmlです。

base.html

<!doctype html>
<html lang="ja">
<head>
  <meta charset=utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
</head>
<body>
  <div class="container">
    {% block content %}
    {% endblock %}
  </div>
</body>
</html>

JavaScriptを使用しないためCSSファイルのみを追加しています。


BasicなFormレンダリング

冒頭のフォームをレンダリングするためのコードは次のようになります。

STATES = (
    ('', '選択してください'),
    ('TOKYO', '東京'),
    ('KANAGAW', '神奈川'),
    ('CHIBA', '千葉')
)


class AddressForm(forms.Form):
    email = forms.CharField(label='Eメールアドレス', widget=forms.TextInput(
        attrs={'placeholder': 'Email'}))
    password = forms.CharField(label='パスワード', widget=forms.PasswordInput())
    zip_code = forms.CharField(label='郵便番号')
    state = forms.ChoiceField(label='都道府県', choices=STATES)
    city = forms.CharField(label='市区町村')
    address_1 = forms.CharField(label='番地', widget=forms.TextInput(
        attrs={'placeholder': '111-111'}))
    address_2 = forms.CharField(label='建物名', widget=forms.TextInput(
        attrs={'placeholder': 'ハリネズミタワー'}))
    check_me_out = forms.BooleanField(label='ハリネズミ?', required=False)

今回は通常の forms.Form を使用していますが、同様のフィールドを持たせたModelFormも使用可能です。
stateフィールドとSTATESの選択肢は外部キーまたはその他のいずれかになります。
全部突っ込むのは大変なので東京近郊の3つの都道府県で静的なサンプルを置きました。


テンプレート:

{% extends 'base.html' %}

{% block content %}
  <form method="post">
    {% csrf_token %}
    <table>{{ form.as_table }}</table>
    <button type="submit">Sign in</button>
  </form>
{% endblock %}


レンダリング内容:

バリデーションのレンダリング内容:


Basicな Crispy Form レンダリング

前項と殆ど変わらないテンプレートを使用します。
Crispy Form をロードしただけです。

{% extends 'base.html' %}

{% load crispy_forms_tags %}

{% block content %}
  <form method="post">
    {% csrf_token %}
    {{ form|crispy }}
    <button type="submit" class="btn btn-primary">Sign in</button>
  </form>
{% endblock %}


レンダリング内容:

バリデーションのレンダリング内容:


Crispy Form でカスタムフィールドを使用する例

まずは力技でテンプレートにゴリゴリ書いて直接フォームを作成します。 

テンプレート:

{% extends 'base.html' %} {% load crispy_forms_tags %} {% block content %}
<form method="post" novalidate>
  {% csrf_token %}
  <div class="form-row">
    <div class="form-group col-md-6 mb-0">
      {{ form.email | as_crispy_field }}
    </div>
    <div class="form-group col-md-6 mb-0">
      {{ form.password | as_crispy_field }}
    </div>
  </div>
  <div class="form-row">
    <div class="form-group col-md-2 mb-0">
      {{ form.zip_code | as_crispy_field }}
    </div>
    <div class="form-group col-md-4 mb-0">
      {{ form.state | as_crispy_field }}
    </div>
    <div class="form-group col-md-6 mb-0">
      {{ form.city | as_crispy_field }}
    </div>
  </div>
  {{ form.address_1 | as_crispy_field }}
  {{ form.address_2 | as_crispy_field }}
  {{ form.check_me_out | as_crispy_field }}
  <button type="submit" class="btn btn-primary">Sign in</button>
</form>
{% endblock %}

レンダリング内容:

バリデーションのレンダリング内容:


Crispy Form レイアウトヘルパー

Crispy Form レイアウトヘルパーを使用することでテンプレートではなくforms.py 側で前項と同じフォームを生成することができます。
この場合は __init__ 内で実装します。

class CrispyAddressForm(AddressForm):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.helper = FormHelper()
        self.helper.layout = Layout(
            Row(
                Column('email', css_class='form-group col-md-6 mb-0'),
                Column('password', css_class='form-group col-md-6 mb-0'),
                css_class='form-row'
            ),
            Row(
                Column('zip_code', css_class='form-group col-md-2 mb-0'),
                Column('state', css_class='form-group col-md-4 mb-0'),
                Column('city', css_class='form-group col-md-6 mb-0'),
                css_class='form-row'
            ),
            'address_1',
            'address_2',
            'check_me_out',
            Submit('submit', 'Sign in')
        )

テンプレートは以下のように最低限の記述で完了します。

{% extends 'base.html' %}

{% load crispy_forms_tags %}

{% block content %}
  {% crispy form %}
{% endblock %}

レンダリング内容:

バリデーションのレンダリング内容:


Custom Crispy Field を使用する

フィールドのテンプレートをカスタマイズし、アプリケーション全体で簡単に使い回すことも可能です。
試しにBootstrap4 のチェックボックスを使用するとします。

crispy forms APIを使用し「templates」フォルダーにカスタムフィールドの新しいテンプレートを作成します。

custom_checkbox.html

{% load crispy_forms_field %}

<div class="form-group">
  <div class="custom-control custom-checkbox">
    {% crispy_field field 'class' 'custom-control-input' %}
    <label class="custom-control-label" for="{{ field.id_for_label }}">{{ field.label }}</label>
  </div>
</div>

これによって forms.py またはfields.py などで、新しいクリスピーフィールドを作成できるようになりました。

forms.py

class CustomFieldForm(AddressForm):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.helper = FormHelper()
        self.helper.layout = Layout(
            Row(
                Column('email', css_class='form-group col-md-6 mb-0'),
                Column('password', css_class='form-group col-md-6 mb-0'),
                css_class='form-row'
            ),
            Row(
                Column('zip_code', css_class='form-group col-md-2 mb-0'),
                Column('state', css_class='form-group col-md-4 mb-0'),
                Column('city', css_class='form-group col-md-6 mb-0'),
                css_class='form-row'
            ),
            'address_1',
            'address_2',
            CustomCheckbox('check_me_out'),
            Submit('submit', 'Sign in')
        )

レンダリング内容:


まとめ

Django Crispy Formsにはさらに多くの機能があります。
公式のドキュメントが最も有益な情報源であるため是非チェックしてみてください。

また今回のサンプルは後日GitHubへアップロードしておきます。

Python,TIPSDjango

Posted by Kenny