【Django】Ajaxで非同期通信する

非同期通信とは、「ページの読み込みをすることなく、サーバーと通信する」ことです。

Webサイトの基本的な挙動として、ページを読み込む(サーバーにリクエストを送る、レスポンスをもらう)際にはページの読み込み動作が入ります。

ブラウザ上では、ページの読み込み時には一時的にページがフリーズし、操作を受け付けないようになります。

しかし、それではWebアプリにおいては都合が悪いことが多いです。例えば、バックグラウンドの処理に数秒かかる場合、その数秒間はページが操作を受け付けない時間が発生するのです。こういった挙動はUX(ユーザーエクスペリエンス)が悪いため、避けたほうがよいでしょう。

では有名どころのWebアプリはどのような挙動をしているか再確認してみます。

例えばX(Twitter)のブラウザ版の場合、TLの更新は非同期で行われています。スマホの場合、TLを上から下にスワイプするとロードされますが、非同期通信が行われているため、ページがリロードされることはありません(=フリーズしない)。そのため、ロードを待っている数秒間もTLを遡って閲覧したり、トレンドタブの方をクリックしてチェックすることなどができます。

非同期通信の実現方法

非同期通信を実現するにはAjaxという技術を使用します。言語はJavaScriptで、フロントエンドに実装します。

この記事のゴール

DjangoとAjaxの組み合わせで非同期通信を行う簡単なページを作成します。

ページ上の入力欄に数字を入力すると、その秒数だけバックエンド側で待ち(時間がかかる処理を想定)、時間が終われば「〇秒の処理が完了しました」という文字を返却し、ページに表示するというシンプルなアプリです。

前提

Pythonの実行環境を構築した上で、Djangoをインストールしておきましょう。

「Windows」キー+「R」で出てきたウィンドウに「cmd」と入力→エンターして出てきたプロンプトで以下を実行。

pip install django

プロジェクトの作成→アプリの立ち上げ→アプリ側のurls.py作成

作業用のディレクトリを作り、その場所でコマンドを開きます。それから以下のコマンドを実行していきます。

1.プロジェクト立ち上げ

python -m django startproject project

※django-adminコマンドではありません。

2.ディレクトリを変更

cd project

3.アプリを作成

python manage.py startapp app

4./project/appディレクトリ内にurls.pyを作成(空ファイルでOK)

5./project/project/settings.py内のINSTALLED_APPSの部分に’app’を追加

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'app', #追記
]

templatesフォルダの設定を追加

この後にhtmlファイルなどを格納するための/projectディレクトリにtemplatesフォルダを作成するのですが(index.htmlを作成する段階で紹介)、このフォルダをDjangoに認識させるためにはsettinngs.pyに設定を追加する必要があります。

settings.pyを以下のように編集します(該当箇所のみ)。

from pathlib import Path
import os #追記

#~~~中略~~~

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates')], #変更
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

views.pyを編集

/project/app/views.pyを編集します。

from django.shortcuts import render
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
import time

def index(request):
    return render(request, 'index.html')

@csrf_exempt #csrf_tokenを無効にする
def process(request):
    if request.method == 'POST':
        seconds = int(request.POST.get('seconds', 0))
        print(str(seconds) + '秒が指定されました')
        time.sleep(seconds)
        data = {'message': f'{seconds}秒の処理が完了しました'}
        return JsonResponse(data, json_dumps_params={'ensure_ascii': False})

フロントで入力された秒数を受け取り、コンソールに表示した上でその秒数分だけsleep(処理をストップ)します。sleepが終わったらフロント側にJson形式でデータを返却します。

DjangoでPost送信を受ける際にはデフォルトでcsrfトークンをフロント側から渡す必要があります。今回はプログラムを簡略化するために@csrf_exemptを付与してcsrf対策を免除します。(実運用では注意する必要があります)

json_dumps_params={‘ensure_ascii’: False}は文字がエスケープされないように設定しています。

urls.pyを編集(プロジェクト側とアプリ側の両方)

まずプロジェクト側のurls.pyである/project/project/urls.pyを編集します。

from django.contrib import admin
from django.urls import path, include #追加

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('app.urls')), #追加
]

次に、アプリ側のurls.pyである/project/app/urls.pyを編集します。

from django.urls import path
from . import views

urlpatterns = [
    path('', views.index, name='index'),
    path('process/', views.process, name='process'),
]

templatesフォルダを作り、その中にindex.htmlを作成

/projectディレクトリにhtmlファイルを入れておくためのtemplatesフォルダを作成してください。作成したら、その中にindex.htmlを新規作成します。

/project/templates/index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Ajaxのデモ</title>
</head>
<body>
    <label for="secondsInput">秒数を入力:</label>
    <input type="number" id="secondsInput" name="seconds" min="1" value="1">
    <button onclick="execute()">実行</button>
    <div id="result"></div>

    <script>
        function execute() {
            var seconds = document.getElementById('secondsInput').value;
            
            // Ajaxリクエストの作成
            var xhr = new XMLHttpRequest();
            xhr.open('POST', '/process/', true);
            xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
            xhr.onreadystatechange = function() {
                if (xhr.readyState === 4 && xhr.status === 200) {
                    // レスポンスの処理
                    var resultDiv = document.getElementById('result');
                    var responseData = JSON.parse(xhr.responseText);
                    resultDiv.innerText = responseData.message;
                }
            };
            // リクエストの送信
            xhr.send('seconds=' + seconds);
        }
    </script>
    
</body>
</html>

実行

manage.pyが存在するディレクトリにてコマンドを開き、以下のコマンドでサーバーを実行します。

python manage.py runserver

実行したら、ブラウザから「localhost:8000」にアクセスし、秒数を入力して「実行」ボタンを押してみてください。

試しに2と入力して実行すると、コマンド上で「2秒が指定されました」と表示されあと、2秒待ってから以下のように表示されます。

この時ページがリロードされずに処理が帰ってきていることに注目してください。

以上より、非同期処理ができていることが確認できました。

コメント