経緯
Django rest frameworkで作成したAPIに対してPUTを行うと500 (Internal Server Error)
と表示されてしまう。
原因と解決法を整理します。
設計
.
├── 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
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
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']
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ファイルの3つ。
<!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>
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を切り替え)、内容を更新できるようになっています。
入力フォームのフチの部分をクリック、もしくはタブキーでフォームから別の要素に切り替わることで更新内容が確定され、テキストに戻るはずなのですが...
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
上記issueの中で、これはaxiosの問題ではなくバックエンド側に問題がある。デベロッパーツールのネットワークで問題の部分を見てみてください
とあったため、見てみることに
エラー
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
の第一引数に、+ '/'
を追加しました。
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(); })
},