Метод запрещен (PUT) при использовании Django Rest в паре с интерфейсом Vue.js

Я пытаюсь сделать запрос PUT к моему RestAPI с помощью Vue.js. Он полностью работает в моем представлении ModelViewSet, но я сталкиваюсь с запрещенным методом (PUT), когда пробую его через Vue.js. Я также могу использовать "POST" в той же функции onSubmit. Вот код функции onSubmit в моем 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>

и вот функция apiService, используемая для получения данных из 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 };

и, наконец, представление api:

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)

обновление: сериализатор вопросов:

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()

и url.py для 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: файл разрешений:

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.

Решено!!! Я понизил альфа-версию web-pack версии 1 до 0.4.3, и теперь все работает нормально. Я совершил ошибку, выбрав полуразработанную версию.

3 ответа

Думаю, теперь я могу понять, что происходит.

  1. Вам либо нужно добавить поле pk в сериализатор (fields в Meta должен иметь id поле)

  2. или ваша конечная точка неверна.

            } 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}
                        })
                    })

Если все, что вы здесь делаете, это изменение метода, этого недостаточно, так как вы отправляете POST на /resource но ПОСТАВИТЬ на /resource/<id>.

Следовательно, PUT в /resource не допускается.

Вы можете переопределить эту логику в ModelViewSet взять id от тела, но я бы не рекомендовал это делать.

РЕДАКТИРОВАТЬ: я только что заметил, что вы добавили endpoint += ${ this.slug }/;. Не могли бы вы показать, как здесь выглядит ваша конечная точка для PUT? С этой пулей. Это правильный идентификатор в вашем сериализаторе?

Я думаю, вам нужно разрешить заголовки cors на стороне вашего сервераpip install django-cors-headers

в приложении django settings.py:

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

Похоже, у вас проблема с разрешениями

permission_classes = [IsAuthenticated, IsAuthorOrReadOnly]

Я предполагаю, что ты IsAuthorOrReadOnlyвызывает это. Не могли бы вы предоставить его исходный код?

Другие вопросы по тегам