1

現在のプロジェクト用にカスタム Django ファイル アップロード ハンドラを作成しました。これは、アップロードされたファイルをディスクに保存せずにそのハッシュを計算できる概念実証です。確かにこれは概念実証ですが、それを機能させることができれば、私の仕事の本当の目的に取り掛かることができます。

基本的に、ここに私がこれまでに持っているものがありますが、これは1つの大きな例外を除いてうまく機能しています:

from django.core.files.uploadhandler import *
from hashlib import sha256
from myproject.upload.files import MyProjectUploadedFile

class MyProjectUploadHandler(FileUploadHandler):
    def __init__(self, *args, **kwargs):
        super(MyProjectUploadHandler, self).__init__(*args, **kwargs)

    def handle_raw_input(self, input_data, META, content_length, boundary,
            encoding = None):
        self.activated = True

    def new_file(self, *args, **kwargs):
        super(MyProjectUploadHandler, self).new_file(*args, **kwargs)

        self.digester = sha256()
        raise StopFutureHandlers()

    def receive_data_chunk(self, raw_data, start):
        self.digester.update(raw_data)

    def file_complete(self, file_size):
        return MyProjectUploadedFile(self.digester.hexdigest())

カスタム アップロード ハンドラはうまく機能します。ハッシュは正確で、アップロードされたファイルをディスクに保存することなく機能し、一度に 64kb のメモリしか使用しません。

私が抱えている唯一の問題は、ファイルを処理する前に POST リクエストから別のフィールド、つまりユーザーが入力したテキストソルトにアクセスする必要があることです。私のフォームは次のようになります。

<form id="myForm" method="POST" enctype="multipart/form-data" action="/upload/">
    <fieldset>
        <input name="salt" type="text" placeholder="Salt">
        <input name="uploadfile" type="file">
        <input type="submit">
    </fieldset>
</form>

「salt」POST 変数は、リクエストが処理され、ファイルがアップロードされた後にのみ利用可能になります。これは、私のユース ケースでは機能しません。アップロード ハンドラーでこの変数にアクセスする方法、形状、またはフォームを見つけることができないようです。

アップロードされたファイルにアクセスするだけでなく、各マルチパート変数にアクセスする方法はありますか?

4

2 に答える 2

2

私の解決策は簡単ではありませんでしたが、ここにあります:

class IntelligentUploadHandler(FileUploadHandler):
    """
    An upload handler which overrides the default multipart parser to allow
    simultaneous parsing of fields and files... intelligently. Subclass this
    for real and true awesomeness.
    """

    def __init__(self, *args, **kwargs):
        super(IntelligentUploadHandler, self).__init__(*args, **kwargs)

    def field_parsed(self, field_name, field_value):
        """
        A callback method triggered when a non-file field has been parsed 
        successfully by the parser. Use this to listen for new fields being
        parsed.
        """
        pass

    def handle_raw_input(self, input_data, META, content_length, boundary,
            encoding = None):
        """
        Parse the raw input from the HTTP request and split items into fields
        and files, executing callback methods as necessary.

        Shamelessly adapted and borrowed from django.http.multiparser.MultiPartParser.
        """
        # following suit from the source class, this is imported here to avoid
        # a potential circular import
        from django.http import QueryDict

        # create return values
        self.POST = QueryDict('', mutable=True)
        self.FILES = MultiValueDict()

        # initialize the parser and stream
        stream = LazyStream(ChunkIter(input_data, self.chunk_size))

        # whether or not to signal a file-completion at the beginning of the loop.
        old_field_name = None
        counter = 0

        try:
            for item_type, meta_data, field_stream in Parser(stream, boundary):
                if old_field_name:
                    # we run this test at the beginning of the next loop since
                    # we cannot be sure a file is complete until we hit the next
                    # boundary/part of the multipart content.
                    file_obj = self.file_complete(counter)

                    if file_obj:
                        # if we return a file object, add it to the files dict
                        self.FILES.appendlist(force_text(old_field_name, encoding,
                            errors='replace'), file_obj)

                    # wipe it out to prevent havoc
                    old_field_name = None
                try: 
                    disposition = meta_data['content-disposition'][1]
                    field_name = disposition['name'].strip()
                except (KeyError, IndexError, AttributeError):
                    continue

                transfer_encoding = meta_data.get('content-transfer-encoding')

                if transfer_encoding is not None:
                    transfer_encoding = transfer_encoding[0].strip()

                field_name = force_text(field_name, encoding, errors='replace')

                if item_type == FIELD:
                    # this is a POST field
                    if transfer_encoding == "base64":
                        raw_data = field_stream.read()
                        try:
                            data = str(raw_data).decode('base64')
                        except:
                            data = raw_data
                    else:
                        data = field_stream.read()

                    self.POST.appendlist(field_name, force_text(data, encoding,
                        errors='replace'))

                    # trigger listener
                    self.field_parsed(field_name, self.POST.get(field_name))
                elif item_type == FILE:
                    # this is a file
                    file_name = disposition.get('filename')

                    if not file_name:
                        continue

                    # transform the file name
                    file_name = force_text(file_name, encoding, errors='replace')
                    file_name = self.IE_sanitize(unescape_entities(file_name))

                    content_type = meta_data.get('content-type', ('',))[0].strip()

                    try:
                        charset = meta_data.get('content-type', (0, {}))[1].get('charset', None)
                    except:
                        charset = None

                    try:
                        file_content_length = int(meta_data.get('content-length')[0])
                    except (IndexError, TypeError, ValueError):
                        file_content_length = None

                    counter = 0

                    # now, do the important file stuff
                    try:
                        # alert on the new file
                        self.new_file(field_name, file_name, content_type,
                                file_content_length, charset)

                        # chubber-chunk it
                        for chunk in field_stream:
                            if transfer_encoding == "base64":
                                # base 64 decode it if need be
                                over_bytes = len(chunk) % 4

                                if over_bytes:
                                    over_chunk = field_stream.read(4 - over_bytes)
                                    chunk += over_chunk

                                try:
                                    chunk = base64.b64decode(chunk)
                                except Exception as e:
                                    # since this is anly a chunk, any error is an unfixable error
                                    raise MultiPartParserError("Could not decode base64 data: %r" % e)

                            chunk_length = len(chunk)
                            self.receive_data_chunk(chunk, counter)
                            counter += chunk_length
                            # ... and we're done
                    except SkipFile:
                        # just eat the rest
                        exhaust(field_stream)
                    else:
                        # handle file upload completions on next iteration
                        old_field_name = field_name

        except StopUpload as e:
            # if we get a request to stop the upload, exhaust it if no con reset
            if not e.connection_reset:
                exhaust(input_data)
        else:
            # make sure that the request data is all fed
            exhaust(input_data)

        # signal the upload has been completed
        self.upload_complete()

        return self.POST, self.FILES

    def IE_sanitize(self, filename):
        """Cleanup filename from Internet Explorer full paths."""
        return filename and filename[filename.rfind("\\")+1:].strip()

基本的に、このクラスをサブクラス化することで、よりインテリジェントなアップロード ハンドラーを作成できます。フィールドはfield_parsed、目的に応じてサブクラスへのメソッドでアナウンスされます。

私はこれをDjango チームに機能要求として報告しました。うまくいけば、この機能は、上記のようにソース コードにモンキー パッチを適用するのではなく、Django の通常のツールボックスの一部になります。

于 2013-03-13T05:50:33.820 に答える
0

のコードに基づくと、FileUploadHandlerここの 62 行目にあります。

https://github.com/django/django/blob/master/django/core/files/uploadhandler.py

リクエストオブジェクトがハンドラーに渡され、次のように保存されているようですself.request

その場合、次のようにして、アップロード ハンドラの任意の時点でソルトにアクセスできる必要があります。

salt = self.request.POST.get('salt')

私があなたの質問を誤解していない限り。

于 2013-03-13T00:03:44.230 に答える