Ticket #2070: 5313_updated_file_progress.diff
File 5313_updated_file_progress.diff, 36.7 KB (added by , 17 years ago) |
---|
-
django/http/file_descriptor.py
1 """ 2 This file contains a fallback FileProgressDescriptor 3 for file upload progress. 4 """ 5 6 class NullFileProgressDescriptor(object): 7 """ 8 This descriptor doesn't do anything, but 9 is here to be overridden. 10 """ 11 12 def __init__(self, exception): 13 pass 14 15 def __get__(self, request, HttpRequest): 16 return {} 17 18 def __set__(self, request, new_progress): 19 return None 20 21 def __delete__(self, request): 22 return None -
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 from django.http.file_descriptor import NullFileProgressDescriptor 7 import re 6 8 9 try: 10 from cStringIO import StringIO 11 except ImportError: 12 from StringIO import StringIO 13 7 14 RESERVED_CHARS="!*'();:@&=+$,/?%#[]" 8 15 9 16 try: … … 17 24 18 25 class HttpRequest(object): 19 26 "A basic HTTP request" 27 upload_id_re = re.compile(r'^[a-fA-F0-9]{32}$') 28 20 29 def __init__(self): 21 30 self.GET, self.POST, self.COOKIES, self.META, self.FILES = {}, {}, {}, {}, {} 22 31 self.path = '' … … 42 51 def is_secure(self): 43 52 return os.environ.get("HTTPS") == "on" 44 53 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 54 def _get_file_progress_from_args(self, headers, get, querystring): 55 if 'X-Upload-ID' in headers: 56 progress_id = headers['X-Upload-ID'] 57 elif 'X-Progress-ID' in headers: 58 progress_id = headers['X-Progress-ID'] 59 elif 'HTTP_X_UPLOAD_ID' in headers: 60 progress_id = headers['HTTP_X_UPLOAD_ID'] 61 elif 'HTTP_X_PROGRESS_ID' in headers: 62 progress_id = headers['HTTP_X_PROGRESS_ID'] 63 elif 'upload_id' in get: 64 progress_id = get['upload_id'] 65 elif 'progress_id' in get: 66 progress_id = get['progress_id'] 67 elif querystring and len(querystring.strip()) == 32: 68 progress_id = querystring 69 else: 70 return None 75 71 72 if not self.upload_id_re.match(progress_id): 73 return None 74 75 return progress_id 76 77 def parse_file_upload(headers, input, request): 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, request, file_upload_dir, streaming_min_post_size) 86 return parser.parse() 87 except MultiPartParserError, e: 88 return MultiValueDict({ '_file_upload_error': [e.message] }), {} 89 90 class MultiPartParserError(Exception): 91 def __init__(self, message): 92 self.message = message 93 def __str__(self): 94 return repr(self.message) 95 96 class 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 X-Progress-ID is sent (in one of many formats), then 111 object.file_progress will be given a dictionary of the progress. 112 """ 113 def __init__(self, headers, input, request, file_upload_dir=None, streaming_min_post_size=None, chunk_size=1024*64): 114 try: 115 content_length = int(headers['Content-Length']) 116 except: 117 raise MultiPartParserError('Invalid Content-Length: %s' % headers.get('Content-Length')) 118 119 content_type = headers.get('Content-Type') 120 121 if not content_type or not content_type.startswith('multipart/'): 122 raise MultiPartParserError('Invalid Content-Type: %s' % content_type) 123 124 ctype, opts = self.parse_header(content_type) 125 boundary = opts.get('boundary') 126 from cgi import valid_boundary 127 if not boundary or not valid_boundary(boundary): 128 raise MultiPartParserError('Invalid boundary in multipart form: %s' % boundary) 129 130 progress_id = request.META['UPLOAD_PROGRESS_ID'] 131 132 if file_upload_dir and progress_id: 133 self._progress_filename = os.path.join(file_upload_dir, progress_id) 134 else: 135 self._progress_filename = None 136 self._boundary = '--' + boundary 137 self._input = input 138 self._size = content_length 139 self._received = 0 140 self._file_upload_dir = file_upload_dir 141 self._chunk_size = chunk_size 142 self._state = 'PREAMBLE' 143 self._partial = '' 144 self._post = MultiValueDict() 145 self._files = MultiValueDict() 146 self._request = request 147 148 if streaming_min_post_size is not None and content_length < streaming_min_post_size: 149 self._file_upload_dir = None # disable file streaming for small request 150 elif self._progress_filename: 151 request.file_progress = {'state': 'starting'} 152 153 try: 154 # Use mx fast string search if available. 155 from mx.TextTools import FS 156 self._fs = FS(self._boundary) 157 except ImportError: 158 self._fs = None 159 160 def parse(self): 161 try: 162 self._parse() 163 finally: 164 if self._progress_filename: 165 self._request.file_progress = {'state': 'done'} 166 return self._post, self._files 167 168 def _parse(self): 169 size = self._size 170 171 try: 172 while size > 0: 173 n = self._read(self._input, min(self._chunk_size, size)) 174 if not n: 175 break 176 size -= n 177 except: 178 # consume any remaining data so we dont generate a "Connection Reset" error 179 size = self._size - self._received 180 while size > 0: 181 data = self._input.read(min(self._chunk_size, size)) 182 size -= len(data) 183 raise 184 185 def _find_boundary(self, data, start, stop): 186 """ 187 Find the next boundary and return the end of current part 188 and start of next part. 189 """ 190 if self._fs: 191 boundary = self._fs.find(data, start, stop) 192 else: 193 boundary = data.find(self._boundary, start, stop) 194 if boundary >= 0: 195 end = boundary 196 next = boundary + len(self._boundary) 197 198 # backup over CRLF 199 if end > 0 and data[end-1] == '\n': end -= 1 200 if end > 0 and data[end-1] == '\r': end -= 1 201 # skip over --CRLF 202 if next < stop and data[next] == '-': next += 1 203 if next < stop and data[next] == '-': next += 1 204 if next < stop and data[next] == '\r': next += 1 205 if next < stop and data[next] == '\n': next += 1 206 207 return True, end, next 208 else: 209 return False, stop, stop 210 211 class TemporaryFile(object): 212 "A temporary file that tries to delete itself when garbage collected." 213 def __init__(self, dir): 214 import tempfile 215 (fd, name) = tempfile.mkstemp(suffix='.upload', dir=dir) 216 self.file = os.fdopen(fd, 'w+b') 217 self.name = name 218 219 def __getattr__(self, name): 220 a = getattr(self.__dict__['file'], name) 221 if type(a) != type(0): 222 setattr(self, name, a) 223 return a 224 225 def __del__(self): 226 try: 227 os.unlink(self.name) 228 except OSError: 229 pass 230 231 class LazyContent(dict): 232 """ 233 A lazy FILES dictionary entry that reads the contents from 234 tmpfile only when referenced. 235 """ 236 def __init__(self, data): 237 dict.__init__(self, data) 238 239 def __getitem__(self, key): 240 if key == 'content' and not self.has_key(key): 241 self['tmpfile'].seek(0) 242 self['content'] = self['tmpfile'].read() 243 return dict.__getitem__(self, key) 244 245 def _read(self, input, size): 246 data = input.read(size) 247 248 if not data: 249 return 0 250 251 read_size = len(data) 252 self._received += read_size 253 254 if self._partial: 255 data = self._partial + data 256 257 start = 0 258 stop = len(data) 259 260 while start < stop: 261 boundary, end, next = self._find_boundary(data, start, stop) 262 263 if not boundary and read_size: 264 # make sure we dont treat a partial boundary (and its separators) as data 265 stop -= len(self._boundary) + 16 266 end = next = stop 267 if end <= start: 268 break # need more data 269 270 if self._state == 'PREAMBLE': 271 # Preamble, just ignore it 272 self._state = 'HEADER' 273 274 elif self._state == 'HEADER': 275 # Beginning of header, look for end of header and parse it if found. 276 277 header_end = data.find('\r\n\r\n', start, stop) 278 if header_end == -1: 279 break # need more data 280 281 header = data[start:header_end] 282 283 self._fieldname = None 284 self._filename = None 285 self._content_type = None 286 287 for line in header.split('\r\n'): 288 ctype, opts = self.parse_header(line) 289 if ctype == 'content-disposition: form-data': 290 self._fieldname = opts.get('name') 291 self._filename = opts.get('filename') 292 elif ctype.startswith('content-type: '): 293 self._content_type = ctype[14:] 294 295 if self._filename is not None: 296 # cleanup filename from IE full paths: 297 self._filename = self._filename[self._filename.rfind("\\")+1:].strip() 298 299 if self._filename: # ignore files without filenames 300 if self._file_upload_dir: 301 try: 302 self._file = self.TemporaryFile(dir=self._file_upload_dir) 303 except: 304 raise MultiPartParserError("Failed to create temporary file.") 305 else: 306 self._file = StringIO() 307 else: 308 self._file = None 309 self._filesize = 0 310 self._state = 'FILE' 311 else: 312 self._field = StringIO() 313 self._state = 'FIELD' 314 next = header_end + 4 315 316 elif self._state == 'FIELD': 317 # In a field, collect data until a boundary is found. 318 319 self._field.write(data[start:end]) 320 if boundary: 321 if self._fieldname: 322 self._post.appendlist(self._fieldname, self._field.getvalue()) 323 self._field.close() 324 self._state = 'HEADER' 325 326 elif self._state == 'FILE': 327 # In a file, collect data until a boundary is found. 328 329 if self._file: 330 try: 331 self._file.write(data[start:end]) 332 except IOError, e: 333 raise MultiPartParserError("Failed to write to temporary file.") 334 self._filesize += end-start 335 336 if self._progress_filename: 337 self._request.file_progress = {'received': self._received, 338 'size': self._size, 339 'state': 'uploading'} 340 341 if boundary: 342 if self._file: 343 if self._file_upload_dir: 344 self._file.seek(0) 345 file = self.LazyContent({ 346 'filename': self._filename, 347 'content-type': self._content_type, 348 # 'content': is read on demand 349 'content-length': self._filesize, 350 'tmpfilename': self._file.name, 351 'tmpfile': self._file 352 }) 353 else: 354 file = { 355 'filename': self._filename, 356 'content-type': self._content_type, 357 'content': self._file.getvalue(), 358 'content-length': self._filesize 359 } 360 self._file.close() 361 362 self._files.appendlist(self._fieldname, file) 363 364 self._state = 'HEADER' 365 366 start = next 367 368 self._partial = data[start:] 369 370 return read_size 371 372 def parse_header(self, line): 373 from cgi import parse_header 374 return parse_header(line) 375 76 376 class QueryDict(MultiValueDict): 77 377 """A specialized MultiValueDict that takes a query string when initialized. 78 378 This is immutable unless you create a copy of it.""" -
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
701 701 setattr(cls, 'get_%s_filename' % self.name, curry(cls._get_FIELD_filename, field=self)) 702 702 setattr(cls, 'get_%s_url' % self.name, curry(cls._get_FIELD_url, field=self)) 703 703 setattr(cls, 'get_%s_size' % self.name, curry(cls._get_FIELD_size, field=self)) 704 setattr(cls, 'save_%s_file' % self.name, lambda instance, filename, raw_ contents, save=True: instance._save_FIELD_file(self, filename, raw_contents, save))704 setattr(cls, 'save_%s_file' % self.name, lambda instance, filename, raw_field, save=True: instance._save_FIELD_file(self, filename, raw_field, save)) 705 705 dispatcher.connect(self.delete_file, signal=signals.post_delete, sender=cls) 706 706 707 707 def delete_file(self, instance): … … 724 724 if new_data.get(upload_field_name, False): 725 725 func = getattr(new_object, 'save_%s_file' % self.name) 726 726 if rel: 727 func(new_data[upload_field_name][0]["filename"], new_data[upload_field_name][0] ["content"], save)727 func(new_data[upload_field_name][0]["filename"], new_data[upload_field_name][0], save) 728 728 else: 729 func(new_data[upload_field_name]["filename"], new_data[upload_field_name] ["content"], save)729 func(new_data[upload_field_name]["filename"], new_data[upload_field_name], save) 730 730 731 731 def get_directory_name(self): 732 732 return os.path.normpath(datetime.datetime.now().strftime(self.upload_to)) -
django/conf/global_settings.py
242 242 # isExistingURL validator. 243 243 URL_VALIDATOR_USER_AGENT = "Django/0.96pre (http://www.djangoproject.com)" 244 244 245 # The directory to place streamed file uploads. The web server needs write 246 # permissions on this directory. 247 # If this is None, streaming uploads are disabled. 248 FILE_UPLOAD_DIR = None 249 250 # The minimum size of a POST before file uploads are streamed to disk. 251 # Any less than this number, and the file is uploaded to memory. 252 # Size is in bytes. 253 STREAMING_MIN_POST_SIZE = 512 * (2**10) 254 245 255 ############## 246 256 # MIDDLEWARE # 247 257 ############## -
django/core/handlers/wsgi.py
75 75 self.environ = environ 76 76 self.path = environ['PATH_INFO'] 77 77 self.META = environ 78 self.META['UPLOAD_PROGRESS_ID'] = self._get_file_progress_id() 78 79 self.method = environ['REQUEST_METHOD'].upper() 79 80 80 81 def __repr__(self): … … 111 112 if self.environ.get('CONTENT_TYPE', '').startswith('multipart'): 112 113 header_dict = dict([(k, v) for k, v in self.environ.items() if k.startswith('HTTP_')]) 113 114 header_dict['Content-Type'] = self.environ.get('CONTENT_TYPE', '') 114 self._post, self._files = http.parse_file_upload(header_dict, self.raw_post_data) 115 header_dict['Content-Length'] = self.environ.get('CONTENT_LENGTH', '') 116 header_dict['X-Progress-ID'] = self.environ.get('HTTP_X_PROGRESS_ID', '') 117 try: 118 self._post, self._files = http.parse_file_upload(header_dict, self.environ['wsgi.input'], self) 119 except: 120 self._post, self._files = {}, {} # make sure we dont read the input stream again 121 raise 122 self._raw_post_data = None # raw data is not available for streamed multipart messages 115 123 else: 116 124 self._post, self._files = http.QueryDict(self.raw_post_data), datastructures.MultiValueDict() 117 125 else: … … 167 175 buf.close() 168 176 return self._raw_post_data 169 177 178 def _get_file_progress_id(self): 179 """ 180 Returns the Progress ID of the request, 181 usually provided if there is a file upload 182 going on. 183 Returns ``None`` if no progress ID is specified. 184 """ 185 return self._get_file_progress_from_args(self.environ, 186 self.GET, 187 self.environ.get('QUERY_STRING', '')) 188 170 189 GET = property(_get_get, _set_get) 171 190 POST = property(_get_post, _set_post) 172 191 COOKIES = property(_get_cookies, _set_cookies) -
django/core/handlers/base.py
5 5 6 6 class BaseHandler(object): 7 7 def __init__(self): 8 self._ request_middleware = self._view_middleware = self._response_middleware = self._exception_middleware = None8 self._upload_middleware = self._request_middleware = self._view_middleware = self._response_middleware = self._exception_middleware = None 9 9 10 10 def load_middleware(self): 11 11 """ … … 19 19 self._view_middleware = [] 20 20 self._response_middleware = [] 21 21 self._exception_middleware = [] 22 self._upload_middleware = [] 22 23 for middleware_path in settings.MIDDLEWARE_CLASSES: 23 24 try: 24 25 dot = middleware_path.rindex('.') … … 47 48 self._response_middleware.insert(0, mw_instance.process_response) 48 49 if hasattr(mw_instance, 'process_exception'): 49 50 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) 50 53 54 def file_progress_descriptor(self, request): 55 """ 56 Returns a descriptor that manages the file_progress 57 """ 58 for mw_call in self._upload_middleware: 59 result = mw_call(http.MultiPartParserError) 60 if result != None: 61 return result 62 return http.NullFileProgressDescriptor(http.MultiPartParserError) 63 51 64 def get_response(self, request): 52 65 "Returns an HttpResponse object for the given HttpRequest" 53 66 from django.core import exceptions, urlresolvers 54 67 from django.core.mail import mail_admins 55 68 from django.conf import settings 56 69 70 # Add file_progress descriptor 71 request.__class__.file_progress = self.file_progress_descriptor(request) 72 57 73 # Apply request middleware 58 74 for middleware_method in self._request_middleware: 59 75 response = middleware_method(request) -
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 125 142 GET = property(_get_get, _set_get) 126 143 POST = property(_get_post, _set_post) 127 144 COOKIES = property(_get_cookies, _set_cookies) -
django/utils/file.py
1 import os 2 3 try: 4 import shutils 5 file_move = shutils.move 6 except: 7 file_move = os.rename 8 9 def file_move_safe(old_file_name, new_file_name, chunk_size = 1024*64): 10 """ 11 Moves a file from one location to another in the safest way possible. 12 13 First, it tries using shutils.move, which is OS-dependent but doesn't 14 break with change of filesystems. Then it tries os.rename, which will 15 break if it encounters a change in filesystems. Lastly, it streams 16 it manually from one file to another in python. 17 """ 18 19 if old_file_name == new_file_name: 20 # No file moving takes place. 21 return 22 23 try: 24 file_move(old_file_name, new_file_name) 25 return 26 except: 27 pass 28 29 new_file = open(new_file_name, 'wb') 30 old_file = open(old_file_name, 'rb') 31 current_chunk = None 32 33 while current_chunk != '': 34 current_chunk = old_file.read(chunk_size) 35 new_file.write(current_chunk) 36 37 new_file.close() 38 old_file.close() 39 40 os.remove(old_file_name) -
tests/modeltests/test_client/views.py
46 46 47 47 return HttpResponse(t.render(c)) 48 48 49 def post_file_view(request): 50 "A view that expects a multipart post and returns a file in the context" 51 t = Template('File {{ file.filename }} received', name='POST Template') 52 c = Context({'file': request.FILES['file_file']}) 53 return HttpResponse(t.render(c)) 54 49 55 def redirect_view(request): 50 56 "A view that redirects all requests to the GET view" 51 57 return HttpResponseRedirect('/test_client/get_view/') -
tests/modeltests/test_client/models.py
3 3 4 4 The test client is a class that can act like a simple 5 5 browser for testing purposes. 6 6 7 7 It allows the user to compose GET and POST requests, and 8 8 obtain the response that the server gave to those requests. 9 9 The server Response objects are annotated with the details … … 76 76 self.assertEqual(response.template.name, "Book template") 77 77 self.assertEqual(response.content, "Blink - Malcolm Gladwell") 78 78 79 def test_post_file_view(self): 80 "POST this python file to a view" 81 import os, tempfile 82 from django.conf import settings 83 file = __file__.replace('.pyc', '.py') 84 for upload_dir in [None, tempfile.gettempdir()]: 85 settings.FILE_UPLOAD_DIR = upload_dir 86 post_data = { 'name': file, 'file': open(file) } 87 response = self.client.post('/test_client/post_file_view/', post_data) 88 self.failUnless('models.py' in response.context['file']['filename']) 89 self.failUnless(len(response.context['file']['content']) == os.path.getsize(file)) 90 if upload_dir: 91 self.failUnless(response.context['file']['tmpfilename']) 92 79 93 def test_redirect(self): 80 94 "GET a URL that redirects elsewhere" 81 95 response = self.client.get('/test_client/redirect_view/') -
tests/modeltests/test_client/urls.py
5 5 urlpatterns = patterns('', 6 6 (r'^get_view/$', views.get_view), 7 7 (r'^post_view/$', views.post_view), 8 (r'^post_file_view/$', views.post_file_view), 8 9 (r'^raw_post_view/$', views.raw_post_view), 9 10 (r'^redirect_view/$', views.redirect_view), 10 11 (r'^permanent_redirect_view/$', redirect_to, { 'url': '/test_client/get_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
448 448 449 449 .. _Testing Django Applications: ../testing/ 450 450 451 FILE_UPLOAD_DIR 452 --------------- 453 454 Default: ``None`` 455 456 Path to a directory where temporary files should be written during 457 file uploads. Leaving this as ``None`` will disable streaming file uploads, 458 and cause all uploaded files to be stored (temporarily) in memory. 459 451 460 IGNORABLE_404_ENDS 452 461 ------------------ 453 462 … … 764 773 765 774 .. _site framework docs: ../sites/ 766 775 776 STREAMING_MIN_POST_SIZE 777 ----------------------- 778 779 Default: 524288 (``512*1024``) 780 781 An integer specifying the minimum number of bytes that has to be 782 received (in a POST) for file upload streaming to take place. Any 783 request smaller than this will be handled in memory. 784 Note: ``FILE_UPLOAD_DIR`` has to be defined to enable streaming. 785 767 786 TEMPLATE_CONTEXT_PROCESSORS 768 787 --------------------------- 769 788 -
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 … … 698 711 .. _`generic views`: ../generic_views/ 699 712 .. _`models API`: ../model-api/ 700 713 .. _settings: ../settings/ 714 .. _request object: ../request_response/#httprequest-objects