Ticket #2070: 2070-r7695.patch
File 2070-r7695.patch, 100.6 KB (added by , 16 years ago) |
---|
-
AUTHORS
diff --git a/AUTHORS b/AUTHORS index 06e80e1..bed524e 100644
a b answer newbie questions, and generally made Django that much better: 59 59 Arthur <avandorp@gmail.com> 60 60 av0000@mail.ru 61 61 David Avsajanishvili <avsd05@gmail.com> 62 axiak@mit.edu62 Mike Axiak <axiak@mit.edu> 63 63 Niran Babalola <niran@niran.org> 64 64 Morten Bagai <m@bagai.com> 65 65 Mikaël Barbero <mikael.barbero nospam at nospam free.fr> … … answer newbie questions, and generally made Django that much better: 140 140 Marc Fargas <telenieko@telenieko.com> 141 141 Szilveszter Farkas <szilveszter.farkas@gmail.com> 142 142 favo@exoweb.net 143 fdr <drfarina@gmail.com> 143 144 Dmitri Fedortchenko <zeraien@gmail.com> 145 Jonathan Feignberg <jdf@pobox.com> 144 146 Liang Feng <hutuworm@gmail.com> 145 147 Bill Fenner <fenner@gmail.com> 146 148 Stefane Fermgier <sf@fermigier.com> -
django/conf/global_settings.py
diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py index 9f24f0c..44d1dfb 100644
a b MEDIA_ROOT = '' 229 229 # Example: "http://media.lawrence.com" 230 230 MEDIA_URL = '' 231 231 232 # List of upload handler classes to be applied in order. 233 FILE_UPLOAD_HANDLERS = ( 234 'django.core.files.uploadhandler.MemoryFileUploadHandler', 235 'django.core.files.uploadhandler.TemporaryFileUploadHandler', 236 ) 237 238 # Maximum size, in bytes, of a request before it will be streamed to the 239 # file system instead of into memory. 240 FILE_UPLOAD_MAX_MEMORY_SIZE = 2621440 # i.e. 2.5 MB 241 242 # Directory in which upload streamed files will be temporarily saved. A value of 243 # `None` will make Django use the operating system's default temporary directory 244 # (i.e. "/tmp" on *nix systems). 245 FILE_UPLOAD_TEMP_DIR = None 246 232 247 # Default formatting for date objects. See all available format strings here: 233 248 # http://www.djangoproject.com/documentation/templates/#now 234 249 DATE_FORMAT = 'N j, Y' -
new file django/core/files/__init__.py
diff --git a/django/core/files/__init__.py b/django/core/files/__init__.py new file mode 100644 index 0000000..8b13789
- + 1 -
new file django/core/files/locks.py
diff --git a/django/core/files/locks.py b/django/core/files/locks.py new file mode 100644 index 0000000..212b51a
- + 1 """ 2 Portable file locking utilities. 3 4 Based partially on example by Jonathan Feignberg <jdf@pobox.com> in the Python 5 Cookbook, licensed under the Python Software License. 6 7 http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/65203 8 9 Example Usage:: 10 11 >>> from django.core.files import locks 12 >>> f = open('./file', 'wb') 13 >>> locks.lock(f, locks.LOCK_EX) 14 >>> f.write('Django') 15 >>> f.close() 16 """ 17 18 __all__ = ('LOCK_EX','LOCK_SH','LOCK_NB','lock','unlock') 19 20 system_type = None 21 22 try: 23 import win32con 24 import win32file 25 import pywintypes 26 LOCK_EX = win32con.LOCKFILE_EXCLUSIVE_LOCK 27 LOCK_SH = 0 28 LOCK_NB = win32con.LOCKFILE_FAIL_IMMEDIATELY 29 __overlapped = pywintypes.OVERLAPPED() 30 system_type = 'nt' 31 except (ImportError, AttributeError): 32 pass 33 34 try: 35 import fcntl 36 LOCK_EX = fcntl.LOCK_EX 37 LOCK_SH = fcntl.LOCK_SH 38 LOCK_NB = fcntl.LOCK_NB 39 system_type = 'posix' 40 except (ImportError, AttributeError): 41 pass 42 43 if system_type == 'nt': 44 def lock(file, flags): 45 hfile = win32file._get_osfhandle(file.fileno()) 46 win32file.LockFileEx(hfile, flags, 0, -0x10000, __overlapped) 47 48 def unlock(file): 49 hfile = win32file._get_osfhandle(file.fileno()) 50 win32file.UnlockFileEx(hfile, 0, -0x10000, __overlapped) 51 elif system_type == 'posix': 52 def lock(file, flags): 53 fcntl.flock(file.fileno(), flags) 54 55 def unlock(file): 56 fcntl.flock(file.fileno(), fcntl.LOCK_UN) 57 else: 58 # File locking is not supported. 59 LOCK_EX = LOCK_SH = LOCK_NB = None 60 61 # Dummy functions that don't do anything. 62 def lock(file, flags): 63 pass 64 65 def unlock(file): 66 pass -
new file django/core/files/move.py
diff --git a/django/core/files/move.py b/django/core/files/move.py new file mode 100644 index 0000000..66873d4
- + 1 """ 2 Move a file in the safest way possible:: 3 4 >>> from django.core.files.move import file_move_save 5 >>> file_move_save("/tmp/old_file", "/tmp/new_file") 6 """ 7 8 import os 9 from django.core.files import locks 10 11 __all__ = ['file_move_safe'] 12 13 try: 14 import shutil 15 file_move = shutil.move 16 except ImportError: 17 file_move = os.rename 18 19 def file_move_safe(old_file_name, new_file_name, chunk_size = 1024*64, allow_overwrite=False): 20 """ 21 Moves a file from one location to another in the safest way possible. 22 23 First, try using ``shutils.move``, which is OS-dependent but doesn't break 24 if moving across filesystems. Then, try ``os.rename``, which will break 25 across filesystems. Finally, streams manually from one file to another in 26 pure Python. 27 28 If the destination file exists and ``allow_overwrite`` is ``False``, this 29 function will throw an ``IOError``. 30 """ 31 32 # There's no reason to move if we don't have to. 33 if old_file_name == new_file_name: 34 return 35 36 if not allow_overwrite and os.path.exists(new_file_name): 37 raise IOError("Cannot overwrite existing file '%s'." % new_file_name) 38 39 try: 40 file_move(old_file_name, new_file_name) 41 return 42 except OSError: 43 # This will happen with os.rename if moving to another filesystem 44 pass 45 46 # If the built-in didn't work, do it the hard way. 47 new_file = open(new_file_name, 'wb') 48 locks.lock(new_file, locks.LOCK_EX) 49 old_file = open(old_file_name, 'rb') 50 current_chunk = None 51 52 while current_chunk != '': 53 current_chunk = old_file.read(chunk_size) 54 new_file.write(current_chunk) 55 56 new_file.close() 57 old_file.close() 58 59 os.remove(old_file_name) -
new file django/core/files/uploadedfile.py
diff --git a/django/core/files/uploadedfile.py b/django/core/files/uploadedfile.py new file mode 100644 index 0000000..bd14453
- + 1 """ 2 Classes representing uploaded files. 3 """ 4 5 try: 6 from cStringIO import StringIO 7 except ImportError: 8 from StringIO import StringIO 9 10 __all__ = ('UploadedFile', 'TemporaryUploadedFile', 'InMemoryUploadedFile') 11 12 class UploadedFile(object): 13 """ 14 A abstract uploadded file (``TemporaryUploadedFile`` and 15 ``InMemoryUploadedFile`` are the built-in concrete subclasses). 16 17 An ``UploadedFile`` object behaves somewhat like a file object and 18 represents some file data that the user submitted with a form. 19 """ 20 DEFAULT_CHUNK_SIZE = 64 * 2**10 21 22 def __init__(self): 23 self.file_size = None 24 self.file_name = None 25 self.content_type = None 26 self.charset = None 27 pass 28 29 def __repr__(self): 30 return "<%s: %s (%s)>" % (self.__class__.__name__, self.file_name, self.content_type) 31 32 def chunk(self, chunk_size=None): 33 """ 34 Read the file and yield chucks of ``chunk_size`` bytes (defaults to 35 ``UploadedFile.DEFAULT_CHUNK_SIZE``). 36 """ 37 if not chunk_size: 38 chunk_size = UploadedFile.DEFAULT_CHUNK_SIZE 39 40 if hasattr(self, 'seek'): 41 self.seek(0) 42 # Assume the pointer is at zero... 43 counter = self.file_size 44 45 while counter > 0: 46 yield self.read(chunk_size) 47 counter -= chunk_size 48 49 def multiple_chunks(self, chunk_size=None): 50 """ 51 Returns ``True`` if you can expect multiple chunks. 52 53 NB: If a particular file representation is in memory, subclasses should 54 always return ``False`` -- there's no good reason to read from memory in 55 chunks. 56 """ 57 if not chunk_size: 58 chunk_size = UploadedFile.DEFAULT_CHUNK_SIZE 59 return self.file_size < chunk_size 60 61 # Abstract methods; subclasses *must* default read() and probably should 62 # define open/close. 63 def read(self, num_bytes=None): 64 raise NotImplementedError() 65 66 def open(self): 67 pass 68 69 def close(self): 70 pass 71 72 # Backwards-compatible support for uploaded-files-as-dictionaries. 73 def __getitem__(self, key): 74 import warnings 75 warnings.warn( 76 message = "The dictionary access of uploaded file objects is deprecated. Use the new object interface instead.", 77 category = DeprecationWarning, 78 stacklevel = 2 79 ) 80 backwards_translate = { 81 'filename': 'file_name', 82 'content-type': 'content_type', 83 } 84 85 if key == 'content': 86 return self.read() 87 elif key == 'filename': 88 return self.file_name 89 elif key == 'content-type': 90 return self.content_type 91 else: 92 return getattr(self, key) 93 94 class TemporaryUploadedFile(UploadedFile): 95 """ 96 A file uploaded to a temporary location (i.e. stream-to-disk). 97 """ 98 99 def __init__(self, file, file_name, content_type, file_size, charset): 100 self.file = file 101 self.file_name = file_name 102 self.path = file.name 103 self.content_type = content_type 104 self.file_size = file_size 105 self.charset = charset 106 self.file.seek(0) 107 108 def temporary_file_path(self): 109 """ 110 Returns the full path of this file. 111 """ 112 return self.path 113 114 def read(self, *args, **kwargs): 115 return self.file.read(*args, **kwargs) 116 117 def open(self): 118 self.seek(0) 119 120 def seek(self, *args, **kwargs): 121 self.file.seek(*args, **kwargs) 122 123 class InMemoryUploadedFile(UploadedFile): 124 """ 125 A file uploaded into memory (i.e. stream-to-memory). 126 """ 127 def __init__(self, file, field_name, file_name, content_type, charset, file_size): 128 self.file = file 129 self.field_name = field_name 130 self.file_name = file_name 131 self.content_type = content_type 132 self.charset = charset 133 self.file.seek(0) 134 self.file_size = file_size 135 136 def seek(self, *args, **kwargs): 137 self.file.seek(*args, **kwargs) 138 139 def open(self): 140 self.seek(0) 141 142 def read(self, *args, **kwargs): 143 return self.file.read(*args, **kwargs) 144 145 def chunk(self, chunk_size=None): 146 self.file.seek(0) 147 return self.read() 148 149 def multiple_chunks(self, chunk_size=None): 150 # Since it's in memory, we'll never have multiple chunks. 151 return False 152 153 class SimpleUploadedFile(InMemoryUploadedFile): 154 """ 155 A simple representation of a file, which just has content, size, and a name. 156 """ 157 def __init__(self, name, content, content_type='text/plain'): 158 self.file = StringIO(content or '') 159 self.file_name = name 160 self.field_name = None 161 self.file_size = len(content or '') 162 self.content_type = content_type 163 self.charset = None 164 self.file.seek(0) 165 166 def from_dict(cls, file_dict): 167 """ 168 Creates a SimpleUploadedFile object from 169 a dictionary object with the following keys: 170 - filename 171 - content-type 172 - content 173 """ 174 return cls(file_dict['filename'], 175 file_dict['content'], 176 file_dict.get('content-type', 'text/plain')) 177 178 from_dict = classmethod(from_dict) -
new file django/core/files/uploadhandler.py
diff --git a/django/core/files/uploadhandler.py b/django/core/files/uploadhandler.py new file mode 100644 index 0000000..b1302b0
- + 1 """ 2 Base file upload handler classes, and the built-in concrete subclasses 3 """ 4 import os 5 import tempfile 6 try: 7 from cStringIO import StringIO 8 except ImportError: 9 from StringIO import StringIO 10 11 from django.conf import settings 12 from django.core.exceptions import ImproperlyConfigured 13 from django.core.files.uploadedfile import TemporaryUploadedFile, InMemoryUploadedFile 14 15 __all__ = ['UploadFileException','StopUpload', 'SkipFile', 'FileUploadHandler', 16 'TemporaryFileUploadHandler', 'MemoryFileUploadHandler', 17 'load_handler'] 18 19 class UploadFileException(Exception): 20 """ 21 Any error having to do with uploading files. 22 """ 23 pass 24 25 class StopUpload(UploadFileException): 26 """ 27 This exception is raised when an upload must abort. 28 """ 29 def __init__(self, connection_reset=False): 30 """ 31 If ``connection_reset`` is ``True``, Django knows will halt the upload 32 without consuming the rest of the upload. This will cause the browser to 33 show a "connection reset" error. 34 """ 35 self.connection_reset = connection_reset 36 37 def __unicode__(self): 38 if self.connection_reset: 39 return u'StopUpload: Halt current upload.' 40 else: 41 return u'StopUpload: Consume request data, then halt.' 42 43 class SkipFile(UploadFileException): 44 """ 45 This exception is raised by an upload handler that wants to skip a given file. 46 """ 47 pass 48 49 class FileUploadHandler(object): 50 """ 51 Base class for streaming upload handlers. 52 """ 53 chunk_size = 64 * 2 ** 10 #: The default chunk size is 64 KB. 54 55 def __init__(self, request=None): 56 self.file_name = None 57 self.content_type = None 58 self.content_length = None 59 self.charset = None 60 self.request = request 61 62 def handle_raw_input(self, input_data, META, content_length, boundary, encoding=None): 63 """ 64 Handle the raw input from the client. 65 66 Parameters: 67 68 :input_data: 69 An object that supports reading via .read(). 70 :META: 71 ``request.META``. 72 :content_length: 73 The (integer) value of the Content-Length header from the 74 client. 75 :boundary: The boundary from the Content-Type header. Be sure to 76 prepend two '--'. 77 """ 78 pass 79 80 def new_file(self, field_name, file_name, content_type, content_length, charset=None): 81 """ 82 Signal that a new file has been started. 83 84 Warning: As with any data from the client, you should not trust 85 content_length (and sometimes won't even get it). 86 """ 87 self.field_name = field_name 88 self.file_name = file_name 89 self.content_type = content_type 90 self.content_length = content_length 91 self.charset = charset 92 93 def receive_data_chunk(self, raw_data, start): 94 """ 95 Receive data from the streamed upload parser. ``start`` is the position 96 in the file of the chunk. 97 """ 98 raise NotImplementedError() 99 100 def file_complete(self, file_size): 101 """ 102 Signal that a file has completed. File size corresponds to the actual 103 size accumulated by all the chunks. 104 105 Subclasses must should return a valid ``UploadedFile`` object. 106 """ 107 raise NotImplementedError() 108 109 def upload_complete(self): 110 """ 111 Signal that the upload is complete. Subclasses should perform cleanup 112 that is necessary for this handler. 113 """ 114 pass 115 116 class TemporaryFileUploadHandler(FileUploadHandler): 117 """ 118 Upload handler that streams data into a temporary file. 119 """ 120 def __init__(self, *args, **kwargs): 121 super(TemporaryFileUploadHandler, self).__init__(*args, **kwargs) 122 123 def new_file(self, file_name, *args, **kwargs): 124 """ 125 Create the file object to append to as data is coming in. 126 """ 127 super(TemporaryFileUploadHandler, self).new_file(file_name, *args, **kwargs) 128 self.file = TemporaryFile(settings.FILE_UPLOAD_TEMP_DIR) 129 self.write = self.file.write 130 131 def receive_data_chunk(self, raw_data, start): 132 self.write(raw_data) 133 134 def file_complete(self, file_size): 135 self.file.seek(0) 136 return TemporaryUploadedFile(self.file, self.file_name, 137 self.content_type, file_size, 138 self.charset) 139 140 141 class MemoryFileUploadHandler(FileUploadHandler): 142 """ 143 File upload handler to stream uploads into memory (used for small files). 144 """ 145 146 def handle_raw_input(self, input_data, META, content_length, boundary, encoding=None): 147 """ 148 Use the content_length to signal whether or not this handler should be in use. 149 """ 150 # Check the content-length header to see if we should 151 # If the the post is too large, we cannot use the Memory handler. 152 if content_length > settings.FILE_UPLOAD_MAX_MEMORY_SIZE: 153 self.activated = False 154 else: 155 self.activated = True 156 157 def new_file(self, *args, **kwargs): 158 super(MemoryFileUploadHandler, self).new_file(*args, **kwargs) 159 if self.activated: 160 self.file = StringIO() 161 return "Stop" 162 163 def receive_data_chunk(self, raw_data, start): 164 """ 165 Add the data to the StringIO file. 166 """ 167 if self.activated: 168 self.file.write(raw_data) 169 else: 170 return raw_data 171 172 def file_complete(self, file_size): 173 """ 174 Return a file object if we're activated. 175 """ 176 if not self.activated: 177 return 178 179 return InMemoryUploadedFile(self.file, self.field_name, self.file_name, 180 self.content_type, self.charset, file_size) 181 182 class TemporaryFile(object): 183 """ 184 A temporary file that tries to delete itself when garbage collected. 185 """ 186 def __init__(self, dir): 187 if not dir: 188 dir = tempfile.gettempdir() 189 try: 190 (fd, name) = tempfile.mkstemp(suffix='.upload', dir=dir) 191 self.file = os.fdopen(fd, 'w+b') 192 except (OSError, IOError): 193 raise OSError("Could not create temporary file for uploading, have you set settings.FILE_UPLOAD_TEMP_DIR correctly?") 194 self.name = name 195 196 def __getattr__(self, name): 197 a = getattr(self.__dict__['file'], name) 198 if type(a) != type(0): 199 setattr(self, name, a) 200 return a 201 202 def __del__(self): 203 try: 204 os.unlink(self.name) 205 except OSError: 206 pass 207 208 def load_handler(path, *args, **kwargs): 209 """ 210 Given a path to a handler, return an instance of that handler. 211 212 E.g.:: 213 >>> load_handler('django.core.files.uploadhandler.TemporaryFileUploadHandler', request) 214 <TemporaryFileUploadHandler object at 0x...> 215 216 """ 217 i = path.rfind('.') 218 module, attr = path[:i], path[i+1:] 219 try: 220 mod = __import__(module, {}, {}, [attr]) 221 except ImportError, e: 222 raise ImproperlyConfigured('Error importing upload handler module %s: "%s"' % (module, e)) 223 except ValueError, e: 224 raise ImproperlyConfigured('Error importing upload handler module. Is FILE_UPLOAD_HANDLERS a correctly defined list or tuple?') 225 try: 226 cls = getattr(mod, attr) 227 except AttributeError: 228 raise ImproperlyConfigured('Module "%s" does not define a "%s" upload handler backend' % (module, attr)) 229 return cls(*args, **kwargs) -
django/core/handlers/modpython.py
diff --git a/django/core/handlers/modpython.py b/django/core/handlers/modpython.py index abab399..332df6f 100644
a b class ModPythonRequest(http.HttpRequest): 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 = '' 57 self._post, self._files = self.parse_file_upload(self.META, self._req) 57 58 else: 58 59 self._post, self._files = http.QueryDict(self.raw_post_data, encoding=self._encoding), datastructures.MultiValueDict() 59 60 -
django/core/handlers/wsgi.py
diff --git a/django/core/handlers/wsgi.py b/django/core/handlers/wsgi.py index df2ba19..795f139 100644
a b class WSGIRequest(http.HttpRequest): 112 112 # Populates self._post and self._files 113 113 if self.method == 'POST': 114 114 if self.environ.get('CONTENT_TYPE', '').startswith('multipart'): 115 header_dict = dict([(k, v) for k, v in self.environ.items() if k.startswith('HTTP_')]) 116 header_dict['Content-Type'] = self.environ.get('CONTENT_TYPE', '') 117 self._post, self._files = http.parse_file_upload(header_dict, self.raw_post_data) 115 self._raw_post_data = '' 116 self._post, self._files = self.parse_file_upload(self.META, self.environ['wsgi.input']) 118 117 else: 119 118 self._post, self._files = http.QueryDict(self.raw_post_data, encoding=self._encoding), datastructures.MultiValueDict() 120 119 else: -
django/db/models/base.py
diff --git a/django/db/models/base.py b/django/db/models/base.py index 5dd11a9..e1407ca 100644
a b from django.dispatch import dispatcher 19 19 from django.utils.datastructures import SortedDict 20 20 from django.utils.functional import curry 21 21 from django.utils.encoding import smart_str, force_unicode, smart_unicode 22 from django.core.files.move import file_move_safe 23 from django.core.files import locks 22 24 from django.conf import settings 23 25 24 26 try: … … class Model(object): 452 454 def _get_FIELD_size(self, field): 453 455 return os.path.getsize(self._get_FIELD_filename(field)) 454 456 455 def _save_FIELD_file(self, field, filename, raw_ contents, save=True):457 def _save_FIELD_file(self, field, filename, raw_field, save=True): 456 458 directory = field.get_directory_name() 457 459 try: # Create the date-based directory if it doesn't exist. 458 460 os.makedirs(os.path.join(settings.MEDIA_ROOT, directory)) 459 461 except OSError: # Directory probably already exists. 460 462 pass 463 464 # 465 # Check for old-style usage (files-as-dictionaries). Warn here first 466 # since there are multiple locations where we need to support both new 467 # and old usage. 468 # 469 if isinstance(raw_field, dict): 470 import warnings 471 warnings.warn( 472 message = "Representing uploaded files as dictionaries is"\ 473 " deprected. Use django.core.files.SimpleUploadedFile"\ 474 " instead.", 475 category = DeprecationWarning, 476 stacklevel = 2 477 ) 478 from django.core.files.uploadedfile import SimpleUploadedFile 479 raw_field = SimpleUploadedFile.from_dict(raw_field) 480 481 elif isinstance(raw_field, basestring): 482 import warnings 483 warnings.warn( 484 message = "Representing uploaded files as strings is "\ 485 " deprecated. Use django.core.files.SimpleUploadedFile "\ 486 " instead.", 487 category = DeprecationWarning, 488 stacklevel = 2 489 ) 490 from django.core.files.uploadedfile import SimpleUploadedFile 491 raw_field = SimpleUploadedFile(filename, raw_field) 492 493 if filename is None: 494 filename = raw_field.file_name 495 461 496 filename = field.get_filename(filename) 462 497 498 # 463 499 # If the filename already exists, keep adding an underscore to the name of 464 500 # the file until the filename doesn't exist. 501 # 465 502 while os.path.exists(os.path.join(settings.MEDIA_ROOT, filename)): 466 503 try: 467 504 dot_index = filename.rindex('.') … … class Model(object): 469 506 filename += '_' 470 507 else: 471 508 filename = filename[:dot_index] + '_' + filename[dot_index:] 509 # 510 # Save the file name on the object and write the file to disk 511 # 472 512 473 # Write the file to disk.474 513 setattr(self, field.attname, filename) 475 514 476 515 full_filename = self._get_FIELD_filename(field) 477 fp = open(full_filename, 'wb') 478 fp.write(raw_contents) 479 fp.close() 516 517 if hasattr(raw_field, 'temporary_file_path'): 518 # This file has a file path that we can move. 519 raw_field.close() 520 file_move_safe(raw_field.temporary_file_path(), full_filename) 521 522 else: 523 # This is a normal uploadedfile that we can stream. 524 fp = open(full_filename, 'wb') 525 locks.lock(fp, locks.LOCK_EX) 526 for chunk in raw_field.chunk(65535): 527 fp.write(chunk) 528 locks.unlock(fp) 529 fp.close() 480 530 481 531 # Save the width and/or height, if applicable. 482 532 if isinstance(field, ImageField) and (field.width_field or field.height_field): -
django/db/models/fields/__init__.py
diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index 96bb466..eb4fc59 100644
a b class FileField(Field): 806 806 setattr(cls, 'get_%s_filename' % self.name, curry(cls._get_FIELD_filename, field=self)) 807 807 setattr(cls, 'get_%s_url' % self.name, curry(cls._get_FIELD_url, field=self)) 808 808 setattr(cls, 'get_%s_size' % self.name, curry(cls._get_FIELD_size, field=self)) 809 setattr(cls, 'save_%s_file' % self.name, lambda instance, filename, raw_ contents, save=True: instance._save_FIELD_file(self, filename, raw_contents, save))809 setattr(cls, 'save_%s_file' % self.name, lambda instance, filename, raw_field, save=True: instance._save_FIELD_file(self, filename, raw_field, save)) 810 810 dispatcher.connect(self.delete_file, signal=signals.post_delete, sender=cls) 811 811 812 812 def delete_file(self, instance): … … class FileField(Field): 829 829 if new_data.get(upload_field_name, False): 830 830 func = getattr(new_object, 'save_%s_file' % self.name) 831 831 if rel: 832 f unc(new_data[upload_field_name][0]["filename"], new_data[upload_field_name][0]["content"], save)832 file = new_data[upload_field_name][0] 833 833 else: 834 func(new_data[upload_field_name]["filename"], new_data[upload_field_name]["content"], save) 834 file = new_data[upload_field_name] 835 836 # Backwards-compatible support for files-as-dictionaries. 837 # We don't need to raise a warning because Model._save_FIELD_file will 838 # do so for us. 839 try: 840 file_name = file.file_name 841 except AttributeError: 842 file_name = file['filename'] 843 844 func(file_name, file, save) 835 845 836 846 def get_directory_name(self): 837 847 return os.path.normpath(force_unicode(datetime.datetime.now().strftime(smart_str(self.upload_to)))) … … class FileField(Field): 844 854 def save_form_data(self, instance, data): 845 855 from django.newforms.fields import UploadedFile 846 856 if data and isinstance(data, UploadedFile): 847 getattr(instance, "save_%s_file" % self.name)(data.filename, data. content, save=False)857 getattr(instance, "save_%s_file" % self.name)(data.filename, data.data, save=False) 848 858 849 859 def formfield(self, **kwargs): 850 860 defaults = {'form_class': forms.FileField} -
django/http/__init__.py
diff --git a/django/http/__init__.py b/django/http/__init__.py index 7faa3c8..ef15479 100644
a b try: 9 9 except ImportError: 10 10 from cgi import parse_qsl 11 11 12 from django.utils.datastructures import MultiValueDict, FileDict12 from django.utils.datastructures import MultiValueDict, ImmutableList 13 13 from django.utils.encoding import smart_str, iri_to_uri, force_unicode 14 14 from django.http.multipartparser import MultiPartParser 15 from django.conf import settings 16 from django.core.files import uploadhandler 15 17 from utils import * 16 18 17 19 RESERVED_CHARS="!*'();:@&=+$,/?%#[]" 18 20 19 20 21 class Http404(Exception): 21 22 pass 22 23 … … class HttpRequest(object): 25 26 26 27 # The encoding used in GET/POST dicts. None means use default setting. 27 28 _encoding = None 29 _upload_handlers = [] 28 30 29 31 def __init__(self): 30 32 self.GET, self.POST, self.COOKIES, self.META, self.FILES = {}, {}, {}, {}, {} … … class HttpRequest(object): 102 104 103 105 encoding = property(_get_encoding, _set_encoding) 104 106 105 def parse_file_upload(header_dict, post_data): 106 """Returns a tuple of (POST QueryDict, FILES MultiValueDict).""" 107 import email, email.Message 108 from cgi import parse_header 109 raw_message = '\r\n'.join(['%s:%s' % pair for pair in header_dict.items()]) 110 raw_message += '\r\n\r\n' + post_data 111 msg = email.message_from_string(raw_message) 112 POST = QueryDict('', mutable=True) 113 FILES = MultiValueDict() 114 for submessage in msg.get_payload(): 115 if submessage and isinstance(submessage, email.Message.Message): 116 name_dict = parse_header(submessage['Content-Disposition'])[1] 117 # name_dict is something like {'name': 'file', 'filename': 'test.txt'} for file uploads 118 # or {'name': 'blah'} for POST fields 119 # We assume all uploaded files have a 'filename' set. 120 if 'filename' in name_dict: 121 assert type([]) != type(submessage.get_payload()), "Nested MIME messages are not supported" 122 if not name_dict['filename'].strip(): 123 continue 124 # IE submits the full path, so trim everything but the basename. 125 # (We can't use os.path.basename because that uses the server's 126 # directory separator, which may not be the same as the 127 # client's one.) 128 filename = name_dict['filename'][name_dict['filename'].rfind("\\")+1:] 129 FILES.appendlist(name_dict['name'], FileDict({ 130 'filename': filename, 131 'content-type': 'Content-Type' in submessage and submessage['Content-Type'] or None, 132 'content': submessage.get_payload(), 133 })) 134 else: 135 POST.appendlist(name_dict['name'], submessage.get_payload()) 136 return POST, FILES 137 107 def _initialize_handlers(self): 108 self._upload_handlers = [uploadhandler.load_handler(handler, self) 109 for handler in settings.FILE_UPLOAD_HANDLERS] 110 111 def _set_upload_handlers(self, upload_handlers): 112 if hasattr(self, '_files'): 113 raise AttributeError("You cannot set the upload handlers after the upload has been processed.") 114 self._upload_handlers = upload_handlers 115 116 def _get_upload_handlers(self): 117 if not self._upload_handlers: 118 # If thre are no upload handlers defined, initialize them from settings. 119 self._initialize_handlers() 120 return self._upload_handlers 121 122 upload_handlers = property(_get_upload_handlers, _set_upload_handlers) 123 124 def parse_file_upload(self, META, post_data): 125 """Returns a tuple of (POST QueryDict, FILES MultiValueDict).""" 126 self.upload_handlers = ImmutableList( 127 self.upload_handlers, 128 warning = "You cannot alter upload handlers after the upload has been processed." 129 ) 130 parser = MultiPartParser(META, post_data, self.upload_handlers, self.encoding) 131 return parser.parse() 138 132 139 133 class QueryDict(MultiValueDict): 140 134 """ -
new file django/http/multipartparser.py
diff --git a/django/http/multipartparser.py b/django/http/multipartparser.py new file mode 100644 index 0000000..31b3c23
- + 1 """ 2 Multi-part parsing for file uploads. 3 4 Exposes one class, ``MultiPartParser``, which feeds chunks of uploaded data to 5 file upload handlers for processing. 6 """ 7 import cgi 8 from django.conf import settings 9 from django.utils.datastructures import MultiValueDict 10 from django.utils.encoding import force_unicode 11 from django.utils.text import unescape_entities 12 from django.core.files.uploadhandler import StopUpload, SkipFile 13 14 __all__ = ('MultiPartParser','MultiPartParserError','InputStreamExhausted') 15 16 class MultiPartParserError(Exception): 17 pass 18 19 class InputStreamExhausted(Exception): 20 """ 21 No more reads are allowed from this device. 22 """ 23 pass 24 25 class FieldType(object): 26 """ 27 Denotes what type of field a given section of the multipart stream is. 28 29 The types can be one of ("FILE", "RAW", "FIELD") 30 """ 31 32 def __init__(self, name): 33 self.name = name 34 35 def __str__(self): 36 return self.name 37 38 RAW = FieldType('RAW') 39 FILE = FieldType('FILE') 40 FIELD = FieldType('FIELD') 41 42 class MultiPartParser(object): 43 """ 44 A rfc2388 multipart/form-data parser. 45 46 ``MultiValueDict.parse()`` reads the input stream in ``chunk_size`` chunks 47 and returns a tuple of ``(MultiValueDict(POST), MultiValueDict(FILES))``. If 48 ``file_upload_dir`` is defined files will be streamed to temporary files in 49 that directory. 50 """ 51 def __init__(self, META, input_data, upload_handlers, encoding=None): 52 """ 53 Initialize the MultiPartParser object. 54 55 :META: 56 The standard ``META`` dictionary in Django request objects. 57 :input_data: 58 The raw post data, as a bytestring. 59 :upload_handler: 60 An UploadHandler instance that performs operations on the uploaded 61 data. 62 :encoding: 63 The encoding with which to treat the incoming data. 64 """ 65 66 # 67 # Content-Type should containt multipart and the boundary information. 68 # 69 70 content_type = META.get('HTTP_CONTENT_TYPE', META.get('CONTENT_TYPE', '')) 71 if not content_type.startswith('multipart/'): 72 raise MultiPartParserError('Invalid Content-Type: %s' % content_type) 73 74 # Parse the header to get the boundary to split the parts. 75 ctypes, opts = parse_header(content_type) 76 boundary = opts.get('boundary') 77 if not boundary or not cgi.valid_boundary(boundary): 78 raise MultiPartParserError('Invalid boundary in multipart: %s' % boundary) 79 80 81 # 82 # Content-Length should contain the length of the body we are about 83 # to receive. 84 # 85 try: 86 content_length = int(META.get('HTTP_CONTENT_LENGTH', META.get('CONTENT_LENGTH',0))) 87 except (ValueError, TypeError): 88 # For now set it to 0; we'll try again later on down. 89 content_length = 0 90 91 if content_length <= 0: 92 # This means we shouldn't continue...raise an error. 93 raise MultiPartParserError("Invalid content length: %r" % content_length) 94 95 self._boundary = boundary 96 self._input_data = input_data 97 98 # For compatibility with low-level network APIs (with 32-bit integers), 99 # the chunk size should be < 2^31, but still divisible by 4. 100 self._chunk_size = min(2147483644, *[x.chunk_size for x in upload_handlers if x.chunk_size]) 101 102 self._meta = META 103 self._encoding = encoding or settings.DEFAULT_CHARSET 104 self._content_length = content_length 105 self._upload_handlers = upload_handlers 106 107 def parse(self): 108 """ 109 Parse the POST data and break it into a FILES MultiValueDict and a POST 110 MultiValueDict. 111 112 Returns a tuple containing the POST and FILES dictionary, respectively. 113 """ 114 # We have to import QueryDict down here to avoid a circular import. 115 from django.http import QueryDict 116 117 encoding = self._encoding 118 handlers = self._upload_handlers 119 120 limited_input_data = LimitBytes(self._input_data, self._content_length) 121 122 # See if the handler will want to take care of the parsing. 123 # This allows overriding everything if somebody wants it. 124 for handler in handlers: 125 result = handler.handle_raw_input(limited_input_data, 126 self._meta, 127 self._content_length, 128 self._boundary, 129 encoding) 130 if result is not None: 131 return result[0], result[1] 132 133 # Create the data structures to be used later. 134 self._post = QueryDict('', mutable=True) 135 self._files = MultiValueDict() 136 137 # Instantiate the parser and stream: 138 stream = LazyStream(ChunkIter(limited_input_data, self._chunk_size)) 139 140 # Whether or not to signal a file-completion at the beginning of the loop. 141 old_field_name = None 142 counters = [0] * len(handlers) 143 144 try: 145 for item_type, meta_data, field_stream in Parser(stream, self._boundary): 146 if old_field_name: 147 # We run this at the beginning of the next loop 148 # since we cannot be sure a file is complete until 149 # we hit the next boundary/part of the multipart content. 150 self.handle_file_complete(old_field_name, counters) 151 152 try: 153 disposition = meta_data['content-disposition'][1] 154 field_name = disposition['name'].strip() 155 except (KeyError, IndexError, AttributeError): 156 continue 157 158 transfer_encoding = meta_data.get('content-transfer-encoding') 159 field_name = force_unicode(field_name, encoding, errors='replace') 160 161 if item_type is FIELD: 162 # This is a post field, we can just set it in the post 163 if transfer_encoding == 'base64': 164 raw_data = field_stream.read() 165 try: 166 data = str(raw_data).decode('base64') 167 except: 168 data = raw_data 169 else: 170 data = field_stream.read() 171 172 self._post.appendlist(field_name, 173 force_unicode(data, encoding, errors='replace')) 174 elif item_type is FILE: 175 # This is a file, use the handler... 176 file_successful = True 177 file_name = disposition.get('filename') 178 if not file_name: 179 continue 180 file_name = force_unicode(file_name, encoding, errors='replace') 181 file_name = self.IE_sanitize(unescape_entities(file_name)) 182 183 184 content_type = meta_data.get('content-type', ('',))[0].strip() 185 try: 186 charset = meta_data.get('content-type', (0,{}))[1].get('charset', None) 187 except: 188 charset = None 189 190 try: 191 content_length = int(meta_data.get('content-length')[0]) 192 except (IndexError, TypeError, ValueError): 193 content_length = None 194 195 counters = [0] * len(handlers) 196 try: 197 for handler in handlers: 198 retval = handler.new_file(field_name, file_name, 199 content_type, content_length, 200 charset) 201 if retval: 202 break 203 204 for chunk in field_stream: 205 if transfer_encoding == 'base64': 206 # We only special-case base64 transfer encoding 207 try: 208 chunk = str(chunk).decode('base64') 209 except Exception, e: 210 # Since this is only a chunk, any error is an unfixable error. 211 raise MultiPartParserError("Could not decode base64 data: %r" % e) 212 213 for i, handler in enumerate(handlers): 214 chunk_length = len(chunk) 215 chunk = handler.receive_data_chunk(chunk, 216 counters[i]) 217 counters[i] += chunk_length 218 if chunk is None: 219 # If the chunk received by the handler is None, then don't continue. 220 break 221 222 except SkipFile, e: 223 file_successful = False 224 # Just use up the rest of this file... 225 exhaust(field_stream) 226 else: 227 # Handle file upload completions on next iteration. 228 old_field_name = field_name 229 else: 230 # If this is neither a FIELD or a FILE, just exhaust the stream. 231 exhaust(stream) 232 except StopUpload, e: 233 if not e.connection_reset: 234 exhaust(limited_input_data) 235 else: 236 # Make sure that the request data is all fed 237 exhaust(limited_input_data) 238 239 # Signal that the upload has completed. 240 for handler in handlers: 241 retval = handler.upload_complete() 242 if retval: 243 break 244 245 return self._post, self._files 246 247 def handle_file_complete(self, old_field_name, counters): 248 """ 249 Handle all the signalling that takes place when a file is complete. 250 """ 251 for i, handler in enumerate(self._upload_handlers): 252 file_obj = handler.file_complete(counters[i]) 253 if file_obj: 254 # If it returns a file object, then set the files dict. 255 self._files.appendlist(force_unicode(old_field_name, 256 self._encoding, 257 errors='replace'), 258 file_obj) 259 break 260 261 def IE_sanitize(self, filename): 262 """Cleanup filename from Internet Explorer full paths.""" 263 return filename and filename[filename.rfind("\\")+1:].strip() 264 265 class LazyStream(object): 266 """ 267 The LazyStream wrapper allows one to get and "unget" bytes from a stream. 268 269 Given a producer object (an iterator that yields bytestrings), the 270 LazyStream object will support iteration, reading, and keeping a "look-back" 271 variable in case you need to "unget" some bytes. 272 """ 273 def __init__(self, producer, length=None): 274 """ 275 Every LazyStream must have a producer when instantiated. 276 277 A producer is an iterable that returns a string each time it 278 is called. 279 """ 280 self._producer = producer 281 self._empty = False 282 self._leftover = '' 283 self.length = length 284 self._position = 0 285 self._remaining = length 286 287 # These fields are to do sanity checking to make sure we don't 288 # have infinite loops getting/ungetting from the stream. The 289 # purpose overall is to raise an exception if we perform lots 290 # of stream get/unget gymnastics without getting 291 # anywhere. Naturally this is not sound, but most probably 292 # would indicate a bug if the exception is raised. 293 294 # largest position tell us how far this lazystream has ever 295 # been advanced 296 self._largest_position = 0 297 298 # "modifications since" will start at zero and increment every 299 # time the position is modified but a new largest position is 300 # not achieved. 301 self._modifications_since = 0 302 303 def tell(self): 304 return self.position 305 306 def read(self, size=None): 307 def parts(): 308 remaining = (size is not None and [size] or [self._remaining])[0] 309 # do the whole thing in one shot if no limit was provided. 310 if remaining is None: 311 yield ''.join(self) 312 return 313 314 # otherwise do some bookkeeping to return exactly enough 315 # of the stream and stashing any extra content we get from 316 # the producer 317 while remaining != 0: 318 assert remaining > 0, 'remaining bytes to read should never go negative' 319 320 chunk = self.next() 321 322 emitting = chunk[:remaining] 323 self.unget(chunk[remaining:]) 324 remaining -= len(emitting) 325 yield emitting 326 327 out = ''.join(parts()) 328 return out 329 330 def next(self): 331 """ 332 Used when the exact number of bytes to read is unimportant. 333 334 This procedure just returns whatever is chunk is conveniently returned 335 from the iterator instead. Useful to avoid unnecessary bookkeeping if 336 performance is an issue. 337 """ 338 if self._leftover: 339 output = self._leftover 340 self._leftover = '' 341 else: 342 output = self._producer.next() 343 self.position += len(output) 344 return output 345 346 def close(self): 347 """ 348 Used to invalidate/disable this lazy stream. 349 350 Replaces the producer with an empty list. Any leftover bytes that have 351 already been read will still be reported upon read() and/or next(). 352 """ 353 self._producer = [] 354 355 def __iter__(self): 356 return self 357 358 def unget(self, bytes): 359 """ 360 Places bytes back onto the front of the lazy stream. 361 362 Future calls to read() will return those bytes first. The 363 stream position and thus tell() will be rewound. 364 """ 365 self.position -= len(bytes) 366 self._leftover = ''.join([bytes, self._leftover]) 367 368 def _set_position(self, value): 369 if value > self._largest_position: 370 self._modifications_since = 0 371 self._largest_position = value 372 else: 373 self._modifications_since += 1 374 if self._modifications_since > 500: 375 raise MultiPartParserError("LazyStream thinks it is somehow stuck. Report this to the Django developers\n" + repr(vars(self))) 376 377 self._position = value 378 379 position = property(lambda self: self._position, _set_position) 380 381 class ChunkIter(object): 382 """ 383 An iterable that will yield chunks of data. Given a file-like object as the 384 constructor, this object will yield chunks of read operations from that 385 object. 386 """ 387 def __init__(self, flo, chunk_size=64 * 1024): 388 self.flo = flo 389 self.chunk_size = chunk_size 390 391 def next(self): 392 try: 393 data = self.flo.read(self.chunk_size) 394 except InputStreamExhausted: 395 raise StopIteration() 396 if data: 397 return data 398 else: 399 raise StopIteration() 400 401 def __iter__(self): 402 return self 403 404 class LimitBytes(object): 405 """ Limit bytes for a file object. """ 406 def __init__(self, fileobject, length): 407 self._file = fileobject 408 self.remaining = length 409 410 def read(self, num_bytes=None): 411 """ 412 Read data from the underlying file. 413 If you ask for too much or there isn't anything left, 414 this will raise an InputStreamExhausted error. 415 """ 416 if self.remaining <= 0: 417 raise InputStreamExhausted() 418 if num_bytes is None: 419 num_bytes = self.remaining 420 else: 421 num_bytes = min(num_bytes, self.remaining) 422 self.remaining -= num_bytes 423 return self._file.read(num_bytes) 424 425 class InterBoundaryIter(object): 426 """ 427 A Producer that will iterate over boundaries. 428 """ 429 def __init__(self, stream, boundary): 430 self._stream = stream 431 self._boundary = boundary 432 433 def __iter__(self): 434 return self 435 436 def next(self): 437 try: 438 return LazyStream(BoundaryIter(self._stream, self._boundary)) 439 except InputStreamExhausted: 440 raise StopIteration() 441 442 class BoundaryIter(object): 443 """ 444 A Producer that is sensitive to boundaries. 445 446 Will happily yield bytes until a boundary is found. Will yield the bytes 447 before the boundary, throw away the boundary bytes themselves, and push the 448 post-boundary bytes back on the stream. 449 450 The future calls to .next() after locating the boundary will raise a 451 StopIteration exception. 452 """ 453 454 def __init__(self, stream, boundary): 455 self._stream = stream 456 self._boundary = boundary 457 self._done = False 458 # rollback an additional six bytes because the format is like 459 # this: CRLF<boundary>[--CRLF] 460 self._rollback = len(boundary) + 6 461 462 # Try to use mx fast string search if available. Otherwise 463 # use Python find. Wrap the latter for consistency. 464 unused_char = self._stream.read(1) 465 if not unused_char: 466 raise InputStreamExhausted() 467 self._stream.unget(unused_char) 468 try: 469 from mx.TextTools import FS 470 self._fs = FS(boundary).find 471 except ImportError: 472 self._fs = lambda data: data.find(boundary) 473 474 def __iter__(self): 475 return self 476 477 def next(self): 478 if self._done: 479 raise StopIteration() 480 481 stream = self._stream 482 rollback = self._rollback 483 484 bytes_read = 0 485 chunks = [] 486 for bytes in stream: 487 bytes_read += len(bytes) 488 chunks.append(bytes) 489 if bytes_read > rollback: 490 break 491 if not bytes: 492 break 493 else: 494 self._done = True 495 496 if not chunks: 497 raise StopIteration() 498 499 chunk = ''.join(chunks) 500 boundary = self._find_boundary(chunk, len(chunk) < self._rollback) 501 502 if boundary: 503 end, next = boundary 504 stream.unget(chunk[next:]) 505 self._done = True 506 return chunk[:end] 507 else: 508 # make sure we dont treat a partial boundary (and 509 # its separators) as data 510 if not chunk[:-rollback]:# and len(chunk) >= (len(self._boundary) + 6): 511 # There's nothing left, we should just return and mark as done. 512 self._done = True 513 return chunk 514 else: 515 stream.unget(chunk[-rollback:]) 516 return chunk[:-rollback] 517 518 def _find_boundary(self, data, eof = False): 519 """ 520 Finds a multipart boundary in data. 521 522 Should no boundry exist in the data None is returned instead. Otherwise 523 a tuple containing the indices of the following are returned: 524 525 * the end of current encapsulation 526 * the start of the next encapsulation 527 """ 528 index = self._fs(data) 529 if index < 0: 530 return None 531 else: 532 end = index 533 next = index + len(self._boundary) 534 data_len = len(data) - 1 535 # backup over CRLF 536 if data[max(0,end-1)] == '\n': 537 end -= 1 538 if data[max(0,end-1)] == '\r': 539 end -= 1 540 # skip over --CRLF 541 #if data[min(data_len,next)] == '-': 542 # next += 1 543 #if data[min(data_len,next)] == '-': 544 # next += 1 545 #if data[min(data_len,next)] == '\r': 546 # next += 1 547 #if data[min(data_len,next)] == '\n': 548 # next += 1 549 return end, next 550 551 def exhaust(stream_or_iterable): 552 """ 553 Completely exhausts an iterator or stream. 554 555 Raise a MultiPartParserError if the argument is not a stream or an iterable. 556 """ 557 iterator = None 558 try: 559 iterator = iter(stream_or_iterable) 560 except TypeError: 561 iterator = ChunkIter(stream_or_iterable, 16384) 562 563 if iterator is None: 564 raise MultiPartParserError('multipartparser.exhaust() was passed a non-iterable or stream parameter') 565 566 for __ in iterator: 567 pass 568 569 def ParseBoundaryStream(stream, max_header_size): 570 """ 571 Parses one and exactly one stream that encapsulates a boundary. 572 """ 573 # Stream at beginning of header, look for end of header 574 # and parse it if found. The header must fit within one 575 # chunk. 576 chunk = stream.read(max_header_size) 577 578 # 'find' returns the top of these four bytes, so we'll 579 # need to munch them later to prevent them from polluting 580 # the payload. 581 header_end = chunk.find('\r\n\r\n') 582 583 def _parse_header(line): 584 main_value_pair, params = parse_header(line) 585 try: 586 name, value = main_value_pair.split(':', 1) 587 except: 588 raise ValueError("Invalid header: %r" % line) 589 return name, (value, params) 590 591 if header_end == -1: 592 # we find no header, so we just mark this fact and pass on 593 # the stream verbatim 594 stream.unget(chunk) 595 return (RAW, {}, stream) 596 597 header = chunk[:header_end] 598 599 # here we place any excess chunk back onto the stream, as 600 # well as throwing away the CRLFCRLF bytes from above. 601 stream.unget(chunk[header_end + 4:]) 602 603 TYPE = RAW 604 outdict = {} 605 606 # Eliminate blank lines 607 for line in header.split('\r\n'): 608 # This terminology ("main value" and "dictionary of 609 # parameters") is from the Python docs. 610 try: 611 name, (value, params) = _parse_header(line) 612 except: 613 continue 614 615 if name == 'content-disposition': 616 TYPE = FIELD 617 if params.get('filename'): 618 TYPE = FILE 619 620 outdict[name] = value, params 621 622 if TYPE == RAW: 623 stream.unget(chunk) 624 625 return (TYPE, outdict, stream) 626 627 class Parser(object): 628 def __init__(self, stream, boundary): 629 self._stream = stream 630 self._separator = '--' + boundary 631 632 def __iter__(self): 633 boundarystream = InterBoundaryIter(self._stream, self._separator) 634 for sub_stream in boundarystream: 635 # Iterate over each part 636 yield ParseBoundaryStream(sub_stream, 1024) 637 638 def parse_header(line): 639 """ Parse the header into a key-value. """ 640 plist = _parse_header_params(';' + line) 641 key = plist.pop(0).lower() 642 pdict = {} 643 for p in plist: 644 i = p.find('=') 645 if i >= 0: 646 name = p[:i].strip().lower() 647 value = p[i+1:].strip() 648 if len(value) >= 2 and value[0] == value[-1] == '"': 649 value = value[1:-1] 650 value = value.replace('\\\\', '\\').replace('\\"', '"') 651 pdict[name] = value 652 return key, pdict 653 654 def _parse_header_params(s): 655 plist = [] 656 while s[:1] == ';': 657 s = s[1:] 658 end = s.find(';') 659 while end > 0 and s.count('"', 0, end) % 2: 660 end = s.find(';', end + 1) 661 if end < 0: 662 end = len(s) 663 f = s[:end] 664 plist.append(f.strip()) 665 s = s[end:] 666 return plist -
django/newforms/fields.py
diff --git a/django/newforms/fields.py b/django/newforms/fields.py index dfe46a2..7d73396 100644
a b import datetime 7 7 import os 8 8 import re 9 9 import time 10 try: 11 from cStringIO import StringIO 12 except ImportError: 13 from StringIO import StringIO 14 10 15 # Python 2.3 fallbacks 11 16 try: 12 17 from decimal import Decimal, DecimalException … … except ImportError: 416 421 417 422 class UploadedFile(StrAndUnicode): 418 423 "A wrapper for files uploaded in a FileField" 419 def __init__(self, filename, content):424 def __init__(self, filename, data): 420 425 self.filename = filename 421 self. content = content426 self.data = data 422 427 423 428 def __unicode__(self): 424 429 """ … … class FileField(Field): 444 449 return None 445 450 elif not data and initial: 446 451 return initial 452 453 if isinstance(data, dict): 454 # We warn once, then support both ways below. 455 import warnings 456 warnings.warn( 457 message = "Representing uploaded files as dictionaries is"\ 458 " deprecated. Use django.core.files.SimpleUploadedFile "\ 459 " instead.", 460 category = DeprecationWarning, 461 stacklevel = 2 462 ) 463 447 464 try: 448 f = UploadedFile(data['filename'], data['content']) 449 except TypeError: 465 file_name = data.file_name 466 file_size = data.file_size 467 except AttributeError: 468 try: 469 file_name = data.get('filename') 470 file_size = bool(data['content']) 471 except (AttributeError, KeyError): 472 raise ValidationError(self.error_messages['invalid']) 473 474 if not file_name: 450 475 raise ValidationError(self.error_messages['invalid']) 451 except KeyError: 452 raise ValidationError(self.error_messages['missing']) 453 if not f.content: 476 if not file_size: 454 477 raise ValidationError(self.error_messages['empty']) 455 return f 478 479 return UploadedFile(file_name, data) 456 480 457 481 class ImageField(FileField): 458 482 default_error_messages = { … … class ImageField(FileField): 470 494 elif not data and initial: 471 495 return initial 472 496 from PIL import Image 473 from cStringIO import StringIO 497 498 # We need to get a file object for PIL. We might have a path or we might 499 # have to read the data into memory. 500 if hasattr(data, 'temporary_file_path'): 501 file = data.temporary_file_path() 502 else: 503 if hasattr(data, 'read'): 504 file = StringIO(data.read()) 505 else: 506 file = StringIO(data['content']) 507 474 508 try: 475 509 # load() is the only method that can spot a truncated JPEG, 476 510 # but it cannot be called sanely after verify() 477 trial_image = Image.open( StringIO(f.content))511 trial_image = Image.open(file) 478 512 trial_image.load() 513 514 # Since we're about to use the file again we have to reset the 515 # file object if possible. 516 if hasattr(file, 'reset'): 517 file.reset() 518 479 519 # verify() is the only method that can spot a corrupt PNG, 480 520 # but it must be called immediately after the constructor 481 trial_image = Image.open( StringIO(f.content))521 trial_image = Image.open(file) 482 522 trial_image.verify() 483 523 except Exception: # Python Imaging Library doesn't recognize it as an image 484 524 raise ValidationError(self.error_messages['invalid_image']) -
django/oldforms/__init__.py
diff --git a/django/oldforms/__init__.py b/django/oldforms/__init__.py index fc87271..ee838d2 100644
a b class FileUploadField(FormField): 680 680 self.field_name, self.is_required = field_name, is_required 681 681 self.validator_list = [self.isNonEmptyFile] + validator_list 682 682 683 def isNonEmptyFile(self, field_data, all_data): 683 def isNonEmptyFile(self, new_data, all_data): 684 if hasattr(new_data, 'upload_errors'): 685 upload_errors = new_data.upload_errors() 686 if upload_errors: 687 raise validators.CriticalValidationError, upload_errors 684 688 try: 685 content = field_data['content']686 except TypeError:687 raise validators.CriticalValidationError, ugettext("No file was submitted. Check the encoding type on the form.")688 if not content:689 file_size = new_data.file_size 690 except AttributeError: 691 file_size = len(new_data['content']) 692 if not file_size: 689 693 raise validators.CriticalValidationError, ugettext("The submitted file is empty.") 690 694 691 695 def render(self, data): 692 696 return mark_safe(u'<input type="file" id="%s" class="v%s" name="%s" />' % \ 693 697 (self.get_id(), self.__class__.__name__, self.field_name)) 694 698 699 def prepare(self, new_data): 700 if hasattr(new_data, 'upload_errors'): 701 upload_errors = new_data.upload_errors() 702 new_data[self.field_name] = { '_file_upload_error': upload_errors } 703 695 704 def html2python(data): 696 705 if data is None: 697 706 raise EmptyValue -
django/test/client.py
diff --git a/django/test/client.py b/django/test/client.py index a15876e..6313181 100644
a b 1 1 import urllib 2 2 import sys 3 3 import os 4 from cStringIO import StringIO 4 try: 5 from cStringIO import StringIO 6 except ImportError: 7 from StringIO import StringIO 5 8 from django.conf import settings 6 9 from django.contrib.auth import authenticate, login 7 10 from django.core.handlers.base import BaseHandler … … from django.utils.itercompat import is_iterable 19 22 BOUNDARY = 'BoUnDaRyStRiNg' 20 23 MULTIPART_CONTENT = 'multipart/form-data; boundary=%s' % BOUNDARY 21 24 25 class FakePayload(object): 26 """ 27 A wrapper around StringIO that restricts what can be read since data from 28 the network can't be seeked and cannot be read outside of its content 29 length. This makes sure that views can't do anything under the test client 30 that wouldn't work in Real Life. 31 """ 32 def __init__(self, content): 33 self.__content = StringIO(content) 34 self.__len = len(content) 35 36 def read(self, num_bytes=None): 37 if num_bytes is None: 38 num_bytes = self.__len or 1 39 assert self.__len >= num_bytes, "Cannot read more than the available bytes from the HTTP incoming data." 40 content = self.__content.read(num_bytes) 41 self.__len -= num_bytes 42 return content 43 22 44 class ClientHandler(BaseHandler): 23 45 """ 24 46 A HTTP Handler that can be used for testing purposes. … … class Client: 236 258 'CONTENT_TYPE': content_type, 237 259 'PATH_INFO': urllib.unquote(path), 238 260 'REQUEST_METHOD': 'POST', 239 'wsgi.input': StringIO(post_data),261 'wsgi.input': FakePayload(post_data), 240 262 } 241 263 r.update(extra) 242 264 -
django/utils/datastructures.py
diff --git a/django/utils/datastructures.py b/django/utils/datastructures.py index 4c278c0..7c8e246 100644
a b class DotExpandedDict(dict): 332 332 except TypeError: # Special-case if current isn't a dict. 333 333 current = {bits[-1]: v} 334 334 335 class FileDict(dict):335 class ImmutableList(tuple): 336 336 """ 337 A dictionary used to hold uploaded file contents. The only special feature 338 here is that repr() of this object won't dump the entire contents of the 339 file to the output. A handy safeguard for a large file upload. 337 A tuple-like object that raises useful errors when it is asked to mutate. 338 339 Example:: 340 341 >>> a = ImmutableList(range(5), warning="You cannot mutate this.") 342 >>> a[3] = '4' 343 Traceback (most recent call last): 344 ... 345 AttributeError: You cannot mutate this. 340 346 """ 341 def __repr__(self): 342 if 'content' in self: 343 d = dict(self, content='<omitted>') 344 return dict.__repr__(d) 345 return dict.__repr__(self) 347 348 def __new__(cls, *args, **kwargs): 349 if 'warning' in kwargs: 350 warning = kwargs['warning'] 351 del kwargs['warning'] 352 else: 353 warning = 'ImmutableList object is immutable.' 354 self = tuple.__new__(cls, *args, **kwargs) 355 self.warning = warning 356 return self 357 358 def complain(self, *wargs, **kwargs): 359 if isinstance(self.warning, Exception): 360 raise self.warning 361 else: 362 raise AttributeError, self.warning 363 364 # All list mutation functions complain. 365 __delitem__ = complain 366 __delslice__ = complain 367 __iadd__ = complain 368 __imul__ = complain 369 __setitem__ = complain 370 __setslice__ = complain 371 append = complain 372 extend = complain 373 insert = complain 374 pop = complain 375 remove = complain 376 sort = complain 377 reverse = complain 378 No newline at end of file -
django/utils/text.py
diff --git a/django/utils/text.py b/django/utils/text.py index aa190c8..3686a45 100644
a b from django.conf import settings 3 3 from django.utils.encoding import force_unicode 4 4 from django.utils.functional import allow_lazy 5 5 from django.utils.translation import ugettext_lazy 6 from htmlentitydefs import name2codepoint 6 7 7 8 # Capitalizes the first letter of a string. 8 9 capfirst = lambda x: x and force_unicode(x)[0].upper() + force_unicode(x)[1:] … … def smart_split(text): 222 223 yield bit 223 224 smart_split = allow_lazy(smart_split, unicode) 224 225 226 def _replace_entity(match): 227 text = match.group(1) 228 if text[0] == u'#': 229 text = text[1:] 230 try: 231 if text[0] in u'xX': 232 c = int(text[1:], 16) 233 else: 234 c = int(text) 235 return unichr(c) 236 except ValueError: 237 return match.group(0) 238 else: 239 try: 240 return unichr(name2codepoint[text]) 241 except (ValueError, KeyError): 242 return match.group(0) 243 244 _entity_re = re.compile(r"&(#?[xX]?(?:[0-9a-fA-F]+|\w{1,8}));") 245 246 def unescape_entities(text): 247 return _entity_re.sub(_replace_entity, text) 248 unescape_entities = allow_lazy(unescape_entities, unicode) -
docs/newforms.txt
diff --git a/docs/newforms.txt b/docs/newforms.txt index 04e4c1a..296fc04 100644
a b ContactForm to include an ``ImageField`` called ``mugshot``, we 805 805 need to bind the file data containing the mugshot image:: 806 806 807 807 # Bound form with an image field 808 >>> from django.core.files.uploadedfile import SimpleUploadedFile 808 809 >>> data = {'subject': 'hello', 809 810 ... 'message': 'Hi there', 810 811 ... 'sender': 'foo@example.com', 811 812 ... 'cc_myself': True} 812 >>> file_data = {'mugshot': {'filename':'face.jpg' 813 ... 'content': <file data>}} 813 >>> file_data = {'mugshot': SimpleUploadedFile('face.jpg', <file data>)} 814 814 >>> f = ContactFormWithMugshot(data, file_data) 815 815 816 816 In practice, you will usually specify ``request.FILES`` as the source -
docs/request_response.txt
diff --git a/docs/request_response.txt b/docs/request_response.txt index 866a697..2d2010a 100644
a b All attributes except ``session`` should be considered read-only. 80 80 strings. 81 81 82 82 ``FILES`` 83 **New in Django development version** 83 84 A dictionary-like object containing all uploaded files. Each key in 84 85 ``FILES`` is the ``name`` from the ``<input type="file" name="" />``. Each 85 value in ``FILES`` is a standard Python dictionary with the following three 86 keys: 86 value in ``FILES`` is an ``UploadedFile`` object containing at least the 87 following attributes: 88 89 * ``read(num_bytes=None)`` -- Read a number of bytes from the file. 90 * ``file_name`` -- The name of the uploaded file. 91 * ``file_size`` -- The size, in bytes, of the uploaded file. 92 * ``chunk()`` -- A generator that yields sequential chunks of data. 87 93 88 * ``filename`` -- The name of the uploaded file, as a Python string. 89 * ``content-type`` -- The content type of the uploaded file. 90 * ``content`` -- The raw content of the uploaded file. 94 See `File Uploads`_ for more information. Note that ``FILES`` will only 95 contain data if the request method was POST and the ``<form>`` that posted 96 to the request had ``enctype="multipart/form-data"``. Otherwise, ``FILES`` 97 will be a blank dictionary-like object. 91 98 92 Note that ``FILES`` will only contain data if the request method was POST 93 and the ``<form>`` that posted to the request had 94 ``enctype="multipart/form-data"``. Otherwise, ``FILES`` will be a blank 95 dictionary-like object. 99 .. _File Uploads: ../upload_handling/ 96 100 97 101 ``META`` 98 102 A standard Python dictionary containing all available HTTP headers. -
docs/settings.txt
diff --git a/docs/settings.txt b/docs/settings.txt index a1c8c74..1460f00 100644
a b Default: ``''`` (Empty string) 279 279 280 280 The database backend to use. The build-in database backends are 281 281 ``'postgresql_psycopg2'``, ``'postgresql'``, ``'mysql'``, ``'mysql_old'``, 282 ``'sqlite3'`` and ``'oracle'``.282 ``'sqlite3'``, ``'oracle'``, and ``'oracle'``. 283 283 284 284 In the Django development version, you can use a database backend that doesn't 285 285 ship with Django by setting ``DATABASE_ENGINE`` to a fully-qualified path (i.e. … … Default: ``'utf-8'`` 525 525 The character encoding used to decode any files read from disk. This includes 526 526 template files and initial SQL data files. 527 527 528 FILE_UPLOAD_HANDLERS 529 -------------------- 530 531 **New in Django development version** 532 533 Default:: 534 535 ("django.core.files.fileuploadhandler.MemoryFileUploadHandler", 536 "django.core.files.fileuploadhandler.TemporaryFileUploadHandler",) 537 538 A tuple of handlers to use for uploading. 539 540 FILE_UPLOAD_MAX_MEMORY_SIZE 541 --------------------------- 542 543 **New in Django development version** 544 545 Default: ``2621440`` (i.e. 2.5 MB). 546 547 The maximum size (in bytes) that an upload will be before it gets streamed to the file system. 548 549 FILE_UPLOAD_TEMP_DIR 550 -------------------- 551 552 **New in Django development version** 553 554 Default: ``None`` 555 556 The directory to store data temporarily while uploading files. If ``None``, Django will use the standard temporary directory for the operating system. For example, this will default to '/tmp' on *nix-style operating systems. 557 528 558 FIXTURE_DIRS 529 559 ------------- 530 560 -
new file docs/upload_handling.txt
diff --git a/docs/upload_handling.txt b/docs/upload_handling.txt new file mode 100644 index 0000000..28c7dbe
- + 1 ============ 2 File Uploads 3 ============ 4 5 **New in Django development version** 6 7 Most Web sites wouldn't be complete without a way to upload files. Before the 8 file even gets into ``request.FILES`` Django has to decide where to put the 9 all the incoming data. This document describes how Django takes the incoming 10 request and populates ``request.FILES``. 11 12 .. _Request and response objects: ../request_response/#attributes 13 14 Default Behavior 15 ================ 16 17 After setting up a quick model that contains a `FileField`_ and enabling the 18 `Admin interface`_ you can quickly use a form to upload files to your site. 19 By default, if the upload is smaller than **2.5 Megabytes** in size, Django 20 will hold the entire contents of the upload in memory, and the file will 21 quickly be saved to its final destination (as defined by 22 `settings.MEDIA_ROOT`_) without any intermediary. 23 24 If the entire upload is larger than 2.5 Megabytes, then it will -- by 25 default -- write the contents of the uploaded file to a temporary file 26 in your operating system's default temporary directory. On a posix platform, 27 this means that you could expect Django to generate a file similar in name 28 to ``/tmp/tmpzfp6I6.upload``. During the upload, you may notice this file 29 grow in size as Django loads the data onto disk. 30 31 .. note:: 32 You may find that the default temporary directory is not writable. 33 This is especially true if you are on shared hosting. If this is 34 the case, Django will raise an error when you try to upload. Please 35 read below in `Extending the Default`_ to change this directory. 36 37 38 .. _FileField: ../model-api/#filefield 39 .. _Admin interface: ../tutorial02/#activate-the-admin-site 40 .. _settings.MEDIA_ROOT: ../settings/#media-root 41 42 Extending the Default 43 ===================== 44 45 Suppose you have quite a bit of memory. You may decide that you want Django 46 to stream only if the entire upload exceeds 10 Megabytes. You may even 47 decide that whatever directory is the platform default is not suitable 48 for your project. If this is the case, Django provides these two settings: 49 50 =============================== ====================================== 51 Setting Description 52 =============================== ====================================== 53 ``FILE_UPLOAD_MAX_MEMORY_SIZE`` The maximum size of a request in bytes 54 for which Django will try to load the 55 entire upload contents in memory. 56 57 ``FILE_UPLOAD_TEMP_DIR`` The directory on the file system where 58 uploaded contents will be temporarily 59 stored if not completely in memory. 60 E.g.: ``"/tmp"`` 61 =============================== ====================================== 62 63 There is one final setting -- ``FILE_UPLOAD_HANDLERS`` -- which allows complete 64 customization of the upload process. 65 66 Upload Handlers 67 =============== 68 69 Through upload handlers Django provides the flexibility to extend the 70 upload process beyond simple storage. You can use custom handlers to enforce 71 user-level quotas, compress data on the fly, render progress bars, and 72 even send data to another warehouse directly without storing it locally. 73 74 There are two pieces to the Django upload handling: the upload handler and the 75 uploaded file. These are both represented by python classes -- 76 ``FileUploadHandler`` and ``UploadedFile`` respectively. Throughout the lifetime 77 of the upload process, Django will call on the upload handler to handle the 78 upload, while the upload handler is expected to provide Django an ``UploadedFile`` 79 object to successfully complete a file's upload. 80 81 Setting Default Upload Handlers for your Project 82 ------------------------------------------------ 83 84 Similar to `Middleware`_, upload handlers have an order that is initially 85 defined in ``settings.FILE_UPLOAD_HANDLERS``. The default value is:: 86 87 ("django.core.files.fileuploadhandler.MemoryFileUploadHandler", 88 "django.core.files.fileuploadhandler.TemporaryFileUploadHandler",) 89 90 This literally means: Try putting the upload in memory first and failing 91 that put the upload in a temporary file. 92 93 This behavior, however, is completely dependent on how each of those handlers 94 is written. For example, if someone gave you a ``Rot13UploadHandler``, you can 95 update your ``settings`` to contain:: 96 97 FILE_UPLOAD_HANDLERS = ( 98 "app.uploadhandlers.rot13.Rot13UploadHandler", 99 "django.core.files.fileuploadhandler.MemoryFileUploadHandler", 100 "django.core.files.fileuploadhandler.TemporaryFileUploadHandler", 101 ) 102 103 And the ``Rot13UploadHandler`` will perform the ``rot13`` operation on all 104 data before the data get to subsequent handlers. 105 106 .. _Middleware: ../middleware/ 107 108 Modifying your Upload Handlers Dynamically 109 ------------------------------------------ 110 111 During the lifetime of your project, you may realize that a particular 112 view or views require different uploading behavior. For this reason, 113 the ``request`` object contains a list of handlers 114 (``request.upload_handlers``) that will be called in order. To append 115 a handler to the list, you would append to it like any other list. 116 For example, suppose you had an ``ProgressBarUploadHandler`` class. 117 To append it to your upload handlers you would write:: 118 119 request.upload_handlers.append(ProgressBarUploadHandler()) 120 121 However, since the progress bar handler would probably need to run before the 122 other handlers get a chance, you'd probably want to insert it. That is, you'd 123 write:: 124 125 request.upload_handlers.insert(0, ProgressBarUploadHandler()) 126 127 If you want to replace the upload handlers completely, you can just assign a new 128 list:: 129 130 request.upload_handlers = [ProgressBarUploadHandler()] 131 132 And all Django will do is keep a progress, but do nothing with the file itself! 133 One more note: After the upload has completed, you are no longer allowed to 134 assign or modify this list, and Django will raise an error if you try to modify 135 this list after an upload. 136 137 Writing a File Upload Handler 138 ----------------------------- 139 140 All file upload handlers are subclasses of ``FileUploadHandler``, found in 141 ``django.core.files.fileuploadhandler``. To create your handler, you need to 142 define the required methods followed by the methods that make most sense to you: 143 144 chunk_size 145 ~~~~~~~~~~ 146 147 An integer attribute that specifies what sized chunks Django should store 148 into memory and feed into the handler. The chunk sizes should be divisible by 149 ``4`` and should not exceed ``2 ** 31 - 1`` in size. When there are multiple 150 chunk sizes provided by multiple handlers, Django will use the smallest chunk 151 size. 152 153 __init__ 154 ~~~~~~~~ 155 156 The constructor, which should optionally support taking a request object as its 157 first argument. When called via ``settings.FILE_UPLOAD_HANDLERS``, it will be 158 passed a request object. 159 160 Interface: ``__init__(self, request=None)`` 161 162 ``request`` is the request object. Do not access ``request.POST`` or 163 ``request.FILES``. 164 165 new_file 166 ~~~~~~~~ 167 168 ``new_file`` signals that a new file is starting. You can initialize a file or 169 whatever else is needed on a new file upload. 170 171 Interface: ``new_file(self, field_name, file_name, content_type, content_length, 172 charset)`` 173 174 ``field_name`` is a string name of the field this was POSTed as. 175 176 ``file_name`` is the unicode filename that was provided by the browser. 177 178 ``content_type`` is the MIME type provided by the browser -- E.g. 179 ``'image/jpeg'``. 180 181 ``content_length`` is the length of the image given by the browser if provided, 182 ``None`` otherwise. 183 184 ``charset`` is the charset given by the browser if provided, ``None`` otherwise. 185 186 Returns: ``None`` if you want other handlers to get ``new_file`` called. 187 Something nonzero if you don't want subsequent handlers to get a chance. 188 189 receive_data_chunk 190 ~~~~~~~~~~~~~~~~~~ 191 *required* 192 193 Receives a segment of data from the file upload. For example: The 194 ``TemporaryFileUploadHandler`` takes the data and writes it to disk. 195 196 Interface: ``receive_data_chunk(self, raw_data, start)`` 197 198 ``raw_data`` is a byte string containing the uploaded data. 199 200 ``start`` is the position in the file where this ``raw_data`` chunk begins. 201 202 Returns: ``None`` if you don't want the subsequent upload handlers to receive 203 the data. Whatever else you return gets fed into the subsequent upload handlers' 204 ``receive_data_chunk`` method. In this way, one handler can be a "filter" for 205 other handlers. 206 207 Exceptions: If you raise a ``StopUpload`` or a ``SkipFile`` exception, the 208 upload will abort or the file will be skipped respectively. 209 210 file_complete 211 ~~~~~~~~~~~~~ 212 *required* 213 214 Signals that a file has finished uploading. Expected to return an 215 ``UploadedFile`` object if the file has been packaged successfully. 216 217 Interface: ``file_complete(self, file_size)`` 218 219 ``file_size`` is the number of bytes you have received for this file. 220 221 Returns: An ``UploadedFile`` object to set in the ``request.FILES`` dictionary. 222 ``None`` if you want the subsequent upload handlers to get an ``UploadedFile`` 223 object. 224 225 upload_complete 226 ~~~~~~~~~~~~~~~ 227 228 Defines when the entire upload has completed. 229 230 Interface: ``upload_complete(self)`` 231 232 handle_raw_input 233 ~~~~~~~~~~~~~~~~ 234 235 Allows the handler to completely override the parsing of the raw HTTP-layered 236 input. 237 238 Interface: ``handle_raw_input(self, input_data, META, content_length, boundary, 239 encoding)`` 240 241 ``input_data`` is a file-like object that supports the read operation. 242 243 ``META`` is the same object as ``request.META``. 244 245 ``content_length`` is the length of the data in ``input_data``. Don't read more 246 than ``content_length`` bytes from ``input_data``. 247 248 ``boundary`` is the MIME boundary for this request. 249 250 ``encoding`` is the encoding of the request. 251 252 Returns: ``None`` if you want to go on to the next stage, ``(POST, FILES)`` if 253 you want to return the new data structures suitable for the request directly. 254 255 Defining an Uploaded File 256 ------------------------- 257 258 All file upload handlers are subclasses of ``UploadedFile``, found in 259 ``django.core.files.uploadedfile``. The uploaded file object is returned by the 260 handler above in ``file_complete``. To create your own uploaded file class, you 261 need to define the required methods followed by the methods that make most sense 262 to you: 263 264 read 265 ~~~~ 266 *required* 267 268 Interface: ``read(self, num_bytes=None)`` 269 270 Returns: A byte string of length ``num_bytes`` (or the size of the file if 271 ``num_bytes`` is not supplied). 272 273 chunk 274 ~~~~~ 275 276 A generator to yield small chunks from the file. With the ``read()`` defined, 277 the ``UploadedFile`` class already defines a ``chunk()`` method that's probably 278 suitable. 279 280 Interface: ``chunk(self, chunk_size=None)`` 281 282 multiple_chunks 283 ~~~~~~~~~~~~~~~ 284 285 Interface: ``multiple_chunks(self, chunk_size=None)`` 286 287 Returns: ``True`` or ``False`` depending on whether or not the user of this 288 file can expect more than one chunk when calling ``chunk(self, chunk_size)``. 289 If all the data is in memory, you should return ``False``. 290 291 temporary_file_path 292 ~~~~~~~~~~~~~~~~~~~ 293 294 If defined, this method should return the file path of the file on the 295 operating system. This will let users move files rather than write to 296 new files if the option is available. 297 298 Interface: ``temporary_file_path(self)`` 299 300 Returns: A file path in a file system on the local computer. 301 302 upload_errors 303 ~~~~~~~~~~~~~ 304 305 If defined, this method should return a string describing errors that 306 occured during uploads. 307 308 Interface: ``upload_errors(self)`` 309 310 Returns: A ``unicode`` string describing an error that occured with the 311 file's upload. -
tests/modeltests/model_forms/models.py
diff --git a/tests/modeltests/model_forms/models.py b/tests/modeltests/model_forms/models.py index 470312f..0ad5686 100644
a b class TextFile(models.Model): 67 67 68 68 class ImageFile(models.Model): 69 69 description = models.CharField(max_length=20) 70 image = models.FileField(upload_to=tempfile.gettempdir()) 70 try: 71 # If PIL is available, try testing PIL. 72 # Otherwise, it's equivalent to TextFile above. 73 import Image 74 image = models.ImageField(upload_to=tempfile.gettempdir()) 75 except ImportError: 76 image = models.FileField(upload_to=tempfile.gettempdir()) 71 77 72 78 def __unicode__(self): 73 79 return self.description … … class ImageFile(models.Model): 75 81 __test__ = {'API_TESTS': """ 76 82 >>> from django import newforms as forms 77 83 >>> from django.newforms.models import ModelForm 84 >>> from django.core.files.uploadedfile import SimpleUploadedFile 85 >>> from warnings import filterwarnings 86 >>> filterwarnings("ignore") 78 87 79 88 The bare bones, absolutely nothing custom, basic case. 80 89 … … False 792 801 793 802 # Upload a file and ensure it all works as expected. 794 803 804 >>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': SimpleUploadedFile('test1.txt', 'hello world')}) 805 >>> f.is_valid() 806 True 807 >>> type(f.cleaned_data['file']) 808 <class 'django.newforms.fields.UploadedFile'> 809 >>> instance = f.save() 810 >>> instance.file 811 u'...test1.txt' 812 813 >>> os.unlink(instance.get_file_filename()) 814 795 815 >>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': {'filename': 'test1.txt', 'content': 'hello world'}}) 796 816 >>> f.is_valid() 797 817 True … … u'...test1.txt' 814 834 u'...test1.txt' 815 835 816 836 # Delete the current file since this is not done by Django. 817 818 837 >>> os.unlink(instance.get_file_filename()) 819 838 820 839 # Override the file by uploading a new one. 821 840 822 >>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': {'filename': 'test2.txt', 'content': 'hello world'}}, instance=instance)841 >>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': SimpleUploadedFile('test2.txt', 'hello world')}, instance=instance) 823 842 >>> f.is_valid() 824 843 True 825 844 >>> instance = f.save() 826 845 >>> instance.file 827 846 u'...test2.txt' 828 847 848 # Delete the current file since this is not done by Django. 849 >>> os.unlink(instance.get_file_filename()) 850 851 >>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': {'filename': 'test2.txt', 'content': 'hello world'}}) 852 >>> f.is_valid() 853 True 854 >>> instance = f.save() 855 >>> instance.file 856 u'...test2.txt' 857 858 # Delete the current file since this is not done by Django. 859 >>> os.unlink(instance.get_file_filename()) 860 829 861 >>> instance.delete() 830 862 831 863 # Test the non-required FileField … … True 838 870 >>> instance.file 839 871 '' 840 872 841 >>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': {'filename': 'test3.txt', 'content': 'hello world'}}, instance=instance)873 >>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': SimpleUploadedFile('test3.txt', 'hello world')}, instance=instance) 842 874 >>> f.is_valid() 843 875 True 844 876 >>> instance = f.save() 845 877 >>> instance.file 846 878 u'...test3.txt' 879 880 # Delete the current file since this is not done by Django. 881 >>> os.unlink(instance.get_file_filename()) 882 >>> instance.delete() 883 884 >>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': {'filename': 'test3.txt', 'content': 'hello world'}}) 885 >>> f.is_valid() 886 True 887 >>> instance = f.save() 888 >>> instance.file 889 u'...test3.txt' 890 891 # Delete the current file since this is not done by Django. 892 >>> os.unlink(instance.get_file_filename()) 847 893 >>> instance.delete() 848 894 849 895 # ImageField ################################################################### … … u'...test3.txt' 858 904 859 905 >>> image_data = open(os.path.join(os.path.dirname(__file__), "test.png")).read() 860 906 907 >>> f = ImageFileForm(data={'description': u'An image'}, files={'image': SimpleUploadedFile('test.png', image_data)}) 908 >>> f.is_valid() 909 True 910 >>> type(f.cleaned_data['image']) 911 <class 'django.newforms.fields.UploadedFile'> 912 >>> instance = f.save() 913 >>> instance.image 914 u'...test.png' 915 916 # Delete the current file since this is not done by Django. 917 >>> os.unlink(instance.get_image_filename()) 918 861 919 >>> f = ImageFileForm(data={'description': u'An image'}, files={'image': {'filename': 'test.png', 'content': image_data}}) 862 920 >>> f.is_valid() 863 921 True … … u'...test.png' 885 943 886 944 # Override the file by uploading a new one. 887 945 888 >>> f = ImageFileForm(data={'description': u'Changed it'}, files={'image': {'filename': 'test2.png', 'content': image_data}}, instance=instance) 946 >>> f = ImageFileForm(data={'description': u'Changed it'}, files={'image': SimpleUploadedFile('test2.png', image_data)}, instance=instance) 947 >>> f.is_valid() 948 True 949 >>> instance = f.save() 950 >>> instance.image 951 u'...test2.png' 952 953 # Delete the current file since this is not done by Django. 954 >>> os.unlink(instance.get_image_filename()) 955 >>> instance.delete() 956 957 >>> f = ImageFileForm(data={'description': u'Changed it'}, files={'image': {'filename': 'test2.png', 'content': image_data}}) 889 958 >>> f.is_valid() 890 959 True 891 960 >>> instance = f.save() 892 961 >>> instance.image 893 962 u'...test2.png' 894 963 964 # Delete the current file since this is not done by Django. 965 >>> os.unlink(instance.get_image_filename()) 895 966 >>> instance.delete() 896 967 897 968 # Test the non-required ImageField … … True 904 975 >>> instance.image 905 976 '' 906 977 907 >>> f = ImageFileForm(data={'description': u'And a final one'}, files={'image': {'filename': 'test3.png', 'content': image_data}}, instance=instance) 978 >>> f = ImageFileForm(data={'description': u'And a final one'}, files={'image': SimpleUploadedFile('test3.png', image_data)}, instance=instance) 979 >>> f.is_valid() 980 True 981 >>> instance = f.save() 982 >>> instance.image 983 u'...test3.png' 984 985 # Delete the current file since this is not done by Django. 986 >>> os.unlink(instance.get_image_filename()) 987 >>> instance.delete() 988 989 >>> f = ImageFileForm(data={'description': u'And a final one'}, files={'image': {'filename': 'test3.png', 'content': image_data}}) 908 990 >>> f.is_valid() 909 991 True 910 992 >>> instance = f.save() -
tests/regressiontests/bug639/tests.py
diff --git a/tests/regressiontests/bug639/tests.py b/tests/regressiontests/bug639/tests.py index f9596d0..2726dec 100644
a b import unittest 9 9 from regressiontests.bug639.models import Photo 10 10 from django.http import QueryDict 11 11 from django.utils.datastructures import MultiValueDict 12 from django.core.files.uploadedfile import SimpleUploadedFile 12 13 13 14 class Bug639Test(unittest.TestCase): 14 15 … … class Bug639Test(unittest.TestCase): 21 22 22 23 # Fake a request query dict with the file 23 24 qd = QueryDict("title=Testing&image=", mutable=True) 24 qd["image_file"] = { 25 "filename" : "test.jpg", 26 "content-type" : "image/jpeg", 27 "content" : img 28 } 29 25 qd["image_file"] = SimpleUploadedFile('test.jpg', img, 'image/jpeg') 26 30 27 manip = Photo.AddManipulator() 31 28 manip.do_html2python(qd) 32 29 p = manip.save(qd) … … class Bug639Test(unittest.TestCase): 39 36 Make sure to delete the "uploaded" file to avoid clogging /tmp. 40 37 """ 41 38 p = Photo.objects.get() 42 os.unlink(p.get_image_filename()) 43 No newline at end of file 39 os.unlink(p.get_image_filename()) -
tests/regressiontests/datastructures/tests.py
diff --git a/tests/regressiontests/datastructures/tests.py b/tests/regressiontests/datastructures/tests.py index b51b4b1..1d6a846 100644
a b Init from sequence of tuples 117 117 >>> d['person']['2']['firstname'] 118 118 ['Adrian'] 119 119 120 ### FileDict ################################################################ 121 122 >>> d = FileDict({'content': 'once upon a time...'}) 123 >>> repr(d) 124 "{'content': '<omitted>'}" 125 >>> d = FileDict({'other-key': 'once upon a time...'}) 120 ### ImmutableList ################################################################ 121 >>> d = ImmutableList(range(10)) 122 >>> d.sort() 123 Traceback (most recent call last): 124 File "<stdin>", line 1, in <module> 125 File "/var/lib/python-support/python2.5/django/utils/datastructures.py", line 359, in complain 126 raise AttributeError, self.warning 127 AttributeError: ImmutableList object is immutable. 126 128 >>> repr(d) 127 "{'other-key': 'once upon a time...'}" 129 '(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)' 130 >>> d = ImmutableList(range(10), warning="Object is immutable!") 131 >>> d[1] 132 1 133 >>> d[1] = 'test' 134 Traceback (most recent call last): 135 File "<stdin>", line 1, in <module> 136 File "/var/lib/python-support/python2.5/django/utils/datastructures.py", line 359, in complain 137 raise AttributeError, self.warning 138 AttributeError: Object is immutable! 128 139 """ -
tests/regressiontests/forms/error_messages.py
diff --git a/tests/regressiontests/forms/error_messages.py b/tests/regressiontests/forms/error_messages.py index 9f972f5..c7b301b 100644
a b 1 1 # -*- coding: utf-8 -*- 2 2 tests = r""" 3 3 >>> from django.newforms import * 4 >>> from django.core.files.uploadedfile import SimpleUploadedFile 4 5 5 6 # CharField ################################################################### 6 7 … … ValidationError: [u'REQUIRED'] 214 215 Traceback (most recent call last): 215 216 ... 216 217 ValidationError: [u'INVALID'] 217 >>> f.clean( {})218 >>> f.clean(SimpleUploadedFile('name', None)) 218 219 Traceback (most recent call last): 219 220 ... 220 ValidationError: [u' MISSING']221 >>> f.clean( {'filename': 'name', 'content':''})221 ValidationError: [u'EMPTY FILE'] 222 >>> f.clean(SimpleUploadedFile('name', '')) 222 223 Traceback (most recent call last): 223 224 ... 224 225 ValidationError: [u'EMPTY FILE'] -
tests/regressiontests/forms/fields.py
diff --git a/tests/regressiontests/forms/fields.py b/tests/regressiontests/forms/fields.py index f3b6a96..7bfadb7 100644
a b 2 2 tests = r""" 3 3 >>> from django.newforms import * 4 4 >>> from django.newforms.widgets import RadioFieldRenderer 5 >>> from django.core.files.uploadedfile import SimpleUploadedFile 5 6 >>> import datetime 6 7 >>> import time 7 8 >>> import re … … ValidationError: [u'This field is required.'] 773 774 >>> f.clean({}) 774 775 Traceback (most recent call last): 775 776 ... 776 ValidationError: [u'No file was submitted. ']777 ValidationError: [u'No file was submitted. Check the encoding type on the form.'] 777 778 778 779 >>> f.clean({}, '') 779 780 Traceback (most recent call last): 780 781 ... 781 ValidationError: [u'No file was submitted. ']782 ValidationError: [u'No file was submitted. Check the encoding type on the form.'] 782 783 783 784 >>> f.clean({}, 'files/test3.pdf') 784 785 'files/test3.pdf' … … Traceback (most recent call last): 788 789 ... 789 790 ValidationError: [u'No file was submitted. Check the encoding type on the form.'] 790 791 791 >>> f.clean( {'filename': 'name', 'content': None})792 >>> f.clean(SimpleUploadedFile('name', None)) 792 793 Traceback (most recent call last): 793 794 ... 794 795 ValidationError: [u'The submitted file is empty.'] 795 796 796 >>> f.clean( {'filename': 'name', 'content': ''})797 >>> f.clean(SimpleUploadedFile('name', '')) 797 798 Traceback (most recent call last): 798 799 ... 799 800 ValidationError: [u'The submitted file is empty.'] 800 801 801 >>> type(f.clean( {'filename': 'name', 'content': 'Some File Content'}))802 >>> type(f.clean(SimpleUploadedFile('name', 'Some File Content'))) 802 803 <class 'django.newforms.fields.UploadedFile'> 803 804 804 >>> type(f.clean( {'filename': 'name', 'content': 'Some File Content'}, 'files/test4.pdf'))805 >>> type(f.clean(SimpleUploadedFile('name', 'Some File Content'), 'files/test4.pdf')) 805 806 <class 'django.newforms.fields.UploadedFile'> 806 807 807 808 # URLField ################################################################## -
tests/regressiontests/forms/forms.py
diff --git a/tests/regressiontests/forms/forms.py b/tests/regressiontests/forms/forms.py index 7fc206d..041fa40 100644
a b 1 1 # -*- coding: utf-8 -*- 2 2 tests = r""" 3 3 >>> from django.newforms import * 4 >>> from django.core.files.uploadedfile import SimpleUploadedFile 4 5 >>> import datetime 5 6 >>> import time 6 7 >>> import re … … not request.POST. 1465 1466 >>> print f 1466 1467 <tr><th>File1:</th><td><ul class="errorlist"><li>This field is required.</li></ul><input type="file" name="file1" /></td></tr> 1467 1468 1468 >>> f = FileForm(data={}, files={'file1': {'filename': 'name', 'content':''}}, auto_id=False)1469 >>> f = FileForm(data={}, files={'file1': SimpleUploadedFile('name', '')}, auto_id=False) 1469 1470 >>> print f 1470 1471 <tr><th>File1:</th><td><ul class="errorlist"><li>The submitted file is empty.</li></ul><input type="file" name="file1" /></td></tr> 1471 1472 … … not request.POST. 1473 1474 >>> print f 1474 1475 <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> 1475 1476 1476 >>> f = FileForm(data={}, files={'file1': {'filename': 'name', 'content':'some content'}}, auto_id=False)1477 >>> f = FileForm(data={}, files={'file1': SimpleUploadedFile('name', 'some content')}, auto_id=False) 1477 1478 >>> print f 1478 1479 <tr><th>File1:</th><td><input type="file" name="file1" /></td></tr> 1479 1480 >>> f.is_valid() -
tests/regressiontests/forms/tests.py
diff --git a/tests/regressiontests/forms/tests.py b/tests/regressiontests/forms/tests.py index bb0e30b..136e691 100644
a b from localflavor.za import tests as localflavor_za_tests 26 26 from regressions import tests as regression_tests 27 27 from util import tests as util_tests 28 28 from widgets import tests as widgets_tests 29 from warnings import filterwarnings 30 filterwarnings("ignore") 29 31 30 32 __test__ = { 31 33 'extra_tests': extra_tests, -
tests/regressiontests/test_client_regress/models.py
diff --git a/tests/regressiontests/test_client_regress/models.py b/tests/regressiontests/test_client_regress/models.py index a204ec3..7f89b3a 100644
a b from django.test import Client, TestCase 6 6 from django.core.urlresolvers import reverse 7 7 from django.core.exceptions import SuspiciousOperation 8 8 import os 9 import sha 9 10 10 11 class AssertContainsTests(TestCase): 11 12 def test_contains(self): … … class FileUploadTests(TestCase): 250 251 response = self.client.post('/test_client_regress/file_upload/', post_data) 251 252 self.assertEqual(response.status_code, 200) 252 253 254 def test_large_upload(self): 255 import tempfile 256 dir = tempfile.gettempdir() 257 258 (fd, name1) = tempfile.mkstemp(suffix='.file1', dir=dir) 259 file1 = os.fdopen(fd, 'w+b') 260 file1.write('a' * (2 ** 21)) 261 file1.seek(0) 262 263 (fd, name2) = tempfile.mkstemp(suffix='.file2', dir=dir) 264 file2 = os.fdopen(fd, 'w+b') 265 file2.write('a' * (10 * 2 ** 20)) 266 file2.seek(0) 267 268 # This file contains chinese symbols for a name. 269 name3 = os.path.join(dir, u'test_中文_Orl\u00e9ans.jpg') 270 file3 = open(name3, 'w+b') 271 file3.write('b' * (2 ** 10)) 272 file3.seek(0) 273 274 post_data = { 275 'name': 'Ringo', 276 'file_field1': file1, 277 'file_field2': file2, 278 'file_unicode': file3, 279 } 280 281 for key in post_data.keys(): 282 try: 283 post_data[key + '_hash'] = sha.new(post_data[key].read()).hexdigest() 284 post_data[key].seek(0) 285 except AttributeError: 286 post_data[key + '_hash'] = sha.new(post_data[key]).hexdigest() 287 288 response = self.client.post('/test_client_regress/file_upload_verify/', post_data) 289 290 for name in (name1, name2, name3): 291 try: 292 os.unlink(name) 293 except: 294 pass 295 296 self.assertEqual(response.status_code, 200) 297 298 253 299 class LoginTests(TestCase): 254 300 fixtures = ['testdata'] 255 301 -
tests/regressiontests/test_client_regress/urls.py
diff --git a/tests/regressiontests/test_client_regress/urls.py b/tests/regressiontests/test_client_regress/urls.py index dc26d12..a667766 100644
a b import views 4 4 urlpatterns = patterns('', 5 5 (r'^no_template_view/$', views.no_template_view), 6 6 (r'^file_upload/$', views.file_upload_view), 7 (r'^file_upload_verify/$', views.file_upload_view_verify), 7 8 (r'^staff_only/$', views.staff_only_view), 8 9 (r'^get_view/$', views.get_view), 9 10 url(r'^arg_view/(?P<name>.+)/$', views.view_with_argument, name='arg_view'), -
tests/regressiontests/test_client_regress/views.py
diff --git a/tests/regressiontests/test_client_regress/views.py b/tests/regressiontests/test_client_regress/views.py index 9632c17..1ad3ea1 100644
a b import os 3 3 from django.contrib.auth.decorators import login_required 4 4 from django.http import HttpResponse, HttpResponseRedirect, HttpResponseServerError 5 5 from django.core.exceptions import SuspiciousOperation 6 import sha 6 7 7 8 def no_template_view(request): 8 9 "A simple view that expects a GET request, and returns a rendered template" … … def file_upload_view(request): 13 14 Check that a file upload can be updated into the POST dictionary without 14 15 going pear-shaped. 15 16 """ 17 from django.core.files.uploadedfile import UploadedFile 16 18 form_data = request.POST.copy() 17 19 form_data.update(request.FILES) 18 if isinstance(form_data ['file_field'], dict) and isinstance(form_data['name'], unicode):20 if isinstance(form_data.get('file_field'), UploadedFile) and isinstance(form_data['name'], unicode): 19 21 # If a file is posted, the dummy client should only post the file name, 20 22 # not the full path. 21 if os.path.dirname(form_data['file_field'] ['filename']) != '':23 if os.path.dirname(form_data['file_field'].file_name) != '': 22 24 return HttpResponseServerError() 23 25 return HttpResponse('') 24 26 else: … … def staff_only_view(request): 31 33 else: 32 34 raise SuspiciousOperation() 33 35 36 def file_upload_view_verify(request): 37 """ 38 Use the sha digest hash to verify the uploaded contents. 39 """ 40 from django.core.files.uploadedfile import UploadedFile 41 form_data = request.POST.copy() 42 form_data.update(request.FILES) 43 44 # Check to see if unicode names worked out. 45 if not request.FILES['file_unicode'].file_name.endswith(u'test_\u4e2d\u6587_Orl\xe9ans.jpg'): 46 return HttpResponseServerError() 47 48 for key, value in form_data.items(): 49 if key.endswith('_hash'): 50 continue 51 if key + '_hash' not in form_data: 52 continue 53 submitted_hash = form_data[key + '_hash'] 54 if isinstance(value, UploadedFile): 55 new_hash = sha.new(value.read()).hexdigest() 56 else: 57 new_hash = sha.new(value).hexdigest() 58 if new_hash != submitted_hash: 59 return HttpResponseServerError() 60 61 return HttpResponse('') 62 34 63 def get_view(request): 35 64 "A simple login protected view" 36 65 return HttpResponse("Hello world") … … def view_with_argument(request, name): 51 80 def login_protected_redirect_view(request): 52 81 "A view that redirects all requests to the GET view" 53 82 return HttpResponseRedirect('/test_client_regress/get_view/') 54 login_protected_redirect_view = login_required(login_protected_redirect_view) 55 No newline at end of file 83 login_protected_redirect_view = login_required(login_protected_redirect_view)