こんにちは。sinyです。
この記事はDjango Advent Calendar 2021の11目の記事です。
この記事では、Djangoで依存チェーンドロップダウン選択リストを作成する方法を簡単にご紹介します。
「Web画面で都道府県をリストから1つ選択すると、選択した都道府県に対応する市区町村だけが一覧表示される」といった機能を例にして実装してみます。
実装物のデモ画面は以下の通りです。
サンプルコードは以下のGITHUBリポジトリにUPしていますので参考にしてみてください。
市区町村データのダウンロード
今回は、市区町村のJsonデータをあらかじめダウンロードしておき、Djangoプロジェクト内で利用する形とします。
都道府県のJsonデータは以下のサイトから拝借させていただきました。
上記サイトの「都道府県 - 市町村エンドポイント」の下記URLをクリックします。
https://geolonia.github.io/japanese-addresses/api/ja.json
都道府県名のJsonデータが表示されるので「右クリック→名前を付けて保存」→ja_prefecture.jsonとして一旦保存しておきましょう。
Djangoの初期設定
まず最初に必要なモジュールの導入、プロジェクト、アプリの作成と初期設定を行っておきます。
今回は以下のモジュールをインストールします。
pip install django-widgets-improved django
プロジェクトとアプリを作成します。
django-admin startproject config . python manage.py startapp app
settings.pyを以下の通り設定します。
INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'app.apps.AppConfig', #追加 'widget_tweaks',#追加 ] LANGUAGE_CODE = 'ja' #変更 TIME_ZONE = 'Asia/Tokyo' #変更 STATIC_URL = '/static/' STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')] #追加
また、Djangoプロジェクトフォルダ直下にstatic/dataフォルダを作成して先ほどダウンロードしたja_prefecture.jsonファイルを配置しておきましょう。
フォームの定義
app/forms.pyを新規に作成して以下のコードを定義します。
from django import forms import json def readJson(filename): with open(filename, 'r',encoding="utf-8_sig") as fp: return json.load(fp) def get_prefecture(): """ 都道府県を選択する """ filepath = './static/data/ja_prefecture.json' all_data = readJson(filepath) prefectures = list(all_data.keys()) all_prefectures = [('-----', '---都道府県の選択---')] for prefecture in prefectures: all_prefectures.append((prefecture, prefecture)) return all_prefectures def return_cities_by_prefecture(prefecture): """ 都道府県の選択を取得 """ filepath = './static/data/ja_prefecture.json' all_data = readJson(filepath) #指定の都道府県の市区町村データを取得 all_cities = all_data[prefecture] return all_cities class AddressForm(forms.Form): country = forms.ChoiceField( choices = get_prefecture(), required = False, label='都道府県', widget=forms.Select(attrs={'class': 'form-control', 'id': 'id_prefecture'}), )
各関数の役割は以下の通りです。
関数名 | 処理内容 |
readJson
|
ja_prefecture.jsonファイルを読み込む関数 |
get_prefecture
|
ja_prefecture.jsonから都道府県名の全情報を返す関数
|
return_cities_by_prefecture
|
引数に渡した都道府県名から市区町村のデータを返す関数
|
AddressFormクラスは都道府県を選択するリストフォームの定義です。
formのChoiceFiledを使ってchoicesにget_prefecture関数を指定してjsonファイルから都道府県名を表示させるようにしています。
ビューの定義
次にロジックをビューに定義していきます。
app/views.pyに以下のコードを追加しましょう。
from django.shortcuts import render from app.forms import * from django.http.response import JsonResponse def getPrefecture(request): prefecture = request.POST.get('prefecture') prefecutres = return_cities_by_prefecture(prefecture) return JsonResponse({'prefecutres': prefecutres}) def processForm(request): context = {} if request.method == 'GET': form = AddressForm() context['form'] = form return render(request, 'address.html', context) if request.method == 'POST': form = AddressForm(request.POST) if form.is_valid(): selected_province = request.POST['city'] obj = form.save(commit=False) obj.state = selected_province obj.save()
各関数の処理内容は以下の通りです。
関数名 | 処理内容 |
processForm
|
http://127.0.0.1:8000/process-formにアクセスした際にgetメソッドだったら、都道府県を選択するフォーム(AddressForm)を表示し、POSTメソッドだったら選択された市区町村データをフォームの保存するような想定です。 ※今回は特に実際に保存はしていません。 |
getPrefecture
|
この関数はテンプレート側で選択した都道府県情報をAjaxで受け取り、ビュー側で都道府県に該当する市区町村データを取得してテンプレート側に市区町村データを返してあげる関数です。 |
URLパターンの定義
プロジェクトルートのurls.pyは以下の様に設定します。
from django.contrib import admin from django.urls import path, include urlpatterns = [ path('admin/', admin.site.urls), path('', include('app.urls')), #追加 ]
appフォルダにurls.pyを作成し、以下の通り定義します。
from django.urls import path from .views import * urlpatterns = [ path('process-form', processForm, name='process-form'), path('get-prefecture', getPrefecture, name='get-prefecture'), ]
1番目のURLパターンはhttp://127.0.0.1:8000/process-formにアクセスした際に都道府県の選択画面を表示するものです。
2番目は選択画面で選択された都道府県名をAjaxで通信で受け取るためのURLパターンです。
getPrefecture関数が呼ばれて都道府県に対応する市区町村データをテンプレートに返します。
テンプレートの作成
最後にテンプレートを作成します。
まずベースとなるbase.htmlを以下の通り記載します。
bootstrapを使ったシンプルな定義ですので、説明は省略します。
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>と</title> <meta name="description" content="Source code generated using layoutit.com"> <meta name="author" content="LayoutIt!"> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"> <link href="https://use.fontawesome.com/releases/v5.6.1/css/all.css" rel="stylesheet"> </head> <body> <div class="container-fluid"> <div class="row"> <div class="col-md-12"> <div class="jumbotron card card-block"> <h2>ドロップダウンリスト機能デモ</h2> </div> <h4> {% block title %} {% endblock %} </h4> {% block content %} {% endblock %} </div> </div> </div> <!-- フッター --> <footer class="py-1 bg-dark text-light"> <div class="container text-center"> <!-- ナビゲーション --> <ul class="nav justify-content-center mb-3"> <li class="nav-item"> <a class="nav-link" href="#">Top</a> </li> </ul> <!-- /ナビゲーション --> <p><small>Copyright ©2021 xxx, All Rights Reserved.</small></p> </div> </footer> </body> </html>
メインとなるaddress.htmlを以下の通り定義します。
{% extends '.\base.html' %} {% load widget_tweaks %} {% block content %} <div class="row"> <div class="col-md-6"> <div class="card border-secondary mb-3" style="max-width: 35rem;"> <div class="card-header"><h5>選択した都道府県の市区町村をリスト表示</h5></div> <div class="card-body text-secondary"> <form class="" action="" method="post"> {% csrf_token %} {% for error in errors %} <div class="alert alert-danger mb-4" role="alert"> <strong>{{ error }}</strong> </div> {% endfor %} <div class="row"> <div class="col-lg-6"> <div class="mb-4"> <label>都道府県を選択</label> {{ form.country}} </div> </div> <div class="col-lg-6"> <div class="mb-4"> <div class="form-group"> <label>市区町村を選択</label> <select id="id_city" class="form-control" name="city"> <option value="-----">市区町村を選択</option> </select> </div> </div> </div> </div> </form> </div> </div> </div> </div> <script src="https://code.jquery.com/jquery-3.5.0.js" integrity="sha256-r/AaFHrszJtwpe+tHyNi/XCfMxYpbsRg2Uqn0x3s2zc=" crossorigin="anonymous"></script> <script> $("#id_prefecture").change(function () { var prefecture = $(this).val(); $.ajax({ type: "POST", url: "{% url 'get-prefecture' %}", data: { 'csrfmiddlewaretoken': '{{ csrf_token }}', 'prefecture': prefecture }, success: function (data) { console.log(data.prefecutres); let html_data = '<option value="-----">市区町村を選択</option>'; data.prefecutres.forEach(function (data) { html_data += `<option value="${data}">${data}</option>` }); $("#id_city").html(html_data); } }); }); </script> {% endblock %}
冒頭でbase.htmlを拡張、フォームデザインを整えるためにwidget_tweaksをロードしておきます。
都道府県の選択と市区町村データの選択フォームの定義が以下の部分です。
<form class="" action="" method="post"> {% csrf_token %} {% for error in errors %} <div class="alert alert-danger mb-4" role="alert"> <strong>{{ error }}</strong> </div> {% endfor %} <div class="row"> <div class="col-lg-6"> <div class="mb-4"> <label>都道府県を選択</label> {{ form.country}} </div> </div> <div class="col-lg-6"> <div class="mb-4"> <div class="form-group"> <label>市区町村を選択</label> <select id="id_city" class="form-control" name="city"> <option value="-----">市区町村を選択</option> </select> </div> </div> </div> </div> </form>
<label>市区町村を選択</label> <select id="id_city" class="form-control" name="city"> <option value="-----">市区町村を選択</option> </select>
処理の流れとしては、「{{ form.country}}で選択された都道府県名をajaxでビュー側に返し、市区町村データを取得してテンプレートに戻してid="id_city"に市区町村データのリストを表示する」という感じになります。
最後にAjax通信の定義が以下の箇所です。
<script src="https://code.jquery.com/jquery-3.5.0.js" integrity="sha256-r/AaFHrszJtwpe+tHyNi/XCfMxYpbsRg2Uqn0x3s2zc=" crossorigin="anonymous"></script> <script> $("#id_prefecture").change(function () { var prefecture = $(this).val(); $.ajax({ type: "POST", url: "{% url 'get-prefecture' %}", data: { 'csrfmiddlewaretoken': '{{ csrf_token }}', 'prefecture': prefecture }, success: function (data) { console.log(data.prefecutres); let html_data = '<option value="-----">市区町村を選択</option>'; data.prefecutres.forEach(function (data) { html_data += `<option value="${data}">${data}</option>` }); $("#id_city").html(html_data); } }); }); </script>
他にもいろいろなやり方があると思いますが、1つの方法として参考にしていただければ幸いです。