LoginSignup
3
4

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

Last updated at Posted at 2020-04-19

経緯

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

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

設計

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

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
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']
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ファイルの3つ。

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>
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を切り替え)、内容を更新できるようになっています。

スクリーンショット 2020-04-18 23.52.30.png

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

スクリーンショット 2020-04-18 23.51.15.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

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

スクリーンショット 2020-04-18 23.52.43.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の第一引数に、+ '/'を追加しました。

        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

3
4
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
3
4