Ticket #2070: 5100_file_upload_core.diff
File 5100_file_upload_core.diff, 34.1 KB (added by , 18 years ago) |
---|
-
django/http/__init__.py
1 import os 1 import os, pickle 2 2 from Cookie import SimpleCookie 3 3 from pprint import pformat 4 4 from urllib import urlencode, quote 5 5 from django.utils.datastructures import MultiValueDict 6 import re 6 7 8 try: 9 from cStringIO import StringIO 10 except ImportError: 11 from StringIO import StringIO 12 7 13 RESERVED_CHARS="!*'();:@&=+$,/?%#[]" 8 14 15 9 16 try: 10 17 # The mod_python version is more efficient, so try importing it first. 11 18 from mod_python.util import parse_qsl … … 17 24 18 25 class HttpRequest(object): 19 26 "A basic HTTP request" 27 28 upload_id_re = re.compile(r'^[a-fA-F0-9]{32}$') 29 30 20 31 def __init__(self): 21 32 self.GET, self.POST, self.COOKIES, self.META, self.FILES = {}, {}, {}, {}, {} 22 33 self.path = '' … … 42 53 def is_secure(self): 43 54 return os.environ.get("HTTPS") == "on" 44 55 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 }) 72 else: 73 POST.appendlist(name_dict['name'], submessage.get_payload()) 74 return POST, FILES 56 def _get_file_progress(self): 57 """ 58 Returns the file progress for this request. 59 If no file progress is known, returns an empty 60 dictionary. 61 The request also keeps a local copy so that 62 the file is not accessed every time one wants to 63 ask for something. 64 """ 65 from django.conf import settings 75 66 67 file_upload_dir = settings.FILE_UPLOAD_DIR 68 progress_id = self.META['UPLOAD_PROGRESS_ID'] 69 70 if not progress_id or not file_upload_dir: 71 return {} 72 73 if getattr(self, '_file_progress', False) != False: 74 return self._file_progress 75 76 try: 77 f = open(os.path.join(file_upload_dir, progress_id), 'rb') 78 progress = pickle.load(f) 79 f.close() 80 self._file_progress = progress 81 return progress 82 83 except: 84 self._file_progress = {} 85 return {} 86 87 def _set_file_progress(self, new_progress): 88 """ 89 Sets the value of the file progress for this request. 90 If no file progress is underway, fails silently unless 91 DEBUG = True 92 """ 93 94 class NoUploadInProgress(Exception): 95 pass 96 97 from django.conf import settings 98 99 file_upload_dir = settings.FILE_UPLOAD_DIR 100 progress_id = self.META['UPLOAD_PROGRESS_ID'] 101 102 if not progress_id or not file_upload_dir: 103 if settings.DEBUG: 104 raise NoUploadInProgress, 'There is no upload in progress.' 105 106 107 self._file_progress = new_progress 108 f = open(os.path.join(file_upload_dir, progress_id), 'wb') 109 pickle.dump(new_progress, f) 110 f.close() 111 112 file_progress = property(_get_file_progress, _set_file_progress) 113 114 def _get_file_progress_from_args(self, headers, get, querystring): 115 116 if 'X-Upload-ID' in headers: 117 progress_id = headers['X-Upload-ID'] 118 elif 'X-Progress-ID' in headers: 119 progress_id = headers['X-Progress-ID'] 120 elif 'upload_id' in get: 121 progress_id = get['upload_id'] 122 elif 'progress_id' in get: 123 progress_id = get['progress_id'] 124 elif querystring != None and len(querystring.strip()) == 32: 125 progress_id = querystring 126 else: 127 return None 128 129 if not self.upload_id_re.match(progress_id): 130 return None 131 132 return progress_id 133 134 135 def parse_file_upload(headers, input, request): 136 from django.conf import settings 137 138 # Only stream files to disk if FILE_STREAMING_DIR is set 139 file_upload_dir = settings.FILE_UPLOAD_DIR 140 streaming_min_post_size = settings.STREAMING_MIN_POST_SIZE 141 142 try: 143 parser = MultiPartParser(headers, input, request, file_upload_dir, streaming_min_post_size) 144 return parser.parse() 145 except MultiPartParserError, e: 146 return MultiValueDict({ '_file_upload_error': [e.message] }), {} 147 148 class MultiPartParserError(Exception): 149 def __init__(self, message): 150 self.message = message 151 def __str__(self): 152 return repr(self.message) 153 154 class MultiPartParser(object): 155 """ 156 A rfc2388 multipart/form-data parser. 157 158 parse() reads the input stream in chunk_size chunks and returns a 159 tuple of (POST MultiValueDict, FILES MultiValueDict). If 160 file_upload_dir is defined files will be streamed to temporary 161 files in the specified directory. 162 163 The FILES dictionary will have 'filename', 'content-type', 164 'content' and 'content-length' entries. For streamed files it will 165 also have 'tmpfilename' and 'tmpfile'. The 'content' entry will 166 only be read from disk when referenced for streamed files. 167 168 If the header X-Progress-ID is sent with a 32 character hex string 169 a temporary file with the same name will be created in 170 `file_upload_dir`` with a pickled { 'received', 'size' } 171 dictionary with the number of bytes received and the size expected 172 respectively. The file will be unlinked when the parser finishes. 173 174 """ 175 176 def __init__(self, headers, input, request, file_upload_dir=None, streaming_min_post_size=None, chunk_size=1024*64): 177 try: 178 content_length = int(headers['Content-Length']) 179 except: 180 raise MultiPartParserError('Invalid Content-Length: %s' % headers.get('Content-Length')) 181 182 content_type = headers.get('Content-Type') 183 184 if not content_type or not content_type.startswith('multipart/'): 185 raise MultiPartParserError('Invalid Content-Type: %s' % content_type) 186 187 ctype, opts = self.parse_header(content_type) 188 boundary = opts.get('boundary') 189 from cgi import valid_boundary 190 if not boundary or not valid_boundary(boundary): 191 raise MultiPartParserError('Invalid boundary in multipart form: %s' % boundary) 192 193 progress_id = request.META['UPLOAD_PROGRESS_ID'] 194 195 if file_upload_dir and progress_id: 196 self._progress_filename = os.path.join(file_upload_dir, progress_id) 197 else: 198 self._progress_filename = None 199 self._boundary = '--' + boundary 200 self._input = input 201 self._size = content_length 202 self._received = 0 203 self._file_upload_dir = file_upload_dir 204 self._chunk_size = chunk_size 205 self._state = 'PREAMBLE' 206 self._partial = '' 207 self._post = MultiValueDict() 208 self._files = MultiValueDict() 209 self._request = request 210 211 if streaming_min_post_size is not None and content_length < streaming_min_post_size: 212 self._file_upload_dir = None # disable file streaming for small request 213 214 try: 215 # use mx fast string search if available 216 from mx.TextTools import FS 217 self._fs = FS(self._boundary) 218 except ImportError: 219 self._fs = None 220 221 def parse(self): 222 try: 223 self._parse() 224 finally: 225 if self._progress_filename: 226 self._request.file_progress = {'state': 'done'} 227 228 229 return self._post, self._files 230 231 def _parse(self): 232 size = self._size 233 234 try: 235 while size > 0: 236 n = self._read(self._input, min(self._chunk_size, size)) 237 if not n: 238 break 239 size -= n 240 except: 241 # consume any remaining data so we dont generate a "Connection Reset" error 242 size = self._size - self._received 243 while size > 0: 244 data = self._input.read(min(self._chunk_size, size)) 245 size -= len(data) 246 raise 247 248 def _find_boundary(self, data, start, stop): 249 """ 250 Find the next boundary and return the end of current part 251 and start of next part. 252 """ 253 if self._fs: 254 boundary = self._fs.find(data, start, stop) 255 else: 256 boundary = data.find(self._boundary, start, stop) 257 if boundary >= 0: 258 end = boundary 259 next = boundary + len(self._boundary) 260 261 # backup over CRLF 262 if end > 0 and data[end-1] == '\n': end -= 1 263 if end > 0 and data[end-1] == '\r': end -= 1 264 # skip over --CRLF 265 if next < stop and data[next] == '-': next += 1 266 if next < stop and data[next] == '-': next += 1 267 if next < stop and data[next] == '\r': next += 1 268 if next < stop and data[next] == '\n': next += 1 269 270 return True, end, next 271 else: 272 return False, stop, stop 273 274 class TemporaryFile(object): 275 "A temporary file that tries to delete itself when garbage collected." 276 def __init__(self, dir): 277 import tempfile 278 (fd, name) = tempfile.mkstemp(suffix='.upload', dir=dir) 279 self.file = os.fdopen(fd, 'w+b') 280 self.name = name 281 282 def __getattr__(self, name): 283 a = getattr(self.__dict__['file'], name) 284 if type(a) != type(0): 285 setattr(self, name, a) 286 return a 287 288 def __del__(self): 289 try: 290 os.unlink(self.name) 291 except OSError: 292 pass 293 294 class LazyContent(dict): 295 """ 296 A lazy FILES dictionary entry that reads the contents from 297 tmpfile only when referenced. 298 """ 299 def __init__(self, data): 300 dict.__init__(self, data) 301 302 def __getitem__(self, key): 303 if key == 'content' and not self.has_key(key): 304 self['tmpfile'].seek(0) 305 self['content'] = self['tmpfile'].read() 306 return dict.__getitem__(self, key) 307 308 def _read(self, input, size): 309 data = input.read(size) 310 311 if not data: 312 return 0 313 314 read_size = len(data) 315 self._received += read_size 316 317 if self._partial: 318 data = self._partial + data 319 320 start = 0 321 stop = len(data) 322 323 while start < stop: 324 boundary, end, next = self._find_boundary(data, start, stop) 325 326 if not boundary and read_size: 327 # make sure we dont treat a partial boundary (and its separators) as data 328 stop -= len(self._boundary) + 16 329 end = next = stop 330 if end <= start: 331 break # need more data 332 333 if self._state == 'PREAMBLE': 334 # Preamble, just ignore it 335 self._state = 'HEADER' 336 337 elif self._state == 'HEADER': 338 # Beginning of header, look for end of header and parse it if found. 339 340 header_end = data.find('\r\n\r\n', start, stop) 341 if header_end == -1: 342 break # need more data 343 344 header = data[start:header_end] 345 346 self._fieldname = None 347 self._filename = None 348 self._content_type = None 349 350 for line in header.split('\r\n'): 351 ctype, opts = self.parse_header(line) 352 if ctype == 'content-disposition: form-data': 353 self._fieldname = opts.get('name') 354 self._filename = opts.get('filename') 355 elif ctype.startswith('content-type: '): 356 self._content_type = ctype[14:] 357 358 if self._filename is not None: 359 # cleanup filename from IE full paths: 360 self._filename = self._filename[self._filename.rfind("\\")+1:].strip() 361 362 if self._filename: # ignore files without filenames 363 if self._file_upload_dir: 364 try: 365 self._file = self.TemporaryFile(dir=self._file_upload_dir) 366 except: 367 raise MultiPartParserError("Failed to create temporary file.") 368 else: 369 self._file = StringIO() 370 else: 371 self._file = None 372 self._filesize = 0 373 self._state = 'FILE' 374 else: 375 self._field = StringIO() 376 self._state = 'FIELD' 377 next = header_end + 4 378 379 elif self._state == 'FIELD': 380 # In a field, collect data until a boundary is found. 381 382 self._field.write(data[start:end]) 383 if boundary: 384 if self._fieldname: 385 self._post.appendlist(self._fieldname, self._field.getvalue()) 386 self._field.close() 387 self._state = 'HEADER' 388 389 elif self._state == 'FILE': 390 # In a file, collect data until a boundary is found. 391 392 if self._file: 393 try: 394 self._file.write(data[start:end]) 395 except IOError, e: 396 raise MultiPartParserError("Failed to write to temporary file.") 397 self._filesize += end-start 398 399 if self._progress_filename: 400 self._request.file_progress = {'received': self._received, 401 'size': self._size, 402 'state': 'uploading'} 403 404 if boundary: 405 if self._file: 406 if self._file_upload_dir: 407 self._file.seek(0) 408 file = self.LazyContent({ 409 'filename': self._filename, 410 'content-type': self._content_type, 411 # 'content': is read on demand 412 'content-length': self._filesize, 413 'tmpfilename': self._file.name, 414 'tmpfile': self._file 415 }) 416 else: 417 file = { 418 'filename': self._filename, 419 'content-type': self._content_type, 420 'content': self._file.getvalue(), 421 'content-length': self._filesize 422 } 423 self._file.close() 424 425 self._files.appendlist(self._fieldname, file) 426 427 self._state = 'HEADER' 428 429 start = next 430 431 self._partial = data[start:] 432 433 return read_size 434 435 def parse_header(self, line): 436 from cgi import parse_header 437 return parse_header(line) 438 76 439 class QueryDict(MultiValueDict): 77 440 """A specialized MultiValueDict that takes a query string when initialized. 78 441 This is immutable unless you create a copy of it.""" … … 306 669 if not host: 307 670 host = request.META.get('HTTP_HOST', '') 308 671 return host 672 -
django/oldforms/__init__.py
666 666 self.validator_list = [self.isNonEmptyFile] + validator_list 667 667 668 668 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'): 672 672 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']: 674 674 raise validators.CriticalValidationError, gettext("The submitted file is empty.") 675 675 676 676 def render(self, data): 677 677 return '<input type="file" id="%s" class="v%s" name="%s" />' % \ 678 678 (self.get_id(), self.__class__.__name__, self.field_name) 679 679 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 680 685 def html2python(data): 681 686 if data is None: 682 687 raise EmptyValue -
django/db/models/base.py
12 12 from django.dispatch import dispatcher 13 13 from django.utils.datastructures import SortedDict 14 14 from django.utils.functional import curry 15 from django.utils.file import file_move_safe 15 16 from django.conf import settings 16 17 from itertools import izip 17 18 import types 18 19 import sys 19 20 import os 20 21 22 21 23 class ModelBase(type): 22 24 "Metaclass for all models" 23 25 def __new__(cls, name, bases, attrs): … … 361 363 def _get_FIELD_size(self, field): 362 364 return os.path.getsize(self._get_FIELD_filename(field)) 363 365 364 def _save_FIELD_file(self, field, filename, raw_ contents, save=True):366 def _save_FIELD_file(self, field, filename, raw_field, save=True): 365 367 directory = field.get_directory_name() 366 368 try: # Create the date-based directory if it doesn't exist. 367 369 os.makedirs(os.path.join(settings.MEDIA_ROOT, directory)) … … 383 385 setattr(self, field.attname, filename) 384 386 385 387 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() 389 395 390 396 # Save the width and/or height, if applicable. 391 397 if isinstance(field, ImageField) and (field.width_field or field.height_field): -
django/db/models/fields/__init__.py
636 636 setattr(cls, 'get_%s_filename' % self.name, curry(cls._get_FIELD_filename, field=self)) 637 637 setattr(cls, 'get_%s_url' % self.name, curry(cls._get_FIELD_url, field=self)) 638 638 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)) 640 640 dispatcher.connect(self.delete_file, signal=signals.post_delete, sender=cls) 641 641 642 642 def delete_file(self, instance): … … 659 659 if new_data.get(upload_field_name, False): 660 660 func = getattr(new_object, 'save_%s_file' % self.name) 661 661 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) 663 663 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) 665 665 666 666 def get_directory_name(self): 667 667 return os.path.normpath(datetime.datetime.now().strftime(self.upload_to)) -
django/conf/global_settings.py
240 240 # isExistingURL validator. 241 241 URL_VALIDATOR_USER_AGENT = "Django/0.96pre (http://www.djangoproject.com)" 242 242 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. 246 FILE_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. 252 STREAMING_MIN_POST_SIZE = 512 * (2**10) 253 254 255 256 243 257 ############## 244 258 # MIDDLEWARE # 245 259 ############## … … 335 349 336 350 # The list of directories to search for fixtures 337 351 FIXTURE_DIRS = () 352 353 -
django/core/handlers/wsgi.py
111 111 if self.environ.get('CONTENT_TYPE', '').startswith('multipart'): 112 112 header_dict = dict([(k, v) for k, v in self.environ.items() if k.startswith('HTTP_')]) 113 113 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) 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 115 122 else: 116 123 self._post, self._files = http.QueryDict(self.raw_post_data), datastructures.MultiValueDict() 117 124 else: … … 167 174 buf.close() 168 175 return self._raw_post_data 169 176 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 170 188 GET = property(_get_get, _set_get) 171 189 POST = property(_get_post, _set_post) 172 190 COOKIES = property(_get_cookies, _set_cookies) -
django/core/handlers/modpython.py
47 47 def _load_post_and_files(self): 48 48 "Populates self._post and self._files" 49 49 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) 53 except: 54 self._post, self._files = {}, {} # make sure we dont read the input stream again 55 raise 51 56 else: 52 57 self._post, self._files = http.QueryDict(self.raw_post_data), datastructures.MultiValueDict() 53 58 … … 92 97 'AUTH_TYPE': self._req.ap_auth_type, 93 98 'CONTENT_LENGTH': self._req.clength, # This may be wrong 94 99 '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' 109 115 } 110 116 for key, value in self._req.headers_in.items(): 111 117 key = 'HTTP_' + key.upper().replace('-', '_') … … 122 128 def _get_method(self): 123 129 return self.META['REQUEST_METHOD'].upper() 124 130 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 125 143 GET = property(_get_get, _set_get) 126 144 POST = property(_get_post, _set_post) 127 145 COOKIES = property(_get_cookies, _set_cookies) -
tests/modeltests/test_client/views.py
44 44 45 45 return HttpResponse(t.render(c)) 46 46 47 def 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 47 53 def redirect_view(request): 48 54 "A view that redirects all requests to the GET view" 49 55 return HttpResponseRedirect('/test_client/get_view/') -
tests/modeltests/test_client/models.py
75 75 self.assertEqual(response.template.name, "Book template") 76 76 self.assertEqual(response.content, "Blink - Malcolm Gladwell") 77 77 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 78 93 def test_redirect(self): 79 94 "GET a URL that redirects elsewhere" 80 95 response = self.client.get('/test_client/redirect_view/') -
tests/modeltests/test_client/urls.py
4 4 urlpatterns = patterns('', 5 5 (r'^get_view/$', views.get_view), 6 6 (r'^post_view/$', views.post_view), 7 (r'^post_file_view/$', views.post_file_view), 7 8 (r'^raw_post_view/$', views.raw_post_view), 8 9 (r'^redirect_view/$', views.redirect_view), 9 10 (r'^form_view/$', views.form_view), -
docs/request_response.txt
72 72 ``FILES`` 73 73 A dictionary-like object containing all uploaded files. Each key in 74 74 ``FILES`` is the ``name`` from the ``<input type="file" name="" />``. Each 75 value in ``FILES`` is a standard Python dictionary with the following three75 value in ``FILES`` is a standard Python dictionary with the following four 76 76 keys: 77 77 78 78 * ``filename`` -- The name of the uploaded file, as a Python string. 79 79 * ``content-type`` -- The content type of the uploaded file. 80 80 * ``content`` -- The raw content of the uploaded file. 81 * ``content-length`` -- The length of the content in bytes. 81 82 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 82 94 Note that ``FILES`` will only contain data if the request method was POST 83 95 and the ``<form>`` that posted to the request had 84 96 ``enctype="multipart/form-data"``. Otherwise, ``FILES`` will be a blank -
docs/settings.txt
437 437 438 438 .. _Testing Django Applications: ../testing/ 439 439 440 FILE_UPLOAD_DIR 441 --------------- 442 443 Default: ``None`` 444 445 Path to a directory where temporary files should be written during 446 file uploads. Leaving this as ``None`` will disable streaming file uploads, 447 and cause all uploaded files to be stored (temporarily) in memory. 448 440 449 IGNORABLE_404_ENDS 441 450 ------------------ 442 451 … … 780 789 781 790 .. _site framework docs: ../sites/ 782 791 792 STREAMING_MIN_POST_SIZE 793 ----------------------- 794 795 Default: 524288 (``512*1024``) 796 797 An integer specifying the minimum number of bytes that has to be 798 received (in a POST) for file upload streaming to take place. Any 799 request smaller than this will be handled in memory. 800 Note: ``FILE_UPLOAD_DIR`` has to be defined to enable streaming. 801 783 802 TEMPLATE_CONTEXT_PROCESSORS 784 803 --------------------------- 785 804 -
docs/forms.txt
475 475 new_data = request.POST.copy() 476 476 new_data.update(request.FILES) 477 477 478 Streaming file uploads. 479 ----------------------- 480 481 File uploads will be read into memory by default. This works fine for 482 small to medium sized uploads (from 1MB to 100MB depending on your 483 setup and usage). If you want to support larger uploads you can enable 484 upload streaming where only a small part of the file will be in memory 485 at any time. To do this you need to specify the ``FILE_UPLOAD_DIR`` 486 setting (see the settings_ document for more details). 487 488 See `request object`_ for more details about ``request.FILES`` objects 489 with streaming file uploads enabled. 490 478 491 Validators 479 492 ========== 480 493 … … 693 706 .. _`generic views`: ../generic_views/ 694 707 .. _`models API`: ../model-api/ 695 708 .. _settings: ../settings/ 709 .. _request object: ../request_response/#httprequest-objects