7
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Nuxt&Django REST FrameworkでCRUD GET編①

Last updated at Posted at 2020-05-10

概要

フロント: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

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

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_CODETIME_ZONEを変更することで日本語対応をします。

最後のMEDIA...というのはこの後画像を取り扱うために追加しました。画像ファイルを使用しない場合は必要ありませんが、多くの場合使うことになるので記述しておくことをお勧めします。

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

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

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を書きます。

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()
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でデータを扱えるように以下を追加します。

admin.py
from django.contrib import admin

from .models import Item

# Register your models here.
admin.site.register(Item)

なぜItemListViewItemDetailViewを分けているのかについてですが、権限周りを実装する際に分けておくと使いやすくなるという点と、後々分ける必要が出てくるからです。

ここまでで基本的なバックエンドの設定は完了しました。

$ 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

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

スクリーンショット 2020-05-10 19.28.58.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

こんな感じになります。

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

$ npm install -s @nuxtjs/axios

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

nuxt.config.js

....

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

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

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

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

スクリーンショット 2020-05-10 20.36.55.png

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

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

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

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で読み込んでみましょう。

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

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

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

書きました!
Nuxt&Django REST FrameworkでCRUD GET編②

7
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?