Ticket #2070: 2070-r7728.patch
File 2070-r7728.patch, 114.5 KB (added by , 16 years ago) |
---|
-
AUTHORS
diff --git a/AUTHORS b/AUTHORS index 94dc889..28ac6b7 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 006ab42..2c9720d 100644
a b MEDIA_ROOT = '' 231 231 # Example: "http://media.lawrence.com" 232 232 MEDIA_URL = '' 233 233 234 # List of upload handler classes to be applied in order. 235 FILE_UPLOAD_HANDLERS = ( 236 'django.core.files.uploadhandler.MemoryFileUploadHandler', 237 'django.core.files.uploadhandler.TemporaryFileUploadHandler', 238 ) 239 240 # Maximum size, in bytes, of a request before it will be streamed to the 241 # file system instead of into memory. 242 FILE_UPLOAD_MAX_MEMORY_SIZE = 2621440 # i.e. 2.5 MB 243 244 # Directory in which upload streamed files will be temporarily saved. A value of 245 # `None` will make Django use the operating system's default temporary directory 246 # (i.e. "/tmp" on *nix systems). 247 FILE_UPLOAD_TEMP_DIR = None 248 234 249 # Default formatting for date objects. See all available format strings here: 235 250 # http://www.djangoproject.com/documentation/templates/#now 236 251 DATE_FORMAT = 'N j, Y' -
new file django/core/files/locks.py
diff --git a/django/core/files/__init__.py b/django/core/files/__init__.py new file mode 100644 index 0000000..e69de29 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..51cec17
- + 1 """ 2 Classes representing uploaded files. 3 """ 4 5 import os 6 try: 7 from cStringIO import StringIO 8 except ImportError: 9 from StringIO import StringIO 10 11 __all__ = ('UploadedFile', 'TemporaryUploadedFile', 'InMemoryUploadedFile') 12 13 class UploadedFile(object): 14 """ 15 A abstract uploadded file (``TemporaryUploadedFile`` and 16 ``InMemoryUploadedFile`` are the built-in concrete subclasses). 17 18 An ``UploadedFile`` object behaves somewhat like a file object and 19 represents some file data that the user submitted with a form. 20 """ 21 DEFAULT_CHUNK_SIZE = 64 * 2**10 22 23 def __init__(self, file_name=None, content_type=None, file_size=None, charset=None): 24 self.file_name = file_name 25 self.file_size = file_size 26 self.content_type = content_type 27 self.charset = charset 28 29 def __repr__(self): 30 return "<%s: %s (%s)>" % (self.__class__.__name__, self.file_name, self.content_type) 31 32 def _set_file_name(self, name): 33 # Sanitize the file name so that it can't be dangerous. 34 if name is not None: 35 # Just use the basename of the file -- anything else is dangerous. 36 name = os.path.basename(name) 37 38 # File names longer than 255 characters can cause problems on older OSes. 39 if len(name) > 255: 40 name, ext = os.path.splitext(name) 41 name = name[:255 - len(ext)] + ext 42 43 self._file_name = name 44 45 def _get_file_name(self): 46 return self._file_name 47 48 file_name = property(_get_file_name, _set_file_name) 49 50 def chunk(self, chunk_size=None): 51 """ 52 Read the file and yield chucks of ``chunk_size`` bytes (defaults to 53 ``UploadedFile.DEFAULT_CHUNK_SIZE``). 54 """ 55 if not chunk_size: 56 chunk_size = UploadedFile.DEFAULT_CHUNK_SIZE 57 58 if hasattr(self, 'seek'): 59 self.seek(0) 60 # Assume the pointer is at zero... 61 counter = self.file_size 62 63 while counter > 0: 64 yield self.read(chunk_size) 65 counter -= chunk_size 66 67 def multiple_chunks(self, chunk_size=None): 68 """ 69 Returns ``True`` if you can expect multiple chunks. 70 71 NB: If a particular file representation is in memory, subclasses should 72 always return ``False`` -- there's no good reason to read from memory in 73 chunks. 74 """ 75 if not chunk_size: 76 chunk_size = UploadedFile.DEFAULT_CHUNK_SIZE 77 return self.file_size < chunk_size 78 79 # Abstract methods; subclasses *must* default read() and probably should 80 # define open/close. 81 def read(self, num_bytes=None): 82 raise NotImplementedError() 83 84 def open(self): 85 pass 86 87 def close(self): 88 pass 89 90 # Backwards-compatible support for uploaded-files-as-dictionaries. 91 def __getitem__(self, key): 92 import warnings 93 warnings.warn( 94 message = "The dictionary access of uploaded file objects is deprecated. Use the new object interface instead.", 95 category = DeprecationWarning, 96 stacklevel = 2 97 ) 98 backwards_translate = { 99 'filename': 'file_name', 100 'content-type': 'content_type', 101 } 102 103 if key == 'content': 104 return self.read() 105 elif key == 'filename': 106 return self.file_name 107 elif key == 'content-type': 108 return self.content_type 109 else: 110 return getattr(self, key) 111 112 class TemporaryUploadedFile(UploadedFile): 113 """ 114 A file uploaded to a temporary location (i.e. stream-to-disk). 115 """ 116 117 def __init__(self, file, file_name, content_type, file_size, charset): 118 super(TemporaryUploadedFile, self).__init__(file_name, content_type, file_size, charset) 119 self.file = file 120 self.path = file.name 121 self.file.seek(0) 122 123 def temporary_file_path(self): 124 """ 125 Returns the full path of this file. 126 """ 127 return self.path 128 129 def read(self, *args, **kwargs): 130 return self.file.read(*args, **kwargs) 131 132 def open(self): 133 self.seek(0) 134 135 def seek(self, *args, **kwargs): 136 self.file.seek(*args, **kwargs) 137 138 class InMemoryUploadedFile(UploadedFile): 139 """ 140 A file uploaded into memory (i.e. stream-to-memory). 141 """ 142 def __init__(self, file, field_name, file_name, content_type, charset, file_size): 143 super(InMemoryUploadedFile, self).__init__(file_name, content_type, charset, file_size) 144 self.file = file 145 self.field_name = field_name 146 self.file.seek(0) 147 148 def seek(self, *args, **kwargs): 149 self.file.seek(*args, **kwargs) 150 151 def open(self): 152 self.seek(0) 153 154 def read(self, *args, **kwargs): 155 return self.file.read(*args, **kwargs) 156 157 def chunk(self, chunk_size=None): 158 self.file.seek(0) 159 yield self.read() 160 161 def multiple_chunks(self, chunk_size=None): 162 # Since it's in memory, we'll never have multiple chunks. 163 return False 164 165 class SimpleUploadedFile(InMemoryUploadedFile): 166 """ 167 A simple representation of a file, which just has content, size, and a name. 168 """ 169 def __init__(self, name, content, content_type='text/plain'): 170 self.file = StringIO(content or '') 171 self.file_name = name 172 self.field_name = None 173 self.file_size = len(content or '') 174 self.content_type = content_type 175 self.charset = None 176 self.file.seek(0) 177 178 def from_dict(cls, file_dict): 179 """ 180 Creates a SimpleUploadedFile object from 181 a dictionary object with the following keys: 182 - filename 183 - content-type 184 - content 185 """ 186 return cls(file_dict['filename'], 187 file_dict['content'], 188 file_dict.get('content-type', 'text/plain')) 189 190 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..0349539
- + 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 StopFutureHandlers(UploadFileException): 50 """ 51 Upload handers that have handled a file and do not want future handlers to 52 run should raise this exception instead of returning None. 53 """ 54 pass 55 56 class FileUploadHandler(object): 57 """ 58 Base class for streaming upload handlers. 59 """ 60 chunk_size = 64 * 2 ** 10 #: The default chunk size is 64 KB. 61 62 def __init__(self, request=None): 63 self.file_name = None 64 self.content_type = None 65 self.content_length = None 66 self.charset = None 67 self.request = request 68 69 def handle_raw_input(self, input_data, META, content_length, boundary, encoding=None): 70 """ 71 Handle the raw input from the client. 72 73 Parameters: 74 75 :input_data: 76 An object that supports reading via .read(). 77 :META: 78 ``request.META``. 79 :content_length: 80 The (integer) value of the Content-Length header from the 81 client. 82 :boundary: The boundary from the Content-Type header. Be sure to 83 prepend two '--'. 84 """ 85 pass 86 87 def new_file(self, field_name, file_name, content_type, content_length, charset=None): 88 """ 89 Signal that a new file has been started. 90 91 Warning: As with any data from the client, you should not trust 92 content_length (and sometimes won't even get it). 93 """ 94 self.field_name = field_name 95 self.file_name = file_name 96 self.content_type = content_type 97 self.content_length = content_length 98 self.charset = charset 99 100 def receive_data_chunk(self, raw_data, start): 101 """ 102 Receive data from the streamed upload parser. ``start`` is the position 103 in the file of the chunk. 104 """ 105 raise NotImplementedError() 106 107 def file_complete(self, file_size): 108 """ 109 Signal that a file has completed. File size corresponds to the actual 110 size accumulated by all the chunks. 111 112 Subclasses must should return a valid ``UploadedFile`` object. 113 """ 114 raise NotImplementedError() 115 116 def upload_complete(self): 117 """ 118 Signal that the upload is complete. Subclasses should perform cleanup 119 that is necessary for this handler. 120 """ 121 pass 122 123 class TemporaryFileUploadHandler(FileUploadHandler): 124 """ 125 Upload handler that streams data into a temporary file. 126 """ 127 def __init__(self, *args, **kwargs): 128 super(TemporaryFileUploadHandler, self).__init__(*args, **kwargs) 129 130 def new_file(self, file_name, *args, **kwargs): 131 """ 132 Create the file object to append to as data is coming in. 133 """ 134 super(TemporaryFileUploadHandler, self).new_file(file_name, *args, **kwargs) 135 self.file = TemporaryFile(settings.FILE_UPLOAD_TEMP_DIR) 136 self.write = self.file.write 137 138 def receive_data_chunk(self, raw_data, start): 139 self.write(raw_data) 140 141 def file_complete(self, file_size): 142 self.file.seek(0) 143 return TemporaryUploadedFile(self.file, self.file_name, 144 self.content_type, file_size, 145 self.charset) 146 147 class MemoryFileUploadHandler(FileUploadHandler): 148 """ 149 File upload handler to stream uploads into memory (used for small files). 150 """ 151 152 def handle_raw_input(self, input_data, META, content_length, boundary, encoding=None): 153 """ 154 Use the content_length to signal whether or not this handler should be in use. 155 """ 156 # Check the content-length header to see if we should 157 # If the the post is too large, we cannot use the Memory handler. 158 if content_length > settings.FILE_UPLOAD_MAX_MEMORY_SIZE: 159 self.activated = False 160 else: 161 self.activated = True 162 163 def new_file(self, *args, **kwargs): 164 super(MemoryFileUploadHandler, self).new_file(*args, **kwargs) 165 if self.activated: 166 self.file = StringIO() 167 raise StopFutureHandlers() 168 169 def receive_data_chunk(self, raw_data, start): 170 """ 171 Add the data to the StringIO file. 172 """ 173 if self.activated: 174 self.file.write(raw_data) 175 else: 176 return raw_data 177 178 def file_complete(self, file_size): 179 """ 180 Return a file object if we're activated. 181 """ 182 if not self.activated: 183 return 184 185 return InMemoryUploadedFile(self.file, self.field_name, self.file_name, 186 self.content_type, self.charset, file_size) 187 188 class TemporaryFile(object): 189 """ 190 A temporary file that tries to delete itself when garbage collected. 191 """ 192 def __init__(self, dir): 193 if not dir: 194 dir = tempfile.gettempdir() 195 try: 196 (fd, name) = tempfile.mkstemp(suffix='.upload', dir=dir) 197 self.file = os.fdopen(fd, 'w+b') 198 except (OSError, IOError): 199 raise OSError("Could not create temporary file for uploading, have you set settings.FILE_UPLOAD_TEMP_DIR correctly?") 200 self.name = name 201 202 def __getattr__(self, name): 203 a = getattr(self.__dict__['file'], name) 204 if type(a) != type(0): 205 setattr(self, name, a) 206 return a 207 208 def __del__(self): 209 try: 210 os.unlink(self.name) 211 except OSError: 212 pass 213 214 def load_handler(path, *args, **kwargs): 215 """ 216 Given a path to a handler, return an instance of that handler. 217 218 E.g.:: 219 >>> load_handler('django.core.files.uploadhandler.TemporaryFileUploadHandler', request) 220 <TemporaryFileUploadHandler object at 0x...> 221 222 """ 223 i = path.rfind('.') 224 module, attr = path[:i], path[i+1:] 225 try: 226 mod = __import__(module, {}, {}, [attr]) 227 except ImportError, e: 228 raise ImproperlyConfigured('Error importing upload handler module %s: "%s"' % (module, e)) 229 except ValueError, e: 230 raise ImproperlyConfigured('Error importing upload handler module. Is FILE_UPLOAD_HANDLERS a correctly defined list or tuple?') 231 try: 232 cls = getattr(mod, attr) 233 except AttributeError: 234 raise ImproperlyConfigured('Module "%s" does not define a "%s" upload handler backend' % (module, attr)) 235 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 14a4804..24add11 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): 451 453 def _get_FIELD_size(self, field): 452 454 return os.path.getsize(self._get_FIELD_filename(field)) 453 455 454 def _save_FIELD_file(self, field, filename, raw_ contents, save=True):456 def _save_FIELD_file(self, field, filename, raw_field, save=True): 455 457 directory = field.get_directory_name() 456 458 try: # Create the date-based directory if it doesn't exist. 457 459 os.makedirs(os.path.join(settings.MEDIA_ROOT, directory)) 458 460 except OSError: # Directory probably already exists. 459 461 pass 462 463 # 464 # Check for old-style usage (files-as-dictionaries). Warn here first 465 # since there are multiple locations where we need to support both new 466 # and old usage. 467 # 468 if isinstance(raw_field, dict): 469 import warnings 470 warnings.warn( 471 message = "Representing uploaded files as dictionaries is"\ 472 " deprected. Use django.core.files.SimpleUploadedFile"\ 473 " instead.", 474 category = DeprecationWarning, 475 stacklevel = 2 476 ) 477 from django.core.files.uploadedfile import SimpleUploadedFile 478 raw_field = SimpleUploadedFile.from_dict(raw_field) 479 480 elif isinstance(raw_field, basestring): 481 import warnings 482 warnings.warn( 483 message = "Representing uploaded files as strings is "\ 484 " deprecated. Use django.core.files.SimpleUploadedFile "\ 485 " instead.", 486 category = DeprecationWarning, 487 stacklevel = 2 488 ) 489 from django.core.files.uploadedfile import SimpleUploadedFile 490 raw_field = SimpleUploadedFile(filename, raw_field) 491 492 if filename is None: 493 filename = raw_field.file_name 494 460 495 filename = field.get_filename(filename) 461 496 497 # 462 498 # If the filename already exists, keep adding an underscore to the name of 463 499 # the file until the filename doesn't exist. 500 # 464 501 while os.path.exists(os.path.join(settings.MEDIA_ROOT, filename)): 465 502 try: 466 503 dot_index = filename.rindex('.') … … class Model(object): 468 505 filename += '_' 469 506 else: 470 507 filename = filename[:dot_index] + '_' + filename[dot_index:] 508 # 509 # Save the file name on the object and write the file to disk 510 # 471 511 472 # Write the file to disk.473 512 setattr(self, field.attname, filename) 474 513 475 514 full_filename = self._get_FIELD_filename(field) 476 fp = open(full_filename, 'wb') 477 fp.write(raw_contents) 478 fp.close() 515 516 if hasattr(raw_field, 'temporary_file_path'): 517 # This file has a file path that we can move. 518 raw_field.close() 519 file_move_safe(raw_field.temporary_file_path(), full_filename) 520 521 else: 522 # This is a normal uploadedfile that we can stream. 523 fp = open(full_filename, 'wb') 524 locks.lock(fp, locks.LOCK_EX) 525 for chunk in raw_field.chunk(): 526 fp.write(chunk) 527 locks.unlock(fp) 528 fp.close() 479 529 480 530 # Save the width and/or height, if applicable. 481 531 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 a893c25..d088729 100644
a b class FileField(Field): 808 808 setattr(cls, 'get_%s_filename' % self.name, curry(cls._get_FIELD_filename, field=self)) 809 809 setattr(cls, 'get_%s_url' % self.name, curry(cls._get_FIELD_url, field=self)) 810 810 setattr(cls, 'get_%s_size' % self.name, curry(cls._get_FIELD_size, field=self)) 811 setattr(cls, 'save_%s_file' % self.name, lambda instance, filename, raw_ contents, save=True: instance._save_FIELD_file(self, filename, raw_contents, save))811 setattr(cls, 'save_%s_file' % self.name, lambda instance, filename, raw_field, save=True: instance._save_FIELD_file(self, filename, raw_field, save)) 812 812 dispatcher.connect(self.delete_file, signal=signals.post_delete, sender=cls) 813 813 814 814 def delete_file(self, instance): … … class FileField(Field): 831 831 if new_data.get(upload_field_name, False): 832 832 func = getattr(new_object, 'save_%s_file' % self.name) 833 833 if rel: 834 f unc(new_data[upload_field_name][0]["filename"], new_data[upload_field_name][0]["content"], save)834 file = new_data[upload_field_name][0] 835 835 else: 836 func(new_data[upload_field_name]["filename"], new_data[upload_field_name]["content"], save) 836 file = new_data[upload_field_name] 837 838 # Backwards-compatible support for files-as-dictionaries. 839 # We don't need to raise a warning because Model._save_FIELD_file will 840 # do so for us. 841 try: 842 file_name = file.file_name 843 except AttributeError: 844 file_name = file['filename'] 845 846 func(file_name, file, save) 837 847 838 848 def get_directory_name(self): 839 849 return os.path.normpath(force_unicode(datetime.datetime.now().strftime(smart_str(self.upload_to)))) … … class FileField(Field): 846 856 def save_form_data(self, instance, data): 847 857 from django.newforms.fields import UploadedFile 848 858 if data and isinstance(data, UploadedFile): 849 getattr(instance, "save_%s_file" % self.name)(data.filename, data. content, save=False)859 getattr(instance, "save_%s_file" % self.name)(data.filename, data.data, save=False) 850 860 851 861 def formfield(self, **kwargs): 852 862 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..8bed568
- + 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.core.exceptions import SuspiciousOperation 10 from django.utils.datastructures import MultiValueDict 11 from django.utils.encoding import force_unicode 12 from django.utils.text import unescape_entities 13 from django.core.files.uploadhandler import StopUpload, SkipFile, StopFutureHandlers 14 15 __all__ = ('MultiPartParser','MultiPartParserError','InputStreamExhausted') 16 17 class MultiPartParserError(Exception): 18 pass 19 20 class InputStreamExhausted(Exception): 21 """ 22 No more reads are allowed from this device. 23 """ 24 pass 25 26 RAW = "raw" 27 FILE = "file" 28 FIELD = "field" 29 30 class MultiPartParser(object): 31 """ 32 A rfc2388 multipart/form-data parser. 33 34 ``MultiValueDict.parse()`` reads the input stream in ``chunk_size`` chunks 35 and returns a tuple of ``(MultiValueDict(POST), MultiValueDict(FILES))``. If 36 ``file_upload_dir`` is defined files will be streamed to temporary files in 37 that directory. 38 """ 39 def __init__(self, META, input_data, upload_handlers, encoding=None): 40 """ 41 Initialize the MultiPartParser object. 42 43 :META: 44 The standard ``META`` dictionary in Django request objects. 45 :input_data: 46 The raw post data, as a bytestring. 47 :upload_handler: 48 An UploadHandler instance that performs operations on the uploaded 49 data. 50 :encoding: 51 The encoding with which to treat the incoming data. 52 """ 53 54 # 55 # Content-Type should containt multipart and the boundary information. 56 # 57 58 content_type = META.get('HTTP_CONTENT_TYPE', META.get('CONTENT_TYPE', '')) 59 if not content_type.startswith('multipart/'): 60 raise MultiPartParserError('Invalid Content-Type: %s' % content_type) 61 62 # Parse the header to get the boundary to split the parts. 63 ctypes, opts = parse_header(content_type) 64 boundary = opts.get('boundary') 65 if not boundary or not cgi.valid_boundary(boundary): 66 raise MultiPartParserError('Invalid boundary in multipart: %s' % boundary) 67 68 69 # 70 # Content-Length should contain the length of the body we are about 71 # to receive. 72 # 73 try: 74 content_length = int(META.get('HTTP_CONTENT_LENGTH', META.get('CONTENT_LENGTH',0))) 75 except (ValueError, TypeError): 76 # For now set it to 0; we'll try again later on down. 77 content_length = 0 78 79 if content_length <= 0: 80 # This means we shouldn't continue...raise an error. 81 raise MultiPartParserError("Invalid content length: %r" % content_length) 82 83 self._boundary = boundary 84 self._input_data = input_data 85 86 # For compatibility with low-level network APIs (with 32-bit integers), 87 # the chunk size should be < 2^31, but still divisible by 4. 88 self._chunk_size = min(2**31-4, *[x.chunk_size for x in upload_handlers if x.chunk_size]) 89 90 self._meta = META 91 self._encoding = encoding or settings.DEFAULT_CHARSET 92 self._content_length = content_length 93 self._upload_handlers = upload_handlers 94 95 def parse(self): 96 """ 97 Parse the POST data and break it into a FILES MultiValueDict and a POST 98 MultiValueDict. 99 100 Returns a tuple containing the POST and FILES dictionary, respectively. 101 """ 102 # We have to import QueryDict down here to avoid a circular import. 103 from django.http import QueryDict 104 105 encoding = self._encoding 106 handlers = self._upload_handlers 107 108 limited_input_data = LimitBytes(self._input_data, self._content_length) 109 110 # See if the handler will want to take care of the parsing. 111 # This allows overriding everything if somebody wants it. 112 for handler in handlers: 113 result = handler.handle_raw_input(limited_input_data, 114 self._meta, 115 self._content_length, 116 self._boundary, 117 encoding) 118 if result is not None: 119 return result[0], result[1] 120 121 # Create the data structures to be used later. 122 self._post = QueryDict('', mutable=True) 123 self._files = MultiValueDict() 124 125 # Instantiate the parser and stream: 126 stream = LazyStream(ChunkIter(limited_input_data, self._chunk_size)) 127 128 # Whether or not to signal a file-completion at the beginning of the loop. 129 old_field_name = None 130 counters = [0] * len(handlers) 131 132 try: 133 for item_type, meta_data, field_stream in Parser(stream, self._boundary): 134 if old_field_name: 135 # We run this at the beginning of the next loop 136 # since we cannot be sure a file is complete until 137 # we hit the next boundary/part of the multipart content. 138 self.handle_file_complete(old_field_name, counters) 139 140 try: 141 disposition = meta_data['content-disposition'][1] 142 field_name = disposition['name'].strip() 143 except (KeyError, IndexError, AttributeError): 144 continue 145 146 transfer_encoding = meta_data.get('content-transfer-encoding') 147 field_name = force_unicode(field_name, encoding, errors='replace') 148 149 if item_type == FIELD: 150 # This is a post field, we can just set it in the post 151 if transfer_encoding == 'base64': 152 raw_data = field_stream.read() 153 try: 154 data = str(raw_data).decode('base64') 155 except: 156 data = raw_data 157 else: 158 data = field_stream.read() 159 160 self._post.appendlist(field_name, 161 force_unicode(data, encoding, errors='replace')) 162 elif item_type == FILE: 163 # This is a file, use the handler... 164 file_successful = True 165 file_name = disposition.get('filename') 166 if not file_name: 167 continue 168 file_name = force_unicode(file_name, encoding, errors='replace') 169 file_name = self.IE_sanitize(unescape_entities(file_name)) 170 171 content_type = meta_data.get('content-type', ('',))[0].strip() 172 try: 173 charset = meta_data.get('content-type', (0,{}))[1].get('charset', None) 174 except: 175 charset = None 176 177 try: 178 content_length = int(meta_data.get('content-length')[0]) 179 except (IndexError, TypeError, ValueError): 180 content_length = None 181 182 counters = [0] * len(handlers) 183 try: 184 for handler in handlers: 185 try: 186 handler.new_file(field_name, file_name, 187 content_type, content_length, 188 charset) 189 except StopFutureHandlers: 190 break 191 192 for chunk in field_stream: 193 if transfer_encoding == 'base64': 194 # We only special-case base64 transfer encoding 195 try: 196 chunk = str(chunk).decode('base64') 197 except Exception, e: 198 # Since this is only a chunk, any error is an unfixable error. 199 raise MultiPartParserError("Could not decode base64 data: %r" % e) 200 201 for i, handler in enumerate(handlers): 202 chunk_length = len(chunk) 203 chunk = handler.receive_data_chunk(chunk, 204 counters[i]) 205 counters[i] += chunk_length 206 if chunk is None: 207 # If the chunk received by the handler is None, then don't continue. 208 break 209 210 except SkipFile, e: 211 file_successful = False 212 # Just use up the rest of this file... 213 exhaust(field_stream) 214 else: 215 # Handle file upload completions on next iteration. 216 old_field_name = field_name 217 else: 218 # If this is neither a FIELD or a FILE, just exhaust the stream. 219 exhaust(stream) 220 except StopUpload, e: 221 if not e.connection_reset: 222 exhaust(limited_input_data) 223 else: 224 # Make sure that the request data is all fed 225 exhaust(limited_input_data) 226 227 # Signal that the upload has completed. 228 for handler in handlers: 229 retval = handler.upload_complete() 230 if retval: 231 break 232 233 return self._post, self._files 234 235 def handle_file_complete(self, old_field_name, counters): 236 """ 237 Handle all the signalling that takes place when a file is complete. 238 """ 239 for i, handler in enumerate(self._upload_handlers): 240 file_obj = handler.file_complete(counters[i]) 241 if file_obj: 242 # If it returns a file object, then set the files dict. 243 self._files.appendlist(force_unicode(old_field_name, 244 self._encoding, 245 errors='replace'), 246 file_obj) 247 break 248 249 def IE_sanitize(self, filename): 250 """Cleanup filename from Internet Explorer full paths.""" 251 return filename and filename[filename.rfind("\\")+1:].strip() 252 253 class LazyStream(object): 254 """ 255 The LazyStream wrapper allows one to get and "unget" bytes from a stream. 256 257 Given a producer object (an iterator that yields bytestrings), the 258 LazyStream object will support iteration, reading, and keeping a "look-back" 259 variable in case you need to "unget" some bytes. 260 """ 261 def __init__(self, producer, length=None): 262 """ 263 Every LazyStream must have a producer when instantiated. 264 265 A producer is an iterable that returns a string each time it 266 is called. 267 """ 268 self._producer = producer 269 self._empty = False 270 self._leftover = '' 271 self.length = length 272 self._position = 0 273 self._remaining = length 274 275 # These fields are to do sanity checking to make sure we don't 276 # have infinite loops getting/ungetting from the stream. The 277 # purpose overall is to raise an exception if we perform lots 278 # of stream get/unget gymnastics without getting 279 # anywhere. Naturally this is not sound, but most probably 280 # would indicate a bug if the exception is raised. 281 282 # largest position tell us how far this lazystream has ever 283 # been advanced 284 self._largest_position = 0 285 286 # "modifications since" will start at zero and increment every 287 # time the position is modified but a new largest position is 288 # not achieved. 289 self._modifications_since = 0 290 291 def tell(self): 292 return self.position 293 294 def read(self, size=None): 295 def parts(): 296 remaining = (size is not None and [size] or [self._remaining])[0] 297 # do the whole thing in one shot if no limit was provided. 298 if remaining is None: 299 yield ''.join(self) 300 return 301 302 # otherwise do some bookkeeping to return exactly enough 303 # of the stream and stashing any extra content we get from 304 # the producer 305 while remaining != 0: 306 assert remaining > 0, 'remaining bytes to read should never go negative' 307 308 chunk = self.next() 309 310 emitting = chunk[:remaining] 311 self.unget(chunk[remaining:]) 312 remaining -= len(emitting) 313 yield emitting 314 315 out = ''.join(parts()) 316 return out 317 318 def next(self): 319 """ 320 Used when the exact number of bytes to read is unimportant. 321 322 This procedure just returns whatever is chunk is conveniently returned 323 from the iterator instead. Useful to avoid unnecessary bookkeeping if 324 performance is an issue. 325 """ 326 if self._leftover: 327 output = self._leftover 328 self._leftover = '' 329 else: 330 output = self._producer.next() 331 self.position += len(output) 332 return output 333 334 def close(self): 335 """ 336 Used to invalidate/disable this lazy stream. 337 338 Replaces the producer with an empty list. Any leftover bytes that have 339 already been read will still be reported upon read() and/or next(). 340 """ 341 self._producer = [] 342 343 def __iter__(self): 344 return self 345 346 def unget(self, bytes): 347 """ 348 Places bytes back onto the front of the lazy stream. 349 350 Future calls to read() will return those bytes first. The 351 stream position and thus tell() will be rewound. 352 """ 353 self.position -= len(bytes) 354 self._leftover = ''.join([bytes, self._leftover]) 355 356 def _set_position(self, value): 357 if value > self._largest_position: 358 self._modifications_since = 0 359 self._largest_position = value 360 else: 361 self._modifications_since += 1 362 if self._modifications_since > 500: 363 raise SuspiciousOperation( 364 "The multipart parser got stuck, which shouldn't happen with" 365 " normal uploaded files. Check for malicious upload activity;" 366 " if there is none, report this to the Django developers." 367 ) 368 369 self._position = value 370 371 position = property(lambda self: self._position, _set_position) 372 373 class ChunkIter(object): 374 """ 375 An iterable that will yield chunks of data. Given a file-like object as the 376 constructor, this object will yield chunks of read operations from that 377 object. 378 """ 379 def __init__(self, flo, chunk_size=64 * 1024): 380 self.flo = flo 381 self.chunk_size = chunk_size 382 383 def next(self): 384 try: 385 data = self.flo.read(self.chunk_size) 386 except InputStreamExhausted: 387 raise StopIteration() 388 if data: 389 return data 390 else: 391 raise StopIteration() 392 393 def __iter__(self): 394 return self 395 396 class LimitBytes(object): 397 """ Limit bytes for a file object. """ 398 def __init__(self, fileobject, length): 399 self._file = fileobject 400 self.remaining = length 401 402 def read(self, num_bytes=None): 403 """ 404 Read data from the underlying file. 405 If you ask for too much or there isn't anything left, 406 this will raise an InputStreamExhausted error. 407 """ 408 if self.remaining <= 0: 409 raise InputStreamExhausted() 410 if num_bytes is None: 411 num_bytes = self.remaining 412 else: 413 num_bytes = min(num_bytes, self.remaining) 414 self.remaining -= num_bytes 415 return self._file.read(num_bytes) 416 417 class InterBoundaryIter(object): 418 """ 419 A Producer that will iterate over boundaries. 420 """ 421 def __init__(self, stream, boundary): 422 self._stream = stream 423 self._boundary = boundary 424 425 def __iter__(self): 426 return self 427 428 def next(self): 429 try: 430 return LazyStream(BoundaryIter(self._stream, self._boundary)) 431 except InputStreamExhausted: 432 raise StopIteration() 433 434 class BoundaryIter(object): 435 """ 436 A Producer that is sensitive to boundaries. 437 438 Will happily yield bytes until a boundary is found. Will yield the bytes 439 before the boundary, throw away the boundary bytes themselves, and push the 440 post-boundary bytes back on the stream. 441 442 The future calls to .next() after locating the boundary will raise a 443 StopIteration exception. 444 """ 445 446 def __init__(self, stream, boundary): 447 self._stream = stream 448 self._boundary = boundary 449 self._done = False 450 # rollback an additional six bytes because the format is like 451 # this: CRLF<boundary>[--CRLF] 452 self._rollback = len(boundary) + 6 453 454 # Try to use mx fast string search if available. Otherwise 455 # use Python find. Wrap the latter for consistency. 456 unused_char = self._stream.read(1) 457 if not unused_char: 458 raise InputStreamExhausted() 459 self._stream.unget(unused_char) 460 try: 461 from mx.TextTools import FS 462 self._fs = FS(boundary).find 463 except ImportError: 464 self._fs = lambda data: data.find(boundary) 465 466 def __iter__(self): 467 return self 468 469 def next(self): 470 if self._done: 471 raise StopIteration() 472 473 stream = self._stream 474 rollback = self._rollback 475 476 bytes_read = 0 477 chunks = [] 478 for bytes in stream: 479 bytes_read += len(bytes) 480 chunks.append(bytes) 481 if bytes_read > rollback: 482 break 483 if not bytes: 484 break 485 else: 486 self._done = True 487 488 if not chunks: 489 raise StopIteration() 490 491 chunk = ''.join(chunks) 492 boundary = self._find_boundary(chunk, len(chunk) < self._rollback) 493 494 if boundary: 495 end, next = boundary 496 stream.unget(chunk[next:]) 497 self._done = True 498 return chunk[:end] 499 else: 500 # make sure we dont treat a partial boundary (and 501 # its separators) as data 502 if not chunk[:-rollback]:# and len(chunk) >= (len(self._boundary) + 6): 503 # There's nothing left, we should just return and mark as done. 504 self._done = True 505 return chunk 506 else: 507 stream.unget(chunk[-rollback:]) 508 return chunk[:-rollback] 509 510 def _find_boundary(self, data, eof = False): 511 """ 512 Finds a multipart boundary in data. 513 514 Should no boundry exist in the data None is returned instead. Otherwise 515 a tuple containing the indices of the following are returned: 516 517 * the end of current encapsulation 518 * the start of the next encapsulation 519 """ 520 index = self._fs(data) 521 if index < 0: 522 return None 523 else: 524 end = index 525 next = index + len(self._boundary) 526 data_len = len(data) - 1 527 # backup over CRLF 528 if data[max(0,end-1)] == '\n': 529 end -= 1 530 if data[max(0,end-1)] == '\r': 531 end -= 1 532 # skip over --CRLF 533 #if data[min(data_len,next)] == '-': 534 # next += 1 535 #if data[min(data_len,next)] == '-': 536 # next += 1 537 #if data[min(data_len,next)] == '\r': 538 # next += 1 539 #if data[min(data_len,next)] == '\n': 540 # next += 1 541 return end, next 542 543 def exhaust(stream_or_iterable): 544 """ 545 Completely exhausts an iterator or stream. 546 547 Raise a MultiPartParserError if the argument is not a stream or an iterable. 548 """ 549 iterator = None 550 try: 551 iterator = iter(stream_or_iterable) 552 except TypeError: 553 iterator = ChunkIter(stream_or_iterable, 16384) 554 555 if iterator is None: 556 raise MultiPartParserError('multipartparser.exhaust() was passed a non-iterable or stream parameter') 557 558 for __ in iterator: 559 pass 560 561 def parse_boundary_stream(stream, max_header_size): 562 """ 563 Parses one and exactly one stream that encapsulates a boundary. 564 """ 565 # Stream at beginning of header, look for end of header 566 # and parse it if found. The header must fit within one 567 # chunk. 568 chunk = stream.read(max_header_size) 569 570 # 'find' returns the top of these four bytes, so we'll 571 # need to munch them later to prevent them from polluting 572 # the payload. 573 header_end = chunk.find('\r\n\r\n') 574 575 def _parse_header(line): 576 main_value_pair, params = parse_header(line) 577 try: 578 name, value = main_value_pair.split(':', 1) 579 except: 580 raise ValueError("Invalid header: %r" % line) 581 return name, (value, params) 582 583 if header_end == -1: 584 # we find no header, so we just mark this fact and pass on 585 # the stream verbatim 586 stream.unget(chunk) 587 return (RAW, {}, stream) 588 589 header = chunk[:header_end] 590 591 # here we place any excess chunk back onto the stream, as 592 # well as throwing away the CRLFCRLF bytes from above. 593 stream.unget(chunk[header_end + 4:]) 594 595 TYPE = RAW 596 outdict = {} 597 598 # Eliminate blank lines 599 for line in header.split('\r\n'): 600 # This terminology ("main value" and "dictionary of 601 # parameters") is from the Python docs. 602 try: 603 name, (value, params) = _parse_header(line) 604 except: 605 continue 606 607 if name == 'content-disposition': 608 TYPE = FIELD 609 if params.get('filename'): 610 TYPE = FILE 611 612 outdict[name] = value, params 613 614 if TYPE == RAW: 615 stream.unget(chunk) 616 617 return (TYPE, outdict, stream) 618 619 class Parser(object): 620 def __init__(self, stream, boundary): 621 self._stream = stream 622 self._separator = '--' + boundary 623 624 def __iter__(self): 625 boundarystream = InterBoundaryIter(self._stream, self._separator) 626 for sub_stream in boundarystream: 627 # Iterate over each part 628 yield parse_boundary_stream(sub_stream, 1024) 629 630 def parse_header(line): 631 """ Parse the header into a key-value. """ 632 plist = _parse_header_params(';' + line) 633 key = plist.pop(0).lower() 634 pdict = {} 635 for p in plist: 636 i = p.find('=') 637 if i >= 0: 638 name = p[:i].strip().lower() 639 value = p[i+1:].strip() 640 if len(value) >= 2 and value[0] == value[-1] == '"': 641 value = value[1:-1] 642 value = value.replace('\\\\', '\\').replace('\\"', '"') 643 pdict[name] = value 644 return key, pdict 645 646 def _parse_header_params(s): 647 plist = [] 648 while s[:1] == ';': 649 s = s[1:] 650 end = s.find(';') 651 while end > 0 and s.count('"', 0, end) % 2: 652 end = s.find(';', end + 1) 653 if end < 0: 654 end = len(s) 655 f = s[:end] 656 plist.append(f.strip()) 657 s = s[end:] 658 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 21a72f2..f27bc1c 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 346 378 347 379 class DictWrapper(dict): 348 380 """ -
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..54fc24d 100644
a b All attributes except ``session`` should be considered read-only. 80 80 strings. 81 81 82 82 ``FILES`` 83 84 .. admonition:: Changed in Django development version 85 86 In previous versions of Django, ``request.FILES`` contained 87 simple ``dict`` objects representing uploaded files. This is 88 no longer true -- files are represented by ``UploadedFile`` 89 objects as described below. 90 91 These ``UploadedFile`` objects will emulate the old-style ``dict`` 92 interface, but this is deprecated and will be removed in the next 93 release of Django. 94 83 95 A dictionary-like object containing all uploaded files. Each key in 84 96 ``FILES`` is the ``name`` from the ``<input type="file" name="" />``. Each 85 value in ``FILES`` is a standard Python dictionary with the following three86 keys:97 value in ``FILES`` is an ``UploadedFile`` object containing the following 98 attributes: 87 99 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. 100 * ``read(num_bytes=None)`` -- Read a number of bytes from the file. 101 * ``file_name`` -- The name of the uploaded file. 102 * ``file_size`` -- The size, in bytes, of the uploaded file. 103 * ``chunk()`` -- A generator that yields sequential chunks of data. 91 104 105 See `File Uploads`_ for more information. 106 92 107 Note that ``FILES`` will only contain data if the request method was POST 93 108 and the ``<form>`` that posted to the request had 94 109 ``enctype="multipart/form-data"``. Otherwise, ``FILES`` will be a blank 95 110 dictionary-like object. 111 112 .. _File Uploads: ../upload_handling/ 96 113 97 114 ``META`` 98 115 A standard Python dictionary containing all available HTTP headers. -
docs/settings.txt
diff --git a/docs/settings.txt b/docs/settings.txt index 3fe999d..a68d2ff 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'`` 530 530 The character encoding used to decode any files read from disk. This includes 531 531 template files and initial SQL data files. 532 532 533 FILE_UPLOAD_HANDLERS 534 -------------------- 535 536 **New in Django development version** 537 538 Default:: 539 540 ("django.core.files.fileuploadhandler.MemoryFileUploadHandler", 541 "django.core.files.fileuploadhandler.TemporaryFileUploadHandler",) 542 543 A tuple of handlers to use for uploading. See `file uploads`_ for details. 544 545 .. _file uploads: ../upload_handling/ 546 547 FILE_UPLOAD_MAX_MEMORY_SIZE 548 --------------------------- 549 550 **New in Django development version** 551 552 Default: ``2621440`` (i.e. 2.5 MB). 553 554 The maximum size (in bytes) that an upload will be before it gets streamed to 555 the file system. See `file uploads`_ for details. 556 557 FILE_UPLOAD_TEMP_DIR 558 -------------------- 559 560 **New in Django development version** 561 562 Default: ``None`` 563 564 The directory to store data temporarily while uploading files. If ``None``, 565 Django will use the standard temporary directory for the operating system. For 566 example, this will default to '/tmp' on *nix-style operating systems. 567 568 See `file uploads`_ for details. 569 533 570 FIXTURE_DIRS 534 571 ------------- 535 572 -
new file docs/upload_handling.txt
diff --git a/docs/upload_handling.txt b/docs/upload_handling.txt new file mode 100644 index 0000000..068acf3
- + 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. When Django 8 handles a file upload, the file data ends up placed in ``request.FILES`` (for 9 more on the ``request`` object see the documentation for `request and response 10 objects`_). This document explains how files are stored on disk an in memory, 11 and how to customize the default behavior. 12 13 .. _request and response objects: ../request_response/#attributes 14 15 Basic file uploads 16 ================== 17 18 Consider a simple form containing a ``FileField``:: 19 20 from django import newforms as forms 21 22 class UploadFileForm(forms.Form): 23 title = forms.CharField(max_length=50) 24 file = forms.FileField() 25 26 A view handling this form will receive the file data in ``request.FILES``, which 27 is a dictionary containing a key for each ``FileField`` (or ``ImageField``, or 28 other ``FileField`` subclass) in the form. So the data from the above form would 29 be accessible as ``request.FILES['file']``. 30 31 Most of the time, you'll simply pass the file data from ``request`` into the 32 form as described in `binding uploaded files to a form`_. This would look 33 something like:: 34 35 from django.http import HttpResponseRedirect 36 from django.shortcuts import render_to_response 37 38 # Imaginary function to handle an uploaded file. 39 from somewhere import handle_uploaded_file 40 41 def upload_file(request): 42 if request.method == 'POST': 43 form = UploadFileForm(request.POST, request.FILES) 44 if form.is_valid(): 45 handle_uploaded_file(request.FILES['file']) 46 return HttpResponseRedirect('/success/url/') 47 else: 48 form = UploadFileForm() 49 return render_to_response('upload.html', {'form': form}) 50 51 .. _binding uploaded files to a form: ../newforms/#binding-uploaded-files-to-a- form 52 53 Notice that we have to pass ``request.FILES`` into the form's constructor; this 54 is how file data gets bound into a form. 55 56 Handling uploaded files 57 ----------------------- 58 59 The final piece of the puzzle is handling the actual file data from 60 ``request.FILES``. Each entry in this dictionary is an ``UploadedFile`` object 61 -- a simple wrapper around an uploaded file. You'll usually use one of these 62 methods to access the uploaded content: 63 64 ``UploadedFile.read()`` 65 Read the entire uploaded data from the file. Be careful with this 66 method: if the uploaded file is huge it can overwhelm your system if you 67 try to read it into memory. You'll probably want to use ``chunk()`` 68 instead; see below. 69 70 ``UploadedFile.multiple_chunks()`` 71 Returns ``True`` if the uploaded file is big enough to require 72 reading in multiple chunks. By default this will be any file 73 larger than 2.5 megabytes, but that's configurable; see below. 74 75 ``UploadedFile.chunks()`` 76 A generator returning chunks of the file. If ``multiple_chunks()`` is 77 ``True``, you should use this method in a loop instead of ``read()``. 78 79 In practice, it's often easiest simply to use ``chunks()`` all the time; 80 see the example below. 81 82 ``UploadedFile.file_name`` 83 The name of the uploaded file (e.g. ``my_file.txt``). 84 85 ``UploadedFile.file_size`` 86 The size, in bytes, of the uploaded file. 87 88 There are a few other methods and attributes available on ``UploadedFile`` 89 objects; see `UploadedFile objects`_ for a complete reference. 90 91 Putting it all together, here's a common way you might handle an uploaded file:: 92 93 def handle_uploaded_file(f): 94 destination = open('some/file/name.txt', 'wb') 95 for chunk in f.chunks(): 96 destination.write(chunk) 97 98 Looping over ``UploadedFile.chunks()`` instead of using ``read()`` ensures that 99 large files don't overwhelm your system's memory. 100 101 Where uploaded data is stored 102 ----------------------------- 103 104 Before you save uploaded files, the data needs to be stored somewhere. 105 106 By default, if an uploaded file is smaller than 2.5 megabytes, Django will hold 107 the entire contents of the upload in memory. This means that saving the file 108 involves only a read from memory and a write to disk and thus is very fast. 109 110 However, if an uploaded file is too large, Django will write the uploaded file 111 to a temporary file stored in your system's temporary directory. On a Unix-like 112 platform this means you can expect Django to generate a file called something 113 like ``/tmp/tmpzfp6I6.upload``. If an upload is large enough, you can watch this 114 file grow in size as Django streams the data onto disk. 115 116 These specifics -- 2.5 megabytes; ``/tmp``; etc. -- are simply "reasonable 117 defaults". Read on for details on how you can customize or completely replace 118 upload behavior. 119 120 Changing upload handler behavior 121 -------------------------------- 122 123 Three `settings`_ control Django's file upload behavior: 124 125 ``FILE_UPLOAD_MAX_MEMORY_SIZE`` 126 The maximum size, in bytes, for files that will be uploaded 127 into memory. Files larger than ``FILE_UPLOAD_MAX_MEMORY_SIZE`` 128 will be streamed to disk. 129 130 Defaults to 2.5 megabytes. 131 132 ``FILE_UPLOAD_TEMP_DIR`` 133 The directory where uploaded files larger than ``FILE_UPLOAD_TEMP_DIR`` 134 will be stored. 135 136 Defaults to your system's standard temporary directory (i.e. ``/tmp`` on 137 most Unix-like systems). 138 139 ``FILE_UPLOAD_HANDLERS`` 140 The actual handlers for uploaded files. Changing this setting 141 allows complete customization -- even replacement -- of 142 Django's upload process. See `upload handlers`_, below, 143 for details. 144 145 Defaults to:: 146 147 ("django.core.files.uploadhandler.MemoryFileUploadHandler", 148 "django.core.files.uploadhandler.TemporaryFileUploadHandler",) 149 150 Which means "try to upload to memory first, then fall back to temporary 151 files." 152 153 .. _settings: ../settings/ 154 155 ``UploadedFile`` objects 156 ======================== 157 158 All ``UploadedFile`` objects define the following methods/attributes: 159 160 ``UploadedFile.read(self, num_bytes=None)`` 161 Returns a byte string of length ``num_bytes``, or the complete file if 162 ``num_bytes`` is ``None``. 163 164 ``UploadedFile.chunk(self, chunk_size=None)`` 165 A generator yielding small chunks from the file. If ``chunk_size`` isn't 166 given, chunks will be 64 kb. 167 168 ``UploadedFile.multiple_chunks(self, chunk_size=None)`` 169 Returns ``True`` if you can expect more than one chunk when calling 170 ``UploadedFile.chunk(self, chunk_size)``. 171 172 ``UploadedFile.file_size`` 173 The size, in bytes, of the uploaded file. 174 175 ``UploadedFile.file_name`` 176 The name of the uploaded file as provided by the user. 177 178 ``UploadedFile.content_type`` 179 The content-type header uploaded with the file (e.g. ``text/plain`` or 180 ``application/pdf``). Like any data supplied by the user, you shouldn't 181 trust that the uploaded file is actually this type. You'll still need to 182 validate that the file contains the content that the content-type header 183 claims -- "trust but verify." 184 185 ``UploadedFile.charset`` 186 For ``text/*`` content-types, the character set (i.e. ``utf8``) supplied 187 by the browser. Again, "trust but verify" is the best policy here. 188 189 ``UploadedFile.temporary_file_path()`` 190 Only files uploaded onto disk will have this method; it returns the full 191 path to the temporary uploaded file. 192 193 Upload Handlers 194 =============== 195 196 When a user uploads a file, Django passes off the file data to an *upload 197 handler* -- a small class that handles file data as it gets uploaded. Upload 198 handlers are initially defined in the ``FILE_UPLOAD_HANDLERS`` setting, which 199 defaults to:: 200 201 ("django.core.files.uploadhandler.MemoryFileUploadHandler", 202 "django.core.files.uploadhandler.TemporaryFileUploadHandler",) 203 204 Together the ``MemoryFileUploadHandler`` and ``TemporaryFileUploadHandler`` 205 provide Django's default file upload behavior of reading small files into memory 206 and large ones onto disk. 207 208 You can write custom handlers that customize how Django handles files. You 209 could, for example, use custom handlers to enforce user-level quotas, compress 210 data on the fly, render progress bars, and even send data to another storage 211 location directly without storing it locally. 212 213 Modifying upload handlers on the fly 214 ------------------------------------ 215 216 Sometimes particular views require different upload behavior. In these cases, 217 you can override upload handlers on a per-request basis by modifying 218 ``request.upload_handlers``. By default, this list will contain the upload 219 handlers given by ``FILE_UPLOAD_HANDLERS``, but you can modify the list as you 220 would any other list. 221 222 For instance, suppose you've written a ``ProgressBarUploadHandler`` that 223 provides feedback on upload progress to some sort of AJAX widget. You'd add this 224 handler to your upload handers like this:: 225 226 request.upload_handlers.insert(0, ProgressBarUploadHandler()) 227 228 You'd probably want to use ``list.insert()`` in this case (instead of 229 ``append()``) because a progress bar handler would need to run *before* any 230 other handlers. Remember, the upload handlers are processed in order. 231 232 If you want to replace the upload handlers completely, you can just assign a new 233 list:: 234 235 request.upload_handlers = [ProgressBarUploadHandler()] 236 237 .. note:: 238 239 You can only modify upload handlers *before* accessing ``request.FILES`` -- 240 it doesn't make sense to change upload handlers after upload handling has 241 already started. If you try to modify ``request.upload_handlers`` after 242 reading from ``request.FILES`` Django will throw an error. 243 244 Thus, you should always modify uploading handlers as early in your view as 245 possible. 246 247 Writing custom upload handlers 248 ------------------------------ 249 250 All file upload handlers should be subclasses of 251 ``django.core.files.uploadhandler.FileUploadHandler``. You can define upload 252 handlers wherever you wish. 253 254 Required methods 255 ~~~~~~~~~~~~~~~~ 256 257 Custom file upload handlers **must** define the following methods: 258 259 ``FileUploadHandler.receive_data_chunk(self, raw_data, start)`` 260 Receives a "chunk" of data from the file upload. 261 262 ``raw_data`` is a byte string containing the uploaded data. 263 264 ``start`` is the position in the file where this ``raw_data`` chunk 265 begins. 266 267 The data you return will get fed into the subsequent upload handlers' 268 ``receive_data_chunk`` methods. In this way, one handler can be a 269 "filter" for other handlers. 270 271 Return ``None`` from ``receive_data_chunk`` to sort-circuit remaining 272 upload handlers from getting this chunk.. This is useful if you're 273 storing the uploaded data yourself and don't want future handlers to 274 store a copy of the data. 275 276 If you raise a ``StopUpload`` or a ``SkipFile`` exception, the upload 277 will abort or the file will be completely skipped. 278 279 ``FileUploadHandler.file_complete(self, file_size)`` 280 Called when a file has finished uploading. 281 282 The handler should return an ``UploadedFile`` object that will be stored 283 in ``request.FILES``. Handlers may also return ``None`` to indicate that 284 the ``UploadedFile`` object should come from subsequent upload handlers. 285 286 Optional methods 287 ~~~~~~~~~~~~~~~~ 288 289 Custom upload handlers may also define any of the following optional methods or 290 attributes: 291 292 ``FileUploadHandler.chunk_size`` 293 Size, in bytes, of the "chunks" Django should store into memory and feed 294 into the handler. That is, this attribute controls the size of chunks 295 fed into ``FileUploadHandler.receive_data_chunk``. 296 297 For maximum performance the chunk sizes should be divisible by ``4`` and 298 should not exceed 2 GB (2\ :sup:`31` bytes) in size. When there are 299 multiple chunk sizes provided by multiple handlers, Django will use the 300 smallest chunk size defined by any handler. 301 302 The default is 64*2\ :sup:`10` bytes, or 64 Kb. 303 304 ``FileUploadHandler.new_file(self, field_name, file_name, content_type, content_length, charset)`` 305 Callback signaling that a new file upload is starting. This is called 306 before any data has been fed to any upload handlers. 307 308 ``field_name`` is a string name of the file ``<input>`` field. 309 310 ``file_name`` is the unicode filename that was provided by the browser. 311 312 ``content_type`` is the MIME type provided by the browser -- E.g. 313 ``'image/jpeg'``. 314 315 ``content_length`` is the length of the image given by the browser. 316 Sometimes this won't be provided and will be ``None``., ``None`` 317 otherwise. 318 319 ``charset`` is the character set (i.e. ``utf8``) given by the browser. 320 Like ``content_length``, this sometimes won't be provided. 321 322 This method may raise a ``StopFutureHandlers`` exception to prevent 323 future handlers from handling this file. 324 325 ``FileUploadHandler.upload_complete(self)`` 326 Callback signaling that the entire upload (all files) has completed. 327 328 ``FileUploadHandler.``handle_raw_input(self, input_data, META, content_length, boundary, encoding)`` 329 Allows the handler to completely override the parsing of the raw 330 HTTP input. 331 332 ``input_data`` is a file-like object that supports ``read()``-ing. 333 334 ``META`` is the same object as ``request.META``. 335 336 ``content_length`` is the length of the data in ``input_data``. Don't 337 read more than ``content_length`` bytes from ``input_data``. 338 339 ``boundary`` is the MIME boundary for this request. 340 341 ``encoding`` is the encoding of the request. 342 343 Return ``None`` if you want upload handling to continue, or a tuple of 344 ``(POST, FILES)`` if you want to return the new data structures suitable 345 for the request directly. 346 -
tests/modeltests/model_forms/models.py
diff --git a/tests/modeltests/model_forms/models.py b/tests/modeltests/model_forms/models.py index 470312f..c856720 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 78 85 79 86 The bare bones, absolutely nothing custom, basic case. 80 87 … … False 792 799 793 800 # Upload a file and ensure it all works as expected. 794 801 795 >>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': {'filename': 'test1.txt', 'content': 'hello world'}}) 802 >>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': SimpleUploadedFile('test1.txt', 'hello world')}) 803 >>> f.is_valid() 804 True 805 >>> type(f.cleaned_data['file']) 806 <class 'django.newforms.fields.UploadedFile'> 807 >>> instance = f.save() 808 >>> instance.file 809 u'...test1.txt' 810 811 >>> os.unlink(instance.get_file_filename()) 812 813 >>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': SimpleUploadedFile('test1.txt', 'hello world')}) 796 814 >>> f.is_valid() 797 815 True 798 816 >>> type(f.cleaned_data['file']) … … u'...test1.txt' 814 832 u'...test1.txt' 815 833 816 834 # Delete the current file since this is not done by Django. 817 818 835 >>> os.unlink(instance.get_file_filename()) 819 836 820 837 # Override the file by uploading a new one. 821 838 822 >>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': {'filename': 'test2.txt', 'content': 'hello world'}}, instance=instance)839 >>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': SimpleUploadedFile('test2.txt', 'hello world')}, instance=instance) 823 840 >>> f.is_valid() 824 841 True 825 842 >>> instance = f.save() 826 843 >>> instance.file 827 844 u'...test2.txt' 828 845 846 # Delete the current file since this is not done by Django. 847 >>> os.unlink(instance.get_file_filename()) 848 849 >>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': SimpleUploadedFile('test2.txt', 'hello world')}) 850 >>> f.is_valid() 851 True 852 >>> instance = f.save() 853 >>> instance.file 854 u'...test2.txt' 855 856 # Delete the current file since this is not done by Django. 857 >>> os.unlink(instance.get_file_filename()) 858 829 859 >>> instance.delete() 830 860 831 861 # Test the non-required FileField … … True 838 868 >>> instance.file 839 869 '' 840 870 841 >>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': {'filename': 'test3.txt', 'content': 'hello world'}}, instance=instance)871 >>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': SimpleUploadedFile('test3.txt', 'hello world')}, instance=instance) 842 872 >>> f.is_valid() 843 873 True 844 874 >>> instance = f.save() 845 875 >>> instance.file 846 876 u'...test3.txt' 877 878 # Delete the current file since this is not done by Django. 879 >>> os.unlink(instance.get_file_filename()) 880 >>> instance.delete() 881 882 >>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': SimpleUploadedFile('test3.txt', 'hello world')}) 883 >>> f.is_valid() 884 True 885 >>> instance = f.save() 886 >>> instance.file 887 u'...test3.txt' 888 889 # Delete the current file since this is not done by Django. 890 >>> os.unlink(instance.get_file_filename()) 847 891 >>> instance.delete() 848 892 849 893 # ImageField ################################################################### … … u'...test3.txt' 858 902 859 903 >>> image_data = open(os.path.join(os.path.dirname(__file__), "test.png")).read() 860 904 861 >>> f = ImageFileForm(data={'description': u'An image'}, files={'image': {'filename': 'test.png', 'content': image_data}}) 905 >>> f = ImageFileForm(data={'description': u'An image'}, files={'image': SimpleUploadedFile('test.png', image_data)}) 906 >>> f.is_valid() 907 True 908 >>> type(f.cleaned_data['image']) 909 <class 'django.newforms.fields.UploadedFile'> 910 >>> instance = f.save() 911 >>> instance.image 912 u'...test.png' 913 914 # Delete the current file since this is not done by Django. 915 >>> os.unlink(instance.get_image_filename()) 916 917 >>> f = ImageFileForm(data={'description': u'An image'}, files={'image': SimpleUploadedFile('test.png', image_data)}) 862 918 >>> f.is_valid() 863 919 True 864 920 >>> type(f.cleaned_data['image']) … … u'...test.png' 885 941 886 942 # Override the file by uploading a new one. 887 943 888 >>> f = ImageFileForm(data={'description': u'Changed it'}, files={'image': {'filename': 'test2.png', 'content': image_data}}, instance=instance) 944 >>> f = ImageFileForm(data={'description': u'Changed it'}, files={'image': SimpleUploadedFile('test2.png', image_data)}, instance=instance) 945 >>> f.is_valid() 946 True 947 >>> instance = f.save() 948 >>> instance.image 949 u'...test2.png' 950 951 # Delete the current file since this is not done by Django. 952 >>> os.unlink(instance.get_image_filename()) 953 >>> instance.delete() 954 955 >>> f = ImageFileForm(data={'description': u'Changed it'}, files={'image': SimpleUploadedFile('test2.png', image_data)}) 889 956 >>> f.is_valid() 890 957 True 891 958 >>> instance = f.save() 892 959 >>> instance.image 893 960 u'...test2.png' 894 961 962 # Delete the current file since this is not done by Django. 963 >>> os.unlink(instance.get_image_filename()) 895 964 >>> instance.delete() 896 965 897 966 # Test the non-required ImageField … … True 904 973 >>> instance.image 905 974 '' 906 975 907 >>> f = ImageFileForm(data={'description': u'And a final one'}, files={'image': {'filename': 'test3.png', 'content': image_data}}, instance=instance) 976 >>> f = ImageFileForm(data={'description': u'And a final one'}, files={'image': SimpleUploadedFile('test3.png', image_data)}, instance=instance) 977 >>> f.is_valid() 978 True 979 >>> instance = f.save() 980 >>> instance.image 981 u'...test3.png' 982 983 # Delete the current file since this is not done by Django. 984 >>> os.unlink(instance.get_image_filename()) 985 >>> instance.delete() 986 987 >>> f = ImageFileForm(data={'description': u'And a final one'}, files={'image': SimpleUploadedFile('test3.png', image_data)}) 908 988 >>> f.is_valid() 909 989 True 910 990 >>> 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 d6141b0..62c57bc 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 129 140 ### DictWrapper ############################################################# 130 141 -
new file tests/regressiontests/file_uploads/models.py
diff --git a/tests/regressiontests/file_uploads/__init__.py b/tests/regressiontests/file_uploads/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/regressiontests/file_uploads/models.py b/tests/regressiontests/file_uploads/models.py new file mode 100644 index 0000000..2d5607b
- + 1 # This file unintentionally left blank. 2 # Oops. 3 No newline at end of file -
new file tests/regressiontests/file_uploads/tests.py
diff --git a/tests/regressiontests/file_uploads/tests.py b/tests/regressiontests/file_uploads/tests.py new file mode 100644 index 0000000..8992298
- + 1 import os 2 import sha 3 import tempfile 4 from django.test import TestCase, client 5 from django.utils import simplejson 6 7 class FileUploadTests(TestCase): 8 def test_simple_upload(self): 9 post_data = { 10 'name': 'Ringo', 11 'file_field': open(__file__), 12 } 13 response = self.client.post('/file_uploads/upload/', post_data) 14 self.assertEqual(response.status_code, 200) 15 16 def test_large_upload(self): 17 tdir = tempfile.gettempdir() 18 19 file1 = tempfile.NamedTemporaryFile(suffix=".file1", dir=tdir) 20 file1.write('a' * (2 ** 21)) 21 file1.seek(0) 22 23 file2 = tempfile.NamedTemporaryFile(suffix=".file2", dir=tdir) 24 file2.write('a' * (10 * 2 ** 20)) 25 file2.seek(0) 26 27 # This file contains chinese symbols for a name. 28 file3 = open(os.path.join(tdir, u'test_中文_Orl\u00e9ans.jpg'), 'w+b') 29 file3.write('b' * (2 ** 10)) 30 file3.seek(0) 31 32 post_data = { 33 'name': 'Ringo', 34 'file_field1': open(file1.name), 35 'file_field2': open(file2.name), 36 'file_unicode': file3, 37 } 38 39 for key in post_data.keys(): 40 try: 41 post_data[key + '_hash'] = sha.new(post_data[key].read()).hexdigest() 42 post_data[key].seek(0) 43 except AttributeError: 44 post_data[key + '_hash'] = sha.new(post_data[key]).hexdigest() 45 46 response = self.client.post('/file_uploads/verify/', post_data) 47 48 try: 49 os.unlink(file3.name) 50 except: 51 pass 52 53 self.assertEqual(response.status_code, 200) 54 55 def test_dangerous_file_names(self): 56 """Uploaded file names should be sanitized before ever reaching the view.""" 57 # This test simulates possible directory traversal attacks by a 58 # malicious uploader We have to do some monkeybusiness here to construct 59 # a malicious payload with an invalid file name (containing os.sep or 60 # os.pardir). This similar to what an attacker would need to do when 61 # trying such an attack. 62 scary_file_names = [ 63 "/tmp/hax0rd.txt", # Absolute path, *nix-style. 64 "C:\\Windows\\hax0rd.txt", # Absolute path, win-syle. 65 "C:/Windows/hax0rd.txt", # Absolute path, broken-style. 66 "\\tmp\\hax0rd.txt", # Absolute path, broken in a different way. 67 "/tmp\\hax0rd.txt", # Absolute path, broken by mixing. 68 "subdir/hax0rd.txt", # Descendant path, *nix-style. 69 "subdir\\hax0rd.txt", # Descendant path, win-style. 70 "sub/dir\\hax0rd.txt", # Descendant path, mixed. 71 "../../hax0rd.txt", # Relative path, *nix-style. 72 "..\\..\\hax0rd.txt", # Relative path, win-style. 73 "../..\\hax0rd.txt" # Relative path, mixed. 74 ] 75 76 payload = [] 77 for i, name in enumerate(scary_file_names): 78 payload.extend([ 79 '--' + client.BOUNDARY, 80 'Content-Disposition: form-data; name="file%s"; filename="%s"' % (i, name), 81 'Content-Type: application/octet-stream', 82 '', 83 'You got pwnd.' 84 ]) 85 payload.extend([ 86 '--' + client.BOUNDARY + '--', 87 '', 88 ]) 89 90 payload = "\r\n".join(payload) 91 r = { 92 'CONTENT_LENGTH': len(payload), 93 'CONTENT_TYPE': client.MULTIPART_CONTENT, 94 'PATH_INFO': "/file_uploads/echo/", 95 'REQUEST_METHOD': 'POST', 96 'wsgi.input': client.FakePayload(payload), 97 } 98 response = self.client.request(**r) 99 100 # The filenames should have been sanitized by the time it got to the view. 101 recieved = simplejson.loads(response.content) 102 for i, name in enumerate(scary_file_names): 103 got = recieved["file%s" % i] 104 self.assertEqual(got, "hax0rd.txt") 105 106 def test_filename_overflow(self): 107 """File names over 256 characters (dangerous on some platforms) get fixed up.""" 108 name = "%s.txt" % ("f"*500) 109 payload = "\r\n".join([ 110 '--' + client.BOUNDARY, 111 'Content-Disposition: form-data; name="file"; filename="%s"' % name, 112 'Content-Type: application/octet-stream', 113 '', 114 'Oops.' 115 '--' + client.BOUNDARY + '--', 116 '', 117 ]) 118 r = { 119 'CONTENT_LENGTH': len(payload), 120 'CONTENT_TYPE': client.MULTIPART_CONTENT, 121 'PATH_INFO': "/file_uploads/echo/", 122 'REQUEST_METHOD': 'POST', 123 'wsgi.input': client.FakePayload(payload), 124 } 125 got = simplejson.loads(self.client.request(**r).content) 126 self.assert_(len(got['file']) < 256, "Got a long file name (%s characters)." % len(got['file'])) 127 128 def test_custom_upload_handler(self): 129 # A small file (under the 5M quota) 130 smallfile = tempfile.NamedTemporaryFile() 131 smallfile.write('a' * (2 ** 21)) 132 133 # A big file (over the quota) 134 bigfile = tempfile.NamedTemporaryFile() 135 bigfile.write('a' * (10 * 2 ** 20)) 136 137 # Small file posting should work. 138 response = self.client.post('/file_uploads/quota/', {'f': open(smallfile.name)}) 139 got = simplejson.loads(response.content) 140 self.assert_('f' in got) 141 142 # Large files don't go through. 143 response = self.client.post("/file_uploads/quota/", {'f': open(bigfile.name)}) 144 got = simplejson.loads(response.content) 145 self.assert_('f' not in got) 146 147 def test_broken_custom_upload_handler(self): 148 f = tempfile.NamedTemporaryFile() 149 f.write('a' * (2 ** 21)) 150 151 # AttributeError: You cannot alter upload handlers after the upload has been processed. 152 self.assertRaises( 153 AttributeError, 154 self.client.post, 155 '/file_uploads/quota/broken/', 156 {'f': open(f.name)} 157 ) 158 159 No newline at end of file -
new file tests/regressiontests/file_uploads/uploadhandler.py
diff --git a/tests/regressiontests/file_uploads/uploadhandler.py b/tests/regressiontests/file_uploads/uploadhandler.py new file mode 100644 index 0000000..54f82f6
- + 1 """ 2 Upload handlers to test the upload API. 3 """ 4 5 from django.core.files.uploadhandler import FileUploadHandler, StopUpload 6 7 class QuotaUploadHandler(FileUploadHandler): 8 """ 9 This test upload handler terminates the connection if more than a quota 10 (5MB) is uploaded. 11 """ 12 13 QUOTA = 5 * 2**20 # 5 MB 14 15 def __init__(self, request=None): 16 super(QuotaUploadHandler, self).__init__(request) 17 self.total_upload = 0 18 19 def receive_data_chunk(self, raw_data, start): 20 self.total_upload += len(raw_data) 21 if self.total_upload >= self.QUOTA: 22 raise StopUpload(connection_reset=True) 23 return raw_data 24 25 def file_complete(self, file_size): 26 return None 27 No newline at end of file -
new file tests/regressiontests/file_uploads/urls.py
diff --git a/tests/regressiontests/file_uploads/urls.py b/tests/regressiontests/file_uploads/urls.py new file mode 100644 index 0000000..529bee3
- + 1 from django.conf.urls.defaults import * 2 import views 3 4 urlpatterns = patterns('', 5 (r'^upload/$', views.file_upload_view), 6 (r'^verify/$', views.file_upload_view_verify), 7 (r'^echo/$', views.file_upload_echo), 8 (r'^quota/$', views.file_upload_quota), 9 (r'^quota/broken/$', views.file_upload_quota_broken), 10 ) -
new file tests/regressiontests/file_uploads/views.py
diff --git a/tests/regressiontests/file_uploads/views.py b/tests/regressiontests/file_uploads/views.py new file mode 100644 index 0000000..833cf90
- + 1 import os 2 import sha 3 from django.core.files.uploadedfile import UploadedFile 4 from django.http import HttpResponse, HttpResponseServerError 5 from django.utils import simplejson 6 from uploadhandler import QuotaUploadHandler 7 8 def file_upload_view(request): 9 """ 10 Check that a file upload can be updated into the POST dictionary without 11 going pear-shaped. 12 """ 13 form_data = request.POST.copy() 14 form_data.update(request.FILES) 15 if isinstance(form_data.get('file_field'), UploadedFile) and isinstance(form_data['name'], unicode): 16 # If a file is posted, the dummy client should only post the file name, 17 # not the full path. 18 if os.path.dirname(form_data['file_field'].file_name) != '': 19 return HttpResponseServerError() 20 return HttpResponse('') 21 else: 22 return HttpResponseServerError() 23 24 def file_upload_view_verify(request): 25 """ 26 Use the sha digest hash to verify the uploaded contents. 27 """ 28 form_data = request.POST.copy() 29 form_data.update(request.FILES) 30 31 # Check to see if unicode names worked out. 32 if not request.FILES['file_unicode'].file_name.endswith(u'test_\u4e2d\u6587_Orl\xe9ans.jpg'): 33 return HttpResponseServerError() 34 35 for key, value in form_data.items(): 36 if key.endswith('_hash'): 37 continue 38 if key + '_hash' not in form_data: 39 continue 40 submitted_hash = form_data[key + '_hash'] 41 if isinstance(value, UploadedFile): 42 new_hash = sha.new(value.read()).hexdigest() 43 else: 44 new_hash = sha.new(value).hexdigest() 45 if new_hash != submitted_hash: 46 return HttpResponseServerError() 47 48 return HttpResponse('') 49 50 def file_upload_echo(request): 51 """ 52 Simple view to echo back info about uploaded files for tests. 53 """ 54 r = dict([(k, f.file_name) for k, f in request.FILES.items()]) 55 return HttpResponse(simplejson.dumps(r)) 56 57 def file_upload_quota(request): 58 """ 59 Dynamically add in an upload handler. 60 """ 61 request.upload_handlers.insert(0, QuotaUploadHandler()) 62 return file_upload_echo(request) 63 64 def file_upload_quota_broken(request): 65 """ 66 You can't change handlers after reading FILES; this view shouldn't work. 67 """ 68 response = file_upload_echo(request) 69 request.upload_handlers.insert(0, QuotaUploadHandler()) 70 return response 71 No newline at end of file -
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..8fe4bc1 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.'] 770 771 >>> f.clean(None, 'files/test2.pdf') 771 772 'files/test2.pdf' 772 773 773 >>> f.clean( {})774 >>> f.clean(SimpleUploadedFile('', '')) 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 >>> f.clean( {}, '')779 >>> f.clean(SimpleUploadedFile('', ''), '') 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 >>> f.clean( {}, 'files/test3.pdf')784 >>> f.clean(None, 'files/test3.pdf') 784 785 'files/test3.pdf' 785 786 786 787 >>> f.clean('some content that is not a file') … … 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/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..37b997b 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 AssertFormErrorTests(TestCase): 240 241 except AssertionError, e: 241 242 self.assertEqual(str(e), "The form 'form' in context 0 does not contain the non-field error 'Some error.' (actual errors: )") 242 243 243 class FileUploadTests(TestCase):244 def test_simple_upload(self):245 fd = open(os.path.join(os.path.dirname(__file__), "views.py"))246 post_data = {247 'name': 'Ringo',248 'file_field': fd,249 }250 response = self.client.post('/test_client_regress/file_upload/', post_data)251 self.assertEqual(response.status_code, 200)252 253 244 class LoginTests(TestCase): 254 245 fixtures = ['testdata'] 255 246 … … class LoginTests(TestCase): 269 260 # default client. 270 261 self.assertRedirects(response, "http://testserver/test_client_regress/get_view/") 271 262 272 273 263 class URLEscapingTests(TestCase): 274 264 def test_simple_argument_get(self): 275 265 "Get a view that has a simple string argument" -
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..12f6afa 100644
a b import views 3 3 4 4 urlpatterns = patterns('', 5 5 (r'^no_template_view/$', views.no_template_view), 6 (r'^file_upload/$', views.file_upload_view),7 6 (r'^staff_only/$', views.staff_only_view), 8 7 (r'^get_view/$', views.get_view), 9 8 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..d703c82 100644
a b 1 import os2 3 1 from django.contrib.auth.decorators import login_required 4 from django.http import HttpResponse, HttpResponseRedirect , HttpResponseServerError2 from django.http import HttpResponse, HttpResponseRedirect 5 3 from django.core.exceptions import SuspiciousOperation 6 4 7 5 def no_template_view(request): 8 6 "A simple view that expects a GET request, and returns a rendered template" 9 7 return HttpResponse("No template used. Sample content: twice once twice. Content ends.") 10 8 11 def file_upload_view(request):12 """13 Check that a file upload can be updated into the POST dictionary without14 going pear-shaped.15 """16 form_data = request.POST.copy()17 form_data.update(request.FILES)18 if isinstance(form_data['file_field'], dict) and isinstance(form_data['name'], unicode):19 # If a file is posted, the dummy client should only post the file name,20 # not the full path.21 if os.path.dirname(form_data['file_field']['filename']) != '':22 return HttpResponseServerError()23 return HttpResponse('')24 else:25 return HttpResponseServerError()26 27 9 def staff_only_view(request): 28 10 "A view that can only be visited by staff. Non staff members get an exception" 29 11 if request.user.is_staff: 30 12 return HttpResponse('') 31 13 else: 32 14 raise SuspiciousOperation() 33 15 34 16 def get_view(request): 35 17 "A simple login protected view" 36 18 return HttpResponse("Hello world") … … def view_with_argument(request, name): 51 33 def login_protected_redirect_view(request): 52 34 "A view that redirects all requests to the GET view" 53 35 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 36 login_protected_redirect_view = login_required(login_protected_redirect_view) -
tests/urls.py
diff --git a/tests/urls.py b/tests/urls.py index dbdf9a8..cea453e 100644
a b urlpatterns = patterns('', 5 5 (r'^test_client/', include('modeltests.test_client.urls')), 6 6 (r'^test_client_regress/', include('regressiontests.test_client_regress.urls')), 7 7 8 # File upload test views 9 (r'^file_uploads/', include('regressiontests.file_uploads.urls')), 10 8 11 # Always provide the auth system login and logout views 9 12 (r'^accounts/login/$', 'django.contrib.auth.views.login', {'template_name': 'login.html'}), 10 13 (r'^accounts/logout/$', 'django.contrib.auth.views.logout'),