|
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) |