我正在尝试在tastypie中编写自定义身份验证。基本上,我想使用post参数进行身份验证,我根本不想使用django auth,所以我的代码看起来像:
class MyAuthentication(Authentication):
def is_authenticated(self, request, **kwargs):
if request.method == 'POST':
token = request.POST['token']
key = request.POST['key']
return is_key_valid(token,key)
这或多或少都是这个想法。问题是我一直收到以下错误:
"error_message": "You cannot access body after reading from request's data stream"
我知道这与我正在访问POST的事实有关,但我无法确定是否有办法解决它。有任何想法吗? 感谢。
编辑:也许我忘了提及最重要的事情。我正在使用github中找到的技巧处理表单数据。我的资源来自多部分资源
class MultipartResource(object):
def deserialize(self, request, data, format=None):
if not format:
format = request.META.get('CONTENT_TYPE', 'application/json')
if format == 'application/x-www-form-urlencoded':
return request.POST
if format.startswith('multipart'):
data = request.POST.copy()
data.update(request.FILES)
return data
return super(MultipartResource, self).deserialize(request, data, format)
答案 0 :(得分:12)
问题是您的请求中的Content-Type
标头未正确设置。 [Reference]
Tastypie仅识别xml
,json
,yaml
和bplist
。因此,在发送POST请求时,您需要将请求标头中的Content-Type
设置为其中一个(例如,application/json
)。
修改强>:
您似乎正在尝试通过文件发送多部分表单 Tastypie。
Issac Kelly为Tastypie的文件上传支持提供了一些背景知识 路线图1.0决赛(尚未发布):
- 实现一个Base64FileField,它接受用于PUT / POST的base64编码文件(如问题#42中的那个),并提供GET请求的URL。这将是主要的tastypie repo的一部分。
- 我们希望鼓励其他实施作为独立项目实施。有几种方法可以做到这一点,其中大多数都有点挑剔,它们都有不同的缺点,我们希望有其他选择,并记录每个的利弊
醇>
这意味着至少现在,Tastypie并没有正式支持多部分 上传文件。但是,野外的叉子据说正在工作 好吧,this是其中之一 他们。我没有测试过它。
现在让我试着解释你遇到这个错误的原因。
在Tastypie resource.py
, line 452:
def dispatch(self, request_type, request, **kwargs):
"""
Handles the common operations (allowed HTTP method, authentication,
throttling, method lookup) surrounding most CRUD interactions.
"""
allowed_methods = getattr(self._meta, "%s_allowed_methods" % request_type, None)
if 'HTTP_X_HTTP_METHOD_OVERRIDE' in request.META:
request.method = request.META['HTTP_X_HTTP_METHOD_OVERRIDE']
request_method = self.method_check(request, allowed=allowed_methods)
method = getattr(self, "%s_%s" % (request_method, request_type), None)
if method is None:
raise ImmediateHttpResponse(response=http.HttpNotImplemented())
self.is_authenticated(request)
self.is_authorized(request)
self.throttle_check(request)
# All clear. Process the request.
request = convert_post_to_put(request)
response = method(request, **kwargs)
# Add the throttled request.
self.log_throttled_access(request)
# If what comes back isn't a ``HttpResponse``, assume that the
# request was accepted and that some action occurred. This also
# prevents Django from freaking out.
if not isinstance(response, HttpResponse):
return http.HttpNoContent()
return response
从这里调用 convert_post_to_put(request)
。 here is the code for
convert_post_to_put
:
# Based off of ``piston.utils.coerce_put_post``. Similarly BSD-licensed.
# And no, the irony is not lost on me.
def convert_post_to_VERB(request, verb):
"""
Force Django to process the VERB.
"""
if request.method == verb:
if hasattr(request, '_post'):
del(request._post)
del(request._files)
try:
request.method = "POST"
request._load_post_and_files()
request.method = verb
except AttributeError:
request.META['REQUEST_METHOD'] = 'POST'
request._load_post_and_files()
request.META['REQUEST_METHOD'] = verb
setattr(request, verb, request.POST)
return request
def convert_post_to_put(request):
return convert_post_to_VERB(request, verb='PUT')
并且这种方法并不是真正意图处理多部分
阻止进一步访问request.body
的副作用因为
_load_post_and_files()
方法会将_read_started
标记设置为True
:
Django request.body
和_load_post_and_files()
:
@property
def body(self):
if not hasattr(self, '_body'):
if self._read_started:
raise Exception("You cannot access body after reading from request's data stream")
try:
self._body = self.read()
except IOError as e:
six.reraise(UnreadablePostError, UnreadablePostError(*e.args), sys.exc_info()[2])
self._stream = BytesIO(self._body)
return self._body
def read(self, *args, **kwargs):
self._read_started = True
return self._stream.read(*args, **kwargs)
def _load_post_and_files(self):
# Populates self._post and self._files
if self.method != 'POST':
self._post, self._files = QueryDict('', encoding=self._encoding), MultiValueDict()
return
if self._read_started and not hasattr(self, '_body'):
self._mark_post_parse_error()
return
if self.META.get('CONTENT_TYPE', '').startswith('multipart'):
if hasattr(self, '_body'):
# Use already read data
data = BytesIO(self._body)
else:
data = self
try:
self._post, self._files = self.parse_file_upload(self.META, data)
except:
# An error occured while parsing POST data. Since when
# formatting the error the request handler might access
# self.POST, set self._post and self._file to prevent
# attempts to parse POST data again.
# Mark that an error occured. This allows self.__repr__ to
# be explicit about it instead of simply representing an
# empty POST
self._mark_post_parse_error()
raise
else:
self._post, self._files = QueryDict(self.body, encoding=self._encoding), MultiValueDict()
所以,你可以(虽然可能不应该)修补Tastypie的猴子
通过调用设置convert_post_to_VERB()
的{{1}}方法
request._body
然后立即设置request.body
_read_started=False
将从_load_post_and_files()
读取,但不会设置
_body
:
_read_started=True
答案 1 :(得分:1)
您说您需要自定义身份验证,但请考虑使用Authorization
标头。通过使用POST
,您可以强制Django解析整个有效负载,假设数据是urlencoded或多部分表单编码。这实际上使得无法使用非形式的有效载荷,如JSON或YAML。
class MyAuthentication(Authentication):
def is_authenticated(self, request, **kwargs):
auth_info = request.META.get('HTTP_AUTHORIZATION')
# ...
答案 2 :(得分:0)
当您第二次访问request.body(或者如果您仍然在Django 1.3上时请求.raw_post_data),或者我相信,如果您在访问POST,GET,META或COOKIES属性后访问它,则会发生此错误
在处理PUT或PATCH请求时,Tastypie将访问request.body(raw_post_data)属性。
考虑到这一点并且不知道更多细节,我会:
希望这有帮助!
答案 3 :(得分:0)
我已经创建了一种适合我的实用方法。虽然我不确定这会如何影响Django的底层部分,但它确实有效:
import io
def copy_body(request):
data = getattr(request, '_body', request.body)
request._body = data
request._stream = io.BytesIO(data)
request._files = None
return data
我在中间件中使用它将JSON
属性添加到request
:https://gist.github.com/antonagestam/9add2d69783287025907