Django製のAPIにaxiosでputすると500 Internal Server Errorがでてしまう

# 経緯
Django rest frameworkで作成したAPIに対してPUTを行うと`500 (Internal Server Error)`と表示されてしまう。

原因と解決法を整理します。

# 設計

```shell:sample app内
.
├── README.rst
├── apiv1
│   ├── __init__.py
│   ├── __pycache__
│   ├── admin.py
│   ├── apps.py
│   ├── migrations
│   ├── models.py
│   ├── serializers.py
│   ├── tests.py
│   ├── urls.py
│   └── views.py
├── config
│   ├── __init__.py
│   ├── __pycache__
│   ├── asgi.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
├── db.sqlite3
├── env
│   ├── bin
│   ├── include
│   ├── lib
│   └── pyvenv.cfg
├── manage.py
├── memo.txt
├── vue
│   ├── index.html
│   ├── style.css
│   └── vue_script.js
└── woop # Django models作成用
    ├── __init__.py
    ├── __pycache__
    ├── admin.py
    ├── apps.py
    ├── migrations
    ├── models.py
    ├── tests.py
    └── views.py
```


## バックエンド Django rest framework

```python3:woop/models.py
from django.db import models
from django.utils import timezone

# Create your models here.
import uuid


class Goal(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    title = models.CharField(verbose_name='目標', max_length=40)
    created_at = models.DateTimeField(default=timezone.now)

    def __str__(self):
        return self.title
```
```python3:apiv1/serializer.py
from rest_framework import serializers
# models
from woop.models import Goal, Task

class GoalSerializer(serializers.ModelSerializer):

    class Meta:
        model = Goal
        fields = ['id', 'title', 'created_at']
```

```python3:apiv1/view.py
from django.shortcuts import render
from rest_framework import viewsets
from woop.models import Goal, Task
from .serializers import GoalSerializer, TaskSerializer


class GoalViewSet(viewsets.ModelViewSet):
    queryset = Goal.objects.all()
    serializer_class = GoalSerializer
```

## フロントエンド Vue (CDN)
VueはCDNで使っています。index.html, vue用のjsファイル, cssファイルの３つ。

```html:index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <link rel="stylesheet" href="style.css">
    <title>Document</title>
</head>

<body>
    <div id="app">
  <div>
    <input v-model='new_goal'>
    <button v-on:click='postGoal(new_goal)' class='btn'>登録</button>

    <p>目標の数 : {{ goals.length }}</p>
    <ul>
    <li v-for='(goal, index) in goals' v-bind:key='goal.id'>
      <div style='font-size: 5px;'>id : {{ goal.id }}</div>
      <div v-if='!isEditGoal' v-on:dblclick='isEditGoal = true'>
        {{ index }}: {{ goal.title }}</div>
      <div v-else><input type='text' v-model='goal.title' v-on:blur='updateGoal(goal.id, goal.title)'></div>
      <div style='font-size: 5px;'>{{ goal.created_at }}</div>
      <button v-on:click='deleteGoal(goal.id)' class='btn'>削除</button>
    </li>
    </ul>

    <pre>{{ $data }}</pre>
  </div>
</div>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src='/vue/vue_script.js'></script>
</body>
</html>
```

```javascript:vue_script.js
new Vue({
    el: '#app',
    data: function (){
        return {
            goals: {},
            new_goal: '',
            isEditGoal: false,
    }
},
    created() {
        this.reloadGoal();
    },
    methods: {
        postGoal: function (title) {
            const vm = this;
            axios.post('http://127.0.0.1:8000/api/v1/Goal/',
                { title: title })
                .then(response => { vm.reloadGoal(); })
        },
        deleteGoal: function (id) {
            const vm = this;
            axios.delete('http://127.0.0.1:8000/api/v1/Goal/' + id)
                .then(response => { vm.reloadGoal(); })
        },
        updateGoal: function (id, title) {
            const vm = this;
            console.log(vm)
            axios.put('http://127.0.0.1:8000/api/v1/Goal/' + id,
                { id: id, title: title })
                .then(response => { vm.isEditGoal = false })
                .catch((error) =>{ console.log(error) })
                .then(response => { vm.reloadGoal(); })
        },
        reloadGoal() {
            const vm = this;
            axios.get('http://127.0.0.1:8000/api/v1/Goal/')
                .then((response) => { vm.goals = response.data })
        },
    },
}
)
```
上記のコードで以下のように表示されます。
それぞれ目標が登録されており、id, title, created_idが表示されています。
title部分はダブルクリックをするとinput要素に切り替わり（isEditGoal = false, trueを切り替え）、内容を更新できるようになっています。


<img width="561" alt="スクリーンショット 2020-04-18 23.52.30.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/441606/d79448fa-2907-5279-94ca-fef501c6a697.png">

入力フォームのフチの部分をクリック、もしくはタブキーでフォームから別の要素に切り替わることで更新内容が確定され、テキストに戻るはずなのですが...

<img width="1040" alt="スクリーンショット 2020-04-18 23.51.15.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/441606/7f672c8f-acbc-d7b8-15bd-336393e2cf28.png">

```
spread.js:25 PUT http://127.0.0.1:8000/api/v1/Goal/3932e94a-1894-445e-ba2b-4415899fa2a8 500 (Internal Server Error)

Error: Request failed with status code 500
    at e.exports (spread.js:25)
    at e.exports (spread.js:25)
    at XMLHttpRequest.l.onreadystatechange (spread.js:25)
```
上記の通りエラーが。
status code 500 について調べるとサーバエラーとでるため、おそらくDjango側に問題があるのだろう。


# 原因の突き止め方
`axios status code 500 vue put`などのワードで検索していたところ、同じ境遇の質問がヒット
*[Error: Request failed with status code 500 #1989](https://github.com/axios/axios/issues/1989)*

上記issueの中で、`これはaxiosの問題ではなくバックエンド側に問題がある。デベロッパーツールのネットワークで問題の部分を見てみてください`とあったため、見てみることに

<img width="1593" alt="スクリーンショット 2020-04-18 23.52.43.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/441606/eb5df93d-7221-a989-d115-96521dc1dc26.png">


### エラー
*`You called this URL via PUT, but the URL doesn't end in a slash and you have APPEND_SLASH set. Django can't redirect to the slash URL while maintaining PUT data. Change your form to point to 127.0.0.1:8000/api/v1/Goal/0c08d6b1-03cf-47f8-979e-60cb53704f52/ (note the trailing slash), or set APPEND_SLASH=False in your Django settings.`*

`putで呼び出したurlの末尾にスラッシュが付いていないのでリダイレクトしたい。けどDjangoはputのデータを維持したままリダイレクトできないので、設定を変えてください。`とでてきました。

Djangoでは末尾にスラッシュがない場合、settings.pyの`APPEND_SLASH`がTrueのときはリダイレクトをするようです。（デフォルト値がTrue）


# 解決方法

とりあえずvueで定義した`updateGoal`メソッドの`axios.put`の第一引数に、`+ '/'`を追加しました。

```javascript
        updateGoal: function (id, title) {
            const vm = this;
            console.log(vm)
            axios.put('http://127.0.0.1:8000/api/v1/Goal/' + id + '/', 
              { id: id, title: title })
                .then(response => { vm.isEditGoal = false })
                .catch((error) =>{ console.log(error) })
                .then(response => { vm.reloadGoal(); })
        },
```


ちゃんとputできた↓
![ezgif.com-video-to-gif.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/441606/c9745565-f92e-f606-43fc-52b427c93705.gif)