Nuxt&Django REST FrameworkでCRUD GET編①

# 概要

フロント:Nuxt
バックエンド:Django REST Framework（以下DRF）
で新しいプロジェクトなどを立ち上げた時に設定などをいちいち調べるのがめんどくさくなったため、まとめておきます。

ただ、設定ファイルだけを書くのでは個人的に記事として物足りない。

なので、ついでに基本的な操作であるAPIを叩いてCRUDを行い、会員登録を実装するところまで書く予定です。
なお、今回はDBはSQLiteを使います。PostgreSQLやMySQLなど他のRDBが使いたい場合はDRF側のDBの設定を書きかえるだけなので、そちらの設定は各自よろしくお願いいたします。

この記事では、NuxtからDRFで作ったAPIを叩いて商品の詳細情報をGETするところまでです。

ソースコードは[こちら](https://github.com/Kuehar/nuxt-django)にあるのでわからないところは適宜見てください。

また、[Twitter](https://twitter.com/kueharx)かこの記事のコメント欄でわからないところを聞いていただけると答えられます。


# バックエンド

まずはおなじみのプロジェクト作成からです。
DRF側からの設定から整えていきましょう。

```
$  mkdir nuxt-django
```

```
$ cd nuxt-django
```

```
$ django-admin startproject server
```

```
$ cd server
```

```
$ django-admin startapp practice
```

まずは設定ファイル変更していきましょう。

```Python:settings.py
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...`というのはこの後画像を取り扱うために追加しました。画像ファイルを使用しない場合は必要ありませんが、多くの場合使うことになるので記述しておくことをお勧めします。


```Python:urls.py
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)
```



```Python:models.py
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
```

今回はよくある商品のモデルを作ってみました。
そしてこのアイテムを扱うためのシリアライザーを作成します。

```Python:serializers.py
from rest_framework import serializers
from .models import Item

class ItemSerializer(serializers.ModelSerializer):
    class Meta:
        model = Item
        fields = '__all__'
```

そしてこれをルーティングするためにviews.pyとurls.pyを書きます。

```Python:views.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()
```

```Python:practice/urls.py
from django.urls import path

from .views import ItemListView,ItemDetailView

urlpatterns = [
    path('items/',ItemListView.as_view()),
    path('items/<int:pk>/',ItemDetailView.as_view()),
]
```

最後にadminでデータを扱えるように以下を追加します。

```Python:admin.py
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
にアクセスし、こんな感じにデータを追加してみましょう。

![スクリーンショット 2020-05-10 18.44.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/412789/43914184-978a-5b26-2eea-f3dae5f3bd32.png)


その後に
http://localhost:8000/api/items
にアクセスすると以下のような画面が表示されるはずです。

![スクリーンショット 2020-05-10 19.28.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/412789/9eac879a-42a3-cb91-6782-8979fb4e2022.png)

この段階でフロントエンドから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
```

すると

![スクリーンショット 2020-05-10 20.35.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/412789/51d7832f-2028-2acb-1a27-8339ed32f990.png)

こんな感じになります。

設定を追加していきます。
まず最初に、

```
$ npm install -s @nuxtjs/axios
```

をした後に設定ファイルに以下の記述を加えてください。

```JavaScript:nuxt.config.js

....

modules: [
    '@nuxtjs/axios',
  ],

axios: {
    baseURL: 'http://localhost:8000/api/',
  },

```

を追加します。これはaxiosを使うときにどのローカルサーバに接続するのかを表す要素でこれを追加しないと先ほど作ったapiを叩けません。

さて、inspireは最初はこんな感じなので、

![スクリーンショット 2020-05-10 20.36.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/412789/93c9d35b-4272-54b1-b54d-6b4148bfedd2.png)

コードを追加していきましょう。

今回は商品の概要とリンクを表示するようなカードを作ってみます。

そのカードをまずは作っていきましょう。

```JavaScript:ItemCard.vue
<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で読み込んでみましょう。

```JavaScript:inspire.vue
<template>
  <v-layout>
    <v-flex class="text-center">
      <img
        src="/v.png"
        alt="Vuetify.js"
        class="mb-5"
      >
      <blockquote class="blockquote">
        &#8220;First, solve the problem. Then, write the code.&#8221;
        <footer>
          <small>
            <em>&mdash;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>
```

これらを適用してみると・・・

![スクリーンショット 2020-05-10 21.53.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/412789/6379416e-486c-bfde-6e76-0fe51662f8ab.png)


以上のようにきちんと一覧を取得することができました。

~~今回はここまでで、次回は商品の詳細ページを作ります。~~

書きました！
[Nuxt&Django REST FrameworkでCRUD GET編②](https://qiita.com/KueharX/items/48d44db339e4a2fbaa71)