1
votes

I'm trying to build a single page app (Vue) with a django backend. It's a very simple app to practice Vue + Django Rest Framework. The app allows the user to creating, delete and view (list) stories (just a title, datetime and content (simply text)) and this works fine. I'm now trying to also implement editing capabilities, but I get an error with regards to CORS headers.

My backend code is very simple. Settings.py

# relevant parts of settings.py
INSTALLED_APPS = [
    ...,
    'rest_framework',
    'corsheaders'
]

MIDDLEWARE = [
    ...,
    'corsheaders.middleware.CorsMiddleware',
    'django.middleware.common.CommonMiddleware',
]

CORS_ALLOWED_ORIGINS = [
    "http://localhost:8080",
]
CORS_ALLOW_METHODS = (
    'GET',
    'POST',
    'PUT',
    'PATCH',
    'DELETE',
    'OPTIONS'
)

urls.py:

urlpatterns = [
    path('', views.StoryList.as_view(), name='stories'),
    path('<int:pk>/', views.StoryDetail.as_view(), name='story'),
    path('delete/<int:pk>/', views.StoryDetail.as_view(), name='delete_story'),
    path('update/<int:pk>/', views.StoryDetail.as_view(), name='update_story')
]

views.py:

from django.shortcuts import render
from django.http import HttpResponse, Http404
from rest_framework import generics
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status

from .models import Story
from .serializers import StorySerializer

class StoryList(APIView):
    def get(self, request, format=None):
        stories = Story.objects.all()
        serializer = StorySerializer(stories, many=True)
        return Response(serializer.data)
    
    def post(self, request, format=None):
        serializer = StorySerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.data, status=status.HTTP_400_BAD_REQUEST)

class StoryDetail(APIView):
    def get_object(self, pk):
        try:
            return Story.objects.get(pk=pk)
        except Story.DoesNotExist:
            raise Http404

    def get(self, request, pk, format=None):
        story = self.get_object(pk)
        serializer = StorySerializer(story)
        return Response(serializer.data)

    def delete(self, request, pk, format=None):
        story = self.get_object(pk)
        story.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)

    def update(self, request, pk, *args, **kwargs):
        story = self.get_object(pk)
        print(story)
        serializer = StorySerializer(story)
        if serializer.is_valid():
            serializer.save()
            return Response(status=status.HTTP_201_CREATED, data=serializer.data)
        return Response(status=status.HTTP_400_BAD_REQUEST, data='Wrong parameters')

So the methods get and delete work fine on the detail view. And list and create on the listview also work fine. Only the update method doesn't work. The error I get:

405 Method Not Allowed

The response header indeed does not define it as allowed:

Allow: GET, DELETE, HEAD, OPTIONS

But I thought I had defined everything in settings.py

My frontend code in case necessary (relevant parts):

<template>
  <form
    id="add_story"
    action="http://localhost:8000"
    method="post"
    @submit.prevent="saveStory">

    <input
      id="story_id"
      type="hidden"
      v-model="value.id"
      required />

    <input
      id="story_datetime"
      type="hidden"
      v-model="value.datetime"
      required />

    <b-form-input
      id="story_title"
      v-model="value.title"
      required>
    </b-form-input>
          
    <b-form-textarea
      id="story"
      v-model="value.content">
    </b-form-textarea>

    <b-button type="submit" variant="primary">Submit</b-button>
  </form>
</template>

<script>
export default {
  name: 'Story-form',
  props: {
    // gets the data from the parent which works fine
    value: {
      type: Object,
      required: true
    },
  },
  methods: {
    async saveStory () {
      // if value.id is set: update story
      const now = new Date();
      if (this.value.datetime == ""){
        this.value.datetime = now.toISOString();
      }
      const story_data = {
        // id: this.value.id,
        title: this.value.title,
        content: this.value.content,
        datetime: this.value.datetime
      };
      let send_method = (story_data.id === null) ? "POST" : "PUT";
      let send_url = (story_data.id === null) ? "http://localhost:8000/" : "http://localhost:8000/update/" + String(this.value.id) + "/";
      await fetch(send_url, {
        method: send_method,
        headers: {
          'Accept': 'application/json',
          'Content-Type': 'application/json'
        },
        body: JSON.stringify(story_data)
      }).then( (response) => {
        if (!response.ok){
          console.log(response);
          this.error = false;
        } else {
          // reset everything back to normal
        }
      }).catch(e => {
        console.log(e);
      });
    }
  }
}
</script>

Any idea on how to solve this. I'd prefer not to use viewsets because I'm trying to understand DRF and with viewsets too much happens under the hood.

1

1 Answers

1
votes

The method name should be either patch(...) or put(...) instead of update(...).

Becuase, the APIView class doesn't have any impletemetaions to lookup the update() method, but, the generic APIs does.