Ticket #2070: 5100_upload_request_meta.diff

File 5100_upload_request_meta.diff, 33.8 KB (added by Michael Axiak <axiak@…>, 17 years ago)

This isn't important enough to get its own property in request...request.META now contains 'UPLOAD_PROGRESS_ID'

  • django/http/__init__.py

     
    1 import os
     1import os, pickle
    22from Cookie import SimpleCookie
    33from pprint import pformat
    44from urllib import urlencode, quote
    55from django.utils.datastructures import MultiValueDict
     6import re
    67
     8try:
     9    from cStringIO import StringIO
     10except ImportError:
     11    from StringIO import StringIO
     12
    713RESERVED_CHARS="!*'();:@&=+$,/?%#[]"
    814
     15
    916try:
    1017    # The mod_python version is more efficient, so try importing it first.
    1118    from mod_python.util import parse_qsl
     
    1724
    1825class HttpRequest(object):
    1926    "A basic HTTP request"
     27
     28    upload_id_re = re.compile(r'[a-fA-F0-9]{32}')
     29
     30
    2031    def __init__(self):
    2132        self.GET, self.POST, self.COOKIES, self.META, self.FILES = {}, {}, {}, {}, {}
    2233        self.path = ''
     
    4253    def is_secure(self):
    4354        return os.environ.get("HTTPS") == "on"
    4455
    45 def parse_file_upload(header_dict, post_data):
    46     "Returns a tuple of (POST MultiValueDict, FILES MultiValueDict)"
    47     import email, email.Message
    48     from cgi import parse_header
    49     raw_message = '\r\n'.join(['%s:%s' % pair for pair in header_dict.items()])
    50     raw_message += '\r\n\r\n' + post_data
    51     msg = email.message_from_string(raw_message)
    52     POST = MultiValueDict()
    53     FILES = MultiValueDict()
    54     for submessage in msg.get_payload():
    55         if submessage and isinstance(submessage, email.Message.Message):
    56             name_dict = parse_header(submessage['Content-Disposition'])[1]
    57             # name_dict is something like {'name': 'file', 'filename': 'test.txt'} for file uploads
    58             # or {'name': 'blah'} for POST fields
    59             # We assume all uploaded files have a 'filename' set.
    60             if 'filename' in name_dict:
    61                 assert type([]) != type(submessage.get_payload()), "Nested MIME messages are not supported"
    62                 if not name_dict['filename'].strip():
    63                     continue
    64                 # IE submits the full path, so trim everything but the basename.
    65                 # (We can't use os.path.basename because it expects Linux paths.)
    66                 filename = name_dict['filename'][name_dict['filename'].rfind("\\")+1:]
    67                 FILES.appendlist(name_dict['name'], {
    68                     'filename': filename,
    69                     'content-type': 'Content-Type' in submessage and submessage['Content-Type'] or None,
    70                     'content': submessage.get_payload(),
    71                 })
     56    def _get_file_progress_from_args(self, headers, get, querystring):
     57
     58        if 'X-Upload-ID' in headers:
     59            progress_id = headers['X-Upload-ID']
     60        elif 'X-Progress-ID' in headers:
     61            progress_id = headers['X-Progress-ID']
     62        elif 'upload_id' in get:
     63            progress_id = get['upload_id']
     64        elif 'progress_id' in get:
     65            progress_id = get['progress_id']
     66        elif querystring != None and len(querystring.strip()) == 32:
     67            progress_id = querystring
     68        else:
     69            return None
     70
     71        if not self.upload_id_re.match(progress_id):
     72            return None
     73
     74        return progress_id
     75
     76
     77def parse_file_upload(headers, input, progress_id = None):
     78    from django.conf import settings
     79
     80    # Only stream files to disk if FILE_STREAMING_DIR is set
     81    file_upload_dir = settings.FILE_UPLOAD_DIR
     82    streaming_min_post_size = settings.STREAMING_MIN_POST_SIZE
     83
     84    try:
     85        parser = MultiPartParser(headers, input, progress_id, file_upload_dir, streaming_min_post_size)
     86        return parser.parse()
     87    except MultiPartParserError, e:
     88        return MultiValueDict({ '_file_upload_error': [e.message] }), {}
     89
     90class MultiPartParserError(Exception):
     91    def __init__(self, message):
     92        self.message = message
     93    def __str__(self):
     94        return repr(self.message)
     95       
     96class MultiPartParser(object):
     97    """
     98    A rfc2388 multipart/form-data parser.
     99   
     100    parse() reads the input stream in chunk_size chunks and returns a
     101    tuple of (POST MultiValueDict, FILES MultiValueDict). If
     102    file_upload_dir is defined files will be streamed to temporary
     103    files in the specified directory.
     104
     105    The FILES dictionary will have 'filename', 'content-type',
     106    'content' and 'content-length' entries. For streamed files it will
     107    also have 'tmpfilename' and 'tmpfile'. The 'content' entry will
     108    only be read from disk when referenced for streamed files.
     109
     110    If the header X-Progress-ID is sent with a 32 character hex string
     111    a temporary file with the same name will be created in
     112    `file_upload_dir`` with a pickled { 'received', 'size' }
     113    dictionary with the number of bytes received and the size expected
     114    respectively. The file will be unlinked when the parser finishes.
     115
     116    """
     117
     118    def __init__(self, headers, input, progress_id=None, file_upload_dir=None, streaming_min_post_size=None, chunk_size=1024*64):
     119        try:
     120            content_length = int(headers['Content-Length'])
     121        except:
     122            raise MultiPartParserError('Invalid Content-Length: %s' % headers.get('Content-Length'))
     123
     124        content_type = headers.get('Content-Type')
     125
     126        if not content_type or not content_type.startswith('multipart/'):
     127            raise MultiPartParserError('Invalid Content-Type: %s' % content_type)
     128           
     129        ctype, opts = self.parse_header(content_type)
     130        boundary = opts.get('boundary')
     131        from cgi import valid_boundary
     132        if not boundary or not valid_boundary(boundary):
     133            raise MultiPartParserError('Invalid boundary in multipart form: %s' % boundary)
     134
     135        # check if we got a valid X-Progress-ID id
     136        if file_upload_dir and progress_id:
     137            import re
     138            if re.match(r'^[0-9a-zA-Z]{32}$', progress_id):
     139                self._progress_filename = os.path.join(file_upload_dir, progress_id)
    72140            else:
    73                 POST.appendlist(name_dict['name'], submessage.get_payload())
    74     return POST, FILES
     141                raise MultiPartParserError('Invalid X-Progress-ID: %s' % progress_id)
     142        else:
     143            self._progress_filename = None
     144        self._boundary = '--' + boundary
     145        self._input = input
     146        self._size = content_length
     147        self._received = 0
     148        self._file_upload_dir = file_upload_dir
     149        self._chunk_size = chunk_size
     150        self._state = 'PREAMBLE'
     151        self._partial = ''
     152        self._post = MultiValueDict()
     153        self._files = MultiValueDict()
     154        if streaming_min_post_size is not None and content_length < streaming_min_post_size:
     155            self._file_upload_dir = None # disable file streaming for small request
    75156
     157        try:
     158            # use mx fast string search if available
     159            from mx.TextTools import FS
     160            self._fs = FS(self._boundary)
     161        except ImportError:
     162            self._fs = None
     163
     164    def parse(self):
     165        try:
     166            self._parse()
     167        finally:
     168            if self._progress_filename:
     169                try:
     170                    os.unlink(self._progress_filename)
     171                except OSError:
     172                    pass
     173       
     174        return self._post, self._files
     175
     176    def _parse(self):
     177        size = self._size
     178
     179        try:
     180            while size > 0:
     181                n = self._read(self._input, min(self._chunk_size, size))
     182                if not n:
     183                    break
     184                size -= n
     185        except:
     186            # consume any remaining data so we dont generate a "Connection Reset" error
     187            size = self._size - self._received
     188            while size > 0:
     189                data = self._input.read(min(self._chunk_size, size))
     190                size -= len(data)
     191            raise
     192
     193    def _find_boundary(self, data, start, stop):
     194        """
     195        Find the next boundary and return the end of current part
     196        and start of next part.
     197        """
     198        if self._fs:
     199            boundary = self._fs.find(data, start, stop)
     200        else:
     201            boundary = data.find(self._boundary, start, stop)
     202        if boundary >= 0:
     203            end = boundary
     204            next = boundary + len(self._boundary)
     205
     206            # backup over CRLF
     207            if end > 0 and data[end-1] == '\n': end -= 1
     208            if end > 0 and data[end-1] == '\r': end -= 1
     209            # skip over --CRLF
     210            if next < stop and data[next] == '-': next += 1
     211            if next < stop and data[next] == '-': next += 1
     212            if next < stop and data[next] == '\r': next += 1
     213            if next < stop and data[next] == '\n': next += 1
     214
     215            return True, end, next
     216        else:
     217            return False, stop, stop
     218
     219    class TemporaryFile(object):
     220        "A temporary file that tries to delete itself when garbage collected."
     221        def __init__(self, dir):
     222            import tempfile
     223            (fd, name) = tempfile.mkstemp(suffix='.upload', dir=dir)
     224            self.file = os.fdopen(fd, 'w+b')
     225            self.name = name
     226
     227        def __getattr__(self, name):
     228            a = getattr(self.__dict__['file'], name)
     229            if type(a) != type(0):
     230                setattr(self, name, a)
     231            return a
     232
     233        def __del__(self):
     234            try:
     235                os.unlink(self.name)
     236            except OSError:
     237                pass
     238           
     239    class LazyContent(dict):
     240        """
     241        A lazy FILES dictionary entry that reads the contents from
     242        tmpfile only when referenced.
     243        """
     244        def __init__(self, data):
     245            dict.__init__(self, data)
     246       
     247        def __getitem__(self, key):
     248            if key == 'content' and not self.has_key(key):
     249                self['tmpfile'].seek(0)
     250                self['content'] = self['tmpfile'].read()
     251            return dict.__getitem__(self, key)
     252
     253    def _read(self, input, size):
     254        data = input.read(size)
     255
     256        if not data:
     257            return 0
     258
     259        read_size = len(data)
     260        self._received += read_size
     261
     262        if self._partial:
     263            data = self._partial + data
     264
     265        start = 0
     266        stop = len(data)
     267       
     268        while start < stop:
     269            boundary, end, next = self._find_boundary(data, start, stop)
     270
     271            if not boundary and read_size:
     272                # make sure we dont treat a partial boundary (and its separators) as data
     273                stop -= len(self._boundary) + 16
     274                end = next = stop
     275                if end <= start:
     276                    break # need more data
     277
     278            if self._state == 'PREAMBLE':
     279                # Preamble, just ignore it
     280                self._state = 'HEADER'
     281
     282            elif self._state == 'HEADER':
     283                # Beginning of header, look for end of header and parse it if found.
     284
     285                header_end = data.find('\r\n\r\n', start, stop)
     286                if header_end == -1:
     287                    break # need more data
     288
     289                header = data[start:header_end]
     290
     291                self._fieldname = None
     292                self._filename = None
     293                self._content_type = None
     294
     295                for line in header.split('\r\n'):
     296                    ctype, opts = self.parse_header(line)
     297                    if ctype == 'content-disposition: form-data':
     298                        self._fieldname = opts.get('name')
     299                        self._filename = opts.get('filename')
     300                    elif ctype.startswith('content-type: '):
     301                        self._content_type = ctype[14:]
     302
     303                if self._filename is not None:
     304                    # cleanup filename from IE full paths:
     305                    self._filename = self._filename[self._filename.rfind("\\")+1:].strip()
     306
     307                    if self._filename: # ignore files without filenames
     308                        if self._file_upload_dir:
     309                            try:
     310                                self._file = self.TemporaryFile(dir=self._file_upload_dir)
     311                            except:
     312                                raise MultiPartParserError("Failed to create temporary file.")
     313                        else:
     314                            self._file = StringIO()
     315                    else:
     316                        self._file = None
     317                    self._filesize = 0
     318                    self._state = 'FILE'
     319                else:
     320                    self._field = StringIO()
     321                    self._state = 'FIELD'
     322                next = header_end + 4
     323
     324            elif self._state == 'FIELD':
     325                # In a field, collect data until a boundary is found.
     326
     327                self._field.write(data[start:end])
     328                if boundary:
     329                    if self._fieldname:
     330                        self._post.appendlist(self._fieldname, self._field.getvalue())
     331                    self._field.close()
     332                    self._state = 'HEADER'
     333
     334            elif self._state == 'FILE':
     335                # In a file, collect data until a boundary is found.
     336
     337                if self._file:
     338                    try:
     339                        self._file.write(data[start:end])
     340                    except IOError, e:
     341                        raise MultiPartParserError("Failed to write to temporary file.")
     342                    self._filesize += end-start
     343
     344                    if self._progress_filename:
     345                        f = open(os.path.join(self._file_upload_dir, self._progress_filename), 'w')
     346                        pickle.dump({ 'received': self._received, 'size': self._size }, f)
     347                        f.close()
     348
     349                if boundary:
     350                    if self._file:
     351                        if self._file_upload_dir:
     352                            self._file.seek(0)
     353                            file = self.LazyContent({
     354                                'filename': self._filename,
     355                                'content-type':  self._content_type,
     356                                # 'content': is read on demand
     357                                'content-length': self._filesize,
     358                                'tmpfilename': self._file.name,
     359                                'tmpfile': self._file
     360                            })
     361                        else:
     362                            file = {
     363                                'filename': self._filename,
     364                                'content-type':  self._content_type,
     365                                'content': self._file.getvalue(),
     366                                'content-length': self._filesize
     367                            }
     368                            self._file.close()
     369
     370                        self._files.appendlist(self._fieldname, file)
     371
     372                    self._state = 'HEADER'
     373
     374            start = next
     375               
     376        self._partial = data[start:]
     377
     378        return read_size
     379
     380    def parse_header(self, line):
     381        from cgi import parse_header
     382        return parse_header(line)
     383
    76384class QueryDict(MultiValueDict):
    77385    """A specialized MultiValueDict that takes a query string when initialized.
    78386    This is immutable unless you create a copy of it."""
     
    306614    if not host:
    307615        host = request.META.get('HTTP_HOST', '')
    308616    return host
     617
  • django/oldforms/__init__.py

     
    666666        self.validator_list = [self.isNonEmptyFile] + validator_list
    667667
    668668    def isNonEmptyFile(self, field_data, all_data):
    669         try:
    670             content = field_data['content']
    671         except TypeError:
     669        if field_data.has_key('_file_upload_error'):
     670            raise validators.CriticalValidationError, field_data['_file_upload_error']
     671        if not field_data.has_key('filename'):
    672672            raise validators.CriticalValidationError, gettext("No file was submitted. Check the encoding type on the form.")
    673         if not content:
     673        if not field_data['content-length']:
    674674            raise validators.CriticalValidationError, gettext("The submitted file is empty.")
    675675
    676676    def render(self, data):
    677677        return '<input type="file" id="%s" class="v%s" name="%s" />' % \
    678678            (self.get_id(), self.__class__.__name__, self.field_name)
    679679
     680    def prepare(self, new_data):
     681        if new_data.has_key('_file_upload_error'):
     682            # pretend we got something in the field to raise a validation error later
     683            new_data[self.field_name] = { '_file_upload_error': new_data['_file_upload_error'] }
     684
    680685    def html2python(data):
    681686        if data is None:
    682687            raise EmptyValue
  • django/db/models/base.py

     
    1212from django.dispatch import dispatcher
    1313from django.utils.datastructures import SortedDict
    1414from django.utils.functional import curry
     15from django.utils.file import file_move_safe
    1516from django.conf import settings
    1617from itertools import izip
    1718import types
    1819import sys
    1920import os
    2021
     22               
    2123class ModelBase(type):
    2224    "Metaclass for all models"
    2325    def __new__(cls, name, bases, attrs):
     
    361363    def _get_FIELD_size(self, field):
    362364        return os.path.getsize(self._get_FIELD_filename(field))
    363365
    364     def _save_FIELD_file(self, field, filename, raw_contents, save=True):
     366    def _save_FIELD_file(self, field, filename, raw_field, save=True):
    365367        directory = field.get_directory_name()
    366368        try: # Create the date-based directory if it doesn't exist.
    367369            os.makedirs(os.path.join(settings.MEDIA_ROOT, directory))
     
    383385        setattr(self, field.attname, filename)
    384386
    385387        full_filename = self._get_FIELD_filename(field)
    386         fp = open(full_filename, 'wb')
    387         fp.write(raw_contents)
    388         fp.close()
     388        if raw_field.has_key('tmpfilename'):
     389            raw_field['tmpfile'].close()
     390            file_move_safe(raw_field['tmpfilename'], full_filename)
     391        else:
     392            fp = open(full_filename, 'wb')
     393            fp.write(raw_field['content'])
     394            fp.close()
    389395
    390396        # Save the width and/or height, if applicable.
    391397        if isinstance(field, ImageField) and (field.width_field or field.height_field):
  • django/db/models/fields/__init__.py

     
    636636        setattr(cls, 'get_%s_filename' % self.name, curry(cls._get_FIELD_filename, field=self))
    637637        setattr(cls, 'get_%s_url' % self.name, curry(cls._get_FIELD_url, field=self))
    638638        setattr(cls, 'get_%s_size' % self.name, curry(cls._get_FIELD_size, field=self))
    639         setattr(cls, 'save_%s_file' % self.name, lambda instance, filename, raw_contents, save=True: instance._save_FIELD_file(self, filename, raw_contents, save))
     639        setattr(cls, 'save_%s_file' % self.name, lambda instance, filename, raw_field, save=True: instance._save_FIELD_file(self, filename, raw_field, save))
    640640        dispatcher.connect(self.delete_file, signal=signals.post_delete, sender=cls)
    641641
    642642    def delete_file(self, instance):
     
    659659        if new_data.get(upload_field_name, False):
    660660            func = getattr(new_object, 'save_%s_file' % self.name)
    661661            if rel:
    662                 func(new_data[upload_field_name][0]["filename"], new_data[upload_field_name][0]["content"], save)
     662                func(new_data[upload_field_name][0]["filename"], new_data[upload_field_name][0], save)
    663663            else:
    664                 func(new_data[upload_field_name]["filename"], new_data[upload_field_name]["content"], save)
     664                func(new_data[upload_field_name]["filename"], new_data[upload_field_name], save)
    665665
    666666    def get_directory_name(self):
    667667        return os.path.normpath(datetime.datetime.now().strftime(self.upload_to))
  • django/conf/global_settings.py

     
    240240# isExistingURL validator.
    241241URL_VALIDATOR_USER_AGENT = "Django/0.96pre (http://www.djangoproject.com)"
    242242
     243# The directory to place streamed file uploads. The web server needs write
     244# permissions on this directory.
     245# If this is None, streaming uploads are disabled.
     246FILE_UPLOAD_DIR = None
     247
     248
     249# The minimum size of a POST before file uploads are streamed to disk.
     250# Any less than this number, and the file is uploaded to memory.
     251# Size is in bytes.
     252STREAMING_MIN_POST_SIZE = 512 * (2**10)
     253
     254
     255
     256
    243257##############
    244258# MIDDLEWARE #
    245259##############
     
    335349
    336350# The list of directories to search for fixtures
    337351FIXTURE_DIRS = ()
     352
     353
  • django/core/handlers/wsgi.py

     
    111111            if self.environ.get('CONTENT_TYPE', '').startswith('multipart'):
    112112                header_dict = dict([(k, v) for k, v in self.environ.items() if k.startswith('HTTP_')])
    113113                header_dict['Content-Type'] = self.environ.get('CONTENT_TYPE', '')
    114                 self._post, self._files = http.parse_file_upload(header_dict, self.raw_post_data)
     114                header_dict['Content-Length'] = self.environ.get('CONTENT_LENGTH', '')
     115                header_dict['X-Progress-ID'] = self.environ.get('HTTP_X_PROGRESS_ID', '')
     116                try:
     117                    self._post, self._files = http.parse_file_upload(header_dict, self.environ['wsgi.input'], self.META['UPLOAD_PROGRESS_ID'])
     118                except:
     119                    self._post, self._files = {}, {} # make sure we dont read the input stream again
     120                    raise
     121                self._raw_post_data = None # raw data is not available for streamed multipart messages
    115122            else:
    116123                self._post, self._files = http.QueryDict(self.raw_post_data), datastructures.MultiValueDict()
    117124        else:
     
    167174            buf.close()
    168175            return self._raw_post_data
    169176
     177    def _get_file_progress_id(self):
     178        """
     179        Returns the Progress ID of the request,
     180        usually provided if there is a file upload
     181        going on.
     182        Returns ``None`` if no progress ID is specified.
     183        """
     184        return self._get_file_progress_from_args(self.environ,
     185                                                 self.GET,
     186                                                 self._req.args)
     187
    170188    GET = property(_get_get, _set_get)
    171189    POST = property(_get_post, _set_post)
    172190    COOKIES = property(_get_cookies, _set_cookies)
  • django/core/handlers/base.py

     
    55
    66class BaseHandler(object):
    77    def __init__(self):
    8         self._request_middleware = self._view_middleware = self._response_middleware = self._exception_middleware = None
     8        self._upload_middleware = self._request_middleware = self._view_middleware = self._response_middleware = self._exception_middleware = None
    99
    1010    def load_middleware(self):
    1111        """
     
    1919        self._view_middleware = []
    2020        self._response_middleware = []
    2121        self._exception_middleware = []
     22        self._upload_middleware = []
    2223        for middleware_path in settings.MIDDLEWARE_CLASSES:
    2324            try:
    2425                dot = middleware_path.rindex('.')
     
    4748                self._response_middleware.insert(0, mw_instance.process_response)
    4849            if hasattr(mw_instance, 'process_exception'):
    4950                self._exception_middleware.insert(0, mw_instance.process_exception)
     51            if hasattr(mw_instance, 'process_upload'):
     52                self._upload_middleware.append(mw_instance.process_upload)
    5053
    5154    def get_response(self, request):
    5255        "Returns an HttpResponse object for the given HttpRequest"
  • django/core/handlers/modpython.py

     
    4747    def _load_post_and_files(self):
    4848        "Populates self._post and self._files"
    4949        if 'content-type' in self._req.headers_in and self._req.headers_in['content-type'].startswith('multipart'):
    50             self._post, self._files = http.parse_file_upload(self._req.headers_in, self.raw_post_data)
     50            self._raw_post_data = None # raw data is not available for streamed multipart messages
     51            try:
     52                self._post, self._files = http.parse_file_upload(self._req.headers_in, self._req, self.META['UPLOAD_PROGRESS_ID'])
     53            except:
     54                self._post, self._files = {}, {} # make sure we dont read the input stream again
     55                raise
    5156        else:
    5257            self._post, self._files = http.QueryDict(self.raw_post_data), datastructures.MultiValueDict()
    5358
     
    9297                'AUTH_TYPE':         self._req.ap_auth_type,
    9398                'CONTENT_LENGTH':    self._req.clength, # This may be wrong
    9499                'CONTENT_TYPE':      self._req.content_type, # This may be wrong
    95                 'GATEWAY_INTERFACE': 'CGI/1.1',
    96                 'PATH_INFO':         self._req.path_info,
    97                 'PATH_TRANSLATED':   None, # Not supported
    98                 'QUERY_STRING':      self._req.args,
    99                 'REMOTE_ADDR':       self._req.connection.remote_ip,
    100                 'REMOTE_HOST':       None, # DNS lookups not supported
    101                 'REMOTE_IDENT':      self._req.connection.remote_logname,
    102                 'REMOTE_USER':       self._req.user,
    103                 'REQUEST_METHOD':    self._req.method,
    104                 'SCRIPT_NAME':       None, # Not supported
    105                 'SERVER_NAME':       self._req.server.server_hostname,
    106                 'SERVER_PORT':       self._req.server.port,
    107                 'SERVER_PROTOCOL':   self._req.protocol,
    108                 'SERVER_SOFTWARE':   'mod_python'
     100                'GATEWAY_INTERFACE':  'CGI/1.1',
     101                'PATH_INFO':          self._req.path_info,
     102                'PATH_TRANSLATED':    None, # Not supported
     103                'QUERY_STRING':       self._req.args,
     104                'REMOTE_ADDR':        self._req.connection.remote_ip,
     105                'REMOTE_HOST':        None, # DNS lookups not supported
     106                'REMOTE_IDENT':       self._req.connection.remote_logname,
     107                'REMOTE_USER':        self._req.user,
     108                'REQUEST_METHOD':     self._req.method,
     109                'SCRIPT_NAME':        None, # Not supported
     110                'SERVER_NAME':        self._req.server.server_hostname,
     111                'SERVER_PORT':        self._req.server.port,
     112                'SERVER_PROTOCOL':    self._req.protocol,
     113                'UPLOAD_PROGRESS_ID': self._get_file_progress_id(),
     114                'SERVER_SOFTWARE':    'mod_python'
    109115            }
    110116            for key, value in self._req.headers_in.items():
    111117                key = 'HTTP_' + key.upper().replace('-', '_')
     
    122128    def _get_method(self):
    123129        return self.META['REQUEST_METHOD'].upper()
    124130
     131    def _get_file_progress_id(self):
     132        """
     133        Returns the Progress ID of the request,
     134        usually provided if there is a file upload
     135        going on.
     136        Returns ``None`` if no progress ID is specified.
     137        """
     138        return self._get_file_progress_from_args(self._req.headers_in,
     139                                                 self.GET,
     140                                                 self._req.args)
     141
     142
    125143    GET = property(_get_get, _set_get)
    126144    POST = property(_get_post, _set_post)
    127145    COOKIES = property(_get_cookies, _set_cookies)
  • tests/modeltests/test_client/views.py

     
    4444
    4545    return HttpResponse(t.render(c))
    4646
     47def post_file_view(request):
     48    "A view that expects a multipart post and returns a file in the context"
     49    t = Template('File {{ file.filename }} received', name='POST Template')
     50    c = Context({'file': request.FILES['file_file']})
     51    return HttpResponse(t.render(c))
     52
    4753def redirect_view(request):
    4854    "A view that redirects all requests to the GET view"
    4955    return HttpResponseRedirect('/test_client/get_view/')
  • tests/modeltests/test_client/models.py

     
    7575        self.assertEqual(response.template.name, "Book template")
    7676        self.assertEqual(response.content, "Blink - Malcolm Gladwell")
    7777
     78    def test_post_file_view(self):
     79        "POST this python file to a view"
     80        import os, tempfile
     81        from django.conf import settings
     82        file = __file__.replace('.pyc', '.py')
     83        for upload_dir in [None, tempfile.gettempdir()]:
     84            settings.FILE_UPLOAD_DIR = upload_dir
     85            post_data = { 'name': file, 'file': open(file) }
     86            response = self.client.post('/test_client/post_file_view/', post_data)
     87            self.failUnless('models.py' in response.context['file']['filename'])
     88            self.failUnless(len(response.context['file']['content']) == os.path.getsize(file))
     89            if upload_dir:
     90                self.failUnless(response.context['file']['tmpfilename'])
     91
     92
    7893    def test_redirect(self):
    7994        "GET a URL that redirects elsewhere"
    8095        response = self.client.get('/test_client/redirect_view/')
  • tests/modeltests/test_client/urls.py

     
    44urlpatterns = patterns('',
    55    (r'^get_view/$', views.get_view),
    66    (r'^post_view/$', views.post_view),
     7    (r'^post_file_view/$', views.post_file_view),
    78    (r'^raw_post_view/$', views.raw_post_view),
    89    (r'^redirect_view/$', views.redirect_view),
    910    (r'^form_view/$', views.form_view),
  • docs/request_response.txt

     
    7272``FILES``
    7373    A dictionary-like object containing all uploaded files. Each key in
    7474    ``FILES`` is the ``name`` from the ``<input type="file" name="" />``. Each
    75     value in ``FILES`` is a standard Python dictionary with the following three
     75    value in ``FILES`` is a standard Python dictionary with the following four
    7676    keys:
    7777
    7878        * ``filename`` -- The name of the uploaded file, as a Python string.
    7979        * ``content-type`` -- The content type of the uploaded file.
    8080        * ``content`` -- The raw content of the uploaded file.
     81        * ``content-length`` -- The length of the content in bytes.
    8182
     83    If streaming file uploads are enabled two additional keys
     84    describing the uploaded file will be present:
     85
     86        * ``tmpfilename`` -- The filename for the temporary file.
     87        * ``tmpfile`` -- An open file object for the temporary file.
     88
     89    The temporary file will be removed when the request finishes.
     90
     91    Note that accessing ``content`` when streaming uploads are enabled
     92    will read the whole file into memory which may not be what you want.
     93
    8294    Note that ``FILES`` will only contain data if the request method was POST
    8395    and the ``<form>`` that posted to the request had
    8496    ``enctype="multipart/form-data"``. Otherwise, ``FILES`` will be a blank
  • docs/settings.txt

     
    437437
    438438.. _Testing Django Applications: ../testing/
    439439
     440FILE_UPLOAD_DIR
     441---------------
     442
     443Default: ``None``
     444
     445Path to a directory where temporary files should be written during
     446file uploads. Leaving this as ``None`` will disable streaming file uploads,
     447and cause all uploaded files to be stored (temporarily) in memory.
     448
    440449IGNORABLE_404_ENDS
    441450------------------
    442451
     
    780789
    781790.. _site framework docs: ../sites/
    782791
     792STREAMING_MIN_POST_SIZE
     793-----------------------
     794
     795Default: 524288 (``512*1024``)
     796
     797An integer specifying the minimum number of bytes that has to be
     798received (in a POST) for file upload streaming to take place. Any
     799request smaller than this will be handled in memory.
     800Note: ``FILE_UPLOAD_DIR`` has to be defined to enable streaming.
     801
    783802TEMPLATE_CONTEXT_PROCESSORS
    784803---------------------------
    785804
  • docs/forms.txt

     
    475475   new_data = request.POST.copy()
    476476   new_data.update(request.FILES)
    477477
     478Streaming file uploads.
     479-----------------------
     480
     481File uploads will be read into memory by default. This works fine for
     482small to medium sized uploads (from 1MB to 100MB depending on your
     483setup and usage). If you want to support larger uploads you can enable
     484upload streaming where only a small part of the file will be in memory
     485at any time. To do this you need to specify the ``FILE_UPLOAD_DIR``
     486setting (see the settings_ document for more details).
     487
     488See `request object`_ for more details about ``request.FILES`` objects
     489with streaming file uploads enabled.
     490
    478491Validators
    479492==========
    480493
     
    693706.. _`generic views`: ../generic_views/
    694707.. _`models API`: ../model-api/
    695708.. _settings: ../settings/
     709.. _request object: ../request_response/#httprequest-objects
Back to Top