- 非同期処理でフォーム画面を表示するには?
- Django側からFormをJSONでフロント側に返すには?
本記事ではこのような疑問を解決します。
Djangoにおいて、非同期処理でフォーム画面を表示をしたい場面ってあると思います。
例えば、「新規作成」ボタンをクリックしたら新規作成フォームがパッと表示されるようなシーンです。
このような場合、JavaScriptを使った、Djangoとテンプレート間のデータの受け渡し処理が必要になります。
そこで今回はDjangoにおける非同期処理でフォーム画面を表示する方法を解説していきます。
前提条件
今回は飲食店におけるメニューの新規作成フォームを想定します。
機能としては、「新規作成」ボタンをクリックして表示されたメニュー作成フォームにおいて、新たに作成されたメニューを一覧に追加して表示するというものです。
なお、models.pyにて以下のモデルを定義します。
from django.db import models
class BigCategory(models.Model):
class Meta:
verbose_name = "カテゴリー(大)"
verbose_name_plural = verbose_name
big_category_id = models.IntegerField(primary_key=True, unique=True)
title = models.CharField(max_length=50)
def __str__(self):
return self.title
class SmallCategory(models.Model):
class Meta:
verbose_name = "カテゴリー(小)"
verbose_name_plural = verbose_name
small_category_id = models.IntegerField(primary_key=True, unique=True)
title = models.CharField(max_length=50)
big_category = models.ForeignKey(BigCategory, on_delete=models.CASCADE)
def __str__(self):
return self.title
class Item(models.Model):
class Meta:
verbose_name = "商品"
verbose_name_plural = verbose_name
item_id = models.IntegerField(primary_key=True, unique=True)
title = models.CharField(max_length=50)
price = models.IntegerField()
small_category = models.ForeignKey(SmallCategory, on_delete=models.CASCADE)
def __str__(self):
return self.title
バックエンド側の手順
まずはDjangoでの記述をしていきます。
①URLを設定する
urls.pyにてパスを以下のように記述します。
from django.urls import path
from . import views
from django.conf import settings
from django.conf.urls.static import static
app_name = 'app'
urlpatterns = [
path('menu_list', views.menu_list, name='menu_list'),
path('menu_create', views.menu_create, name='menu_create'),
path('menu_create_post', views.menu_create_post, name='menu_create_post'),
]+ static(settings.STATIC_URL, document_root=settings.STATICFILES_DIRS)
②処理を記述する
views.pyにて、処理を以下のように記述します。
from .models import BigCategory, SmallCategory, Item
from .forms import ItemCreateForm
from django.shortcuts import render
from django.http import JsonResponse
from django.template.loader import render_to_string
# 商品一覧を表示する処理
def menu_list(request):
big_category = BigCategory.objects.all()
item_all = Item.objects.all()
template_name = 'menu_list.html'
context = {
"big_category": big_category,
"item_all": item_all,
}
return render(request, template_name, context)
# 商品作成フォームを表示する処理
def menu_create(request):
form = ItemCreateForm()
template_name = 'menu_create.html'
context = {
'form': form,
}
content = render_to_string(template_name, context, request)
data = {
"content": content,
}
return JsonResponse(data)
# フォームに入力された商品情報を保存し、商品一覧を表示する処理
def menu_create_post(request):
if request.method == 'POST':
form = ItemCreateForm(request.POST)
if form.is_valid():
form.save()
template_name = 'new_menu.html'
item_all = Item.objects.all()
context = {
'item_all': item_all,
}
content = render_to_string(template_name, context, request)#テンプレートとデータを一緒に返す
data = {
"content": content,
}
return JsonResponse(data)
③フォームを作成する
最後にforms.pyにてフォームを以下のように記述します。
from django import forms
from .models import Item
class ItemCreateForm(forms.ModelForm):
class Meta:
model = Item
fields = '__all__'#今回はすべてのフィールドを扱うため、'__all__'とする
今回はModelFormでフォームを作成します。
Metaクラスのmodelには扱うテーブル名、fieldsには扱うフィールド名を指定します。
フロントエンド側の手順
こちらではHTMLとCSS、JavaScriptの記述を行います。
①テンプレートを作成する
表示するテンプレート(HTML)を以下のように記述します。
<!DOCTYPE html>
{% load static %}
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>メニューリスト</title>
<meta name="description" content="">
<link rel="stylesheet" href="{% static 'css/style.css' %}">
</head>
<body>
<main>
<section class="list current" id="list">
<h1>商品一覧</h1>
<button id="create_page">新規作成</button>
<div id="list_container">
{% for item in item_all %}
<div class="product-box">
<div class="product-txt-box">
{{ item.title }}<br>{{ item.price }}円(税込)
</div>
</div>
{% endfor %}
</div>
</section>
<section class="create" id="create">
</section>
</main>
<script src="{% static 'js/menu.js' %}"></script>
</body>
</html>
また、上記のsectionタグ(class=”create”)に挿入させる新規作成フォームのHTMLを以下のように記述します。
<button id="create_back_list">一覧に戻る</button>
<h1>商品登録</h1>
<form action="" id="create_post">
<p>商品ID</p>
<div>{{ form.item_id }}</div>
<p>商品名</p>
<div>{{ form.title }}</div>
<p>金額(税込)</p>
<div>{{ form.price }}円</div>
<p>カテゴリー(小)</p>
<div>{{ form.small_category }}</div>
<button type="submit" class="grn-circle-button">OK</button>
</form>
さらに、新規作成フォームを送信したあとに表示させる商品一覧のHTMLを以下のように記述します。
こちらのHTMLはmenu.htmlのdivタグ(id=”list_container”)に挿入させます。
{% for item in item_all %}
<div class="product-box">
<div class="product-txt-box">
{{ item.title }}<br>{{ item.price }}円(税込)
</div>
</div>
{% endfor %}
</div>
②CSSを記述する
次にCSSの記述をします。
今回は非同期処理に関する画面切り替えのコードを記述します。
※デザインは省略
.list,
.create {
display: none;
}
.list.current,
.create.current {
display: block;
}
まず、テンプレートにてsectionタグ(class=”list”)とsectionタグ(class=”create”)を記述します。
そして、class名に”current”が付いているsectionタグを表示させるという実装をします。
③JavaScriptを記述する
①で作成したHTMLファイルの中で以下のJavaScriptファイルを読み込みます。
// Django側にPOST送信する際に記述する"お決まりのコード"
const getCookie = (name) => {
if (document.cookie && document.cookie !== '') {
for (const cookie of document.cookie.split(';')) {
const [key, value] = cookie.trim().split('=');
if (key === name) {
return decodeURIComponent(value);
}
}
}
};
const csrftoken = getCookie('csrftoken');
// 新規作成フォームを取得・表示
async function menu_create() {
const url = '/menu_create';
let res = await fetch(url, {
method: 'GET',
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8',
},
});
let json = await res.json();
return json.content;
}
// 新規作成フォーム送信
async function menu_create_post(form) {
const url = '/menu_create_post';
let res = await fetch(url, {
method: 'POST',
headers: {
'X-CSRFToken': csrftoken,
},
body: form,
});
let json = await res.json();
return json.content;
}
// 一覧画面→新規作成画面(新規作成フォーム取得)
document.getElementById('create_page').addEventListener('click', (event) => {
event.preventDefault();
window.scrollTo(0, 0);
menu_create()
.then(response => {
let list = document.getElementById("list");
list.className = "list";
let create = document.getElementById("create");
create.innerHTML = response;
create.className = "create current";
})
.then(() => {
// 新規作成画面→一覧画面(戻る)
document.getElementById('create_back_list').addEventListener('click', (event) => {
window.scrollTo(0, 0);
let create = document.getElementById("create");
create.innerHTML = '';
create.className = "create";
let list = document.getElementById("list");
list.className = "list current";
});
// 新規作成画面→一覧画面(新規作成フォーム送信、一覧データ取得)
document.getElementById('create_post').addEventListener('submit', (event) => {
event.preventDefault();
window.scrollTo(0, 0);
const form = new FormData(document.forms[0]);
menu_create_post(form)
.then(response => {
let create = document.getElementById("create");
create.innerHTML = '';
create.className = "create";
let list = document.getElementById("list");
list.className = "list current";
let list_container = document.getElementById("list_container");
list_container.innerHTML = response;
});
});
});
});
完成画面を確認
まとめ
以上がDjangoにおける非同期処理でフォーム画面を表示する方法になります。
今回のポイントはrender_to_string()を使ってモデルデータとテンプレートを一緒にフロントエンドに渡しているという点でした。
この点を押さえて様々なパターンに応用してみてください。
Djangoが学べるプログラミングスクールとは?おすすめのDjangoスクールとは? 本記事ではこのような疑問を解決します。DjangoはPythonで作られた中で1番メジャーなWebフレームワークです。みんなが普段利用するYouT[…]