Djangoのフォームで
- 郵便番号入力
- 都道府県選択
を簡単に作る方法を調べたところ、 django-localflavor
がありました。
- django/django-localflavor: Country-specific Django helpers, formerly of contrib fame
- The “local flavor” app — django-localflavor 2.0 documentation
Web上を調べたところ、Django 1.6以降削除されたの記事を見かけたため、使えないのかなと思いました。
ただ、公式ドキュメントのChangeLogを読むと、Django2.0に対応しているとのことでした。
Changelog — django-localflavor 2.0 documentation
そこで、Django2.0で試してみました。
目次
環境
環境構築
Djangoアプリ作成まで
いつも通りなので省略します。
# 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', # django-localflavor用 'localflavor', ] LANGUAGE_CODE = 'ja' USE_I18N = True
アプリの中身を作成
テンプレート(templates/form.html)
<!DOCTYPE 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に実装してみます。
# MyForm 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)
フォームを表示してみると
<!-- ChoiceFieldのwidgetとして設定したもの --> <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> <!-- CharFieldのwidgetとして設定したもの --> <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)
として表示してみると
<!-- ChoiceFieldのwidgetとして設定したもの --> <p> <label for="id_my_pref_ng">My pref ng:</label> <select name="my_pref_ng" id="id_my_pref_ng"> </select> </p> <!-- CharFieldのwidgetとして設定したもの --> <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
に記載されています。
表示してみます。
<!-- JPPrefectureCodeSelect版 --> <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> <!-- JPPrefectureSelect版 --> <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
以下のような定義となっています。
# https://github.com/django/django-localflavor/blob/master/localflavor/jp/jp_prefectures.py JP_PREFECTURES = ( ('hokkaido', _('Hokkaido'),), ... ) JP_PREFECTURE_CODES = ( ('01', _('Hokkaido'),), ... )
テーブルに含まれている都道府県だけ表示したい場合
今までの方法は、全部の都道府県を表示するものでした。
しかし、場合によっては、都道府県のうちトランザクションテーブルに含まれるものだけ表示したいことがあるかもしれません。
とはいえ、django-localflavorではそのような機能がないため、自分で実装します。
以下のModelがあるとします。
# models.py 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() # choicesの形(tupleのlist)にしておく product_prefs = [(pref, pref) for pref in q] # product_prefsと比較しやすいよう、JP_PREFECTURESを日本語表記の都道府県tupleのlistにしておく # 元々は、(ローマ字表記, 日本語表記)のtuple 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, ('', '都道府県を選ぶ')) # limit_prefフィールドのchoicesとして設定 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>