Djangoで、Ajax

Twitterでシェア FaceBookでシェア はてなブックマークでシェア

Python - Django
2018年12月3日20:47に更新(約10日前)
2018年11月23日3:59に作成(約20日前)

旧ブログ移行記事です。

概要

Djangoを使う上での、Ajaxとのやりとりのサンプルを説明していきます。

データをAjaxで追加するのと、Ajaxを使った絞り込み検索を簡単に作っていきます。

以下のようなモデル、ビュー、テンプレートを作っておきましょう。

models.pyです。タイトルのある記事モデルです。

from django.db import models


class Post(models.Model):
    title = models.CharField('タイトル', max_length=255)

    def __str__(self):
        return self.title

views.pyです。とりあえず、記事の一覧ビューを定義しておきます。

from django.views import generic
from .models import Post


class PostList(generic.ListView):
    model = Post

base.htmlです。

<!doctype html>
<html lang="ja">
  <head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">

    <title>Ajaxのれんしゅう</title>
  </head>
  <body>
    <div class="container">
        {% block content %}{% endblock %}
    </div>


    <!-- Optional JavaScript -->
    <!-- jQuery first, then Popper.js, then Bootstrap JS -->
    <script src="https://code.jquery.com/jquery-3.3.1.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous"></script>
    {% block extrajs %}{% endblock %}
  </body>
</html>

Bootstrap4のスターターテンプレートめいたものですが、後で追加のJavaScriptのコードを書くので、bodyの閉じる直前に{% block extrajs %}{% endblock %}としておきます。jQueryの読み込みの後に定義しておきましょう

また、Ajaxを使うのでスリム版ではないjQueryを読み込むのも忘れないようにしましょう(自分でXMLHttpRequestを書くなら不要)。

<script src="https://code.jquery.com/jquery-3.3.1.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script>

Ajaxでデータの追加

まずは、Ajaxでデータの追加をしてみましょう。

まず、ページが表示されて...

記事タイトルを入力し、送信ボタンを押します。

すると、ページ遷移せずに追加された記事が表示されます。もちろん、データベースにも保存されます。

まず、Ajaxで送信されたデータを処理するビューを作ります。urls.pyでビューと紐づけておいてください。

from django.http import JsonResponse
...
...

def ajax_post_add(request):
    title = request.POST.get('title')
    post = Post.objects.create(title=title)
    d = {
        'title': post.title,
    }
    return JsonResponse(d)

エラーの処理とかは省いています。もしPOSTメソッドのみを許可したいならばdjango.views.decorators.http@require_POSTデコレータを使ってもいいですし、GETでも何かやらせたいならばif request.method == 'GET':...else:といった条件分岐をさせると良いでしょう。

title = request.POST.get('title')として、送信されてきた記事タイトルを取得します。そして、そのタイトルを元に記事を作成します。作成したら、JsonResponseで記事タイトルを含んだ辞書を返す、という流れです。

post_list.htmlですが、こちらは複雑です。メインの処理はコメントで補足しています。

{% extends 'app/base.html' %}

{% block content %}
    <h2>記事一覧</h2>
    <div id="posts">
        {% for post in post_list %}
            <p>{{ post.title }}</p>
        {% endfor %}
    </div>

    <hr>

    <h2>記事の追加</h2>
    <form id="ajax-add-post" action="{% url 'app:ajax_post_add' %}" method="POST">
        <input type="text" id="id_title" required>
        <button type="submit" >送信</button>
        {% csrf_token %}
    </form>

{% endblock %}

{% block extrajs %}
    <script>
        function getCookie(name) {
            var cookieValue = null;
            if (document.cookie && document.cookie !== '') {
                var cookies = document.cookie.split(';');
                for (var i = 0; i < cookies.length; i++) {
                    var cookie = jQuery.trim(cookies[i]);
                    // Does this cookie string begin with the name we want?
                    if (cookie.substring(0, name.length + 1) === (name + '=')) {
                        cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                        break;
                    }
                }
            }
            return cookieValue;
        }

        var csrftoken = getCookie('csrftoken');

        function csrfSafeMethod(method) {
            // these HTTP methods do not require CSRF protection
            return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
        }

        $.ajaxSetup({
            beforeSend: function (xhr, settings) {
                if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
                    xhr.setRequestHeader("X-CSRFToken", csrftoken);
                }
            }
        });

        // 送信ボタンで呼ばれる
        $('#ajax-add-post').on('submit', function (e) {
            // デフォルトのイベントをキャンセルし、ページ遷移しないように!
            e.preventDefault();

            $.ajax({
                'url': '{% url "app:ajax_post_add" %}',
                'type': 'POST',
                'data': {
                    'title': $('#id_title').val(),  // 記事タイトル
                },
                'dataType': 'json'
            }).done(function (response) {
                // <p>はろー</p>のような要素を作成し、それを記事一覧エリアに追加し、入力欄をクリアする。
                var p = $('<p>', {text: response.title});
                $('#posts').prepend(p);
                $('#id_title').val('');
            });

        });
    </script>


{% endblock %}

{% block content %}内は簡単です。記事の一覧を表示する部分があり、記事を送信するためのフォームがあります。

{% block content %}
    <h2>記事一覧</h2>
    <div id="posts">
        {% for post in post_list %}
            <p>{{ post.title }}</p>
        {% endfor %}
    </div>

    <hr>

    <h2>記事の追加</h2>
    <form id="ajax-add-post" action="{% url 'app:ajax_post_add' %}" method="POST">
        <input type="text" id="id_title" required>
        <button type="submit" >送信</button>
        {% csrf_token %}
    </form>

{% endblock %}

<div id="posts">ですが、これは記事の一覧を格納するための要素です。

ビューにデータを送信したら記事が作られます。ただAjaxなので、画面遷移はしません。画面遷移しないということは、新しく作成されたデータをその画面に反映させる処理は自分でしなければなりません。

ビューに送信した後は作成された記事のタイトルが帰りますので、それを記事の一覧部分に追加するだけで今回は済みます。その目汁となるのが<div id="posts">です。

CSRFトークンの対策

{% block extrajs %}のうち、前半部分はおまじないです。

        function getCookie(name) {
            var cookieValue = null;
            if (document.cookie && document.cookie !== '') {
                var cookies = document.cookie.split(';');
                for (var i = 0; i < cookies.length; i++) {
                    var cookie = jQuery.trim(cookies[i]);
                    // Does this cookie string begin with the name we want?
                    if (cookie.substring(0, name.length + 1) === (name + '=')) {
                        cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                        break;
                    }
                }
            }
            return cookieValue;
        }

        var csrftoken = getCookie('csrftoken');

        function csrfSafeMethod(method) {
            // these HTTP methods do not require CSRF protection
            return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
        }

        $.ajaxSetup({
            beforeSend: function (xhr, settings) {
                if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
                    xhr.setRequestHeader("X-CSRFToken", csrftoken);
                }
            }
        });

CSRFトークンに関する処理です。Djangoにおいて、AjaxでデータをPOSTする際はこの記述が必要だと思っても差し支えないです。

他の方法として、django.views.decorators.csrf@csrf_exemptデコレータをビューにつける等すればCSRFのトークンが不要になります。とはいえ、基本的には今回のようにしておくのが良いでしょう。

Ajax部分

メインの処理は以下の部分です。

        // 送信ボタンで呼ばれる
        $('#ajax-add-post').on('submit', function (e) {
            // デフォルトのイベントをキャンセルし、ページ遷移しないように!
            e.preventDefault();

            $.ajax({
                'url': '{% url "app:ajax_post_add" %}',
                'type': 'POST',
                'data': {
                    'title': $('#id_title').val(),  // 記事タイトル
                },
                'dataType': 'json'
            }).done(function (response) {
                // <p>はろー</p>のような要素を作成し、それを記事一覧エリアに追加し、入力欄をクリアする。
                var p = $('<p>', {text: response.title});
                $('#posts').prepend(p);
                $('#id_title').val('');
            });

        });

ほとんどコメントのとおりで、よくあるAjaxの送信処理です。'url': '{% url "app:ajax_post_add" %}',のように、<script>内であろうとテンプレートに渡した変数や各種タグ・フィルタは使えます。

Ajaxでデータの検索・絞り込み

今度はデータの絞り込み・検索処理をやりましょう。画面遷移せずに検索が行われ、すばしこいので実際便利です。

入力欄に検索キーワードを入れて、検索ボタンを押すと...

ちゃんと絞り込まれますね。

views.pyです。

def ajax_post_search(request):
    keyword = request.GET.get('title')

    # 検索キーワードがあればそれで絞り込み、なければ全ての記事
    # JSONシリアライズするには、Querysetをリストにする必要あり
    if keyword:
        title_list = [post.title for post in Post.objects.filter(title__icontains=keyword)]  # タイトルにキーワードを含む。大文字小文字の区別なし
    else:
        title_list = [post.title for post in Post.objects.all()]

    d = {
        'title_list': title_list,
    }
    return JsonResponse(d)

コメントに書いてあるように、JSONとして返すにはQuerysetをリストに変換する必要があります。なので、リスト内包表記です。

今回は関係ないのですが、リスト内の各要素もJSONでシリアライズできる形でなくてはなりません。各要素をモデルインスタンスとしてそのまま返したい場合は、頑張って辞書に変換する必要があります。

post_list.html

{% extends 'app/base.html' %}

{% block content %}
    <h2>記事一覧</h2>
    <div id="posts">
        {% for post in post_list %}
            <p>{{ post.title }}</p>
        {% endfor %}
    </div>

    <hr>

    <h2>記事の検索</h2>
    <form id="ajax-search-post" action="{% url 'app:ajax_post_search' %}" method="GET">
        <input type="text" id="id_title">
        <button type="submit" >検索</button>
    </form>

{% endblock %}

{% block extrajs %}
    <script>
        // 送信ボタンで呼ばれる
        $('#ajax-search-post').on('submit', function (e) {
            // デフォルトのイベントをキャンセルし、ページ遷移しないように!
            e.preventDefault();

            $.ajax({
                'url': '{% url "app:ajax_post_search" %}',
                'type': 'GET',
                'data': {
                    'title': $('#id_title').val(),  // 記事タイトル
                },
                'dataType': 'json'
            }).done(function (response) {
                // 記事欄を真っ白にする。
                $('#posts').empty();

                // タイトルの一覧を順に取り出す
                for (var title of response.title_list) {
                    // <p>タイトル</p>を作成し、記事一覧に追加していく。
                    var p = $('<p>', {text: title});
                    $('#posts').append(p);
                }
            });
        });
    </script>
{% endblock %}

検索はGETメソッドで行うことが多く、この例でもそうしています。CSRF対策が不要になるため、それ関連の関数は不要になっています。

後はコメントのとおりです。

Twitterでシェア FaceBookでシェア はてなブックマークでシェア

記事にコメントする