Context Navigation


Ticket #2070: 4459-streaming-file-upload.diff

File 4459-streaming-file-upload.diff, 25.9 KB (added by Joakim Sernbrant <serbaut@...>, 19 years ago)

Simplified streaming uploads

  • django/http/__init__.py

    1import os(削除) (削除ここまで)
    1import os(追記) , pickle (追記ここまで)
    22from Cookie import SimpleCookie
    33from pprint import pformat
    44from urllib import urlencode, quote
    55from django.utils.datastructures import MultiValueDict
    66
    7(追記) try: (追記ここまで)
    8(追記) from cStringIO import StringIO (追記ここまで)
    9(追記) except ImportError: (追記ここまで)
    10(追記) from StringIO import StringIO (追記ここまで)
    11(追記) (追記ここまで)
    712RESERVED_CHARS="!*'();:@&=+,ドル/?%#[]"
    813
    914try:
    4247 def is_secure(self):
    4348 return os.environ.get("HTTPS") == "on"
    4449
    45def 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 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 name_dict.has_key('filename'):
    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': (submessage.has_key('Content-Type') and submessage['Content-Type'] or None),
    70 'content': submessage.get_payload(),
    71 })
    50def parse_file_upload(headers, input):
    51 from django.conf import settings
    52
    53 # Only stream files to disk if FILE_UPLOAD_DIR is set
    54 file_upload_dir = getattr(settings, 'FILE_UPLOAD_DIR', None)
    55
    56 try:
    57 parser = MultiPartParser(headers, input, file_upload_dir)
    58 return parser.parse()
    59 except MultiPartParserError, e:
    60 return MultiValueDict({ '_file_upload_error': [e.message] }), {}
    61 except Exception:
    62 return MultiValueDict({ '_file_upload_error': ["An unexpected error occured."] }), {}
    63
    64class MultiPartParserError(Exception):
    65 def __init__(self, message):
    66 self.message = message
    67 def __str__(self):
    68 return repr(self.message)
    69
    70class MultiPartParser(object):
    71 """
    72 A rfc2388 multipart/form-data parser.
    73
    74 parse() reads the input stream in chunk_size chunks and returns a
    75 tuple of (POST MultiValueDict, FILES MultiValueDict). If
    76 file_upload_dir is defined files will be streamed to temporary
    77 files in the specified directory.
    78
    79 The FILES dictionary will have 'filename', 'content-type',
    80 'content' and 'content-length' entries. For streamed files it will
    81 also have 'tmpfilename' and 'tmpfile'. The 'content' entry will
    82 only be read from disk when referenced for streamed files.
    83
    84 If the header X-Progress-ID is sent with a 32 character hex string
    85 a temporary file with the same name will be created in
    86 `file_upload_dir`` with a pickled { 'received', 'size' }
    87 dictionary with the number of bytes received and the size expected
    88 respectively. The file will be unlinked when the parser finishes.
    89
    90 """
    91
    92 def __init__(self, headers, input, file_upload_dir=None, file_upload_max_size=None, chunk_size=1024*64):
    93 try:
    94 content_length = int(headers['Content-Length'])
    95 except:
    96 raise MultiPartParserError('Invalid Content-Length: %s' % headers.get('Content-Length'))
    97
    98 content_type = headers.get('Content-Type')
    99
    100 if not content_type or not content_type.startswith('multipart/'):
    101 raise MultiPartParserError('Invalid Content-Type: %s' % content_type)
    102
    103 ctype, opts = self.parse_header(content_type)
    104 boundary = opts.get('boundary')
    105 from cgi import valid_boundary
    106 if not boundary or not valid_boundary(boundary):
    107 raise MultiPartParserError('Invalid boundary in multipart form: %s' % boundary)
    108
    109 # check if we got a valid X-Progress-ID id
    110 progress_id = headers.get('X-Progress-ID')
    111 if file_upload_dir and progress_id:
    112 import re
    113 if re.match(r'^[0-9a-zA-Z]{32}$', progress_id):
    114 self._progress_filename = os.path.join(file_upload_dir, progress_id)
    72115 else:
    73 POST.appendlist(name_dict['name'], submessage.get_payload())
    74 return POST, FILES
    116 raise MultiPartParserError('Invalid X-Progress-ID: %s' % progress_id)
    117 else:
    118 self._progress_filename = None
    75119
    120(追記) self._boundary = '--' + boundary (追記ここまで)
    121(追記) self._input = input (追記ここまで)
    122(追記) self._size = content_length (追記ここまで)
    123(追記) self._received = 0 (追記ここまで)
    124(追記) self._file_upload_dir = file_upload_dir (追記ここまで)
    125(追記) self._chunk_size = chunk_size (追記ここまで)
    126(追記) self._state = 'PREAMBLE' (追記ここまで)
    127(追記) self._partial = '' (追記ここまで)
    128(追記) self._post = MultiValueDict() (追記ここまで)
    129(追記) self._files = MultiValueDict() (追記ここまで)
    130(追記) (追記ここまで)
    131(追記) try: (追記ここまで)
    132(追記) # use mx fast string search if available (追記ここまで)
    133(追記) from mx.TextTools import FS (追記ここまで)
    134(追記) self._fs = FS(self._boundary) (追記ここまで)
    135(追記) except ImportError: (追記ここまで)
    136(追記) self._fs = None (追記ここまで)
    137(追記) (追記ここまで)
    138(追記) def parse(self): (追記ここまで)
    139(追記) try: (追記ここまで)
    140(追記) self._parse() (追記ここまで)
    141(追記) finally: (追記ここまで)
    142(追記) if self._progress_filename: (追記ここまで)
    143(追記) try: (追記ここまで)
    144(追記) os.unlink(self._progress_filename) (追記ここまで)
    145(追記) except OSError: (追記ここまで)
    146(追記) pass (追記ここまで)
    147(追記) (追記ここまで)
    148(追記) return self._post, self._files (追記ここまで)
    149(追記) (追記ここまで)
    150(追記) def _parse(self): (追記ここまで)
    151(追記) size = self._size (追記ここまで)
    152(追記) (追記ここまで)
    153(追記) try: (追記ここまで)
    154(追記) while size > 0: (追記ここまで)
    155(追記) n = self._read(self._input, min(self._chunk_size, size)) (追記ここまで)
    156(追記) if not n: (追記ここまで)
    157(追記) break (追記ここまで)
    158(追記) size -= n (追記ここまで)
    159(追記) except: (追記ここまで)
    160(追記) # consume any remaining data so we dont generate a "Connection Reset" error (追記ここまで)
    161(追記) size = self._size - self._received (追記ここまで)
    162(追記) while size > 0: (追記ここまで)
    163(追記) data = self._input.read(min(self._chunk_size, size)) (追記ここまで)
    164(追記) size -= len(data) (追記ここまで)
    165(追記) raise (追記ここまで)
    166(追記) (追記ここまで)
    167(追記) def _find_boundary(self, data, start, stop): (追記ここまで)
    168(追記) """ (追記ここまで)
    169(追記) Find the next boundary and return the end of current part (追記ここまで)
    170(追記) and start of next part. (追記ここまで)
    171(追記) """ (追記ここまで)
    172(追記) if self._fs: (追記ここまで)
    173(追記) boundary = self._fs.find(data, start, stop) (追記ここまで)
    174(追記) else: (追記ここまで)
    175(追記) boundary = data.find(self._boundary, start, stop) (追記ここまで)
    176(追記) if boundary >= 0: (追記ここまで)
    177(追記) end = boundary (追記ここまで)
    178(追記) next = boundary + len(self._boundary) (追記ここまで)
    179(追記) (追記ここまで)
    180(追記) # backup over CRLF (追記ここまで)
    181(追記) if end > 0 and data[end-1] == '\n': end -= 1 (追記ここまで)
    182(追記) if end > 0 and data[end-1] == '\r': end -= 1 (追記ここまで)
    183(追記) # skip over --CRLF (追記ここまで)
    184(追記) if next < stop and data[next] == '-': next += 1 (追記ここまで)
    185(追記) if next < stop and data[next] == '-': next += 1 (追記ここまで)
    186(追記) if next < stop and data[next] == '\r': next += 1 (追記ここまで)
    187(追記) if next < stop and data[next] == '\n': next += 1 (追記ここまで)
    188(追記) (追記ここまで)
    189(追記) return True, end, next (追記ここまで)
    190(追記) else: (追記ここまで)
    191(追記) return False, stop, stop (追記ここまで)
    192(追記) (追記ここまで)
    193(追記) class TemporaryFile(object): (追記ここまで)
    194(追記) "A temporary file that tries to delete itself when garbage collected." (追記ここまで)
    195(追記) def __init__(self, dir): (追記ここまで)
    196(追記) import tempfile (追記ここまで)
    197(追記) (fd, name) = tempfile.mkstemp(suffix='.upload', dir=dir) (追記ここまで)
    198(追記) self.file = os.fdopen(fd, 'w+b') (追記ここまで)
    199(追記) self.name = name (追記ここまで)
    200(追記) (追記ここまで)
    201(追記) def __getattr__(self, name): (追記ここまで)
    202(追記) a = getattr(self.__dict__['file'], name) (追記ここまで)
    203(追記) if type(a) != type(0): (追記ここまで)
    204(追記) setattr(self, name, a) (追記ここまで)
    205(追記) return a (追記ここまで)
    206(追記) (追記ここまで)
    207(追記) def __del__(self): (追記ここまで)
    208(追記) try: (追記ここまで)
    209(追記) os.unlink(self.name) (追記ここまで)
    210(追記) except OSError: (追記ここまで)
    211(追記) pass (追記ここまで)
    212(追記) (追記ここまで)
    213(追記) class LazyContent(dict): (追記ここまで)
    214(追記) """ (追記ここまで)
    215(追記) A lazy FILES dictionary entry that reads the contents from (追記ここまで)
    216(追記) tmpfile only when referenced. (追記ここまで)
    217(追記) """ (追記ここまで)
    218(追記) def __init__(self, data): (追記ここまで)
    219(追記) dict.__init__(self, data) (追記ここまで)
    220(追記) (追記ここまで)
    221(追記) def __getitem__(self, key): (追記ここまで)
    222(追記) if key == 'content' and not self.has_key(key): (追記ここまで)
    223(追記) self['tmpfile'].seek(0) (追記ここまで)
    224(追記) self['content'] = self['tmpfile'].read() (追記ここまで)
    225(追記) return dict.__getitem__(self, key) (追記ここまで)
    226(追記) (追記ここまで)
    227(追記) def _read(self, input, size): (追記ここまで)
    228(追記) data = input.read(size) (追記ここまで)
    229(追記) (追記ここまで)
    230(追記) if not data: (追記ここまで)
    231(追記) return 0 (追記ここまで)
    232(追記) (追記ここまで)
    233(追記) read_size = len(data) (追記ここまで)
    234(追記) self._received += read_size (追記ここまで)
    235(追記) (追記ここまで)
    236(追記) if self._partial: (追記ここまで)
    237(追記) data = self._partial + data (追記ここまで)
    238(追記) (追記ここまで)
    239(追記) start = 0 (追記ここまで)
    240(追記) stop = len(data) (追記ここまで)
    241(追記) (追記ここまで)
    242(追記) while start < stop: (追記ここまで)
    243(追記) boundary, end, next = self._find_boundary(data, start, stop) (追記ここまで)
    244(追記) (追記ここまで)
    245(追記) if not boundary and read_size: (追記ここまで)
    246(追記) # make sure we dont treat a partial boundary (and its separators) as data (追記ここまで)
    247(追記) stop -= len(self._boundary) + 16 (追記ここまで)
    248(追記) end = next = stop (追記ここまで)
    249(追記) if end <= start: (追記ここまで)
    250(追記) break # need more data (追記ここまで)
    251(追記) (追記ここまで)
    252(追記) if self._state == 'PREAMBLE': (追記ここまで)
    253(追記) # Preamble, just ignore it (追記ここまで)
    254(追記) self._state = 'HEADER' (追記ここまで)
    255(追記) (追記ここまで)
    256(追記) elif self._state == 'HEADER': (追記ここまで)
    257(追記) # Beginning of header, look for end of header and parse it if found. (追記ここまで)
    258(追記) (追記ここまで)
    259(追記) header_end = data.find('\r\n\r\n', start, stop) (追記ここまで)
    260(追記) if header_end == -1: (追記ここまで)
    261(追記) break # need more data (追記ここまで)
    262(追記) (追記ここまで)
    263(追記) header = data[start:header_end] (追記ここまで)
    264(追記) (追記ここまで)
    265(追記) self._fieldname = None (追記ここまで)
    266(追記) self._filename = None (追記ここまで)
    267(追記) self._content_type = None (追記ここまで)
    268(追記) (追記ここまで)
    269(追記) for line in header.split('\r\n'): (追記ここまで)
    270(追記) ctype, opts = self.parse_header(line) (追記ここまで)
    271(追記) if ctype == 'content-disposition: form-data': (追記ここまで)
    272(追記) self._fieldname = opts.get('name') (追記ここまで)
    273(追記) self._filename = opts.get('filename') (追記ここまで)
    274(追記) elif ctype.startswith('content-type: '): (追記ここまで)
    275(追記) self._content_type = ctype[14:] (追記ここまで)
    276(追記) (追記ここまで)
    277(追記) if self._filename is not None: (追記ここまで)
    278(追記) # cleanup filename from IE full paths: (追記ここまで)
    279(追記) self._filename = self._filename[self._filename.rfind("\\")+1:].strip() (追記ここまで)
    280(追記) (追記ここまで)
    281(追記) if self._filename: # ignore files without filenames (追記ここまで)
    282(追記) if self._file_upload_dir: (追記ここまで)
    283(追記) try: (追記ここまで)
    284(追記) self._file = self.TemporaryFile(dir=self._file_upload_dir) (追記ここまで)
    285(追記) except: (追記ここまで)
    286(追記) raise MultiPartParserError("Failed to create temporary file.") (追記ここまで)
    287(追記) else: (追記ここまで)
    288(追記) self._file = StringIO() (追記ここまで)
    289(追記) else: (追記ここまで)
    290(追記) self._file = None (追記ここまで)
    291(追記) self._filesize = 0 (追記ここまで)
    292(追記) self._state = 'FILE' (追記ここまで)
    293(追記) else: (追記ここまで)
    294(追記) self._field = StringIO() (追記ここまで)
    295(追記) self._state = 'FIELD' (追記ここまで)
    296(追記) next = header_end + 4 (追記ここまで)
    297(追記) (追記ここまで)
    298(追記) elif self._state == 'FIELD': (追記ここまで)
    299(追記) # In a field, collect data until a boundary is found. (追記ここまで)
    300(追記) (追記ここまで)
    301(追記) self._field.write(data[start:end]) (追記ここまで)
    302(追記) if boundary: (追記ここまで)
    303(追記) if self._fieldname: (追記ここまで)
    304(追記) self._post.appendlist(self._fieldname, self._field.getvalue()) (追記ここまで)
    305(追記) self._field.close() (追記ここまで)
    306(追記) self._state = 'HEADER' (追記ここまで)
    307(追記) (追記ここまで)
    308(追記) elif self._state == 'FILE': (追記ここまで)
    309(追記) # In a file, collect data until a boundary is found. (追記ここまで)
    310(追記) (追記ここまで)
    311(追記) if self._file: (追記ここまで)
    312(追記) try: (追記ここまで)
    313(追記) self._file.write(data[start:end]) (追記ここまで)
    314(追記) except IOError, e: (追記ここまで)
    315(追記) raise MultiPartParserError("Failed to write to temporary file.") (追記ここまで)
    316(追記) self._filesize += end-start (追記ここまで)
    317(追記) (追記ここまで)
    318(追記) if self._progress_filename: (追記ここまで)
    319(追記) f = open(os.path.join(self._file_upload_dir, self._progress_filename), 'w') (追記ここまで)
    320(追記) pickle.dump({ 'received': self._received, 'size': self._size }, f) (追記ここまで)
    321(追記) f.close() (追記ここまで)
    322(追記) (追記ここまで)
    323(追記) if boundary: (追記ここまで)
    324(追記) if self._file: (追記ここまで)
    325(追記) if self._file_upload_dir: (追記ここまで)
    326(追記) self._file.seek(0) (追記ここまで)
    327(追記) file = self.LazyContent({ (追記ここまで)
    328(追記) 'filename': self._filename, (追記ここまで)
    329(追記) 'content-type': self._content_type, (追記ここまで)
    330(追記) # 'content': is read on demand (追記ここまで)
    331(追記) 'content-length': self._filesize, (追記ここまで)
    332(追記) 'tmpfilename': self._file.name, (追記ここまで)
    333(追記) 'tmpfile': self._file (追記ここまで)
    334(追記) }) (追記ここまで)
    335(追記) else: (追記ここまで)
    336(追記) file = { (追記ここまで)
    337(追記) 'filename': self._filename, (追記ここまで)
    338(追記) 'content-type': self._content_type, (追記ここまで)
    339(追記) 'content': self._file.getvalue(), (追記ここまで)
    340(追記) 'content-length': self._filesize (追記ここまで)
    341(追記) } (追記ここまで)
    342(追記) self._file.close() (追記ここまで)
    343(追記) (追記ここまで)
    344(追記) self._files.appendlist(self._fieldname, file) (追記ここまで)
    345(追記) (追記ここまで)
    346(追記) self._state = 'HEADER' (追記ここまで)
    347(追記) (追記ここまで)
    348(追記) start = next (追記ここまで)
    349(追記) (追記ここまで)
    350(追記) self._partial = data[start:] (追記ここまで)
    351(追記) (追記ここまで)
    352(追記) return read_size (追記ここまで)
    353(追記) (追記ここまで)
    354(追記) def parse_header(self, line): (追記ここまで)
    355(追記) from cgi import parse_header (追記ここまで)
    356(追記) return parse_header(line) (追記ここまで)
    357(追記) (追記ここまで)
    358(追記) (追記ここまで)
    76359class QueryDict(MultiValueDict):
    77360 """A specialized MultiValueDict that takes a query string when initialized.
    78361 This is immutable unless you create a copy of it."""
    302585 if not host:
    303586 host = request.META.get('HTTP_HOST', '')
    304587 return host
    588(追記) (追記ここまで)
  • django/db/models/base.py

    321321 def _get_FIELD_size(self, field):
    322322 return os.path.getsize(self._get_FIELD_filename(field))
    323323
    324 def _save_FIELD_file(self, field, filename, raw_(削除) contents (削除ここまで)):
    324 def _save_FIELD_file(self, field, filename, raw_(追記) field (追記ここまで)):
    325325 directory = field.get_directory_name()
    326326 try: # Create the date-based directory if it doesn't exist.
    327327 os.makedirs(os.path.join(settings.MEDIA_ROOT, directory))
    343343 setattr(self, field.attname, filename)
    344344
    345345 full_filename = self._get_FIELD_filename(field)
    346 fp = open(full_filename, 'wb')
    347 fp.write(raw_contents)
    348 fp.close()
    346 if raw_field.has_key('tmpfilename'):
    347 raw_field['tmpfile'].close()
    348 os.rename(raw_field['tmpfilename'], full_filename)
    349 else:
    350 fp = open(full_filename, 'wb')
    351 fp.write(raw_field['content'])
    352 fp.close()
    349353
    350354 # Save the width and/or height, if applicable.
    351355 if isinstance(field, ImageField) and (field.width_field or field.height_field):
  • django/db/models/fields/__init__.py

    625625 setattr(cls, 'get_%s_filename' % self.name, curry(cls._get_FIELD_filename, field=self))
    626626 setattr(cls, 'get_%s_url' % self.name, curry(cls._get_FIELD_url, field=self))
    627627 setattr(cls, 'get_%s_size' % self.name, curry(cls._get_FIELD_size, field=self))
    628 setattr(cls, 'save_%s_file' % self.name, lambda instance, filename, raw_(削除) contents: instance._save_FIELD_file(self, filename, raw_contents (削除ここまで)))
    628 setattr(cls, 'save_%s_file' % self.name, lambda instance, filename, raw_(追記) field: instance._save_FIELD_file(self, filename, raw_field (追記ここまで)))
    629629 dispatcher.connect(self.delete_file, signal=signals.post_delete, sender=cls)
    630630
    631631 def delete_file(self, instance):
    648648 if new_data.get(upload_field_name, False):
    649649 func = getattr(new_object, 'save_%s_file' % self.name)
    650650 if rel:
    651 func(new_data[upload_field_name][0]["filename"], new_data[upload_field_name][0](削除) ["content"] (削除ここまで))
    651 func(new_data[upload_field_name][0]["filename"], new_data[upload_field_name][0](追記) (追記ここまで))
    652652 else:
    653 func(new_data[upload_field_name]["filename"], new_data[upload_field_name](削除) ["content"] (削除ここまで))
    653 func(new_data[upload_field_name]["filename"], new_data[upload_field_name](追記) (追記ここまで))
    654654
    655655 def get_directory_name(self):
    656656 return os.path.normpath(datetime.datetime.now().strftime(self.upload_to))
  • django/oldforms/__init__.py

    661661 self.validator_list = [self.isNonEmptyFile] + validator_list
    662662
    663663 def isNonEmptyFile(self, field_data, all_data):
    664 (削除) try (削除ここまで):
    665 (削除) content = field_data['content (削除ここまで)']
    666 (削除) except TypeError (削除ここまで):
    664 (追記) if field_data.has_key('_file_upload_error') (追記ここまで):
    665 (追記) raise validators.CriticalValidationError, field_data['_file_upload_error (追記ここまで)']
    666 (追記) if not field_data.has_key('filename') (追記ここまで):
    667667 raise validators.CriticalValidationError, gettext("No file was submitted. Check the encoding type on the form.")
    668 if not (削除) content (削除ここまで):
    668 if not (追記) field_data['content-length'] (追記ここまで):
    669669 raise validators.CriticalValidationError, gettext("The submitted file is empty.")
    670670
    671671 def render(self, data):
    672672 return '<input type="file" id="%s" class="v%s" name="%s" />' % \
    673673 (self.get_id(), self.__class__.__name__, self.field_name)
    674674
    675(追記) def prepare(self, new_data): (追記ここまで)
    676(追記) if new_data.has_key('_file_upload_error'): (追記ここまで)
    677(追記) # pretend we got something in the field to raise a validation error later (追記ここまで)
    678(追記) new_data[self.field_name] = { '_file_upload_error': new_data['_file_upload_error'] } (追記ここまで)
    679(追記) (追記ここまで)
    675680 def html2python(data):
    676681 if data is None:
    677682 raise EmptyValue
  • 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 self._post, self._files = http.parse_file_upload(header_dict, self.environ['wsgi.input'])
    117 self._raw_post_data = None # raw data is not available for streamed multipart messages
    115118 else:
    116119 self._post, self._files = http.QueryDict(self.raw_post_data), datastructures.MultiValueDict()
    117120 else:
  • django/core/handlers/modpython.py

    4747 def _load_post_and_files(self):
    4848 "Populates self._post and self._files"
    4949 if self._req.headers_in.has_key('content-type') 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._post, self._files = http.parse_file_upload(self._req.headers_in, self._req)
    51 self._raw_post_data = None # raw data is not available for streamed multipart messages
    5152 else:
    5253 self._post, self._files = http.QueryDict(self.raw_post_data), datastructures.MultiValueDict()
    5354
  • tests/modeltests/test_client/views.py

    2222
    2323 return HttpResponse(t.render(c))
    2424
    25(追記) def post_file_view(request): (追記ここまで)
    26(追記) "A view that expects a multipart post and returns a file in the context" (追記ここまで)
    27(追記) t = Template('File {{ file.filename }} received', name='POST Template') (追記ここまで)
    28(追記) c = Context({'file': request.FILES['file_file']}) (追記ここまで)
    29(追記) return HttpResponse(t.render(c)) (追記ここまで)
    30(追記) (追記ここまで)
    2531def redirect_view(request):
    2632 "A view that redirects all requests to the GET view"
    2733 return HttpResponseRedirect('/test_client/get_view/')
    3238 c = Context({'user': request.user})
    3339
    3440 return HttpResponse(t.render(c))
    35login_protected_view = login_required(login_protected_view)
    36 No newline at end of file
    41login_protected_view = login_required(login_protected_view)
  • tests/modeltests/test_client/models.py

    6666 self.assertEqual(response.template.name, 'POST Template')
    6767 self.failUnless('Data received' in response.content)
    6868
    69(追記) def test_post_file_view(self): (追記ここまで)
    70(追記) "POST this python file to a view" (追記ここまで)
    71(追記) import os, tempfile (追記ここまで)
    72(追記) from django.conf import settings (追記ここまで)
    73(追記) file = __file__.replace('.pyc', '.py') (追記ここまで)
    74(追記) for upload_dir in [None, tempfile.gettempdir()]: (追記ここまで)
    75(追記) settings.FILE_UPLOAD_DIR = upload_dir (追記ここまで)
    76(追記) post_data = { 'name': file, 'file': open(file) } (追記ここまで)
    77(追記) response = self.client.post('/test_client/post_file_view/', post_data) (追記ここまで)
    78(追記) self.failUnless('models.py' in response.context['file']['filename']) (追記ここまで)
    79(追記) self.failUnless(len(response.context['file']['content']) == os.path.getsize(file)) (追記ここまで)
    80(追記) if upload_dir: (追記ここまで)
    81(追記) self.failUnless(response.context['file']['tmpfilename']) (追記ここまで)
    82(追記) (追記ここまで)
    6983 def test_redirect(self):
    7084 "GET a URL that redirects elsewhere"
    7185 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'^redirect_view/$', views.redirect_view),
    89 (r'^login_protected_view/$', views.login_protected_view),
    910)
  • 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

    409409or ``django.core.mail.mail_managers``. You'll probably want to include the
    410410trailing space.
    411411
    412(追記) FILE_UPLOAD_DIR (追記ここまで)
    413(追記) --------------- (追記ここまで)
    414(追記) (追記ここまで)
    415(追記) Default: Not defined (追記ここまで)
    416(追記) (追記ここまで)
    417(追記) Path to a directory where temporary files should be written during (追記ここまで)
    418(追記) file uploads. Leaving this unset will read files into memory. (追記ここまで)
    419(追記) (追記ここまで)
    420(追記) (追記ここまで)
    412421IGNORABLE_404_ENDS
    413422------------------
    414423
  • docs/forms.txt

    454454 new_data = request.POST.copy()
    455455 new_data.update(request.FILES)
    456456
    457(追記) Streaming file uploads. (追記ここまで)
    458(追記) ----------------------- (追記ここまで)
    459(追記) (追記ここまで)
    460(追記) File uploads will be read into memory by default. This works fine for (追記ここまで)
    461(追記) small to medium sized uploads (from 1MB to to 100MB depending on your (追記ここまで)
    462(追記) setup and usage). If you want to support larger uploads you can enable (追記ここまで)
    463(追記) upload streaming where only a small part of the file will be in memory (追記ここまで)
    464(追記) at any time. To do this you need to specify the ``FILE_UPLOAD_DIR`` (追記ここまで)
    465(追記) setting (see the settings_ document for more details). (追記ここまで)
    466(追記) (追記ここまで)
    467(追記) See `request object`_ for more details about ``request.FILES`` objects (追記ここまで)
    468(追記) with streaming file uploads enabled. (追記ここまで)
    469(追記) (追記ここまで)
    457470Validators
    458471==========
    459472
    668681.. _`generic views`: ../generic_views/
    669682.. _`models API`: ../model_api/
    670683.. _settings: ../settings/
    684(追記) .. _request object: ../request_response/#httprequest-objects (追記ここまで)
Back to Top