Djangoで、Ajax

Python Django JavaScript Ajax

概要

Ajaxを使うことで、ページ遷移することなくページ内容を更新することができます。今回はDjangoフレームワークで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のStarter Templateとは少し違うので注意しましょう。公式のStarter TemplateではjQueryはスリム版を読み込んでいますが、スリム版はajaxメソッドを使えません。jQueryのajaxメソッドを使いたい場合は、スリムじゃないほうを読み込みます。

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

他のテンプレートでJavaScriptを書きやすいように、{% block extrajs %}を定義します。

{% block extrajs %}{% endblock %}

Ajaxでデータの追加

Ajaxでデータの追加をするサンプルです。

記事の一覧画面があり、そのページ内で記事の追加ができます。

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

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

まず、Ajaxで送信されたデータを処理するビューを作ります。

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', e => {
            // デフォルトのイベントをキャンセルし、ページ遷移しないように!
            e.preventDefault();

            $.ajax({
                'url': '{% url "app:ajax_post_add" %}',
                'type': 'POST',
                'data': {
                    'title': $('#id_title').val(),  // 記事タイトル
                },
                'dataType': 'json'
            }).done( response => {
                // <p>はろー</p>のような要素を作成し、それを記事一覧エリアに追加し、入力欄をクリアする。
                const 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公式ドキュメントで紹介されているものをそのまま利用しています。他の方法として、django.views.decorators.csrf@csrf_exemptデコレータをビューにつける等すればCSRFのトークンが不要になります。とはいえ、基本的には今回のようにしておくのが良いでしょう。

Ajax部分

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

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

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

        });

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

Fetch API

jQueryを使わない場合の選択肢で、新しめなブラウザに対応できてりゃいいよ、ということならFetch APIも使えます。

上でも紹介した、DjangoCSRF対策のコードはjQueryを使うことが前提なので、それも今風な感じで書いてみましょう。

    <script>
        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');

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

            const url = '{% url "app:ajax_post_add" %}';
            const postArea = document.getElementById('posts');
            const title = encodeURIComponent(document.getElementById('id_title').value);
            fetch(url, {
                method: 'POST',
                body: `title=${title}`,
                headers: {
                    'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8',
                    'X-CSRFToken': csrftoken,
                },
            }).then(response => {
                return response.json();
            }).then(response => {
                const p = document.createElement('p');
                p.textContent = response.title;
                postArea.insertBefore(p, postArea.firstChild);
            }).catch(error => {
                console.log(error);
            });
        });
    </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', e => {
            // デフォルトのイベントをキャンセルし、ページ遷移しないように!
            e.preventDefault();

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

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

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

Fetch API

次のようになります。

    <script>
        // 検索ボタンで呼ばれる
        document.getElementById('ajax-search-post').addEventListener('submit', e => {
            // デフォルトのイベントをキャンセルし、ページ遷移しないように!
            e.preventDefault();

            const title = encodeURIComponent(document.getElementById('id_title').value);
            const url = `{% url "app:ajax_post_search" %}?title=${title}`;
            const postArea = document.getElementById('posts');

            fetch(url)
                .then(response => {
                    return response.json();
                }).then(response => {
                    postArea.innerHTML = '';
                    for(const title of response.title_list){
                        const p = document.createElement('p');
                        p.textContent = title;
                        postArea.appendChild(p);
                    }
            }).catch(error => {
                console.log(error);
            });
        });
    </script>

Relation Posts

Comment

記事にコメントする

wanshan

CSRFトークンのおまじないを書いても以下のエラーが出てしまいます。

Forbidden (CSRF token missing or incorrect.): /item/detail/12
[02/Jan/2019 05:57:14] "POST /item/detail/12 HTTP/1.1" 403 2513

解決策としてjsonのデータにトークンを追加しました。

$('#watchstatus').on('submit', e => {
    e.preventDefault();
    $.ajax({
        'url': '{% url "main_app:item_detail" item.id %}',
        'type': 'POST',
        'data': {
          'csrfmiddlewaretoken': '{{csrf_token}}', //←ここを追加
          'status': $('#id_status').val(),
        },
        'dataType': 'json'
    }).done( response => {
        alert('done')
    });

現状特に問題なく動作しているのですが、何故おまじないがきか無かったのかが解りません。 理由は何かあるでしょうか。

返信する

wanshan

すいません、ダブルクリックしてしまい2重投稿してしまいました。 片方は消してください。

なりと

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

の時点で、$.ajaxメソッドにCSRFトークンが付与されます。なので、基本的には何もしなくても動作するようになるのですが、コードに抜けている個所はありませんか。

wanshan

コードの抜け探してみましたが、解りませんでした・・。 おかしな点はあるでしょうか;;

<form action="" method="POST" id="watchstatus">
  {% csrf_token %}
  {{ form.non_field_errors }}
  <label for="{{ field.id_for_label }}">{{ from.status.label_tag }}</label>
  {{ form.status }}
  {{ field.status.errors }}
  <button type="submit" class="btn btn-primary">送信</button>
</form>
<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>
<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);
        }
    }
});
$('#watchstatus').on('submit', e => {
    e.preventDefault();
    $.ajax({
        'url': '{% url "main_app:item_detail" item.id %}',
        'type': 'POST',
        'data': {
          'status': $('#id_status').val(),
        },
        'dataType': 'json'
    }).done( response => {
        alert('done')
    });
});
</script>

なりと

$.ajaxSetupを次のように書き換えると、Ajaxでのデータ送信時にブラウザのコンソールにはhelloworld、そしてcsrftokenの値が表示されますか。

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

wanshan

書き換えてみたところ、ハローもワールドも表示されませんでした。 jqueryの予備出しができてないかと思い、もう一度コピーアンドペーストしてもダメでした。

なりと

$.ajaxSetup() だけが上手く動作していないということだと思うのですが、原因がわかりません。コード自体に問題はないように見えます。

Djangoプロジェクトを送ってもらったほうが早いかもしれません。

名無し

こんにちは、突然横から失礼いたします。 私も別件でAJAXを利用しているものなのですが、2019 1/3 前後から突然動かなくなりました、エラーをたどると、 「https://query.yahooapis.com/v1/public/yql?callback=jQuery3210018922264 省略 6904109232 net::ERR_NAME_NOT_RESOLVED」 となっていて、https://developer.yahoo.com/yql/ を確認すると何やら廃止されるとかの表示が、これと関係があるでしょうか?

返信する

名無し

いつもブログで勉強させていただいております。ありがとうございます。 おそらくjavascriptの質問で恐縮なのですが、<br/><br /> $('#ajax-add-post').on('submit', e => { // デフォルトのイベントをキャンセルし、ページ遷移しないように! e.preventDefault();<br />

のように、responseやeに>= の記号が出てきます。これについて調べているのですが、調べきれませんでした。 等号記号と考えてよろしいでしょうか?それとも別の意味があるのでしょうか? Javascriptでなんと検索すれば関連した資料が得られますか? どうぞよろしくお願いいたします。

返信する

なりと

アロー関数と呼ばれるもので、割と新しい機能です。関数を書く際に、今までより簡単に書けるようになりました。thisの固定化といった機能もあります。

アロー関数で検索すると出て来ます。

名無し

javascriptの=> について質問させて頂いたものです。アロー関数を知らなかったです。ご丁寧に教えていただきありがとうございました。

返信する

名無し

いつも勉強させていただいています。 ボタンを押すと1ずつ数値が増えるいいねボタンを作成していたのですが、ボタンを押すと画面が遷移して一番上に戻ってしまうため、色々な方法を試してまいりましたがajax通信を使うとよいのではとアドバイスをいただき、勉強し非遷移での機能の追加に挑戦していましたが詰まってしまいました。 もしよければアドバイスをいただけると幸いです。

views.py

def good(request, pk):
    """いいねボタンをクリック."""
    post = get_object_or_404(Post, pk=pk)

    if request.method == 'POST':
        # データの新規追加
        post.good += 1
        post.save()

    return redirect('board:board')

urls.py

path('good/<int:pk>', views.good, name='good'),

board.html

    <form id="ajax-add-post" action="{% url 'board:good' post.pk %}" method="post">
        {% csrf_token %}
        <input type="submit" name="good" value="いいね" id="test">({{ post.good }} いいね)<p  style="color:red"><font size="1"><strong>押すと1ページ目まで戻ってしまいます!(いつか直す!)</strong></font></p>
    </form>

とすることでPostモデルにあるgoodフィールドの数値を増やしています。

これを改造しまして、{% block extrajs %}以下を付け加えましたが以下のようなエラーが出てしまいます。


Reverse for 'good' with arguments '('',)' not found. 1 pattern(s) tried: ['board/good/(?P<pk>[0-9]+)$']

コードはこのように改造いたしました。


~~~(前略)~~~


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

    $.ajax({
        'url': "{% url 'board:good' post.pk %}",
        'type': 'POST',
        'data': {
            'title': $('#test').val(),  // いいねボタン
        },
        'dataType': 'json'
    }).done( response => {
    });

});

</script>

良ければアドバイスをお願いいたします。

返信する

なりと

board.htmlpostモデルインスタンスが渡されていないと思われますので、渡してください。

名無し

すみません。書き直しをします。

urls.pyがこのようになっておりまして。

app_name = 'board'
urlpatterns = [
    path('', views.ListView.as_view(), name='board'),
    path('good/<int:pk>', views.good, name='good'),
]

以下ListViewでPostモデルのデータを表示しております。

class ListView(generic.ListView, ModelFormMixin):
    model = Post
    form_class = PostForm
    success_url = reverse_lazy('board:board')
    template_name = 'board/board.html'
    paginate_by = 15


    def get(self, request, *args, **kwargs):
        self.object = None
        return super().get(request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        self.object = None
        self.object_list = self.get_queryset()
        form = self.get_form()
        if form.is_valid():
            return self.form_valid(form)
        else:
            return self.form_invalid(form)


    def get_queryset(self):
        return Post.objects.order_by('-date')


    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['question'] = Question.objects.all()
        return context

エラー内容が

Error during template rendering
In template C:\Users\*****\Desktop\mysite\board\templates\board\base.html, error at line 0

Reverse for 'good' with arguments '('',)' not found. 1 pattern(s) tried: ['board/good/(?P<pk>[0-9]+)$']

となっていたので。 class ListViewの最後に1行追加したり、

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['question'] = Question.objects.all()
        context['post'] = Post.objects.all()    ←追加
        return context

base.html, error at line 0エラーが出ていたので、base.htmlにpostを渡すように以下のような関数を作ってみましたがエラー内容は変わりませんでした。

def get_post(value):
    context = Post.objects.all()
    return render('board/base.html', context)

またいいねボタンの構造ですがurls.pyがこのようになっており

app_name = 'board'

urlpatterns = [
    path('', views.ListView.as_view(), name='board'),
    path('good/<int:pk>', views.good, name='good'),
]

ListViewで表示されるboard.html内にある以下のフォームを押すとaction="{% url 'board:good' post.pk %}"に送信

<form id="ajax-add-post" action="{% url 'board:good' post.pk %}" method="post">
    {% csrf_token %}
    <input type="submit" name="good" value="いいね" id="test">({{ post.good }} いいね)<p  style="color:red"><font size="1"><strong>押すと1ページ目まで戻ってしまいます!(いつか直す!)</strong></font></p>
</form>

以下の関数が呼び出されて数値が1増える。という構造になっております。


def good(request, pk): 
    """いいねボタンをクリック."""
    post = get_object_or_404(Post, pk=pk)

    if request.method == 'POST':
        # データの新規追加
        post.good += 1
        post.save()
        context= {
            'data': post.good
        }
    return redirect('board:board')

こちらの方も関数をこのように改造してみて

def good(request, pk):
     """いいねボタンをクリック."""

    post = get_object_or_404(Post, pk=pk)

    if request.method == 'POST':
        # データの新規追加
        post.good += 1
        post.save()
        context= {
            'data': post.good
        }
    return JsonResponse(context)
#return redirect('board:board')

jquery部分も以下のように改造してみましたが変わりませんでした。

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

    $.ajax({
        'url': "{% url 'board:good' post.pk %}" //【どのサーバーに?(サーバーのURLなど)】,//いいねボタンの数値を管理しているURLは'board:good' post.pkだがこれを入力するとエラーが出てしまう。
        'type': 'POST',
        'data': {
            'data': $('#test').val(),  // いいねボタン
        },
        'dataType': 'json'
    }).done( response => {
        //ここでid="test">({{ post.good }} いいね)を+1したい!
        $('#test').val(post.good);
    });
});
</script>
{% endblock %}

JavaScriptもまだ勉強中なので申し訳ないのですがよろしくお願いいたします。

なりと

base.htmlboard.htmlの内容を教えてください。

名無し

base.htmlがこちら

{% load static %}
<link rel="stylesheet" type="text/css" href="{% static 'board/style.css' %}">
<!doctype html>
<html lang="ja">
  <head>







  <title>湘南ベルマーレスーパー掲示板(仮)</title>
  <h4>湘南ベルマーレスーパー掲示板(仮)</h4>

  <!-- jQuery -->
  <script src="/jquery.min.js"></script>

    <!-- 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://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/css/bootstrap.min.css" integrity="sha384-PsH8R72JQ3SOdhVi3uxftmaW6Vc51MKb0q5P2rRUpPvrszuE4W1povHYgTpBfshb" crossorigin="anonymous">
  {% if GOOGLE_ANALYTICS_TRACKING_ID %}
  {% include 'board/ga.html' %}
  {% endif %}
  {% block extracss %}{% endblock %}
  </head>
  <body>

{% block content %}   {% endblock %}


<!-- 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>


board.htmlがこちら


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

{% block content %}

{% load static %}
<link rel="stylesheet" type="text/css" href="{% static 'board/style.css' %}">


<div class="alert alert-primary" role="alert">
<strong><p class="w-normal"><a href="http://160.16.55.98/polls/">現在投票中のアンケート一覧{% for questions in question %}{% if questions.is_date_limit %}<li>{{ questions.question_text }}{% endif %}{% endfor %}</li></a></p></strong>
<!--- サーバー環境で書き換え <strong><p class="w-normal"><a href="http://localhost:8000/polls/">現在投票中のアンケート一覧{% for questions in question %}{% if questions.is_date_limit %}<li>{{ questions.question_text }}{% endif %}{% endfor %}</li></a></p></strong> --->
</div>



<div class="mb-5">
<div class="ml-md-2">
  <form action="" method="POST" enctype="multipart/form-data">
    {{ form.as_p }}
  <button type="submit">送信</button>
    {% csrf_token %}
  </form>


</div>
</div>

{% for post in post_list %}



  <div class="alert alert-success" role="alert"><p class="w-normal"><strong>{{ post.name }}</strong> さん</p></div>
  <div class="mb-2">
    <div class="ml-md-2">
    <p class="w-normal">{{ post.text | linebreaksbr }}</p>
    </div>
      {% if post.file %}
        <p><img src="{{ post.file.url }}"></p>

      {% endif %}

  <div class="ml-md-2">
        <p>{{ post.date }}</p>


    <form id="ajax-add-post" action="{% url 'board:good' post.pk %}" method="post">
        {% csrf_token %}
        <input type="submit" name="good" value="いいね" id="test">({{ post.good }} いいね)<p  style="color:red"><font size="1"><strong>押すと1ページ目まで戻ってしまいます!(いつか直す!)</strong></font></p>
    </form>
</div>


{% endfor %}

{% include 'board/page4.html' %}

</div>



{% 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', e => {
// デフォルトのイベントをキャンセルし、ページ遷移しないように!  
e.preventDefault();

$.ajax({
'url': "{% url 'board:good' post.pk %}" //【どのサーバーに?(サーバーのURLなど)】,//いいねボタンの数値を管理しているURLは'board:good' post.pkだがこれを入力するとエラーが出てしまう。
'type': 'POST',
'data': {
    'data': $('#test').val(),  // いいねボタン
},

'dataType': 'json'
}).done( response => {
  //ここでid="test">({{ post.good }} いいね)を+1したい!
    $('#test').val(post.good);


});

});

</script>


{% endblock %}


となっております。よろしくお願いいたします。

なりと

$.ajax内で'url': "{% url 'board:good' post.pk %}"としていますが、ここでエラーが起きています。この部分は{% for post in post_list %}の外なので、postという名前が解決できません。

例えば、次のように実装できます。まず、各フォームに何かしらのclass名を入れておきます。

<form action="{% url 'board:good' post.pk %}" method="post" class="ajax-add-post">

すべてのフォームに対して、イベントを設定します。

for (const form of document.getElementsByClassName('ajax-add-post')) {

    // 送信ボタンで呼ばれる
    form.on('submit', e => {

        // デフォルトのイベントをキャンセルし、ページ遷移しないように!  
        e.preventDefault();

        $.ajax({
        ...
        ...

$.ajax内の'url'を、そのフォームのaction属性の値にします。

'url': form.action

名無し

お世話になっております。 ありがとうございます。教えていただいた通りコードを書いたところエラーが無くなりました。

ただまだ少し問題がありまして 前述の通りviews.pyの方をこのようにreturnとして

    if request.method == 'POST':
        # データの新規追加
        post.good += 1
        post.save()
        context= {
                'data': post.good
                }

    return JsonResponse(context)

として htmlの方に

e.preventDefault();

のようにしているのですが送信フォームを押すと<br> http://127.0.0.1:8000/board/good/55<br> のようなactionで指定したURLに遷移し<br> {"data": 16}<br> というように数字が表示されます。<br> 機能としてはこれは正しいのですがajaxという技術は<br> http://127.0.0.1:8000/board/good/55<br> のようなactionで指定したページには遷移せずに<br>

<input type="submit" name="good" value="いいね" id="test">({{ post.good }} いいね)<p  style="color:red"><font size="1"><strong>押すと1ページ目まで戻ってしまいます!(いつか直す!)</strong></font></p>

上記の部分の数値が15→16に遷移すると思っていたので、今回のプログラムでは違うアプローチをする必要があるのだろうかと悩んでいます。 それともアプローチは正しいがまだ上手く実装できていないということなのでしょうか? もしよければご教示いただけると幸いです。

返信する

なりと

form.on('submit', e => {の中身が呼ばれていないのではないでしょうか。

class属性に何らかの文字列を指定していることを確認してください。今回はid属性は使えません

<form action="{% url 'board:good' post.pk %}" method="post" class="ajax-add-post">

そして、設定したクラス属性のフォームに対してイベントを設定していることも確認してください。上でajax-add-postとしたならば、getElementsByClassNameの中身がajax-add-postになります。

for (const form of document.getElementsByClassName('ajax-add-post')) {

また、IE11や古いブラウザでは今回のコードは動作しません。

名無し

お世話になっております。このようにclassに修正いたしまして、

    <form  action="{% url 'board:good' post.pk %}" method="post" class="ajax-add-post">
        {% csrf_token %}
        <input type="submit" name="good" value="いいね" id="test">({{ post.good }} いいね)<p  style="color:red"><font size="1"><strong>押すと1ページ目まで戻ってしまいます!(いつか直す!)</strong></font></p>
    </form>
</div>

以下のようにajaxを設定しました。

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

$.ajax({
'url': form.action,//【どのサーバーに?(サーバーのURLなど)】
'type': 'POST',
'data': {
    'data': $('#test').val(),  // いいねボタン
},
'dataType': 'json'
}).done( response => {
  //ここでid="test">({{ post.good }} いいね)を+1したい!
    $('#test').val(post.good);


  });


このように記載しましたが、やはりページが遷移してしまい、 {"data": 16}と表示が出る状態になりました。

views.pyは

def good(request, pk):
    """いいねボタンをクリック."""
    post = get_object_or_404(Post, pk=pk)

    if request.method == 'POST':
        # データの新規追加
        post.good += 1
        post.save()
        context= {
                'data': post.good
                }

    return JsonResponse(context)
    #return redirect('board:board')


このようになっており。コメントアウトされた

return redirect('board:board')

を使用すると普通にリダイレクトするのでやはり、

e.preventDefault();


以下が実行されていないなという印象は持っております。

jquery事態の知識が薄いので以下コードの

'data': {
    'data': $('#test').val(),  // いいねボタン
},
'dataType': 'json'
}).done( response => {
  //ここでid="test">({{ post.good }} いいね)を+1したい!
    $('#test').val(post.good);


  });



いいねに+1をするプログラムも何かしらおかしいとは思うのですが、今回はそれ以前の問題だと思うので。 私も今javascriptを学習して解決方法を見出そうとはしているので、もし解決できる方法が分かるようでしたらご教授いただけると幸いです。

名無し

またブラウザですが、chromeの最新版でしたのでブラウザの影響はないのではないかと考えております。

なりと

form.on('submit', e => {内にreturn false;というコードも追加して確認してみてください。それでもページ遷移する場合は書いているコードに誤りがあります。ブラウザの開発者ツールのコンソールにエラーが出ないことも確認してください。

名無し

お世話になります。 consoleのエラーはですがbase.htmlの

 {% block extrajs %}{% endblock %}

を削除したところ Uncaught SyntaxError: Unexpected end of input

というエラーが出なくなりました。

hfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous"></script>
    {% block extrajs %}{% endblock %} ←ここを削除

しかし

for (const form of document.getElementsByClassName('ajax-add-post')) {
// 送信ボタンで呼ばれる
form.on('submit', e => {
// デフォルトのイベントをキャンセルし、ページ遷移しないように!

e.preventDefault();
return false;
$.ajax({

と書き換えましたが同様に遷移してしまいました。

ボタンを押した瞬間にconsoleでは

Resource interpreted as Document but transferred with MINE type application/json: "https//127.0.0.1:8000/board/good/55"

と表示されその瞬間にページが切り替わりました。

結局今いろいろコードをいじっているページは urls.pyでは

`path('', views.ListView.as_view(), name='board'),


というリストビューであり、


Jsonを使った関数は

url.pyでは

path('good/<int:pk>', views.good, name='good'),

```

というurlで、場所が異なっているのでそういう部分にも原因があるのかなとも思っております。 (だたこのような構成にしないとプライマリーキーごとに数値を増やせないのですが・・・)

大学生

いつも、お世話になっています。

DjangoでAjaxを使いたいと思い、この記事を参考に勉強させていただいている者です。 記事の追加処理で、Ajaxを用い、POSTとしてデータを送るとのことですが、以下のようなエラーが出てしまいました。 非常に初歩的なこととは、思いますが、何卒ご教授いただけると助かります。

[20/Mar/2020 11:23:17] "GET /ajax/ HTTP/1.1" 200 3696 Method Not Allowed (POST): /ajax_post_add/ Method Not Allowed: /ajax_post_add/ [20/Mar/2020 11:23:20] "POST /ajax_post_add/ HTTP/1.1" 405 0

返信する

なりと

ビューとテンプレートファイルの内容を教えてください

大学生

ご返信ありがとうございます。 以下が、関連するファイルになります。 同じアプリ内に、Narito様の「 Django、選択した親カテゴリに合わせて子カテゴリを表示 」という記事のコードも入っています。

何卒よろしくお願いします。

views.py

from django.views import generic
from .forms import PostCreateForm
from .models import Post, ParentCategory, PostAjax
from django.http import JsonResponse


class PostCreate(generic.CreateView):
    model = Post
    form_class = PostCreateForm
    success_url = '/'  # reverse_lazy等のほうが良い。これは手抜き

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['parentcategory_list'] = ParentCategory.objects.all()
        return context


class PostList(generic.ListView):
    model = PostAjax


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

    return JsonResponse(d)

template (postajax_list.html)

{% extends '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 'develop: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', e => {
            // デフォルトのイベントをキャンセルし、ページ遷移しないように!
            e.preventDefault();

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

        });
    </script>


{% endblock %}

models.py

from django.db import models


class ParentCategory(models.Model):
    name = models.CharField('親カテゴリ名', max_length=225)

    def __str__(self):
        return self.name


class Category(models.Model):

    name = models.CharField('カテゴリ名', max_length=225)
    parent = models.ForeignKey(ParentCategory, verbose_name='親カテゴリ', on_delete=models.PROTECT)

    def __str__(self):
        return self.name


class Post(models.Model):
    title = models.CharField('タイトル', max_length=225)
    category = models.ForeignKey(Category, verbose_name='カテゴリ', on_delete=models.PROTECT)

    def __str__(self):
        return self.title


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

    def __str__(self):
        return self.title

urls.py


from django.urls import path
from . import views

app_name = 'develop'

urlpatterns = [
    path('', views.PostCreate.as_view(), name='post_create'),
    path('ajax/', views.PostList.as_view(), name='ajax'),
    path('ajax_post_add/', views.PostList.as_view(), name='ajax_post_add'),
]

なりと

urls.pyを次のように修正してください。ajax_post_addという名前で、PostListビューが呼ばれていました。

from django.urls import path
from . import views

app_name = 'develop'

urlpatterns = [
    path('', views.PostCreate.as_view(), name='post_create'),
    path('ajax/', views.PostList.as_view(), name='ajax'),
    path('ajax_post_add/', views.ajax_post_add, name='ajax_post_add'),
]

大学生

Narito様 ご返信ありがとうございます。

無事、Ajaxを使ったPOST処理を行うことができました。 非常に初歩的な間違いで、お手を煩わせてしまい、大変恐縮です。 素早い返信とご指摘、ありがとうございました。 引き続き、Narito様の記事を参考に勉強させて頂きます。 この度は、本当にありがとうございました。