| 1 |
""" |
|---|
| 2 |
Base file upload handler classes, and the built-in concrete subclasses |
|---|
| 3 |
""" |
|---|
| 4 |
|
|---|
| 5 |
try: |
|---|
| 6 |
from cStringIO import StringIO |
|---|
| 7 |
except ImportError: |
|---|
| 8 |
from StringIO import StringIO |
|---|
| 9 |
|
|---|
| 10 |
from django.conf import settings |
|---|
| 11 |
from django.core.exceptions import ImproperlyConfigured |
|---|
| 12 |
from django.core.files.uploadedfile import TemporaryUploadedFile, InMemoryUploadedFile |
|---|
| 13 |
|
|---|
| 14 |
__all__ = ['UploadFileException','StopUpload', 'SkipFile', 'FileUploadHandler', |
|---|
| 15 |
'TemporaryFileUploadHandler', 'MemoryFileUploadHandler', |
|---|
| 16 |
'load_handler'] |
|---|
| 17 |
|
|---|
| 18 |
class UploadFileException(Exception): |
|---|
| 19 |
""" |
|---|
| 20 |
Any error having to do with uploading files. |
|---|
| 21 |
""" |
|---|
| 22 |
pass |
|---|
| 23 |
|
|---|
| 24 |
class StopUpload(UploadFileException): |
|---|
| 25 |
""" |
|---|
| 26 |
This exception is raised when an upload must abort. |
|---|
| 27 |
""" |
|---|
| 28 |
def __init__(self, connection_reset=False): |
|---|
| 29 |
""" |
|---|
| 30 |
If ``connection_reset`` is ``True``, Django knows will halt the upload |
|---|
| 31 |
without consuming the rest of the upload. This will cause the browser to |
|---|
| 32 |
show a "connection reset" error. |
|---|
| 33 |
""" |
|---|
| 34 |
self.connection_reset = connection_reset |
|---|
| 35 |
|
|---|
| 36 |
def __unicode__(self): |
|---|
| 37 |
if self.connection_reset: |
|---|
| 38 |
return u'StopUpload: Halt current upload.' |
|---|
| 39 |
else: |
|---|
| 40 |
return u'StopUpload: Consume request data, then halt.' |
|---|
| 41 |
|
|---|
| 42 |
class SkipFile(UploadFileException): |
|---|
| 43 |
""" |
|---|
| 44 |
This exception is raised by an upload handler that wants to skip a given file. |
|---|
| 45 |
""" |
|---|
| 46 |
pass |
|---|
| 47 |
|
|---|
| 48 |
class StopFutureHandlers(UploadFileException): |
|---|
| 49 |
""" |
|---|
| 50 |
Upload handers that have handled a file and do not want future handlers to |
|---|
| 51 |
run should raise this exception instead of returning None. |
|---|
| 52 |
""" |
|---|
| 53 |
pass |
|---|
| 54 |
|
|---|
| 55 |
class FileUploadHandler(object): |
|---|
| 56 |
""" |
|---|
| 57 |
Base class for streaming upload handlers. |
|---|
| 58 |
""" |
|---|
| 59 |
chunk_size = 64 * 2 ** 10 #: The default chunk size is 64 KB. |
|---|
| 60 |
|
|---|
| 61 |
def __init__(self, request=None): |
|---|
| 62 |
self.file_name = None |
|---|
| 63 |
self.content_type = None |
|---|
| 64 |
self.content_length = None |
|---|
| 65 |
self.charset = None |
|---|
| 66 |
self.request = request |
|---|
| 67 |
|
|---|
| 68 |
def handle_raw_input(self, input_data, META, content_length, boundary, encoding=None): |
|---|
| 69 |
""" |
|---|
| 70 |
Handle the raw input from the client. |
|---|
| 71 |
|
|---|
| 72 |
Parameters: |
|---|
| 73 |
|
|---|
| 74 |
:input_data: |
|---|
| 75 |
An object that supports reading via .read(). |
|---|
| 76 |
:META: |
|---|
| 77 |
``request.META``. |
|---|
| 78 |
:content_length: |
|---|
| 79 |
The (integer) value of the Content-Length header from the |
|---|
| 80 |
client. |
|---|
| 81 |
:boundary: The boundary from the Content-Type header. Be sure to |
|---|
| 82 |
prepend two '--'. |
|---|
| 83 |
""" |
|---|
| 84 |
pass |
|---|
| 85 |
|
|---|
| 86 |
def new_file(self, field_name, file_name, content_type, content_length, charset=None): |
|---|
| 87 |
""" |
|---|
| 88 |
Signal that a new file has been started. |
|---|
| 89 |
|
|---|
| 90 |
Warning: As with any data from the client, you should not trust |
|---|
| 91 |
content_length (and sometimes won't even get it). |
|---|
| 92 |
""" |
|---|
| 93 |
self.field_name = field_name |
|---|
| 94 |
self.file_name = file_name |
|---|
| 95 |
self.content_type = content_type |
|---|
| 96 |
self.content_length = content_length |
|---|
| 97 |
self.charset = charset |
|---|
| 98 |
|
|---|
| 99 |
def receive_data_chunk(self, raw_data, start): |
|---|
| 100 |
""" |
|---|
| 101 |
Receive data from the streamed upload parser. ``start`` is the position |
|---|
| 102 |
in the file of the chunk. |
|---|
| 103 |
""" |
|---|
| 104 |
raise NotImplementedError() |
|---|
| 105 |
|
|---|
| 106 |
def file_complete(self, file_size): |
|---|
| 107 |
""" |
|---|
| 108 |
Signal that a file has completed. File size corresponds to the actual |
|---|
| 109 |
size accumulated by all the chunks. |
|---|
| 110 |
|
|---|
| 111 |
Subclasses must should return a valid ``UploadedFile`` object. |
|---|
| 112 |
""" |
|---|
| 113 |
raise NotImplementedError() |
|---|
| 114 |
|
|---|
| 115 |
def upload_complete(self): |
|---|
| 116 |
""" |
|---|
| 117 |
Signal that the upload is complete. Subclasses should perform cleanup |
|---|
| 118 |
that is necessary for this handler. |
|---|
| 119 |
""" |
|---|
| 120 |
pass |
|---|
| 121 |
|
|---|
| 122 |
class TemporaryFileUploadHandler(FileUploadHandler): |
|---|
| 123 |
""" |
|---|
| 124 |
Upload handler that streams data into a temporary file. |
|---|
| 125 |
""" |
|---|
| 126 |
def __init__(self, *args, **kwargs): |
|---|
| 127 |
super(TemporaryFileUploadHandler, self).__init__(*args, **kwargs) |
|---|
| 128 |
|
|---|
| 129 |
def new_file(self, file_name, *args, **kwargs): |
|---|
| 130 |
""" |
|---|
| 131 |
Create the file object to append to as data is coming in. |
|---|
| 132 |
""" |
|---|
| 133 |
super(TemporaryFileUploadHandler, self).new_file(file_name, *args, **kwargs) |
|---|
| 134 |
self.file = TemporaryUploadedFile(self.file_name, self.content_type, 0, self.charset) |
|---|
| 135 |
|
|---|
| 136 |
def receive_data_chunk(self, raw_data, start): |
|---|
| 137 |
self.file.write(raw_data) |
|---|
| 138 |
|
|---|
| 139 |
def file_complete(self, file_size): |
|---|
| 140 |
self.file.seek(0) |
|---|
| 141 |
self.file.size = file_size |
|---|
| 142 |
return self.file |
|---|
| 143 |
|
|---|
| 144 |
class MemoryFileUploadHandler(FileUploadHandler): |
|---|
| 145 |
""" |
|---|
| 146 |
File upload handler to stream uploads into memory (used for small files). |
|---|
| 147 |
""" |
|---|
| 148 |
|
|---|
| 149 |
def handle_raw_input(self, input_data, META, content_length, boundary, encoding=None): |
|---|
| 150 |
""" |
|---|
| 151 |
Use the content_length to signal whether or not this handler should be in use. |
|---|
| 152 |
""" |
|---|
| 153 |
# Check the content-length header to see if we should |
|---|
| 154 |
# If the the post is too large, we cannot use the Memory handler. |
|---|
| 155 |
if content_length > settings.FILE_UPLOAD_MAX_MEMORY_SIZE: |
|---|
| 156 |
self.activated = False |
|---|
| 157 |
else: |
|---|
| 158 |
self.activated = True |
|---|
| 159 |
|
|---|
| 160 |
def new_file(self, *args, **kwargs): |
|---|
| 161 |
super(MemoryFileUploadHandler, self).new_file(*args, **kwargs) |
|---|
| 162 |
if self.activated: |
|---|
| 163 |
self.file = StringIO() |
|---|
| 164 |
raise StopFutureHandlers() |
|---|
| 165 |
|
|---|
| 166 |
def receive_data_chunk(self, raw_data, start): |
|---|
| 167 |
""" |
|---|
| 168 |
Add the data to the StringIO file. |
|---|
| 169 |
""" |
|---|
| 170 |
if self.activated: |
|---|
| 171 |
self.file.write(raw_data) |
|---|
| 172 |
else: |
|---|
| 173 |
return raw_data |
|---|
| 174 |
|
|---|
| 175 |
def file_complete(self, file_size): |
|---|
| 176 |
""" |
|---|
| 177 |
Return a file object if we're activated. |
|---|
| 178 |
""" |
|---|
| 179 |
if not self.activated: |
|---|
| 180 |
return |
|---|
| 181 |
|
|---|
| 182 |
return InMemoryUploadedFile( |
|---|
| 183 |
file = self.file, |
|---|
| 184 |
field_name = self.field_name, |
|---|
| 185 |
name = self.file_name, |
|---|
| 186 |
content_type = self.content_type, |
|---|
| 187 |
size = file_size, |
|---|
| 188 |
charset = self.charset |
|---|
| 189 |
) |
|---|
| 190 |
|
|---|
| 191 |
|
|---|
| 192 |
def load_handler(path, *args, **kwargs): |
|---|
| 193 |
""" |
|---|
| 194 |
Given a path to a handler, return an instance of that handler. |
|---|
| 195 |
|
|---|
| 196 |
E.g.:: |
|---|
| 197 |
>>> load_handler('django.core.files.uploadhandler.TemporaryFileUploadHandler', request) |
|---|
| 198 |
<TemporaryFileUploadHandler object at 0x...> |
|---|
| 199 |
|
|---|
| 200 |
""" |
|---|
| 201 |
i = path.rfind('.') |
|---|
| 202 |
module, attr = path[:i], path[i+1:] |
|---|
| 203 |
try: |
|---|
| 204 |
mod = __import__(module, {}, {}, [attr]) |
|---|
| 205 |
except ImportError, e: |
|---|
| 206 |
raise ImproperlyConfigured('Error importing upload handler module %s: "%s"' % (module, e)) |
|---|
| 207 |
except ValueError, e: |
|---|
| 208 |
raise ImproperlyConfigured('Error importing upload handler module. Is FILE_UPLOAD_HANDLERS a correctly defined list or tuple?') |
|---|
| 209 |
try: |
|---|
| 210 |
cls = getattr(mod, attr) |
|---|
| 211 |
except AttributeError: |
|---|
| 212 |
raise ImproperlyConfigured('Module "%s" does not define a "%s" upload handler backend' % (module, attr)) |
|---|
| 213 |
return cls(*args, **kwargs) |
|---|