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を作成する

```console:環境を作る
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
```

```python3: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
```

```python3: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) 
```

```python3: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'] 
```

```python3: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 
```

```python3: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))
]
```

```python3: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')),
]
```

管理画面で適当に本を追加しました
<img width="1198" alt="スクリーンショット 2020-03-28 14.24.55.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/441606/dc042723-39f0-3a38-cf92-e67635a08abe.png">




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

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

```html: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:CSSは適当に
body {
  background: #20262E;
  padding: 20px;
  font-family: Helvetica;
}

#app {
  background: #fff;
  border-radius: 4px;
  padding: 20px;
  transition: all 0.2s;
}
```

```javascript: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してきた結果が表示されるはずですが...


<img width="1230" alt="スクリーンショット 2020-03-28 14.07.25.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/441606/7f4a42c6-a60b-2d6c-7183-01f326a52335.png">

表示されへん


### エラー

```console:コンソール
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

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

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

異なりオリジンへのアクセスを許可するためには、[CORS](https://developer.mozilla.org/ja/docs/Web/HTTP/CORS)という機能を使います。HTTPヘッダに設定を加える必要があります。

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

- DRF : http://127.0.0.1:8000
- Jsfiddle : https://fiddle.jshell.net


### 解決するために...

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

#### DRFに`django-cors-headers`を入れる
https://github.com/adamchainz/django-cors-headers

##### インストール
```console
$ pip install django-cors-headers
```

##### DRFのsettings.pyに追記
```python3:settings.py
INSTALLED_APPS = [
    ...
    'corsheaders',
    ...

MIDDLEWARE = [
    ...
    'corsheaders.middleware.CorsMiddleware',
    ...
]
```


##### CORS_ORIGIN_ALLOW_ALL か　CORS_ORIGIN_WHITELISTを設定する
```python3:settings.py
CORS_ORIGIN_ALLOW_ALL = True

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

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

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

<img width="1254" alt="スクリーンショット 2020-03-28 14.08.57.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/441606/1caab227-bddf-062b-b011-1caac03cfc81.png">

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