|
0
|
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 |
from django.utils import importlib |
|
|
14 |
|
|
|
15 |
__all__ = ['UploadFileException','StopUpload', 'SkipFile', 'FileUploadHandler', |
|
|
16 |
'TemporaryFileUploadHandler', 'MemoryFileUploadHandler', |
|
|
17 |
'load_handler', 'StopFutureHandlers'] |
|
|
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 = TemporaryUploadedFile(self.file_name, self.content_type, 0, self.charset) |
|
|
136 |
|
|
|
137 |
def receive_data_chunk(self, raw_data, start): |
|
|
138 |
self.file.write(raw_data) |
|
|
139 |
|
|
|
140 |
def file_complete(self, file_size): |
|
|
141 |
self.file.seek(0) |
|
|
142 |
self.file.size = file_size |
|
|
143 |
return self.file |
|
|
144 |
|
|
|
145 |
class MemoryFileUploadHandler(FileUploadHandler): |
|
|
146 |
""" |
|
|
147 |
File upload handler to stream uploads into memory (used for small files). |
|
|
148 |
""" |
|
|
149 |
|
|
|
150 |
def handle_raw_input(self, input_data, META, content_length, boundary, encoding=None): |
|
|
151 |
""" |
|
|
152 |
Use the content_length to signal whether or not this handler should be in use. |
|
|
153 |
""" |
|
|
154 |
# Check the content-length header to see if we should |
|
|
155 |
# If the post is too large, we cannot use the Memory handler. |
|
|
156 |
if content_length > settings.FILE_UPLOAD_MAX_MEMORY_SIZE: |
|
|
157 |
self.activated = False |
|
|
158 |
else: |
|
|
159 |
self.activated = True |
|
|
160 |
|
|
|
161 |
def new_file(self, *args, **kwargs): |
|
|
162 |
super(MemoryFileUploadHandler, self).new_file(*args, **kwargs) |
|
|
163 |
if self.activated: |
|
|
164 |
self.file = StringIO() |
|
|
165 |
raise StopFutureHandlers() |
|
|
166 |
|
|
|
167 |
def receive_data_chunk(self, raw_data, start): |
|
|
168 |
""" |
|
|
169 |
Add the data to the StringIO file. |
|
|
170 |
""" |
|
|
171 |
if self.activated: |
|
|
172 |
self.file.write(raw_data) |
|
|
173 |
else: |
|
|
174 |
return raw_data |
|
|
175 |
|
|
|
176 |
def file_complete(self, file_size): |
|
|
177 |
""" |
|
|
178 |
Return a file object if we're activated. |
|
|
179 |
""" |
|
|
180 |
if not self.activated: |
|
|
181 |
return |
|
|
182 |
|
|
|
183 |
self.file.seek(0) |
|
|
184 |
return InMemoryUploadedFile( |
|
|
185 |
file = self.file, |
|
|
186 |
field_name = self.field_name, |
|
|
187 |
name = self.file_name, |
|
|
188 |
content_type = self.content_type, |
|
|
189 |
size = file_size, |
|
|
190 |
charset = self.charset |
|
|
191 |
) |
|
|
192 |
|
|
|
193 |
|
|
|
194 |
def load_handler(path, *args, **kwargs): |
|
|
195 |
""" |
|
|
196 |
Given a path to a handler, return an instance of that handler. |
|
|
197 |
|
|
|
198 |
E.g.:: |
|
|
199 |
>>> load_handler('django.core.files.uploadhandler.TemporaryFileUploadHandler', request) |
|
|
200 |
<TemporaryFileUploadHandler object at 0x...> |
|
|
201 |
|
|
|
202 |
""" |
|
|
203 |
i = path.rfind('.') |
|
|
204 |
module, attr = path[:i], path[i+1:] |
|
|
205 |
try: |
|
|
206 |
mod = importlib.import_module(module) |
|
|
207 |
except ImportError, e: |
|
|
208 |
raise ImproperlyConfigured('Error importing upload handler module %s: "%s"' % (module, e)) |
|
|
209 |
except ValueError, e: |
|
|
210 |
raise ImproperlyConfigured('Error importing upload handler module. Is FILE_UPLOAD_HANDLERS a correctly defined list or tuple?') |
|
|
211 |
try: |
|
|
212 |
cls = getattr(mod, attr) |
|
|
213 |
except AttributeError: |
|
|
214 |
raise ImproperlyConfigured('Module "%s" does not define a "%s" upload handler backend' % (module, attr)) |
|
|
215 |
return cls(*args, **kwargs) |