概要
フロント:Nuxt
バックエンド:Django REST Framework(以下DRF)
で新しいプロジェクトなどを立ち上げた時に設定などをいちいち調べるのがめんどくさくなったため、まとめておきます。
ただ、設定ファイルだけを書くのでは個人的に記事として物足りない。
なので、ついでに基本的な操作であるAPIを叩いてCRUDを行い、会員登録を実装するところまで書く予定です。
なお、今回はDBはSQLiteを使います。PostgreSQLやMySQLなど他のRDBが使いたい場合はDRF側のDBの設定を書きかえるだけなので、そちらの設定は各自よろしくお願いいたします。
この記事では、NuxtからDRFで作ったAPIを叩いて商品の詳細情報をGETするところまでです。
ソースコードはこちらにあるのでわからないところは適宜見てください。
また、Twitterかこの記事のコメント欄でわからないところを聞いていただけると答えられます。
バックエンド
まずはおなじみのプロジェクト作成からです。
DRF側からの設定から整えていきましょう。
$ mkdir nuxt-django
$ cd nuxt-django
$ django-admin startproject server
$ cd server
$ django-admin startapp practice
まずは設定ファイル変更していきましょう。
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'practice', # 追加
'rest_framework', # 追加
'corsheaders', # 追加
]
......
# 追加
# 許可するオリジン
CORS_ORIGIN_WHITELIST = (
'http://localhost:3000',
'http://127.0.0.1:3000',
)
CORS_ORIGIN_ALLOW_ALL = True
......
# 以下変更及び追加
# Internationalization
# https://docs.djangoproject.com/en/2.2/topics/i18n/
LANGUAGE_CODE = 'ja-JP'
TIME_ZONE = 'Asia/Tokyo'
USE_I18N = True
USE_L10N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.2/howto/static-files/
STATIC_URL = '/static/'
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
INSTALLED_APPS
に追加したのはstartapp
で追加したアプリとDRFを利用する上で必須のrest_framework
、そしてaxiosでアクセスするために必要なcorsheaders
です。
CORS_ORIGIN_WHITELIST
というのはどこからのアクセスならばアクセスを許可するのかを設定するために書く必要があります。
その後のLANGUAGE_CODE
とTIME_ZONE
を変更することで日本語対応をします。
最後のMEDIA...
というのはこの後画像を取り扱うために追加しました。画像ファイルを使用しない場合は必要ありませんが、多くの場合使うことになるので記述しておくことをお勧めします。
from django.contrib import admin
from django.urls import path,include # 追加
from django.conf.urls.static import static # 追加
from django.conf import settings # 追加
urlpatterns = [
path('admin/', admin.site.urls),
path('api/',include('practice.urls')),
]
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL,document_root=settings.MEDIA_ROOT)
from django.db import models
# Create your models here.
CATEGORY = (
(1, '飲料'),
(2, '生花'),
)
class Item(models.Model):
name = models.CharField(verbose_name='商品名',max_length=255,null=False)
category = models.IntegerField(verbose_name='カテゴリ',choices=CATEGORY)
image = models.FileField(verbose_name='画像')
price = models.PositiveIntegerField(verbose_name='画像',null=False)
description = models.TextField(verbose_name='詳細',blank=True,null=True)
def __str__(self):
return self.name
今回はよくある商品のモデルを作ってみました。
そしてこのアイテムを扱うためのシリアライザーを作成します。
from rest_framework import serializers
from .models import Item
class ItemSerializer(serializers.ModelSerializer):
class Meta:
model = Item
fields = '__all__'
そしてこれをルーティングするためにviews.pyとurls.pyを書きます。
from django.shortcuts import render
from rest_framework import viewsets,filters,permissions,generics
from .serializers import ItemSerializer
from .models import Item
# Create your views here.
class ItemListView(generics.ListAPIView):
serializer_class = ItemSerializer
queryset = Item.objects.all()
class ItemDetailView(generics.RetrieveAPIView):
serializer_class = ItemSerializer
queryset = Item.objects.all()
from django.urls import path
from .views import ItemListView,ItemDetailView
urlpatterns = [
path('items/',ItemListView.as_view()),
path('items/<int:pk>/',ItemDetailView.as_view()),
]
最後にadminでデータを扱えるように以下を追加します。
from django.contrib import admin
from .models import Item
# Register your models here.
admin.site.register(Item)
なぜItemListView
とItemDetailView
を分けているのかについてですが、権限周りを実装する際に分けておくと使いやすくなるという点と、後々分ける必要が出てくるからです。
ここまでで基本的なバックエンドの設定は完了しました。
$ python manage.py makemigrations
$ python manage.py migrate
$ python manage.py createsuperuser
ユーザー名 (leave blank to use 'kuehar'): kueharx
メールアドレス:
Password:
Password (again):
Superuser created successfully.
$ python manage.py runserver
ローカルサーバを起動した状態で
http://localhost:8000/admin
にアクセスし、こんな感じにデータを追加してみましょう。
その後に
http://localhost:8000/api/items
にアクセスすると以下のような画面が表示されるはずです。
この段階でフロントエンドからGETするためのバックエンドの構築は終わりました。
続いてフロントエンドの構築に移りましょう。
フロントエンド
まずはプロジェクトを作っていきましょう。
今回は以下のような設定で作りました。
$ npx create-nuxt-app client
create-nuxt-app v2.15.0
✨ Generating Nuxt.js project in client
? Project name client
? Project description nuxt-django-auth sample app
? Author name kueharx
? Choose programming language JavaScript
? Choose the package manager Npm
? Choose UI framework Vuetify.js
? Choose custom server framework Express
? Choose Nuxt.js modules Axios
? Choose linting tools ESLint
? Choose test framework None
? Choose rendering mode Universal (SSR)
? Choose development tools (Press <space> to select, <a> to toggle all, <i> to invert selection)
$ cd client
して
$ npm run dev
すると
こんな感じになります。
設定を追加していきます。
まず最初に、
$ npm install -s @nuxtjs/axios
をした後に設定ファイルに以下の記述を加えてください。
....
modules: [
'@nuxtjs/axios',
],
axios: {
baseURL: 'http://localhost:8000/api/',
},
を追加します。これはaxiosを使うときにどのローカルサーバに接続するのかを表す要素でこれを追加しないと先ほど作ったapiを叩けません。
さて、inspireは最初はこんな感じなので、
コードを追加していきましょう。
今回は商品の概要とリンクを表示するようなカードを作ってみます。
そのカードをまずは作っていきましょう。
<template>
<div class="card item-card">
<img :src="item.image" class="card-img-top">
<div class="card-body">
<h5 class="card-title">
{{ item.name }}
</h5>
<p class="card-text">
<strong>カテゴリー</strong> {{ item.category }}
</p>
<p class="card-text">
<strong>値段:</strong> {{ item.price }}
</p>
<div class="action-buttons">
<nuxt-link :to="`/items/${item.id}/`" class="btn btn-sm btn-primary">
詳細
</nuxt-link>
</div>
</div>
</div>
</template>
<script>
export default {
props: ['item', 'onDelete']
}
</script>
<style>
.item-card {
box-shadow: 0 1rem 1.5rem rgba(0,0,0,.6);
}
img.card-img-top{
height:200px;
}
.action-buttons {
text-align: right;
}
</style>
これでカードのレイアウトができました。
このカードをinspireで読み込んでみましょう。
<template>
<v-layout>
<v-flex class="text-center">
<img
src="/v.png"
alt="Vuetify.js"
class="mb-5"
>
<blockquote class="blockquote">
“First, solve the problem. Then, write the code.”
<footer>
<small>
<em>—John Johnson</em>
</small>
<div class="row">
<div class="col-12 text-right mb-4">
<div class="d-flex justify-content-between">
<h3>商品一覧</h3>
<!-- <nuxt-link to="/items/add" class="btn btn-info">
商品を追加する
</nuxt-link> -->
</div>
</div>
<template v-for="item in items">
<div :key="item.id" class="col-lg-3 col-md-4 col-sm-6 mb-4">
<item-card :on-delete="deleteitem" :item="item" />
</div>
</template>
</div>
</footer>
</blockquote>
</v-flex>
</v-layout>
</template>
<script>
import ItemCard from '~/components/ItemCard.vue'
export default {
components: {
ItemCard
},
async asyncData ({ $axios, params }) {
try {
const items = await $axios.$get('items/')
return { items }
} catch (e) {
return { items: [] }
}
},
data () {
return {
items: []
}
},
methods: {
async deleteitem (item_id) { //eslint-disable-line
try {
await this.$axios.$delete(`/items/${item_id}/`) //eslint-disable-line
const newitems = await this.$axios.$get('/items/')
this.items = newitems
} catch (e) {
console.log(e)
}
}
},
head () {
return {
title: '商品一覧'
}
}
}
</script>
これらを適用してみると・・・
以上のようにきちんと一覧を取得することができました。
今回はここまでで、次回は商品の詳細ページを作ります。