uploadhandler.py 6.7 KB

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