11

私はtastypieでカスタム認証を書こうとしています。基本的に、投稿パラメーターを使用して認証を行いたいのですが、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)
4

4 に答える 4

12

問題は、Content-Typeリクエストのヘッダーが正しく設定されていないことです。【参考

Tastypie は、、、および のみxmlを認識しjsonます。そのため、POST リクエストを送信するときは、リクエスト ヘッダーにそれらのいずれかを設定する必要があります (例: )。yamlbplistContent-Typeapplication/json

編集

Tastypie を介してファイルを含むマルチパート フォームを送信しようとしているようです。

ロードマップ 1.0 最終版 (まだリリースされていません) に対する Issac Kelly による Tastypie のファイル アップロード サポートの背景:

  1. PUT/POST 用に base64 でエンコードされたファイル (第 42 号のものなど) を受け入れ、GET 要求の URL を提供する Base64FileField を実装します。これはメインのtastypieレポの一部になります。
  2. 他の実装を独立したプロジェクトとして実装することをお勧めします。これを行うにはいくつかの方法があり、それらのほとんどは少し厄介で、すべて異なる欠点があります。他のオプションを用意し、それぞれの長所と短所を文書化したいと考えています

つまり、少なくとも現時点では、Tastypie はマルチパート ファイルのアップロードを正式にサポートしていません。ただし、実際にはうまく機能していると思われるフォークがあり、これはその 1 つです。私はそれをテストしていません。


それでは、なぜそのエラーが発生するのかを説明してみましょう。

Tastypieresource.pyの 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)ここから呼び出されます。そして、ここにコードがあります 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_startedTrue

ジャンゴ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()メソッドをrequest._body呼び出して設定すること でモンキー パッチを適用しrequest.body、すぐに設定して、から読み取って設定しない _read_started=Falseよう にすることができます (おそらくそうすべきではありません) 。_load_post_and_files()_body_read_started=True

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)

        request.body  # now request._body is set
        request._read_started = False  # so it won't cause side effects

        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
于 2012-09-25T04:54:18.923 に答える
1

カスタム認証が必要だとおっしゃっていますが、Authorization代わりにヘッダーの使用を検討してください。を使用POSTすると、データが urlencoded またはマルチパート形式でエンコードされていると仮定して、Django にペイロード全体を強制的に解析させます。これにより、事実上、JSON や YAML などの非フォーム ペイロードを使用できなくなります。

class MyAuthentication(Authentication):
    def is_authenticated(self, request, **kwargs):
        auth_info = request.META.get('HTTP_AUTHORIZATION')
        # ...
于 2012-09-27T18:47:41.620 に答える
0

このエラーは、request.body (まだ Django 1.3 を使用している場合は request.raw_post_data) に 2 回アクセスした場合、または POST、GET、META、または COOKIES 属性にアクセスした後にアクセスした場合に発生します。

Tastypie は、PUT または PATCH リクエストを処理するときに request.body (raw_post_data) 属性にアクセスします。

これを念頭に置いて、詳細を知らなくても、私は次のようにします。

  • これが POST/PUT でのみ発生するかどうかを確認します。もしそうなら、いくつかのtastypieメソッドをオーバーライドするか、認証のためのアプローチを放棄する必要があります.
  • request.body (raw_post_data) にアクセスするコード内の場所を探します。
  • body/raw_post_data にアクセスしようとするサードパーティ モジュール (おそらくミドルウェア) の呼び出しを探します。

お役に立てれば!

于 2012-09-24T20:22:46.180 に答える
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

于 2014-06-12T15:02:29.063 に答える