JsfiddleのVue.jsからDRFで作成したapiにアクセスしたい


経緯

Vue.jsのプロジェクトをWeb上で手軽に作ることができるjsfiddleで、Django Rest Framework(以下DRF)で作成したapiにアクセスしたいと思い、試しました。

本記事に書くこと

  1. DRFにおけるシンプルなAPIの作成と、シンプルなVue.jsプロジェクトの作成。
  2. Vue.jsからDRFのapiにアクセスする方法と、その際の留意点。

手順

  1. DRFでapiを作成する
  2. Vue.jsからDRFで作成したapiにアクセスする

1. DRFでapiを作成する

環境を作る
mkfir simple_drf
cd simple_drf
python3 -m vena venv
source venv/bin/activate

pip install django
pip install djangorestframework

django-admin startproject config.

-- モデル用のbook API作成用のapiv1 --
python manage.py startapp book
python manage.py startapp apiv1
book/models.py
from django.db import models
import uuid
from django.utils import timezone

# Create your models here.
class Book(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    title = models.CharField(verbose_name='タイトル', max_length=50)
    price = models.IntegerField(verbose_name='価格')
    created_at = models.DateTimeField(default=timezone.now)

    def __str__(self):
        return self.title
book/admin,py
from django.contrib import admin

# Register your models here.
from .models import Book


class BookModelAdmin(admin.ModelAdmin):  
    list_display = ('title', 'price', 'id', 'created_at')  
    ordering = ('-created_at',)  
    readonly_fields = ('id', 'created_at')  

admin.site.register(Book, BookModelAdmin) 
apiv1/serializers.py
from rest_framework import serializers  
from book.models import Book  

class BookSerializer(serializers.ModelSerializer):  
    class Meta:  
        model = Book  
        fields = ['id', 'title', 'price'] 
apiv1/views.py
from rest_framework import viewsets  
from rest_framework.permissions import IsAuthenticatedOrReadOnly  
from book.models import Book  
from .serializers import BookSerializer  

class BookViewSet(viewsets.ModelViewSet):  
    queryset = Book.objects.all()  
    serializer_class = BookSerializer 
apiv1/urls.py
from django.urls import path, include
from rest_framework import routers
from apiv1 import views

router = routers.DefaultRouter()
router.register('book', views.BookViewSet)

app_name = 'apiv1'
urlpatterns = [
    path('', include(router.urls))
]
config/urls,py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('apiv1/', include('apiv1.urls')),
]

管理画面で適当に本を追加しました

2. Vue.jsからDRFで作成したapiにアクセスする

jsfiddleにアクセスし、各フィールド(html, css, javascript)に以下の通り記載。

HTML
<div id="app">
  {{ results }} 
  <hr>
  {{ results.id }} 
  <hr>
  {{ results.title }} 
  <hr>
  {{ results.price }} 
  <hr>
</div>

<script src="https://unpkg.com/axios/dist/axios.min.js"></script>

axiosをcdnで使えるようにしておく

CSSは適当に
body {
  background: #20262E;
  padding: 20px;
  font-family: Helvetica;
}

#app {
  background: #fff;
  border-radius: 4px;
  padding: 20px;
  transition: all 0.2s;
}
Vue
new Vue({
  el: '#app',
  data: {
    results: []
  },
  mounted() {
    axios.get('http://127.0.0.1:8000/apiv1/book/d5da82ff-8f63-4718-9ddb-e989832170d1/')
    .then(response => {this.results = response.data})
 }
 },
)

ライフサイクルフックのmountedでapiを使います。
axiosのgetメソッドに、DRFで作った記事のエンドポイントを指定。レスポンスのデータをresultsリストに格納します。
HTMLのMustache構文で{{ results }}などとしているため、getしてきた結果が表示されるはずですが...

表示されへん

エラー

コンソール
fiddle.jshell.net/takuto/4vjsdpag/64/show/:1 Access to XMLHttpRequest at
'http://127.0.0.1:8000/apiv1/book/d5da82ff-8f63-4718-9ddb-e989832170d1/'
from origin 'https://fiddle.jshell.net' has been blocked by CORS policy: 
No 'Access-Control-Allow-Origin' header is present on the requested resource.

Failed to load resource: net::ERR_FAILED

表示されません。コンソールを見ると上記の通りエラーが。
origin 'https://fiddle.jshell.net' has been blocked by CORS policy
https://fiddle.jshell.netがCORSポリシーによってブロックされたと言ってます。

原因は?

同一オリジンポリシーです。
詳しく:https://developer.mozilla.org/ja/docs/Web/Security/Same-origin_policy

セキュリティの観点から、異なるオリジン同士でリソースをやり取りすることを防ぐものです。
オリジンは、スキーム、ポート、ホストの3つの組み合わせのこと。
これらが一致していない場合は、同一オリジンとは言えません。

オリジンとは:https://www.atmarkit.co.jp/ait/articles/1311/26/news007.html

異なりオリジンへのアクセスを許可するためには、CORSという機能を使います。HTTPヘッダに設定を加える必要があります。

今回エラーが起きているのは、DRFで作成したAPIのオリジンと、Jsfiddleのオリジンが異なるためです。

解決するために...

DRFでdjango-cors-headersを使えば、CORSを設定することができます。

DRFにdjango-cors-headersを入れる

インストール
$ pip install django-cors-headers
DRFのsettings.pyに追記
settings.py
INSTALLED_APPS = [
    ...
    'corsheaders',
    ...

MIDDLEWARE = [
    ...
    'corsheaders.middleware.CorsMiddleware',
    ...
]
CORS_ORIGIN_ALLOW_ALL か CORS_ORIGIN_WHITELISTを設定する
settings.py
CORS_ORIGIN_ALLOW_ALL = True

CORS_ORIGIN_WHITELIST = [
    "https://fiddle.jshell.net", 
]

CORS_ORIGIN_ALLOW_ALLは、他のオリジンをすべて許可する。
CORS_ORIGIN_WHITELISTは、許可するオリジン指定する。

これでもう一度試してみる

やったー。睡眠が大事という本の情報を取得できました。