将 Google App Engine 队列服务与 Django 结合使用
我正在尝试使用 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 文件。
models.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()
admin.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)”才能获取输出。
I am trying to use Google App Engine queues API, I am having problems on testing this. It seems that in some part of the process the CSRF it's not working.
as I understand the api executes the task calling the url and making and http request in background.
The complete url is the API is calling is → http://localhost.localdomain:8000/admin/cooking/recipe/36/chefworker/
When it raises this exception:
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'
So, the csrf middleware, the cookie, some data or the response itself is missing from the request that the GAE api makes to execute the task in the background.
How to solve this without disabling CSRF on Django? however, it's posible with djangoappengine at all?
Down are the models.py and admin.py files I am using.
models.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()
admin.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/
IMPORTANT NOTE:
Was hard to debug this error, cause the dev_appserver logger was just raising 403 errors, no other info; so, I have to patch the file google/appengine/api/taskqueue/taskqueue_stub.py line 574 and add "logging.info('response --- \n%s' % result)" to get the output.
,
wrap(self.cook_view),
name='%s_%s_chefworker' % info),
url(r'^(.+)/chefworker/
IMPORTANT NOTE:
Was hard to debug this error, cause the dev_appserver logger was just raising 403 errors, no other info; so, I have to patch the file google/appengine/api/taskqueue/taskqueue_stub.py line 574 and add "logging.info('response --- \n%s' % result)" to get the output.
,
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)
IMPORTANT NOTE:
Was hard to debug this error, cause the dev_appserver logger was just raising 403 errors, no other info; so, I have to patch the file google/appengine/api/taskqueue/taskqueue_stub.py line 574 and add "logging.info('response --- \n%s' % result)" to get the output.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
如果您启用了
CsrfViewMiddleware
,Django 将在您的视图的所有 POST 中要求一个csrf_token
。Django 提供了一个装饰器
@csrf_exempt
,您应该将其放置在任务队列视图上。这会关闭那些视图的中间件。或者,您可以完全避免使用
CsrfViewMiddleware
,而是在需要时使用@csrf_protect
装饰器。我不建议这样做——保护所有地方并为任务队列视图开辟少量豁免可能更安全。(最后一点:上面的两个答案——你的观点有问题,或者你应该只使用 GET 来处理任务队列——我错了。你的观点没有任何问题,POST 是正确使用的动词对于任务队列任务。)
If you have the
CsrfViewMiddleware
enabled, Django will require acsrf_token
in all POSTs to your views.Django provides a decorator,
@csrf_exempt
, that you should place on your task queue views. This turns off the middleware just for those views.Alternatively, you can avoid using
CsrfViewMiddleware
altogether and instead use the@csrf_protect
decorator where you need it. I don't recommend doing this -- it's probably safer to protect everywhere and carve out a small number of exemptions for your task queue views.(One last note: both answers above -- that something is wrong with your view, or that you should just use GET for the task queue -- strike me wrong. There's nothing wrong with your view, and POST is the right verb to use for task queue tasks.)
查看source ,看起来只有当您的视图函数返回 None (或未显式返回,在这种情况下 Python 将隐式返回 None )时才会发生这种情况。看看你的代码,我不明白这是如何发生的——你确定这是你确切部署的代码吗?
另外,您可能不想在任务队列任务中使用
get_object_or_404
- 如果找不到该对象,它将抛出 404,这将导致任务出错并无限期重试。您也不应该需要 CSRF 保护(根据您的 TODO);相反,请确保任务队列 URL 标记为仅限管理员,并且它只会由任务队列服务调用。
Looking at the source of csrf.py, it looks like this would only occur if your view function is returning None (or not explicitly returning, in which case Python would return None implicitly). Looking at your code, I don't see how that could occur, though - are you sure this is your exact deployed code?
Also, you probably don't want to use
get_object_or_404
inside a task queue task - if it can't find the object, it'll throw a 404, which will cause the task to error and retry indefinitely.You also shouldn't need CSRF protection (per your TODO); instead, make sure the task queue URL is marked admin-only, and it will only ever be called by the task queue service.
我不是专家,但您可以尝试使用 GET 而不是 POST。请参阅 http: //groups.google.com/group/django-non-relational/browse_thread/thread/e6baed5291aed957/d6c42150c8e246e1?lnk=gst&q=queue#d6c42150c8e246e1(最后一个条目)
I'm not an expert, but you may try using GET instead of POST. See http://groups.google.com/group/django-non-relational/browse_thread/thread/e6baed5291aed957/d6c42150c8e246e1?lnk=gst&q=queue#d6c42150c8e246e1 (the last entry)