Djangoのフォームで
を簡単に作る方法を調べたところ、 django-localflavor
がありました。
Web上を調べたところ、Django 1.6以降削除されたの記事を見かけたため、使えないのかなと思いました。
ただ、公式ドキュメントのChangeLogを読むと、Django2.0に対応しているとのことでした。
Changelog — django-localflavor 2.0 documentation
そこで、Django2.0で試してみました。
目次
環境
環境構築
いつも通りなので省略します。
# Djangoアプリを作るまで
$ python -m venv env364
$ source env364/bin/activate
$ pip install django
$ django-admin startproject myproject
$ cd myproject/
$ python manage.py startapp myapp
django-localflavorのインストール
PyPIにあるため、pipでインストールします。
django-localflavor 2.0 : Python Package Index
$ pip install django-localflavor
...
Successfully installed django-localflavor-2.0
django-localflavor向けの設定(settings.py)
settings.pyのうち、
- INSTALLED_APPS
- LANGUAGE_CODE
- USE_I18N
を修正します。
(USE_I18Nは、デフォルトで True
かもしれません)
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'myapp',
'localflavor',
]
LANGUAGE_CODE = 'ja'
USE_I18N = True
アプリの中身を作成
テンプレート(templates/form.html)
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>MyForm</title>
</head>
<body>
<form action="" method="post">
{% csrf_token %}
{{ form.as_p }}
</form>
</body>
</html>
プロジェクトのurls.py
from django.urls import path
from django.views.generic import FormView
from myapp.forms import MyForm
urlpatterns = [
path('', FormView.as_view(
template_name = 'form.html',
form_class = MyForm
))
]
あとは、myapp.forms.MyFormに、django-localflavorを使った実装を書いていきます。
django-localflavorを試す
公式ドキュメントを見ると、以下の機能が含まれていました。
http://django-localflavor.readthedocs.io/en/latest/localflavor/jp/
- localflavor.jp.forms.JPPostalCodeField
- localflavor.jp.forms.JPPrefectureCodeSelect
- localflavor.jp.forms.JPPrefectureSelect
それぞれ試してみます。
JPPostalCodeField
Formに実装してみます。
from django import forms
from localflavor.jp.forms import JPPostalCodeField
class MyForm(forms.Form):
my_postal_code = JPPostalCodeField()
すると、テンプレートには
<p>
<label for="id_my_postal_code">My postal code:</label>
<input type="text" name="my_postal_code" required id="id_my_postal_code" />
</p>
と出力されます。
また、バリデーション機能もあるため、 a
のような誤った値を入力すると
<ul class="errorlist">
<li>XXXXXか、XXXXX-XXXXの形式で郵便番号を入力してください。</li>
</ul>
<p>
<label for="id_my_postal_code">My postal code:</label>
<input type="text" name="my_postal_code" value="a" required id="id_my_postal_code" />
</p>
とエラーが出ます。
JPPrefectureCodeSelect
同じくFormに実装してみます。
注意点としては、
- JPPrefectureCodeSelectは、widget(
django.forms.fields.Select
を継承)であること
- CharFieldのwidgetに設定すること
- ChoiceFieldだと、optionタグが表示されない
です。
from django import forms
from localflavor.jp.forms import JPPrefectureCodeSelect
class MyForm(forms.Form):
my_pref_code_ng = forms.ChoiceField(widget=JPPrefectureCodeSelect)
my_pref_code_ok = forms.CharField(widget=JPPrefectureCodeSelect)
フォームを表示してみると
<p>
<label for="id_my_pref_code_ng">My pref code ng:</label>
<select name="my_pref_code_ng" id="id_my_pref_code_ng"></select>
</p>
<p>
<label for="id_my_pref_code_ok">My pref code ok:</label>
<select name="my_pref_code_ok" id="id_my_pref_code_ok">
<option value="01">北海道</option>
<option value="02">青森県</option>
...
</p>
となります。
なお、都道府県の並び順に関しては、ソースコードに
Prefectures ordered to conform with the Japanese entry of ISO-3166.
This ordering is widely used in Japan.
See:
http://en.wikipedia.org/wiki/ISO_3166-2:JP
# https://github.com/django/django-localflavor/blob/master/localflavor/jp/jp_prefectures.py
とありました。ISO-3166準拠のようです。
JPPrefectureSelect
JPPrefectureCodeSelectとほぼ同じですが、違いはoptionタグのvalue属性の値です。
from django import forms
from localflavor.jp.forms import JPPrefectureSelect
class MyForm(forms.Form):
my_pref_ng = forms.ChoiceField(widget=JPPrefectureSelect)
my_pref_ok = forms.CharField(widget=JPPrefectureSelect)
として表示してみると
<p>
<label for="id_my_pref_ng">My pref ng:</label>
<select name="my_pref_ng" id="id_my_pref_ng">
</select>
</p>
<p>
<label for="id_my_pref_ok">My pref ok:</label>
<select name="my_pref_ok" id="id_my_pref_ok">
<option value="hokkaido">北海道</option>
<option value="aomori">青森県</option>
...
</p>
となります。value属性に都道府県名のアルファベット表記が設定されます。
JPPrefectureCodeSelectとJPPrefectureSelectの初期表示を変更する
引数 initial
を使います。
それぞれコードとアルファベット表記を指定します。
class MyForm(forms.Form):
my_pref_code_default = forms.CharField(widget=JPPrefectureCodeSelect, initial='20')
my_pref_default = forms.CharField(widget=JPPrefectureSelect, initial='nagano')
なお、initialに指定できる値は、localflavor/jp/jp_prefectures.py
にある
- JP_PREFECTURES
- JP_PREFECTURE_CODES
に記載されています。
表示してみます。
<p>
<label for="id_my_pref_code_default">My pref code default:</label>
<select name="my_pref_code_default" id="id_my_pref_code_default">
<option value="01">北海道</option>
...
<option value="20" selected>長野県</option>
</p>
<p>
<label for="id_my_pref_default">My pref default:</label>
<select name="my_pref_default" id="id_my_pref_default">
<option value="hokkaido">北海道</option>
...
<option value="nagano" selected>長野県</option>
</p>
JP_PREFECTURESとJP_PREFECTURE_CODES
以下のような定義となっています。
JP_PREFECTURES = (
('hokkaido', _('Hokkaido'),),
...
)
JP_PREFECTURE_CODES = (
('01', _('Hokkaido'),),
...
)
テーブルに含まれている都道府県だけ表示したい場合
今までの方法は、全部の都道府県を表示するものでした。
しかし、場合によっては、都道府県のうちトランザクションテーブルに含まれるものだけ表示したいことがあるかもしれません。
とはいえ、django-localflavorではそのような機能がないため、自分で実装します。
以下のModelがあるとします。
from django.db import models
class RingoProductingArea(models.Model):
pref = models.CharField('都道府県名', max_length=10)
ratio = models.PositiveSmallIntegerField('割合', default=0)
また、RingoProductingArea Modelのデータ(fixture)は以下であり、ここに含まれる「青森県・長野県・山形県」だけを表示したいとします。
出典:日本国内のりんご生産量|りんご大学
[
{
"model": "myapp.RingoProductingArea",
"pk": 1,
"fields": {
"pref": "青森県",
"ratio": 58
}
},
{
"model": "myapp.RingoProductingArea",
"pk": 2,
"fields": {
"pref": "長野県",
"ratio": 18
}
},
{
"model": "myapp.RingoProductingArea",
"pk": 3,
"fields": {
"pref": "山形県",
"ratio": 5
}
}
]
この場合、Formの __init__()
の中でchoicesの値を作成・設定します。
あまりきれいなロジックではないので、何かいい方法をご存知の方がいらっしゃいましたら、ご指摘ください。
from django import forms
from django.db.models.aggregates import Count
from localflavor.jp.jp_prefectures import JP_PREFECTURES
from localflavor.jp.forms import JPPrefectureCodeSelect, JPPrefectureSelect, JPPostalCodeField
from localflavor.us.forms import USPSSelect, USZipCodeField
from .models import RingoProductingArea
class MyForm(forms.Form):
limit_pref = forms.ChoiceField(label='Modelに存在する都道府県', choices=[('', '')])
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
q = RingoProductingArea.objects.values_list('pref', flat=True) \
.annotate(count_status=Count('pref')) \
.filter(count_status__gt=0).distinct()
product_prefs = [(pref, pref) for pref in q]
all_prefs_by_jp = [(pref_by_jp, pref_by_jp) for pref_by_en, pref_by_jp in JP_PREFECTURES]
exists_prefs = [pref for pref in all_prefs_by_jp if pref in product_prefs]
exists_prefs.insert(0, ('', '都道府県を選ぶ'))
self.fields['limit_pref'].choices = exists_prefs
self.fields['limit_pref'].initial = '長野県'
結果です。
<p>
<label for="id_limit_pref">Modelに存在する都道府県:</label>
<select name="limit_pref" required id="id_limit_pref">
<option value="">都道府県を選ぶ</option>
<option value="青森県">青森県</option>
<option value="山形県">山形県</option>
<option value="長野県" selected>長野県</option>
</select>
</p>
GitHubに上げました。
thinkAmi-sandbox/django-localflavor-sample