【Django】非同期処理でフォーム画面を表示する方法【JSON】

アイキャッチ画像
  • 非同期処理でフォーム画面を表示するには?
  • 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[…]

アイキャッチ画像