Method Not Allowed (PUT) while using Django Rest paired with Vue.js frontend

Viewed 303

I'm trying to make a PUT request to my RestAPI using Vue.js. It totally works in my ModelViewSet view, but i encounter Method Not Allowed (PUT) when i try it through Vue.js. I'm also able to use "POST" in the same onSubmit function. Here is the code for onSubmit function in my QuestionEditor.vue:

<template>
    <div class="container mt-2">
        <h1 class="mb-3">Ask a Question</h1>
        <form @submit.prevent="onSubmit">
            <textarea v-model="question_body" class="form-control" placeholder="What do you want to ask?" row="3">
            </textarea>
            <br>
            <button type="submit" class="btn btn-success">
                Publish
            </button>
        </form>
        <p v-if="error" class="muted mt-2">{{ error }}</p>
    </div>
</template>

<script>
import { apiService } from "../common/api.service.js"
export default {
    name: "QuestionEditor",
    props: {
        slug: {
            type: String,
            required: null
        }
    },
    data() {
        return{
            question_body: null,
            error: null
        }
    },
    methods: {
        onSubmit() {
            if (!this.question_body){
                this.error = "You can't send an empty question!";
            } else if (this.question_body.length > 240) {
                this.error = "Exceeding 240-character limit!";
            } else {
                let endpoint = "/api/questions/";
                let method = "POST";
                if (this.slug !== undefined) {
                    endpoint += `${ this.slug }/`;
                    method = "PUT";
                }
                apiService(endpoint, method, { content: this.question_body })
                    .then(question_data => {
                        this.$router.push({
                            name: 'Question',
                            params: {slug: question_data.slug}
                        })
                    })
            }
        }
    },
    async beforeRouteEnter(to, from, next){
        if (to.params.slug !== undefined) {
            let endpoint = `/api/questions/${to.params.slug}/`;
            let data = await apiService(endpoint);
            return next(vm => (vm.question_body = data.content));
        } else{
            return next();
        }
    },
    created() {
        document.title = "Editor - QuestionTime";
    }
}
</script>

<style scoped>

</style>

and here is apiService function used to retrieve data from API:

import { CSRF_TOKEN } from "./csrf_token.js"

async function getJson(response) {
    if (response.status === 204) return '';
    return response.json()
}

function apiService(endpoint, method, data) {
    const config = {
        method: method || "GET",
        body: data !== undefined ? JSON.stringify(data) : null,
        headers: {
            'content-type': 'application/json',
            'X-CSRFTOKEN': CSRF_TOKEN
        }
    };
    return fetch(endpoint, config)
            .then(getJson)
            .catch(error => console.log(error))
}

export { apiService };

and finally the api view:

class QuestionViewSet(viewsets.ModelViewSet):
    queryset = Question.objects.all().order_by("-created_at")
    lookup_field = "slug"
    serializer_class = QuestionSerializer
    permission_classes = [IsAuthenticated, IsAuthorOrReadOnly]

    def perform_create(self, serializer):
        serializer.save(author=self.request.user)

update: question serializer:

class QuestionSerializer(serializers.ModelSerializer):
    author = serializers.StringRelatedField(read_only=True)
    created_at = serializers.SerializerMethodField(read_only=True)
    slug = serializers.SlugField(read_only=True)
    answers_count = serializers.SerializerMethodField()
    user_has_answered = serializers.SerializerMethodField()

    class Meta:
        model = Question
        exclude = ["updated_at"]

    def get_created_at(self, instance):
        return instance.created_at.strftime("%B %d %Y")

    def get_answers_count(self, instance):
        return instance.answers.count()

    def get_user_has_answered(self, instance):
        request = self.context.get("request")
        return instance.answers.filter(author=request.user).exists()

and url.py for api:

from django.urls import include, path
from rest_framework.routers import DefaultRouter
from .views import QuestionViewSet, AnswerCreateAPIView, AnswerListAPIView, AnswerRUDAPIView, AnswerLikeAPIView

router = DefaultRouter()
router.register(r"questions", QuestionViewSet)

urlpatterns = [
    path("", include(router.urls)),
    path("questions/<slug:slug>/answer/", AnswerCreateAPIView.as_view(), name="answer-create"),
    path("questions/<slug:slug>/answers/", AnswerListAPIView.as_view(), name="answer-list"),
    path("answers/<int:pk>/", AnswerRUDAPIView.as_view(), name="answer-detail"),
    path("answers/<int:pk>/like/", AnswerLikeAPIView.as_view(), name="answer-like"),
]

Update2: permissions file:

from rest_framework import permissions


class IsAuthorOrReadOnly(permissions.BasePermission):

    def has_object_permission(self, request, view, obj):
        if request.method in permissions.SAFE_METHODS:
            return True
        return obj.author == request.

Solved!!! I downgraded from web-pack version 1 alpha to 0.4.3 and now everything seems to work fine. It was my mistake to go with a semi-developed version.

3 Answers

I think I might now what happens.

  1. You either need to add pk field in your serializer (fields in Meta has to have id field)

  2. or your endpoint is wrong.

            } else {
                let endpoint = "api/questions/";
                let method = "POST";
                if (this.slug !== undefined) {
                    endpoint += `${ this.slug }/`;
                    method = "PUT";
                }
                apiService(endpoint, method, { content: this.question_body })
                    .then(question_data => {
                        this.$router.push({
                            name: 'Question',
                            params: {slug: question_data.slug}
                        })
                    })

If all you do in here is changing the method, then it's not enough, as you POST to /resource but PUT to /resource/<id>.

Therefore, PUT to /resource is not allowed.

You can override this logic in ModelViewSet to take the id from body, but I wouldn't recommend doing that.

EDIT: I just noticed you add endpoint += ${ this.slug }/;. Could you please show how your endpoint for PUT looks like over here? With this slug. Is that a proper id in your serializer?

i think you have to allow cors headers on your server side pip install django-cors-headers

in django app settings.py:

INSTALLED_APPS = [
    'corsheaders',
]
MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware',
]
CORS_ORIGIN_ALLOW_ALL = True

Looks like your issue in permissions

permission_classes = [IsAuthenticated, IsAuthorOrReadOnly]

My guess is that your IsAuthorOrReadOnly is causing this. Could you please provide source code for it?

Related