Ticket #2070: ticket2070_rev7277.diff
File ticket2070_rev7277.diff, 48.1 KB (added by , 17 years ago) |
---|
-
django/http/multipartparser.py
1 """ 2 MultiPart parsing for file uploads. 3 If both a progress id is sent (either through ``X-Progress-ID`` 4 header or ``progress_id`` GET) and ``FILE_UPLOAD_DIR`` is set 5 in the settings, then the file progress will be tracked using 6 ``request.file_progress``. 7 8 To use this feature, consider creating a middleware with an appropriate 9 ``process_request``:: 10 11 class FileProgressTrack(object): 12 def __get__(self, request, HttpRequest): 13 progress_id = request.META['UPLOAD_PROGRESS_ID'] 14 status = # get progress from progress_id here 15 16 return status 17 18 def __set__(self, request, new_value): 19 progress_id = request.META['UPLOAD_PROGRESS_ID'] 20 21 # set the progress using progress_id here. 22 23 # example middleware 24 class FileProgressExample(object): 25 def process_request(self, request): 26 request.__class__.file_progress = FileProgressTrack() 27 28 29 30 """ 31 32 __all__ = ['MultiPartParserError','MultiPartParser'] 33 34 35 from django.utils.datastructures import MultiValueDict 36 from django.http.utils import str_to_unicode 37 from django.conf import settings 38 import os 39 40 try: 41 from cStringIO import StringIO 42 except ImportError: 43 from StringIO import StringIO 44 45 46 class MultiPartParserError(Exception): 47 def __init__(self, message): 48 self.message = message 49 def __str__(self): 50 return repr(self.message) 51 52 class MultiPartParser(object): 53 """ 54 A rfc2388 multipart/form-data parser. 55 56 parse() reads the input stream in chunk_size chunks and returns a 57 tuple of (POST MultiValueDict, FILES MultiValueDict). If 58 file_upload_dir is defined files will be streamed to temporary 59 files in the specified directory. 60 61 The FILES dictionary will have 'filename', 'content-type', 62 'content' and 'content-length' entries. For streamed files it will 63 also have 'tmpfilename' and 'tmpfile'. The 'content' entry will 64 only be read from disk when referenced for streamed files. 65 66 If the X-Progress-ID is sent (in one of many formats), then 67 object.file_progress will be given a dictionary of the progress. 68 """ 69 def __init__(self, headers, input, request, file_upload_dir=None, streaming_min_post_size=None, chunk_size=1024*64): 70 try: 71 content_length = int(headers['Content-Length']) 72 except: 73 raise MultiPartParserError('Invalid Content-Length: %s' % headers.get('Content-Length')) 74 75 content_type = headers.get('Content-Type') 76 77 if not content_type or not content_type.startswith('multipart/'): 78 raise MultiPartParserError('Invalid Content-Type: %s' % content_type) 79 80 ctype, opts = self.parse_header(content_type) 81 boundary = opts.get('boundary') 82 from cgi import valid_boundary 83 if not boundary or not valid_boundary(boundary): 84 raise MultiPartParserError('Invalid boundary in multipart form: %s' % boundary) 85 86 progress_id = request.META['UPLOAD_PROGRESS_ID'] 87 88 self._track_progress = file_upload_dir and progress_id # whether or not to track progress 89 self._boundary = '--' + boundary 90 self._input = input 91 self._size = content_length 92 self._received = 0 93 self._file_upload_dir = file_upload_dir 94 self._chunk_size = chunk_size 95 self._state = 'PREAMBLE' 96 self._partial = '' 97 self._post = MultiValueDict() 98 self._files = MultiValueDict() 99 self._request = request 100 self._encoding = request.encoding or settings.DEFAULT_CHARSET 101 102 if streaming_min_post_size is not None and content_length < streaming_min_post_size: 103 self._file_upload_dir = None # disable file streaming for small request 104 elif self._track_progress: 105 request.file_progress = {'state': 'starting'} 106 107 try: 108 # Use mx fast string search if available. 109 from mx.TextTools import FS 110 self._fs = FS(self._boundary) 111 except ImportError: 112 self._fs = None 113 114 def parse(self): 115 try: 116 self._parse() 117 finally: 118 if self._track_progress: 119 self._request.file_progress = {'state': 'done'} 120 return self._post, self._files 121 122 def _parse(self): 123 size = self._size 124 125 try: 126 while size > 0: 127 n = self._read(self._input, min(self._chunk_size, size)) 128 if not n: 129 break 130 size -= n 131 except: 132 # consume any remaining data so we dont generate a "Connection Reset" error 133 size = self._size - self._received 134 while size > 0: 135 data = self._input.read(min(self._chunk_size, size)) 136 size -= len(data) 137 raise 138 139 def _find_boundary(self, data, start, stop): 140 """ 141 Find the next boundary and return the end of current part 142 and start of next part. 143 """ 144 if self._fs: 145 boundary = self._fs.find(data, start, stop) 146 else: 147 boundary = data.find(self._boundary, start, stop) 148 if boundary >= 0: 149 end = boundary 150 next = boundary + len(self._boundary) 151 152 # backup over CRLF 153 if end > 0 and data[end-1] == '\n': end -= 1 154 if end > 0 and data[end-1] == '\r': end -= 1 155 # skip over --CRLF 156 if next < stop and data[next] == '-': next += 1 157 if next < stop and data[next] == '-': next += 1 158 if next < stop and data[next] == '\r': next += 1 159 if next < stop and data[next] == '\n': next += 1 160 161 return True, end, next 162 else: 163 return False, stop, stop 164 165 class TemporaryFile(object): 166 "A temporary file that tries to delete itself when garbage collected." 167 def __init__(self, dir): 168 import tempfile 169 (fd, name) = tempfile.mkstemp(suffix='.upload', dir=dir) 170 self.file = os.fdopen(fd, 'w+b') 171 self.name = name 172 173 def __getattr__(self, name): 174 a = getattr(self.__dict__['file'], name) 175 if type(a) != type(0): 176 setattr(self, name, a) 177 return a 178 179 def __del__(self): 180 try: 181 os.unlink(self.name) 182 except OSError: 183 pass 184 185 class LazyContent(dict): 186 """ 187 A lazy FILES dictionary entry that reads the contents from 188 tmpfile only when referenced. 189 """ 190 def __init__(self, data): 191 dict.__init__(self, data) 192 193 def __getitem__(self, key): 194 if key == 'content' and not self.has_key(key): 195 self['tmpfile'].seek(0) 196 self['content'] = self['tmpfile'].read() 197 return dict.__getitem__(self, key) 198 199 def _read(self, input, size): 200 data = input.read(size) 201 202 if not data: 203 return 0 204 205 read_size = len(data) 206 self._received += read_size 207 208 if self._partial: 209 data = self._partial + data 210 211 start = 0 212 stop = len(data) 213 214 while start < stop: 215 boundary, end, next = self._find_boundary(data, start, stop) 216 217 if not boundary and read_size: 218 # make sure we dont treat a partial boundary (and its separators) as data 219 stop -= len(self._boundary) + 16 220 end = next = stop 221 if end <= start: 222 break # need more data 223 224 if self._state == 'PREAMBLE': 225 # Preamble, just ignore it 226 self._state = 'HEADER' 227 228 elif self._state == 'HEADER': 229 # Beginning of header, look for end of header and parse it if found. 230 231 header_end = data.find('\r\n\r\n', start, stop) 232 if header_end == -1: 233 break # need more data 234 235 header = data[start:header_end] 236 237 self._fieldname = None 238 self._filename = None 239 self._content_type = None 240 241 for line in header.split('\r\n'): 242 ctype, opts = self.parse_header(line) 243 if ctype == 'content-disposition: form-data': 244 self._fieldname = opts.get('name') 245 self._filename = opts.get('filename') 246 elif ctype.startswith('content-type: '): 247 self._content_type = ctype[14:] 248 249 if self._filename is not None: 250 # cleanup filename from IE full paths: 251 self._filename = self._filename[self._filename.rfind("\\")+1:].strip() 252 253 if self._filename: # ignore files without filenames 254 if self._file_upload_dir: 255 try: 256 self._file = self.TemporaryFile(dir=self._file_upload_dir) 257 except (OSError, IOError), e: 258 raise MultiPartParserError("Failed to create temporary file. Error was %s" % e) 259 else: 260 self._file = StringIO() 261 else: 262 self._file = None 263 self._filesize = 0 264 self._state = 'FILE' 265 else: 266 self._field = StringIO() 267 self._state = 'FIELD' 268 next = header_end + 4 269 270 elif self._state == 'FIELD': 271 # In a field, collect data until a boundary is found. 272 273 self._field.write(data[start:end]) 274 if boundary: 275 if self._fieldname: 276 self._post.appendlist(self._fieldname, str_to_unicode(self._field.getvalue(), self._encoding)) 277 self._field.close() 278 self._state = 'HEADER' 279 280 elif self._state == 'FILE': 281 # In a file, collect data until a boundary is found. 282 283 if self._file: 284 try: 285 self._file.write(data[start:end]) 286 except IOError, e: 287 raise MultiPartParserError("Failed to write to temporary file.") 288 self._filesize += end-start 289 290 if self._track_progress: 291 self._request.file_progress = {'received': self._received, 292 'size': self._size, 293 'state': 'uploading'} 294 295 if boundary: 296 if self._file: 297 if self._file_upload_dir: 298 self._file.seek(0) 299 file = self.LazyContent({ 300 'filename': str_to_unicode(self._filename, self._encoding), 301 'content-type': self._content_type, 302 # 'content': is read on demand 303 'content-length': self._filesize, 304 'tmpfilename': self._file.name, 305 'tmpfile': self._file 306 }) 307 else: 308 file = { 309 'filename': str_to_unicode(self._filename, self._encoding), 310 'content-type': self._content_type, 311 'content': self._file.getvalue(), 312 'content-length': self._filesize 313 } 314 self._file.close() 315 316 self._files.appendlist(self._fieldname, file) 317 318 self._state = 'HEADER' 319 320 start = next 321 322 self._partial = data[start:] 323 324 return read_size 325 326 def parse_header(self, line): 327 from cgi import parse_header 328 return parse_header(line) -
django/http/__init__.py
1 1 import os 2 import re 2 3 from Cookie import SimpleCookie, CookieError 3 4 from pprint import pformat 4 5 from urllib import urlencode 5 6 from urlparse import urljoin 7 from django.http.utils import str_to_unicode 8 from django.http.multipartparser import MultiPartParser, MultiPartParserError 6 9 try: 7 10 # The mod_python version is more efficient, so try importing it first. 8 11 from mod_python.util import parse_qsl … … 14 17 15 18 from utils import * 16 19 20 upload_id_re = re.compile(r'^[a-fA-F0-9]{32}$') # file progress id Regular expression 21 17 22 RESERVED_CHARS="!*'();:@&=+$,/?%#[]" 18 23 19 24 … … 81 86 82 87 def is_secure(self): 83 88 return os.environ.get("HTTPS") == "on" 84 89 85 90 def _set_encoding(self, val): 86 91 """ 87 92 Sets the encoding used for GET/POST accesses. If the GET or POST … … 99 104 100 105 encoding = property(_get_encoding, _set_encoding) 101 106 102 def parse_file_upload(header_dict, post_data): 103 """Returns a tuple of (POST QueryDict, FILES MultiValueDict).""" 104 import email, email.Message 105 from cgi import parse_header 106 raw_message = '\r\n'.join(['%s:%s' % pair for pair in header_dict.items()]) 107 raw_message += '\r\n\r\n' + post_data 108 msg = email.message_from_string(raw_message) 109 POST = QueryDict('', mutable=True) 110 FILES = MultiValueDict() 111 for submessage in msg.get_payload(): 112 if submessage and isinstance(submessage, email.Message.Message): 113 name_dict = parse_header(submessage['Content-Disposition'])[1] 114 # name_dict is something like {'name': 'file', 'filename': 'test.txt'} for file uploads 115 # or {'name': 'blah'} for POST fields 116 # We assume all uploaded files have a 'filename' set. 117 if 'filename' in name_dict: 118 assert type([]) != type(submessage.get_payload()), "Nested MIME messages are not supported" 119 if not name_dict['filename'].strip(): 120 continue 121 # IE submits the full path, so trim everything but the basename. 122 # (We can't use os.path.basename because that uses the server's 123 # directory separator, which may not be the same as the 124 # client's one.) 125 filename = name_dict['filename'][name_dict['filename'].rfind("\\")+1:] 126 FILES.appendlist(name_dict['name'], FileDict({ 127 'filename': filename, 128 'content-type': 'Content-Type' in submessage and submessage['Content-Type'] or None, 129 'content': submessage.get_payload(), 130 })) 131 else: 132 POST.appendlist(name_dict['name'], submessage.get_payload()) 133 return POST, FILES 107 def _get_file_progress(self): 108 return {} 134 109 110 def _set_file_progress(self,value): 111 pass 135 112 113 def _del_file_progress(self): 114 pass 115 116 file_progress = property(_get_file_progress, 117 _set_file_progress, 118 _del_file_progress) 119 120 def _get_file_progress_from_args(self, headers, get, querystring): 121 """ 122 This parses the request for a file progress_id value. 123 Note that there are two distinct ways of getting the progress 124 ID -- header and GET. One is used primarily to attach via JavaScript 125 to the end of an HTML form action while the other is used for AJAX 126 communication. 127 128 All progress IDs must be valid 32-digit hexadecimal numbers. 129 """ 130 if 'X-Upload-ID' in headers: 131 progress_id = headers['X-Upload-ID'] 132 elif 'progress_id' in get: 133 progress_id = get['progress_id'] 134 else: 135 return None 136 137 if not upload_id_re.match(progress_id): 138 return None 139 140 return progress_id 141 142 143 def parse_file_upload(headers, input, request): 144 " Parse the headers to upload data. " 145 from django.conf import settings 146 147 # Only stream files to disk if FILE_STREAMING_DIR is set 148 file_upload_dir = settings.FILE_UPLOAD_DIR 149 streaming_min_post_size = settings.STREAMING_MIN_POST_SIZE 150 151 try: 152 parser = MultiPartParser(headers, input, request, file_upload_dir, streaming_min_post_size) 153 return parser.parse() 154 except MultiPartParserError, e: 155 return MultiValueDict({ '_file_upload_error': [e.message] }), {} 156 157 136 158 class QueryDict(MultiValueDict): 137 159 """ 138 160 A specialized MultiValueDict that takes a query string when initialized. … … 429 451 # A backwards compatible alias for HttpRequest.get_host. 430 452 def get_host(request): 431 453 return request.get_host() 432 433 # It's neither necessary nor appropriate to use434 # django.utils.encoding.smart_unicode for parsing URLs and form inputs. Thus,435 # this slightly more restricted function.436 def str_to_unicode(s, encoding):437 """438 Converts basestring objects to unicode, using the given encoding. Illegally439 encoded input characters are replaced with Unicode "unknown" codepoint440 (\ufffd).441 442 Returns any non-basestring objects without change.443 """444 if isinstance(s, str):445 return unicode(s, encoding, 'replace')446 else:447 return s -
django/http/utils.py
1 # It's neither necessary nor appropriate to use 2 # django.utils.encoding.smart_unicode for parsing URLs and form inputs. Thus, 3 # this slightly more restricted function. 4 def str_to_unicode(s, encoding): 5 """ 6 Convert basestring objects to unicode, using the given encoding. Illegaly 7 encoded input characters are replaced with Unicode "unknown" codepoint 8 (\ufffd). 9 10 Returns any non-basestring objects without change. 11 """ 12 if isinstance(s, str): 13 return unicode(s, encoding, 'replace') 14 else: 15 return s 16 1 17 """ 2 18 Functions that modify an HTTP request or response in some way. 3 19 """ -
django/conf/global_settings.py
259 259 DEFAULT_TABLESPACE = '' 260 260 DEFAULT_INDEX_TABLESPACE = '' 261 261 262 # The directory to place streamed file uploads. The web server needs write 263 # permissions on this directory. 264 # If this is None, streaming uploads are disabled. 265 FILE_UPLOAD_DIR = None 266 267 # The minimum size of a POST before file uploads are streamed to disk. 268 # Any less than this number, and the file is uploaded to memory. 269 # Size is in bytes. 270 STREAMING_MIN_POST_SIZE = 512 * (2**10) 271 262 272 ############## 263 273 # MIDDLEWARE # 264 274 ############## -
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.utils.encoding import smart_str, force_unicode, smart_unicode 16 17 from django.conf import settings 17 18 from itertools import izip … … 384 385 def _get_FIELD_size(self, field): 385 386 return os.path.getsize(self._get_FIELD_filename(field)) 386 387 387 def _save_FIELD_file(self, field, filename, raw_ contents, save=True):388 def _save_FIELD_file(self, field, filename, raw_field, save=True): 388 389 directory = field.get_directory_name() 389 390 try: # Create the date-based directory if it doesn't exist. 390 391 os.makedirs(os.path.join(settings.MEDIA_ROOT, directory)) 391 392 except OSError: # Directory probably already exists. 392 393 pass 394 395 if filename is None: 396 filename = raw_field['filename'] 397 393 398 filename = field.get_filename(filename) 394 399 395 400 # If the filename already exists, keep adding an underscore to the name of … … 406 411 setattr(self, field.attname, filename) 407 412 408 413 full_filename = self._get_FIELD_filename(field) 409 fp = open(full_filename, 'wb') 410 fp.write(raw_contents) 411 fp.close() 414 if raw_field.has_key('tmpfilename'): 415 raw_field['tmpfile'].close() 416 file_move_safe(raw_field['tmpfilename'], full_filename) 417 else: 418 from django.utils import file_locks 419 fp = open(full_filename, 'wb') 420 # exclusive lock 421 file_locks.lock(fp, file_locks.LOCK_EX) 422 fp.write(raw_field['content']) 423 fp.close() 412 424 413 425 # Save the width and/or height, if applicable. 414 426 if isinstance(field, ImageField) and (field.width_field or field.height_field): -
django/db/models/fields/__init__.py
785 785 setattr(cls, 'get_%s_filename' % self.name, curry(cls._get_FIELD_filename, field=self)) 786 786 setattr(cls, 'get_%s_url' % self.name, curry(cls._get_FIELD_url, field=self)) 787 787 setattr(cls, 'get_%s_size' % self.name, curry(cls._get_FIELD_size, field=self)) 788 setattr(cls, 'save_%s_file' % self.name, lambda instance, filename, raw_contents, save=True: instance._save_FIELD_file(self, filename, raw_contents, save)) 788 setattr(cls, 'save_%s_file' % self.name, lambda instance, filename, raw_field, save=True: instance._save_FIELD_file(self, filename, raw_field, save)) 789 setattr(cls, 'move_%s_file' % self.name, lambda instance, raw_field, save=True: instance._save_FIELD_file(self, None, raw_field, save)) 789 790 dispatcher.connect(self.delete_file, signal=signals.post_delete, sender=cls) 790 791 791 792 def delete_file(self, instance): … … 808 809 if new_data.get(upload_field_name, False): 809 810 func = getattr(new_object, 'save_%s_file' % self.name) 810 811 if rel: 811 func(new_data[upload_field_name][0]["filename"], new_data[upload_field_name][0] ["content"], save)812 func(new_data[upload_field_name][0]["filename"], new_data[upload_field_name][0], save) 812 813 else: 813 func(new_data[upload_field_name]["filename"], new_data[upload_field_name] ["content"], save)814 func(new_data[upload_field_name]["filename"], new_data[upload_field_name], save) 814 815 815 816 def get_directory_name(self): 816 817 return os.path.normpath(force_unicode(datetime.datetime.now().strftime(smart_str(self.upload_to)))) … … 823 824 def save_form_data(self, instance, data): 824 825 from django.newforms.fields import UploadedFile 825 826 if data and isinstance(data, UploadedFile): 826 getattr(instance, "save_%s_file" % self.name)(data.filename, data. content, save=False)827 getattr(instance, "save_%s_file" % self.name)(data.filename, data.data, save=False) 827 828 828 829 def formfield(self, **kwargs): 829 830 defaults = {'form_class': forms.FileField} -
django/oldforms/__init__.py
681 681 self.validator_list = [self.isNonEmptyFile] + validator_list 682 682 683 683 def isNonEmptyFile(self, field_data, all_data): 684 try:685 content = field_data['content']686 except TypeError:684 if field_data.has_key('_file_upload_error'): 685 raise validators.CriticalValidationError, field_data['_file_upload_error'] 686 if not field_data.has_key('filename'): 687 687 raise validators.CriticalValidationError, ugettext("No file was submitted. Check the encoding type on the form.") 688 if not content:688 if not field_data['content-length']: 689 689 raise validators.CriticalValidationError, ugettext("The submitted file is empty.") 690 690 691 691 def render(self, data): 692 692 return mark_safe(u'<input type="file" id="%s" class="v%s" name="%s" />' % \ 693 693 (self.get_id(), self.__class__.__name__, self.field_name)) 694 694 695 def prepare(self, new_data): 696 if new_data.has_key('_file_upload_error'): 697 # pretend we got something in the field to raise a validation error later 698 new_data[self.field_name] = { '_file_upload_error': new_data['_file_upload_error'] } 699 695 700 def html2python(data): 696 701 if data is None: 697 702 raise EmptyValue -
django/core/validators.py
177 177 from PIL import Image 178 178 from cStringIO import StringIO 179 179 try: 180 content = field_data['content']180 filename = field_data['filename'] 181 181 except TypeError: 182 182 raise ValidationError, _("No file was submitted. Check the encoding type on the form.") 183 183 try: 184 184 # load() is the only method that can spot a truncated JPEG, 185 185 # but it cannot be called sanely after verify() 186 trial_image = Image.open( StringIO(content))186 trial_image = Image.open(field_data.get('tmpfilename') or StringIO(field_data.get('content',''))) 187 187 trial_image.load() 188 188 # verify() is the only method that can spot a corrupt PNG, 189 189 # but it must be called immediately after the constructor 190 trial_image = Image.open( StringIO(content))190 trial_image = Image.open(field_data.get('tmpfilename') or StringIO(field_data.get('content',''))) 191 191 trial_image.verify() 192 192 except Exception: # Python Imaging Library doesn't recognize it as an image 193 193 raise ValidationError, _("Upload a valid image. The file you uploaded was either not an image or a corrupted image.") -
django/core/handlers/wsgi.py
77 77 self.environ = environ 78 78 self.path = force_unicode(environ['PATH_INFO']) 79 79 self.META = environ 80 self.META['UPLOAD_PROGRESS_ID'] = self._get_file_progress_id() 80 81 self.method = environ['REQUEST_METHOD'].upper() 81 82 82 83 def __repr__(self): … … 114 115 if self.environ.get('CONTENT_TYPE', '').startswith('multipart'): 115 116 header_dict = dict([(k, v) for k, v in self.environ.items() if k.startswith('HTTP_')]) 116 117 header_dict['Content-Type'] = self.environ.get('CONTENT_TYPE', '') 117 self._post, self._files = http.parse_file_upload(header_dict, self.raw_post_data) 118 header_dict['Content-Length'] = self.environ.get('CONTENT_LENGTH', '') 119 header_dict['X-Progress-ID'] = self.environ.get('HTTP_X_PROGRESS_ID', '') 120 try: 121 self._post, self._files = http.parse_file_upload(header_dict, self.environ['wsgi.input'], self) 122 except: 123 self._post, self._files = {}, {} # make sure we dont read the input stream again 124 raise 125 self._raw_post_data = None # raw data is not available for streamed multipart messages 118 126 else: 119 127 self._post, self._files = http.QueryDict(self.raw_post_data, encoding=self._encoding), datastructures.MultiValueDict() 120 128 else: … … 172 180 buf.close() 173 181 return self._raw_post_data 174 182 183 def _get_file_progress_id(self): 184 """ 185 Returns the Progress ID of the request, 186 usually provided if there is a file upload 187 going on. 188 Returns ``None`` if no progress ID is specified. 189 """ 190 return self._get_file_progress_from_args(self.environ, 191 self.GET, 192 self.environ.get('QUERY_STRING', '')) 193 175 194 GET = property(_get_get, _set_get) 176 195 POST = property(_get_post, _set_post) 177 196 COOKIES = property(_get_cookies, _set_cookies) -
django/core/handlers/modpython.py
53 53 def _load_post_and_files(self): 54 54 "Populates self._post and self._files" 55 55 if 'content-type' in self._req.headers_in and self._req.headers_in['content-type'].startswith('multipart'): 56 self._post, self._files = http.parse_file_upload(self._req.headers_in, self.raw_post_data) 56 self._raw_post_data = None # raw data is not available for streamed multipart messages 57 try: 58 self._post, self._files = http.parse_file_upload(self._req.headers_in, self._req, self) 59 except: 60 self._post, self._files = {}, {} # make sure we dont read the input stream again 61 raise 57 62 else: 58 63 self._post, self._files = http.QueryDict(self.raw_post_data, encoding=self._encoding), datastructures.MultiValueDict() 59 64 … … 98 103 'AUTH_TYPE': self._req.ap_auth_type, 99 104 'CONTENT_LENGTH': self._req.clength, # This may be wrong 100 105 'CONTENT_TYPE': self._req.content_type, # This may be wrong 101 'GATEWAY_INTERFACE': 'CGI/1.1', 102 'PATH_INFO': self._req.path_info, 103 'PATH_TRANSLATED': None, # Not supported 104 'QUERY_STRING': self._req.args, 105 'REMOTE_ADDR': self._req.connection.remote_ip, 106 'REMOTE_HOST': None, # DNS lookups not supported 107 'REMOTE_IDENT': self._req.connection.remote_logname, 108 'REMOTE_USER': self._req.user, 109 'REQUEST_METHOD': self._req.method, 110 'SCRIPT_NAME': None, # Not supported 111 'SERVER_NAME': self._req.server.server_hostname, 112 'SERVER_PORT': self._req.server.port, 113 'SERVER_PROTOCOL': self._req.protocol, 114 'SERVER_SOFTWARE': 'mod_python' 106 'GATEWAY_INTERFACE': 'CGI/1.1', 107 'PATH_INFO': self._req.path_info, 108 'PATH_TRANSLATED': None, # Not supported 109 'QUERY_STRING': self._req.args, 110 'REMOTE_ADDR': self._req.connection.remote_ip, 111 'REMOTE_HOST': None, # DNS lookups not supported 112 'REMOTE_IDENT': self._req.connection.remote_logname, 113 'REMOTE_USER': self._req.user, 114 'REQUEST_METHOD': self._req.method, 115 'SCRIPT_NAME': None, # Not supported 116 'SERVER_NAME': self._req.server.server_hostname, 117 'SERVER_PORT': self._req.server.port, 118 'SERVER_PROTOCOL': self._req.protocol, 119 'UPLOAD_PROGRESS_ID': self._get_file_progress_id(), 120 'SERVER_SOFTWARE': 'mod_python' 115 121 } 116 122 for key, value in self._req.headers_in.items(): 117 123 key = 'HTTP_' + key.upper().replace('-', '_') … … 128 134 def _get_method(self): 129 135 return self.META['REQUEST_METHOD'].upper() 130 136 137 def _get_file_progress_id(self): 138 """ 139 Returns the Progress ID of the request, 140 usually provided if there is a file upload 141 going on. 142 Returns ``None`` if no progress ID is specified. 143 """ 144 return self._get_file_progress_from_args(self._req.headers_in, 145 self.GET, 146 self._req.args) 147 131 148 GET = property(_get_get, _set_get) 132 149 POST = property(_get_post, _set_post) 133 150 COOKIES = property(_get_cookies, _set_cookies) -
django/utils/file_locks.py
1 """ 2 Locking portability by Jonathan Feignberg <jdf@pobox.com> in python cookbook 3 4 Example Usage:: 5 6 from django.utils import file_locks 7 8 f = open('./file', 'wb') 9 10 file_locks.lock(f, file_locks.LOCK_EX) 11 f.write('Django') 12 f.close() 13 """ 14 15 16 import os 17 18 __all__ = ['LOCK_EX','LOCK_SH','LOCK_NB','lock','unlock'] 19 20 if os.name == 'nt': 21 import win32con 22 import win32file 23 import pywintypes 24 LOCK_EX = win32con.LOCKFILE_EXCLUSIVE_LOCK 25 LOCK_SH = 0 26 LOCK_NB = win32con.LOCKFILE_FAIL_IMMEDIATELY 27 __overlapped = pywintypes.OVERLAPPED() 28 elif os.name == 'posix': 29 import fcntl 30 LOCK_EX = fcntl.LOCK_EX 31 LOCK_SH = fcntl.LOCK_SH 32 LOCK_NB = fcntl.LOCK_NB 33 else: 34 raise RuntimeError("Locking only defined for nt and posix platforms") 35 36 if os.name == 'nt': 37 def lock(file, flags): 38 hfile = win32file._get_osfhandle(file.fileno()) 39 win32file.LockFileEx(hfile, flags, 0, -0x10000, __overlapped) 40 41 def unlock(file): 42 hfile = win32file._get_osfhandle(file.fileno()) 43 win32file.UnlockFileEx(hfile, 0, -0x10000, __overlapped) 44 45 elif os.name =='posix': 46 def lock(file, flags): 47 fcntl.flock(file.fileno(), flags) 48 49 def unlock(file): 50 fcntl.flock(file.fileno(), fcntl.LOCK_UN) -
django/utils/file.py
1 import os 2 3 __all__ = ['file_move_safe'] 4 5 try: 6 import shutil 7 file_move = shutil.move 8 except ImportError: 9 file_move = os.rename 10 11 def file_move_safe(old_file_name, new_file_name, chunk_size = 1024*64, allow_overwrite=False): 12 """ 13 Moves a file from one location to another in the safest way possible. 14 15 First, it tries using shutils.move, which is OS-dependent but doesn't 16 break with change of filesystems. Then it tries os.rename, which will 17 break if it encounters a change in filesystems. Lastly, it streams 18 it manually from one file to another in python. 19 20 Without ``allow_overwrite``, if the destination file exists, the 21 file will raise an IOError. 22 """ 23 24 from django.utils import file_locks 25 26 if old_file_name == new_file_name: 27 # No file moving takes place. 28 return 29 30 if not allow_overwrite and os.path.exists(new_file_name): 31 raise IOError, "Django does not allow overwriting files." 32 33 try: 34 file_move(old_file_name, new_file_name) 35 return 36 except OSError: # moving to another filesystem 37 pass 38 39 new_file = open(new_file_name, 'wb') 40 # exclusive lock 41 file_locks.lock(new_file, file_locks.LOCK_EX) 42 old_file = open(old_file_name, 'rb') 43 current_chunk = None 44 45 while current_chunk != '': 46 current_chunk = old_file.read(chunk_size) 47 new_file.write(current_chunk) 48 49 new_file.close() 50 old_file.close() 51 52 os.remove(old_file_name) -
django/newforms/fields.py
415 415 416 416 class UploadedFile(StrAndUnicode): 417 417 "A wrapper for files uploaded in a FileField" 418 def __init__(self, filename, content):418 def __init__(self, filename, data): 419 419 self.filename = filename 420 self. content = content420 self.data = data 421 421 422 422 def __unicode__(self): 423 423 """ … … 444 444 elif not data and initial: 445 445 return initial 446 446 try: 447 f = UploadedFile(data['filename'], data ['content'])447 f = UploadedFile(data['filename'], data) 448 448 except TypeError: 449 449 raise ValidationError(self.error_messages['invalid']) 450 450 except KeyError: 451 451 raise ValidationError(self.error_messages['missing']) 452 if not f. content:452 if not f.data.get('content-length'): 453 453 raise ValidationError(self.error_messages['empty']) 454 454 return f 455 455 … … 473 473 try: 474 474 # load() is the only method that can spot a truncated JPEG, 475 475 # but it cannot be called sanely after verify() 476 trial_image = Image.open( StringIO(f.content))476 trial_image = Image.open(f.data.get('tmpfilename') or StringIO(f.data['content'])) 477 477 trial_image.load() 478 478 # verify() is the only method that can spot a corrupt PNG, 479 479 # but it must be called immediately after the constructor 480 trial_image = Image.open( StringIO(f.content))480 trial_image = Image.open(f.data.get('tmpfilename') or StringIO(f.data['content'])) 481 481 trial_image.verify() 482 482 except Exception: # Python Imaging Library doesn't recognize it as an image 483 483 raise ValidationError(self.error_messages['invalid_image']) -
tests/modeltests/model_forms/models.py
759 759 760 760 # Upload a file and ensure it all works as expected. 761 761 762 >>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': {'filename': 'test1.txt', 'content': 'hello world' }})762 >>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': {'filename': 'test1.txt', 'content': 'hello world', 'content-length': len('hello world')}}) 763 763 >>> f.is_valid() 764 764 True 765 765 >>> type(f.cleaned_data['file']) … … 786 786 787 787 # Override the file by uploading a new one. 788 788 789 >>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': {'filename': 'test2.txt', 'content': 'hello world' }}, instance=instance)789 >>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': {'filename': 'test2.txt', 'content': 'hello world', 'content-length': len('hello world')}}, instance=instance) 790 790 >>> f.is_valid() 791 791 True 792 792 >>> instance = f.save() … … 805 805 >>> instance.file 806 806 '' 807 807 808 >>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': {'filename': 'test3.txt', 'content': 'hello world' }}, instance=instance)808 >>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': {'filename': 'test3.txt', 'content': 'hello world', 'content-length': len('hello world')}}, instance=instance) 809 809 >>> f.is_valid() 810 810 True 811 811 >>> instance = f.save() … … 825 825 826 826 >>> image_data = open(os.path.join(os.path.dirname(__file__), "test.png")).read() 827 827 828 >>> f = ImageFileForm(data={'description': u'An image'}, files={'image': {'filename': 'test.png', 'content': image_data }})828 >>> f = ImageFileForm(data={'description': u'An image'}, files={'image': {'filename': 'test.png', 'content': image_data, 'content-length': len(image_data)}}) 829 829 >>> f.is_valid() 830 830 True 831 831 >>> type(f.cleaned_data['image']) … … 852 852 853 853 # Override the file by uploading a new one. 854 854 855 >>> f = ImageFileForm(data={'description': u'Changed it'}, files={'image': {'filename': 'test2.png', 'content': image_data }}, instance=instance)855 >>> f = ImageFileForm(data={'description': u'Changed it'}, files={'image': {'filename': 'test2.png', 'content': image_data, 'content-length': len(image_data)}}, instance=instance) 856 856 >>> f.is_valid() 857 857 True 858 858 >>> instance = f.save() … … 871 871 >>> instance.image 872 872 '' 873 873 874 >>> f = ImageFileForm(data={'description': u'And a final one'}, files={'image': {'filename': 'test3.png', 'content': image_data }}, instance=instance)874 >>> f = ImageFileForm(data={'description': u'And a final one'}, files={'image': {'filename': 'test3.png', 'content': image_data, 'content-length': len(image_data)}}, instance=instance) 875 875 >>> f.is_valid() 876 876 True 877 877 >>> instance = f.save() -
tests/modeltests/test_client/views.py
47 47 48 48 return HttpResponse(t.render(c)) 49 49 50 def post_file_view(request): 51 "A view that expects a multipart post and returns a file in the context" 52 t = Template('File {{ file.filename }} received', name='POST Template') 53 c = Context({'file': request.FILES['file_file']}) 54 return HttpResponse(t.render(c)) 55 50 56 def redirect_view(request): 51 57 "A view that redirects all requests to the GET view" 52 58 if request.GET: -
tests/modeltests/test_client/models.py
80 80 self.assertEqual(response.template.name, "Book template") 81 81 self.assertEqual(response.content, "Blink - Malcolm Gladwell") 82 82 83 def test_post_file_view(self): 84 "POST this python file to a view" 85 import os, tempfile 86 from django.conf import settings 87 file = __file__.replace('.pyc', '.py') 88 for upload_dir, streaming_size in [(None,512*1000), (tempfile.gettempdir(), 1)]: 89 settings.FILE_UPLOAD_DIR = upload_dir 90 settings.STREAMING_MIN_POST_SIZE = streaming_size 91 post_data = { 'name': file, 'file_file': open(file) } 92 response = self.client.post('/test_client/post_file_view/', post_data) 93 self.failUnless('models.py' in response.context['file']['filename']) 94 self.failUnless(len(response.context['file']['content']) == os.path.getsize(file)) 95 if upload_dir: 96 self.failUnless(response.context['file']['tmpfilename']) 97 83 98 def test_redirect(self): 84 99 "GET a URL that redirects elsewhere" 85 100 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/' }), -
tests/regressiontests/forms/fields.py
788 788 ... 789 789 ValidationError: [u'No file was submitted. Check the encoding type on the form.'] 790 790 791 >>> f.clean({'filename': 'name', 'content': None })791 >>> f.clean({'filename': 'name', 'content': None, 'content-length': 0}) 792 792 Traceback (most recent call last): 793 793 ... 794 794 ValidationError: [u'The submitted file is empty.'] 795 795 796 >>> f.clean({'filename': 'name', 'content': '' })796 >>> f.clean({'filename': 'name', 'content': '', 'content-length': 0}) 797 797 Traceback (most recent call last): 798 798 ... 799 799 ValidationError: [u'The submitted file is empty.'] 800 800 801 >>> type(f.clean({'filename': 'name', 'content': 'Some File Content' }))801 >>> type(f.clean({'filename': 'name', 'content': 'Some File Content', 'content-length': len('Some File Content')})) 802 802 <class 'django.newforms.fields.UploadedFile'> 803 803 804 >>> type(f.clean({'filename': 'name', 'content': 'Some File Content' }, 'files/test4.pdf'))804 >>> type(f.clean({'filename': 'name', 'content': 'Some File Content', 'content-length': len('Some File Content')}, 'files/test4.pdf')) 805 805 <class 'django.newforms.fields.UploadedFile'> 806 806 807 807 # URLField ################################################################## -
tests/regressiontests/forms/forms.py
1410 1410 >>> print f 1411 1411 <tr><th>File1:</th><td><ul class="errorlist"><li>No file was submitted. Check the encoding type on the form.</li></ul><input type="file" name="file1" /></td></tr> 1412 1412 1413 >>> f = FileForm(data={}, files={'file1': {'filename': 'name', 'content':'some content' }}, auto_id=False)1413 >>> f = FileForm(data={}, files={'file1': {'filename': 'name', 'content':'some content', 'content-length': len('some content')}}, auto_id=False) 1414 1414 >>> print f 1415 1415 <tr><th>File1:</th><td><input type="file" name="file1" /></td></tr> 1416 1416 >>> f.is_valid() -
docs/request_response.txt
82 82 ``FILES`` 83 83 A dictionary-like object containing all uploaded files. Each key in 84 84 ``FILES`` is the ``name`` from the ``<input type="file" name="" />``. Each 85 value in ``FILES`` is a standard Python dictionary with the following three85 value in ``FILES`` is a standard Python dictionary with the following four 86 86 keys: 87 87 88 88 * ``filename`` -- The name of the uploaded file, as a Python string. 89 89 * ``content-type`` -- The content type of the uploaded file. 90 90 * ``content`` -- The raw content of the uploaded file. 91 * ``content-length`` -- The length of the content in bytes. 91 92 93 If streaming file uploads are enabled two additional keys 94 describing the uploaded file will be present: 95 96 * ``tmpfilename`` -- The filename for the temporary file. 97 * ``tmpfile`` -- An open file object for the temporary file. 98 99 The temporary file will be removed when the request finishes. 100 101 Note that accessing ``content`` when streaming uploads are enabled 102 will read the whole file into memory which may not be what you want. 103 92 104 Note that ``FILES`` will only contain data if the request method was POST 93 105 and the ``<form>`` that posted to the request had 94 106 ``enctype="multipart/form-data"``. Otherwise, ``FILES`` will be a blank -
docs/settings.txt
521 521 522 522 .. _Testing Django Applications: ../testing/ 523 523 524 FILE_UPLOAD_DIR 525 --------------- 526 527 Default: ``None`` 528 529 Path to a directory where temporary files should be written during 530 file uploads. Leaving this as ``None`` will disable streaming file uploads, 531 and cause all uploaded files to be stored (temporarily) in memory. 532 524 533 IGNORABLE_404_ENDS 525 534 ------------------ 526 535 … … 899 908 900 909 .. _site framework docs: ../sites/ 901 910 911 STREAMING_MIN_POST_SIZE 912 ----------------------- 913 914 Default: 524288 (``512*1024``) 915 916 An integer specifying the minimum number of bytes that has to be 917 received (in a POST) for file upload streaming to take place. Any 918 request smaller than this will be handled in memory. 919 Note: ``FILE_UPLOAD_DIR`` has to be defined to enable streaming. 920 902 921 TEMPLATE_CONTEXT_PROCESSORS 903 922 --------------------------- 904 923 -
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