はじめに
Django REST frameworkとVue.jsが気になったので、組み合わせて動かしてみた。
構成・動作概要
Djangoのhtmlのテンプレート機能は使わずに、静的ファイルとしてHTMLデータを返却し、ブラウザからVue.jsでRestAPI経由でデータを取得した情報の一覧を表示してみる。
(DBはSQLite3、サンプルデータは自分の記事のストック数を使った。)
動作環境
PythonはPython 3.5.1
パッケージは以下
# pip3 freeze
Django==1.10.5
django-filter==1.0.1
djangorestframework==3.5.3
Vue.jsとaxiosはCDNで取ってきていて、投稿時点(2017/1/30)のバージョンは以下。
- Vue.js: 2.1.10
- axios: 0.15.3
試す
とりあえずDjangoプロジェクト作成
いつもの。
# django-admin startproject qiitalist
# cd qiitalist/
# python manage.py startapp stocks
完成形
こんな感じの構成になった。①~⑥個別に実装を見ていきます。(▲=今回使わないファイル)
qiitalist
│ fixture.json # ②モデル追加
│ manage.py
│
├─qiitalist
│ │ settings.py # ①設定追加
│ │ urls.py # ⑤URL設定追加
│ │ wsgi.py # ▲
│ │ __init__.py
│ │
│ └─static # ⑥静的ファイル作成
│ vue_grid.css
│ vue_grid.html
│ vue_grid.js
│
└─stocks
│ admin.py # ▲
│ apps.py # ▲
│ models.py # ②モデル追加
│ tests.py # ▲
│ serializer.py ③シリアライザ追加
│ urls.py # ⑤URL設定追加
│ views.py # ④View追加
│ __init__.py
│
└─migrations
①設定追加
実装するアプリと、REST Frameworkの設定を追加。
# 追加分だけ抜粋
# アプリの追加
INSTALLED_APPS += [
'stocks',
'rest_framework',
]
# REST API設定(フィルタ、ページング)
REST_FRAMEWORK = {
'DEFAULT_FILTER_BACKENDS': ('rest_framework.filters.DjangoFilterBackend',),
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
'PAGE_SIZE': 5
}
# views.pyで使うパスを定義
PACKAGE_ROOT = os.path.abspath(os.path.dirname(__file__))
STATICFILES_DIRS = (os.path.join(PACKAGE_ROOT, 'static'),)
②モデル追加
REST APIで公開するデータモデル、及び初期データ(フィクスチャ)を準備。
from django.db import models
class Stock(models.Model):
class Meta:
db_table = "stock"
id = models.AutoField(primary_key=True)
title = models.TextField()
stock_count = models.IntegerField()
[
{
"model": "stocks.stock",
"pk": 1,
"fields": {
"title": "Pythonで機械学習はじめました(Qiitaへの投稿もはじめました) データ準備編",
"stock_count": 29
}
},
{
"model": "stocks.stock",
"pk": 2,
"fields": {
"title": "ScrapyとDjangoでラーメンマップ作成",
"stock_count": 23
}
},
{
"model": "stocks.stock",
"pk": 3,
"fields": {
"title": "Pythonで機械学習はじめました データ前処理編",
"stock_count": 22
}
},
{
"model": "stocks.stock",
"pk": 4,
"fields": {
"title": "PythonでRabbitMQメッセージの通知アプリ with Growl ~ラズパイとJuliusを添えて~",
"stock_count": 3
}
}
]
③シリアライザ追加
モデルをシリアライズするSerialzierの定義を追加。
from rest_framework import serializers
from stocks.models import Stock
class StockSerializer(serializers.ModelSerializer):
class Meta:
model = Stock
fields = ("id", "title", 'stock_count')
④View追加
Viewの定義を追加。
import os
from django.conf import settings
from django.http.response import HttpResponse
from rest_framework import viewsets
from stocks.models import Stock
from stocks.serializer import StockSerializer
# 静的ファイルを返すView
def index(_):
# render等でDjangoのtemplateとして処理すると「{{}}」がVue.jsに渡る前消えてしまう。
# 良い解決方法が浮かばなかったので、static配下に置いたファイルをopenして投げることで回避。
html = open(
os.path.join(settings.STATICFILES_DIRS[0], "vue_grid.html")).read()
return HttpResponse(html)
# RestAPIのviewsets
class StockViewSet(viewsets.ModelViewSet):
queryset = Stock.objects.all()
serializer_class = StockSerializer
# APIのフィルタで使えるフィールドを指定
filter_fields = ("id", "title", 'stock_count')
⑤URLの設定追加
親(qiitalist)と子(stocks)で一応分割。
from django.conf.urls import include, url
from stocks.urls import urlpatterns as qiitalist_url
urlpatterns = [
url(r'^qiita/', include(qiitalist_url)),
]
from django.conf.urls import include, url
from rest_framework import routers
from stocks.views import StockViewSet, index
router = routers.DefaultRouter()
router.register(r'stock', StockViewSet)
urlpatterns = [
# qiita/api/stock/
url(r'api/', include(router.urls)),
# qiita/
url(r'', index, name='index'),
]
⑥静的ファイル作成
https://jp.vuejs.org/v2/examples/grid-component.htmlのグリッドコンポーネントの例を真似します。
/* コピペなので割愛 */
//上部のコンポーネント実装部分はコピペなので割愛
//new Vue部分だけRestAPIから指定のデータを取得するように改造
var demo = new Vue({
el: '#demo',
data: {
searchQuery: '',
gridColumns: ["id", "title", 'stock_count'], // 変更
gridData: [] // データはAPIで取ってくるので削除
},
created: function(){ //RestAPIから取ってきてgridDataに追加する。
var self = this //スコープ的に必要っぽい(this.gridData.pushではエラーになる。)
axios.get('/qiita/api/stock/')
.then(function(response){
for(var i = 0; i < response.data.results.length; i++){
self.gridData.push(response.data.results[i]);
}
});
}
})
<!-- vue_grid.htmlは表示項目に合わせてちょっと改造。 -->
<!DOCTYPE html>
<html>
<head>
<title>Welcome to Vue</title>
<!-- CSS読み込み -->
<link rel="stylesheet" type="text/css" href="/static/vue_grid.css">
<!-- JS(CDN)読み込み -->
<!-- Vue.js -->
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<!-- Axios(vue-resourceの代わり) -->
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
</head>
<body>
<script type="text/x-template" id="grid-template">
<table>
<thead>
<tr>
<th v-for="key in columns"
@click="sortBy(key)"
:class="{ active: sortKey == key }">
{{ key | capitalize }}
<span class="arrow" :class="sortOrders[key] > 0 ? 'asc' : 'dsc'">
</span>
</th>
</tr>
</thead>
<tbody>
<tr v-for="entry in filteredData">
<td v-for="key in columns">
{{entry[key]}}
</td>
</tr>
</tbody>
</table>
</script>
<div id="demo">
<form id="search">
Search <input name="query" v-model="searchQuery">
</form>
<demo-grid
:data="gridData"
:columns="gridColumns"
:filter-key="searchQuery">
</demo-grid>
</div>
</body>
<!-- JS読み込み -->
<script src="/static/vue_grid.js"></script>
</html>
動かしてみる。
DBを作成。
# python manage.py makemigrations
# python manage.py migrate
初期データを投入。
# python manage.py loaddata fixture.json
サーバを起動。
# python manage.py runserver
ブラウザからAPIコンソールhttp://localhost:8000/qiita/api/
にアクセス。
http://localhost:8000/qiita/api/stock/
でstockの一覧取得。
(①のページング設定で最大5件まで取得して表示 ※注 登録したStockは4件なので特に意味は無し)
http://localhost:8000/qiita/
にアクセスして表示してみる。
所感
とりあえず動く所までは作れたので、勉強する意欲が沸き始めた。
Vue.jsはほぼ写経だけど、AngularJS、React.jsよりも使いやすい(直感的)気がした。
扱うデータが件数少ないのが致命的。面白くないのでどんどん記事を増やそう…。