Ticket #20034: 1148.patch

File 1148.patch, 10.6 KB (added by tadeck, 12 years ago)

PATCH file for ticket #20034

  • django/core/files/uploadhandler.py

    From 5c68d02854a64cef882c2cc58a9cc3976b3587b8 Mon Sep 17 00:00:00 2001
    From: TK Kocheran <rfkrocktk@gmail.com>
    Date: Tue, 12 Mar 2013 19:17:45 -0700
    Subject: [PATCH 1/2] Modified upload handler API to be able to invoke
     callbacks when new fields/variables are successfully parsed from multipart
     POST request. Fix for #20034.
    
    ---
     django/core/files/uploadhandler.py | 6 ++++++
     django/http/multipartparser.py     | 7 +++++++
     2 files changed, 13 insertions(+)
    
    diff --git a/django/core/files/uploadhandler.py b/django/core/files/uploadhandler.py
    index f5e95cf..080b5d3 100644
    a b def handle_raw_input(self, input_data, META, content_length, boundary, encoding=  
    8484        """
    8585        pass
    8686
     87    def variable_complete(self, variable_name, variable_value):
     88        """
     89        Signal that a new variable has been parsed from the multipart request.
     90        """
     91        pass
     92
    8793    def new_file(self, field_name, file_name, content_type, content_length, charset=None):
    8894        """
    8995        Signal that a new file has been started.
  • django/http/multipartparser.py

    diff --git a/django/http/multipartparser.py b/django/http/multipartparser.py
    index 26e10da..cc5c809 100644
    a b def parse(self):  
    169169
    170170                    self._post.appendlist(field_name,
    171171                                          force_text(data, encoding, errors='replace'))
     172                   
     173                    for handler in handlers:
     174                        try :
     175                            handler.variable_complete(field_name, force_text(data, encoding, errors='replace'))
     176                        except StopFutureHandlers:
     177                            break
     178
    172179                elif item_type == FILE:
    173180                    # This is a file, use the handler...
    174181                    file_name = disposition.get('filename')
  • AUTHORS

    -- 
    1.8.1.6
    
    
    From d9866dad7c6d45d80af6053b2cdec2e885b4005d Mon Sep 17 00:00:00 2001
    From: Tomasz Jaskowski <tadeck@gmail.com>
    Date: Sat, 18 May 2013 18:28:42 +0200
    Subject: [PATCH 2/2] Refactored new feature #20034, added tests, documented.
    
    ---
     AUTHORS                            |   1 +
     django/core/files/uploadhandler.py |  15 +++---
     django/http/multipartparser.py     |   8 +--
     docs/topics/http/file-uploads.txt  |  15 ++++++
     tests/requests/tests.py            | 101 +++++++++++++++++++++++++++++++++++++
     5 files changed, 130 insertions(+), 10 deletions(-)
    
    diff --git a/AUTHORS b/AUTHORS
    index 4a9981d..2f91c08 100644
    a b answer newbie questions, and generally made Django that much better:  
    293293    Baurzhan Ismagulov <ibr@radix50.net>
    294294    Stephan Jaekel <steph@rdev.info>
    295295    james_027@yahoo.com
     296    Tom Jaskowski <tadeck@gmail.com>
    296297    jcrasta@gmail.com
    297298    jdetaeye
    298299    Dmitry Jemerov <intelliyole@gmail.com>
  • django/core/files/uploadhandler.py

    diff --git a/django/core/files/uploadhandler.py b/django/core/files/uploadhandler.py
    index 080b5d3..49ed98d 100644
    a b def handle_raw_input(self, input_data, META, content_length, boundary, encoding=  
    8484        """
    8585        pass
    8686
    87     def variable_complete(self, variable_name, variable_value):
    88         """
    89         Signal that a new variable has been parsed from the multipart request.
    90         """
    91         pass
    92 
    9387    def new_file(self, field_name, file_name, content_type, content_length, charset=None):
    9488        """
    9589        Signal that a new file has been started.
    def file_complete(self, file_size):  
    119113        """
    120114        raise NotImplementedError()
    121115
     116    def variable_complete(self, variable_name, variable_value):
     117        """
     118        Signal that a new variable has been parsed from the multipart request.
     119
     120        :param variable_name: name of the variable
     121        :param variable_value: value of the multipart variable
     122        """
     123        pass
     124
    122125    def upload_complete(self):
    123126        """
    124127        Signal that the upload is complete. Subclasses should perform cleanup
  • django/http/multipartparser.py

    diff --git a/django/http/multipartparser.py b/django/http/multipartparser.py
    index cc5c809..6679899 100644
    a b def parse(self):  
    167167                    else:
    168168                        data = field_stream.read()
    169169
    170                     self._post.appendlist(field_name,
    171                                           force_text(data, encoding, errors='replace'))
     170                    field_text = force_text(data, encoding, errors='replace')
     171                    self._post.appendlist(field_name, field_text)
    172172                   
    173173                    for handler in handlers:
    174                         try :
    175                             handler.variable_complete(field_name, force_text(data, encoding, errors='replace'))
     174                        try:
     175                            handler.variable_complete(field_name, field_text)
    176176                        except StopFutureHandlers:
    177177                            break
    178178
  • docs/topics/http/file-uploads.txt

    diff --git a/docs/topics/http/file-uploads.txt b/docs/topics/http/file-uploads.txt
    index 80bd5f3..aa857af 100644
    a b attributes:  
    425425``FileUploadHandler.upload_complete(self)``
    426426    Callback signaling that the entire upload (all files) has completed.
    427427
     428``FileUploadHandler.variable_complete(self, variable_name, variable_value)``
     429    Callback signaling that a new variable has been parsed. This is called
     430    before new file is parsed.
     431
     432    ``variable_name`` is a string name of the non-file ``<input>`` field.
     433
     434    ``variable_value`` is the value (text) provided in the field by browser.
     435
     436    This method may raise a ``StopFutureHandlers`` exception to prevent
     437    future handlers from handling this variable.
     438
     439.. versionadded:: 1.6
     440
     441    The ``variable_complete`` method was added in Django 1.6.
     442
    428443``FileUploadHandler.handle_raw_input(self, input_data, META, content_length, boundary, encoding)``
    429444    Allows the handler to completely override the parsing of the raw
    430445    HTTP input.
  • tests/requests/tests.py

    diff --git a/tests/requests/tests.py b/tests/requests/tests.py
    index 676cd05..8e76832 100644
    a b  
    11# -*- encoding: utf-8 -*-
    22from __future__ import unicode_literals
    33
     4from contextlib import contextmanager
    45import time
    56import warnings
    67from datetime import datetime, timedelta
    def read(self, len=0):  
    659660        with self.assertRaises(UnreadablePostError):
    660661            request.body
    661662
     663    def test_POST_new_variable_signal_in_multipart(self):
     664        """
     665        Multi part POST requests should allow reading variables even before
     666        parsing whole files. This can be done by overloading file upload
     667        handlers' ``new_variable`` method.
     668        """
     669        collected_variables_memory_handler = []
     670        collected_variables_temporary_handler = []
     671
     672        def get_var_collector(handler):
     673            """Retrieve variable collector for specific handler"""
     674            def _collect_variable(self, variable_name, variable_value):
     675                """Store variable in external list, to check it later"""
     676                if handler == 'memory':
     677                    collected_variables_memory_handler.append(
     678                        (variable_name, variable_value),
     679                    )
     680                elif handler == 'temporary':
     681                    collected_variables_temporary_handler.append(
     682                        (variable_name, variable_value),
     683                    )
     684                else:
     685                    raise NotImplementedError(
     686                        'Collector for %s not implemented' % (handler,),
     687                    )
     688            return _collect_variable
     689
     690        @contextmanager
     691        def override_upload_handler_methods():
     692            """Override parser method temporarily"""
     693            from django.core.files.uploadhandler import (
     694                MemoryFileUploadHandler, TemporaryFileUploadHandler,
     695            )
     696            old_method_memory = MemoryFileUploadHandler.variable_complete
     697            old_method_temporary = TemporaryFileUploadHandler.variable_complete
     698
     699            # Override handler method:
     700            MemoryFileUploadHandler.variable_complete = get_var_collector(
     701                'memory',
     702            )
     703            TemporaryFileUploadHandler.variable_complete = get_var_collector(
     704                'temporary',
     705            )
     706            yield
     707            # Recover previous state:
     708            MemoryFileUploadHandler.new_variable = old_method_memory
     709            TemporaryFileUploadHandler.new_variable = old_method_temporary
     710
     711        payload = FakePayload(
     712            "\r\n".join([
     713                '--boundary',
     714                'Content-Disposition: form-data; name="n1"',
     715                '',
     716                'value',
     717                '--boundary',
     718                'Content-Disposition: form-data; name="n2"; filename="f.txt"',
     719                'Content-Type: text/plain',
     720                '',
     721                '... contents of file1.txt ...',
     722                'value',
     723                '--boundary--',
     724                'Content-Disposition: form-data; name="n3"',
     725                '',
     726                'value',
     727                '--boundary--',
     728                '']))
     729        request = WSGIRequest({
     730            'REQUEST_METHOD': 'POST',
     731            'CONTENT_TYPE': 'multipart/form-data; boundary=boundary',
     732            'CONTENT_LENGTH': len(payload),
     733            'wsgi.input': payload})
     734
     735        # Use specific upload handlers
     736        upload_handlers = (
     737            'django.core.files.uploadhandler.MemoryFileUploadHandler',
     738            'django.core.files.uploadhandler.TemporaryFileUploadHandler',
     739        )
     740        with override_upload_handler_methods(), self.settings(
     741                FILE_UPLOAD_HANDLERS=upload_handlers,
     742        ):
     743            self.assertEqual(
     744                request.POST,
     745                {
     746                    'n1': ['value'],
     747                    'n3': ['value'],
     748                },
     749            )
     750            self.assertEqual(len(request.FILES), 1)
     751            self.assertIn('n2', request.FILES)
     752
     753            # Confirm the variables have been collected:
     754            self.assertEqual(
     755                collected_variables_memory_handler,
     756                [('n1', 'value'), ('n3', 'value')],
     757            )
     758            self.assertEqual(
     759                collected_variables_temporary_handler,
     760                [('n1', 'value'), ('n3', 'value')],
     761            )
     762
    662763
    663764@skipIf(connection.vendor == 'sqlite'
    664765        and connection.settings_dict['NAME'] in ('', ':memory:'),
Back to Top