Ошибка 405 на django ajax POST

У меня есть модель с целочисленным полем, которое будет увеличиваться при нажатии пользователем, например, кнопка «проголосовать за это».

Кнопка отображается только в подробном представлении. Чтобы увеличить количество голосов, он отправляет ajax POST. Проблема в том, что django возвращает ошибку 405 (метод не разрешен) еще до выполнения представления. Что может быть причиной этого?

Вот мой код:

views.py (не выполняется)

@require_POST
def vote_proposal(request, space_name):

    """
    Increment support votes for the proposal in 1.
    """
    prop = get_object_or_404(Proposal, pk=request.POST['propid'])
    proposal_form = VoteProposal(request.POST or None, instance=prop)

    if request.method == "POST" and request.is_ajax:
        if proposal_form.is_valid():
            vote = proposal_form.cleaned_data['propid']
            vote.support_votes += 1
            vote.save()
            msg = "The vote has been saved."
        else:
            msg = "The vote didn't pass validation."
    else:
        msg = "An error has ocurred."

    return HttpResponse(msg)

JQuery-код:

<script type="text/javascript">
    function upvote(proposal) {
        var request = $.ajax({
            type: "POST",
            url: "../add_support_vote/",
            data: { propid: proposal }
        });

        request.done(function(msg) {
            var cur_votes = $("#votes span").html();
            var votes = cur_votes += 1;
            $("#votes span").html().fadeOut(1000, function(){
                $("#votes span").html(votes).fadeIn();
            });
        });

        request.fail(function(jqXHR, textStatus) {
            $("#jsnotify").notify("create", {
                title:"Couldn't vote the proposal",
                text:"There has been an error." + textStatus,
                icon:"alert.png"
            });
        })
     }
</script>

urls.py

urlpatterns = patterns('e_cidadania.apps.proposals.views',

    url(r'^$', ListProposals.as_view(), name='list-proposals'),

    url(r'^add/$', 'add_proposal', name='add-proposal'),

    url(r'^(?P<prop_id>\w+)/edit/$', 'edit_proposal', name='edit-proposal'),

    url(r'^(?P<prop_id>\w+)/delete/$', DeleteProposal.as_view(), name='delete-proposal'),

    url(r'^(?P<prop_id>\w+)/', ViewProposal.as_view(), name='view-proposal'),

    url(r'^add_support_vote/', 'vote_proposal'),

)

Шаблон

<div id="votes">
    <span style="font-size:30px;text-align:center;">
        {{ proposal.support_votes }}
    </span><br/>
    <button onclick="upvote({{ proposal.id }})" class="btn small">{% trans "support" %}</button>
</div>

person Oscar Carballal    schedule 19.10.2011    source источник
comment
возможно, вы пропустили это: docs.djangoproject.com/en/dev /ref/contrib/csrf/#ajax   -  person Ahsan    schedule 19.10.2011
comment
У меня это включено, и событие POST отлично работает с другими функциями на платформе, поэтому я немного запутался.   -  person Oscar Carballal    schedule 20.10.2011
comment
Не может ли проблема быть вызвана относительным URL-адресом url: "../add_support_vote/", в $.ajax? Я могу представить, что вместо vote_proposal() может быть вызвано другое представление, не допускающее POST, в зависимости от местоположения страницы, с которой вы запускаете вызов Ajax.   -  person Jakub Roztocil    schedule 02.01.2012
comment
Я бы также предположил относительный URL ... в настоящее время это голосование будет работать только со страницы предложения просмотра. Что говорит консоль Chrome/Safari, когда вы нажимаете кнопку голосования? Кроме того, у вас есть условие гонки: если несколько человек голосуют одновременно, часть голосов может быть потеряна. Вместо этого используйте поля: voice.support_votes = F(support_votes) + 1.   -  person Rob Osborne    schedule 03.01.2012
comment
@jkbr и Роб Осборн Вы были правы, URL-адрес предложения просмотра путался с голосовым предложением. Простое изменение порядка работало нормально (и после этого я использовал поля ради условия гонки). Если вы оба поместите свои комментарии в качестве ответов, я дам вам награду :)   -  person Oscar Carballal    schedule 03.01.2012
comment
@OscarCarballal рад слышать, что это помогло :) Опубликовал это как ответ.   -  person Jakub Roztocil    schedule 03.01.2012


Ответы (3)


Не может ли проблема быть вызвана относительным URL-адресом url: "../add_support_vote/" в $.ajax? Я могу представить, что вместо vote_proposal() может быть вызвано другое представление, не допускающее POST, в зависимости от местоположения страницы, с которой вы запускаете вызов Ajax.

person Jakub Roztocil    schedule 03.01.2012

Оскар, к сожалению, это небольшое исследование не выявило проблему, но надеюсь, что оно поможет вам прояснить, как исправить код, чтобы он заработал.

В основном urls.py я создал два представления: первое для кнопки, второе в testap для обработчика вызовов ajax.

from django.conf import settings
from django.conf.urls.defaults import patterns, include, url    
from django.views.generic.simple import direct_to_template

urlpatterns = patterns(''    
    url(r'^$', direct_to_template , {'template':'test.html'}),
    url(r'^test/', include('testapp.urls')),
)    

if settings.DEBUG:
    urlpatterns += patterns(
        '',
        url(r'^media/(?P<path>.*)$', 'django.views.static.serve', {'document_root': settings.MEDIA_ROOT, 'show_indexes': True}),
)

Мой шаблон с кнопкой test.html немного упрощен для тестов. Также был добавлен csrf hook для предотвращения ошибки 403 CSRF verification:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<script src="{{ MEDIA_URL }}js/jquery-1.6.1.min.js" type="text/javascript"></script>
<script type="text/javascript">
    $(document).ajaxSend(function(event, xhr, settings) {
        function getCookie(name) {
            var cookieValue = null;
            if (document.cookie && document.cookie != '') {
                var cookies = document.cookie.split(';');
                for (var i = 0; i < cookies.length; i++) {
                    var cookie = jQuery.trim(cookies[i]);
                    // Does this cookie string begin with the name we want?
                    if (cookie.substring(0, name.length + 1) == (name + '=')) {
                        cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                        break;
                    }
                }
            }
            return cookieValue;
        }
        function sameOrigin(url) {
            // url could be relative or scheme relative or absolute
            var host = document.location.host; // host + port
            var protocol = document.location.protocol;
            var sr_origin = '//' + host;
            var origin = protocol + sr_origin;
            // Allow absolute or scheme relative URLs to same origin
            return (url == origin || url.slice(0, origin.length + 1) == origin + '/') ||
                (url == sr_origin || url.slice(0, sr_origin.length + 1) == sr_origin + '/') ||
                // or any other URL that isn't scheme relative or absolute i.e relative.
                !(/^(\/\/|http:|https:).*/.test(url));
        }
        function safeMethod(method) {
            return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
        }

        if (!safeMethod(settings.type) && sameOrigin(settings.url)) {
            xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken'));
        }
    });

       function upvote(proposal) {
           var request = $.ajax({
               type: "POST",
               url: "../test/add_support_vote/",
               data: { propid: proposal }
           });

           request.done(function(msg) {
               var cur_votes = $("#votes span").html();
               var votes = cur_votes += 1;
               $("#votes span").html().fadeOut(1000, function(){
                   $("#votes span").html(votes).fadeIn();
               });
           });

           request.fail(function(jqXHR, textStatus) {
               $("#jsnotify").notify("create", {
                   title:"Couldn't vote the proposal",
                   text:"There has been an error." + textStatus,
                   icon:"alert.png"
               });
           })
        }
   </script>
</head>
<body>
<div id="votes">
    <button onclick="upvote(1)" class="btn small">support</button>
</div>
</body>
</html>

urls.py из testapp выглядит так

from django.conf.urls.defaults import *
from .views import vote_proposal

urlpatterns = patterns('',
    url(r'^add_support_vote/', vote_proposal),

)

и views.py что максимально упростило локализацию проблемы

from django.http import HttpResponse
from django.shortcuts import get_object_or_404
from django.views.decorators.http import require_POST

@require_POST
def vote_proposal(request):
    return HttpResponse('ok')

И это работает. У меня есть 200 HTTP-ответов.


Также последнее небольшое предложение использовать resolve для получения функции просмотра, которая обрабатывает URL-адрес из вызова ajax:

from django.core.urlresolvers import resolve
resolve('/test/add_support_vote/')
# returns ResolverMatch(func=<function vote_proposal at 0x2b17230>, args=(), kwargs={}, url_name='testapp.views.vote_proposal', app_name='None', namespace='')
person Alexey Savanovich    schedule 02.01.2012
comment
+1 за решение и ответ. Я не знал об этой функции. К сожалению, проблема заключалась в URL-адресе, по какой-то причине представление view-proposal путалось с add_support_vote. Просто изменив порядок, это сработало :) - person Oscar Carballal; 03.01.2012

Я получал ошибку 405, потому что пытался выполнить POST для TemplateView, у которого нет метода POST, если это кому-нибудь поможет.

Поэтому я заменил его на FormView (у которого есть метод POST), и это сработало.

person Aaron Lelevier    schedule 24.03.2015
comment
Просто для информации: у меня была проблема с локализацией: возможно, ajax-запрос к /ajaxurl/ сделал перенаправление на /en/ajaxurl/ (и это перенаправление было GET вместо исходного POST) - person mirek; 17.04.2019