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