知識の枝

"All is well"

DjangoでAxiosを使い非同期通信を行う

約50日前 2021年9月8日0:34
デジタル
Django JavaScript Vue.js

改訂履歴


2021/9/8 投稿

1. Djangoと非同期通信


非同期通信って何?という方はこちらの記事をご覧下さい。
リンク
非同期通信とは?


Djangoで非同期通信を行いたい場合、Javascript(node.js)系の「Axios」という選択肢があります。

以前紹介した非同期通信方法(上記リンク先)は「Ajax」という、これも同じくJavascript(jQuery)系の方法でした。


何が違うの?どっちを使えばいいの?という疑問があると思います。


1.1 - 何が違うの?


結論を言うと「できることは同じ」です。

プログラムの書き方も似ています。
(そもそもやりたいことが同じですので。)

POST処理を行う際のcsrf対策に関する記述はAxiosのほうが短く書けます。


1.2 - どっちを使えばいいの?


正直「どっちでも良い」」です。

Ajaxで実装しても問題無いですし、AxiosでもOKです。

ただ歴史的に新しいのはAxiosです。


Axiosが出た当初はAjaxとの大きな違いとして「Promiseベース」で書かれていることが挙げられていました。

現在となってはAjaxでもPromiseベースで書けるようなので、ピンと来たほうを選べば良いと思います。


1.3 - あえて紹介する理由は?


Vue.jsの推奨だったからです。

以前、DjangoでフロントエンドのWEBフレームワークを使うということでVue.jsを導入する記事を書きました。
リンク
Django向けVue.js 「導入」編

Vueでフロントを書く際に

「そういえばVue.jsで非同期通信ってどうやるんだろ?」と思って調べました。


その結果、Vue.js自体が提供する非同期通信手段は無く、代わりに「Axios」を推奨していることが分かりました。
リンク
Vue.js 公式ドキュメント - axios を利用した API の使用

「Axios」は初耳でしたので導入方法を調べてみた次第です。


Axiosの公式ドキュメントによると

AxiosのベースになったのはAngularJSの$httpで、位置づけとしては「Angularを使わなくても使用可能な$http」のようです。

フレームワークの便利な機能をフレームワークの外側で汎用的に使えるようにしました。

ということですね。


2. Axiosの使い方


Django + Vue.jsの環境で使う前提で解説を進めます。

が、Axios自体はただのJavascriptなので環境が異なる方でも使用可能です。

また、バックエンド(例えばDjango)にリクエストを投げる際の書き方は、別のバックエンド系のフレームワークでも同じですので、流れとしては参考になると思います。


2.1 - CDNでお手軽に導入


AxiosはCDNでも提供されていますので、下記一文をhtmlファイルに記述するだけで導入可能です。
.html
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
これが一番手っ取り早いですね。


2.2 - VueでAxiosを使う


Vue.js環境でAxiosを使う例を挙げます。

記述方法はVue3ベースです。
.html
<body>
<div id="test-area">
<p>[[text]]</p>
</div>
</body>

<script>
const testVue = {
data() {
return {
text: "App text.",
}
},
delimiters: ['[[', ']]'],
mounted() {
axios.defaults.xsrfCookieName = 'csrftoken'
axios.defaults.xsrfHeaderName = "X-CSRFTOKEN"
axios({
method: 'POST',
url: 'http://127.0.0.1:8000/',
data:{info:'request_payload_data'},
params: {info:"query_string_data"},
responseType: 'json'
}).then(response => this.text = response.data)
.catch(error => console.log(error))
}
}

const app = Vue.createApp(testVue)

app.mount("#test-area")
</script>
まずAxiosに関係しているのは
mounted(){
// Axios関係
}
の部分です。

それ以外の部分はVue.jsの記述です。

mount()はVue.jsのライフサイクルフックの1つで

「Vue appが特定のエレメントにマウントされたとき」波括弧{}内のスクリプトが実行されます。

今回の場合は実行されるスクリプトが「Axiosの非同期通信」です。


app.mount()によって<div id="test-area">の要素に対し「testVue」appがマウントされ、この要素内では「text」という変数が使えるようになります。

その変数「text」を<body>内で[[text]]として表示します。


補足
「delimiters」について

上記で出てきた [[text]]という表記。

このtextを囲む記号をdelimiter(デリミター)と呼びます。

直訳で「区切り文字」という意味。

変数をhtml上に表示させる際に使われる記法で、Vue.jsではデフォルトでマスタッシュ記法と呼ばれる {{text}} 波括弧2つで囲う方法が採用されています。

しかしDjangoユーザーならお分かりの通り、この記法はDjangoのテンプレート変数の記法 {{ }}と被っています。


対策無しでは互いにバッティングし、変数が上手く表示されません。

そこで下記オプションを付けてVueのデリミターを{{ }}から[[ ]]に変更しています。
delimiters: ['[[', ']]']



Vue.jsの中でAxiosを使う準備ができました!

それでは動作確認を行いながら解説していきましょう。


3. Axiosの動作


先程書いたAxiosの部分を抜粋します。
axios.defaults.xsrfCookieName = 'csrftoken'
axios.defaults.xsrfHeaderName = "X-CSRFTOKEN"
axios({
method: 'POST',
url: 'http://127.0.0.1:8000/',
data:{info:'request_payload_data'},
params: {info:"query_string_data"},
}).then(response => this.text = response.data)
.catch(error => console.log(error))
最初の2文はcsrf対策に関する記述です。

POSTリクエストを行う際は必要です。GETリクエストでは不要です。

method: 'POST'でリクエストメソッドにPOSTを指定します。

「url」にはリクエストを送るURLを記載します。

ここではDjangoの「runserver」コマンドで立ち上がる開発サーバーの「http://127.0.0.1:8000/」を指定しています。


続いてサーバーへデータを送りたいときに使用する2つのオプションです。

「data」はPOSTリクエスト時に使用可能なオプションで、下記のように"KEY", "VALUE"でデータを送ります。
data:{KEY:'VALUE'}

ここに入れられたデータは「Request Payload」としてサーバーへ送られます。

今回は「info」というキーに「request_payload_data」というバリューを入れました。


「params」はGET, POSTどちらでも使用可能なオプションで、クエリストリングを使った方法です。

記述ルールはdataと同じです。

GETリクエストでサーバーへデータを送りたい場合は、このクエリストリングを使用することになります。

ちなみに、上記例のようにPOSTリクエストでは「data」「params」どちらも同時に使うことができます。

今回は「info」というキーに「query_string_data」というバリューを入れました。


「.then」以降はサーバーからレスポンスが来てから実行される部分ですので、今は飛ばします。



さて、AxiosでPOSTリクエストを送ったので、Djangoでリクエストを受け取って処理しましょう。

まずはurls.pyで「http://127.0.0.1:8000/」にリクエストがあった場合にどのビューを実行するか指示します。
urls.py
urlpatterns = [
path('', views.IndexView, name='index'),
]

IndexViewをviews.pyに作ります。
views.py
def IndexView(request):
template = "vue_test/vue_index.html"

if request.method == "POST":
get_data = request.GET.get("info")
post_data = json.loads(request.body).get("info")
return JsonResponse({'get_data': get_data, 'post_data': post_data})

context = {
'django': "This is Django text.",
}

return render(request, template, context)


htmlは下記のように作っておきます。
vue_index.html
<p>{{django}}</p>
<br>
<div id="test-area">
<p v-for="(data, key) in text">[[key]]:[[data]]</p>
</div>


では早速「http://127.0.0.1:8000/」にアクセスしてみます。



リクエストで送ったデータがしっかり画面に表示されていますね!


3.1 - データの受け取り方


ビューの中で送られてきたデータを取得します。

基本的には下記の記事で解説した方法で取得可能です。
リンク
リクエストとデータのやりとり

クエリストリングは下記のように取り出しています。
get_data = request.GET.get("info")
"info"の中身は"query_string_data"ですので、変数「get_data」には「query_string_data」が入ります。

若干ハマりポイントなのがPOSTデータの受け取りです。

上記の記事の方法だと下記のコードで取り出せそうです。
request.POST.get("info")
が、この方法では取得できません。

POSTデータが「フォームで入力されてsubmitされた場合」は上記のコードで取得可能です。


しかし、今回のAxiosやAjaxといった「フォームを使わずに送信する方法」の場合はデータが「request.body」に格納されるので、別の方法をとる必要があります。

これはDjangoの公式ドキュメントにも記載されています。
引用文

HttpRequest.POST
A dictionary-like object containing all given HTTP POST parameters, providing that the request contains form data. If you need to access raw or non-form data posted in the request, access this through the HttpRequest.body attribute instead.

Django - リクエストとレスポンスのオブジェクト


「request.body」からデータを取り出しますがもう一点注意すべき箇所があります。

「request.body」の中にはデータが「バイト文字列」で入っています。

バイト文字列のままではget()メソッドが使えずデータが取り出せないので、辞書型に変換します。

ここでは「json.loads()」を使用して辞書型に変換してからget()メソッドで"info"の中身を取得しています。
post_data = json.loads(request.body).get("info")
"info"の中身は"request_payload_data"ですので、変数「post_data」には「request_payload_data」が入ります。

request.bodyの中身がバイト文字列であることはDjangoの公式ドキュメントに記載されています。
(ここに気付かなくて、なぜバイト文字列になってしまうのか長々調べてました。。。涙)
引用文

HttpRequest.body
The raw HTTP request body as a bytestring. This is useful for processing data in different ways than conventional HTML forms: binary images, XML payload etc. For processing conventional form data, use HttpRequest.POST.

Django - リクエストとレスポンスのオブジェクト


Django側の作業はあと少しです!

取得したデータをhtmlに返してあげます。

Axiosはデフォルトでjson形式のデータをやり取りするので、Djangoの「JsonResponse」でリターンします。

JsonResponseの引数には先程取得したデータを辞書形式で渡します。
views.py
return JsonResponse({'get_data': get_data, 'post_data': post_data})



3.2 - Axiosでデータを受け取る


先程飛ばした下記部分の説明に移ります。
.then(response => this.text = response.data)
.catch(error => console.log(error))
1行目の「.then()」はリクエストは成功してレスポンスを受け取ったときの挙動。

2行目の「.catch()」はリクエスト~レスポンスでエラーが発生した場合の挙動。


今回は成功しているので1行目のみ実行されます。

1行目の説明です。
.then(response => this.text = response.data)
「response」にDjangoからのレスポンスが入っています。

レスポンスは下記のような構成になっています。



構成
・config
・data ←今回必要なのはコレ
・headers
・request
・status
・statusText

「data」の中にはJsonResponseの引数に指定したデータが入っています。



ですので、responseからdataを取り出してhtmlで使えるようにします。
this.text = response.data


「this.text」というのは、これです。
data() {
return {
text: "App text.",
}
}
Vue appで定義した部分ですね。この値をresponse.dataで上書きしています。

そして書き換えた「text」をhtmlで表示しています。
<p v-for="(data, key) in text">[[key]]:[[data]]</p>



これで全体の処理が完了です。



4. Axiosまとめ


やっていることは

「リクエストをしてレスポンスを受け取る」

ただこれを非同期通信で行っているだけですので、恐れる必要はありません。

Ajaxを使ったことがある方であれば、すんなり理解できたと思います。

お疲れ様でした。