3

我正在尝试使用 Google App Engine 队列 API,但在测试时遇到了问题。似乎在流程的某些部分中,CSRF 无法正常工作。

据我了解,api执行调用url并在后台发出http请求的任务。

API 调用的完整 url 是 → http://localhost.localdomain:8000/admin/cooking/recipe/36/chefworker/

当它引发此异常时:

Traceback (most recent call last):
  File "/home/mariocesar/Proyectos/Cooking/cooking/django/core/handlers/base.py", line 100, in get_response
    response = callback(request, *callback_args, **callback_kwargs)
  File "/home/mariocesar/Proyectos/Cooking/cooking/django/views/decorators/csrf.py", line 24, in wrapped_view
    resp.csrf_exempt = True
AttributeError: 'NoneType' object has no attribute 'csrf_exempt'

因此,GAE api 为在后台执行任务而发出的请求中缺少 csrf 中间件、cookie、某些数据或响应本身。

如何在不禁用 Django 上的 CSRF 的情况下解决这个问题?但是,djangoappengine 完全可以吗?

下面是我正在使用的 models.py 和 admin.py 文件。

模型.py

from django.db import models

class Recipe(models.Model):
    name = models.CharField(max_length=140)
    description = models.TextField()
    cooking_time = models.PositiveIntegerField()
    status = models.CharField(max_length=40)

    def __unicode__(self):
        return self.name

    def cookthis(self):
        import time
        self.status = 'The chef is cooking this recipe'
        self.save()
        time.sleep(obj.cooking_time)
        self.status = 'It\'s done ! the recipe is ready to serve'
        self.save()

管理员.py

import logging

from django.contrib import admin, messages
from django.http import HttpResponse
from django.utils.functional import update_wrapper
from django.contrib.admin.util import unquote
from django.shortcuts import get_object_or_404, render_to_response
from django import template
from django.core.urlresolvers import reverse
from google.appengine.api import taskqueue
from google.appengine.api.taskqueue import TaskAlreadyExistsError

from cooking.models import Recipe
from django.views.decorators.csrf import csrf_exempt

class AdminRecipe(admin.ModelAdmin):
    def get_urls(self):
        from django.conf.urls.defaults import patterns, url

        def wrap(view):
            def wrapper(*args, **kwargs):
                return self.admin_site.admin_view(view)(*args, **kwargs)
            return update_wrapper(wrapper, view)

        info = self.model._meta.app_label, self.model._meta.module_name

        urlpatterns = super(AdminRecipe, self).get_urls()
        myurls = patterns('',
            url(r'^(.+)/cook/$',
                wrap(self.cook_view),
                name='%s_%s_chefworker' % info),
            url(r'^(.+)/chefworker/$',
                wrap(self.chefworker_worker),
                name='%s_%s_chefworker' % info),
        )
        return myurls + urlpatterns

    def cook_view(self, request, object_id, extra_context=None):
        obj = get_object_or_404(Recipe, pk=unquote(object_id))
        if request.POST:
            try:
                taskqueue.add(
                    name="recipie-%s" % obj.id,
                    url=reverse('admin:cooking_recipe_chefworker', args=(obj.id,))
                )
                messages.add_message(request, messages.INFO, 'Chef is cooking the recipe.')
            except TaskAlreadyExistsError:
                messages.add_message(request, messages.ERROR, 'chef is already cooking that recipe.')

        context_instance = template.RequestContext(request, current_app=self.admin_site.name)
        return render_to_response("admin/cooking/recipe/cook_view.html", {'object': obj}, context_instance=context_instance)

    #TODO: Add csrf token on form
    @csrf_exempt
    def chefworker_worker(self, request, object_id, extra_context=None):
        import time

        if request.POST:
            obj = get_object_or_404(Recipe, pk=unquote(object_id))
            obj.cookthis()

        return HttpResponse('done')

admin.site.register(Recipe, AdminRecipe)

重要说明: 很难调试此错误,因为 dev_appserver 记录器只是引发 403 错误,没有其他信息;所以,我必须修补文件 google/appengine/api/taskqueue/taskqueue_stub.py 第 574 行并添加“logging.info('response --- \n%s' % result)”以获得输出。

4

3 回答 3

5

如果您已CsrfViewMiddleware启用,Django 将要求csrf_token在您的视图的所有 POST 中添加一个。

Django 提供了一个装饰器,@csrf_exempt你应该将它放在你的任务队列视图中。这会为这些视图关闭中间件。

或者,您可以CsrfViewMiddleware完全避免使用,而是@csrf_protect在需要的地方使用装饰器。我不建议这样做——在任何地方进行保护并为您的任务队列视图划出少量豁免可能会更安全。

(最后一点:上面的两个答案——你的观点有问题,或者你应该只使用 GET 作为任务队列——让我错了。你的观点没有错,POST 是正确的动词用于任务队列任务。)

于 2011-03-18T00:16:31.577 回答
3

查看 csrf.py 的源代码,看起来只有当您的视图函数返回 None(或未显式返回,在这种情况下 Python 将隐式返回 None)时才会发生这种情况。查看您的代码,但我看不出这是如何发生的-您确定这是您部署的确切代码吗?

此外,您可能不想get_object_or_404在任务队列任务中使用 - 如果找不到对象,它将抛出 404,这将导致任务出错并无限期地重试。

您也不应该需要 CSRF 保护(根据您的 TODO);相反,请确保任务队列 URL 被标记为 admin-only,并且它只会被任务队列服务调用。

于 2011-01-27T03:53:47.563 回答
1

我不是专家,但您可以尝试使用 GET 而不是 POST。请参阅http://groups.google.com/group/django-non-relational/browse_thread/thread/e6baed5291aed957/d6c42150c8e246e1?lnk=gst&q=queue#d6c42150c8e246e1(最后一个条目)

于 2011-01-26T20:07:38.697 回答