Fixed #30422 -- Made TemporaryFileUploadHandler handle interrupted uploads.
This patch allows upload handlers to handle interrupted uploads. Co-Authored-By: Mariusz Felisiak <felisiak.mariusz@gmail.com>
This commit is contained in:
parent
21b127bfbc
commit
11c4a4412b
|
@ -1,7 +1,7 @@
|
|||
"""
|
||||
Base file upload handler classes, and the built-in concrete subclasses
|
||||
"""
|
||||
|
||||
import os
|
||||
from io import BytesIO
|
||||
|
||||
from django.conf import settings
|
||||
|
@ -127,6 +127,13 @@ class FileUploadHandler:
|
|||
"""
|
||||
pass
|
||||
|
||||
def upload_interrupted(self):
|
||||
"""
|
||||
Signal that the upload was interrupted. Subclasses should perform
|
||||
cleanup that is necessary for this handler.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class TemporaryFileUploadHandler(FileUploadHandler):
|
||||
"""
|
||||
|
@ -147,6 +154,15 @@ class TemporaryFileUploadHandler(FileUploadHandler):
|
|||
self.file.size = file_size
|
||||
return self.file
|
||||
|
||||
def upload_interrupted(self):
|
||||
if hasattr(self, 'file'):
|
||||
temp_location = self.file.temporary_file_path()
|
||||
try:
|
||||
self.file.close()
|
||||
os.remove(temp_location)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
|
||||
|
||||
class MemoryFileUploadHandler(FileUploadHandler):
|
||||
"""
|
||||
|
|
|
@ -150,6 +150,8 @@ class MultiPartParser:
|
|||
num_post_keys = 0
|
||||
# To limit the amount of data read from the request.
|
||||
read_size = None
|
||||
# Whether a file upload is finished.
|
||||
uploaded_file = True
|
||||
|
||||
try:
|
||||
for item_type, meta_data, field_stream in Parser(stream, self._boundary):
|
||||
|
@ -159,6 +161,7 @@ class MultiPartParser:
|
|||
# we hit the next boundary/part of the multipart content.
|
||||
self.handle_file_complete(old_field_name, counters)
|
||||
old_field_name = None
|
||||
uploaded_file = True
|
||||
|
||||
try:
|
||||
disposition = meta_data['content-disposition'][1]
|
||||
|
@ -225,6 +228,7 @@ class MultiPartParser:
|
|||
content_length = None
|
||||
|
||||
counters = [0] * len(handlers)
|
||||
uploaded_file = False
|
||||
try:
|
||||
for handler in handlers:
|
||||
try:
|
||||
|
@ -279,6 +283,9 @@ class MultiPartParser:
|
|||
if not e.connection_reset:
|
||||
exhaust(self._input_data)
|
||||
else:
|
||||
if not uploaded_file:
|
||||
for handler in handlers:
|
||||
handler.upload_interrupted()
|
||||
# Make sure that the request data is all fed
|
||||
exhaust(self._input_data)
|
||||
|
||||
|
|
|
@ -212,6 +212,13 @@ attributes:
|
|||
|
||||
Callback signaling that the entire upload (all files) has completed.
|
||||
|
||||
.. method:: FileUploadHandler.upload_interrupted()
|
||||
|
||||
.. versionadded:: 3.2
|
||||
|
||||
Callback signaling that the upload was interrupted, e.g. when the user
|
||||
closed their browser during file upload.
|
||||
|
||||
.. method:: FileUploadHandler.handle_raw_input(input_data, META, content_length, boundary, encoding)
|
||||
|
||||
Allows the handler to completely override the parsing of the raw
|
||||
|
|
|
@ -195,8 +195,9 @@ File Storage
|
|||
File Uploads
|
||||
~~~~~~~~~~~~
|
||||
|
||||
* ...
|
||||
|
||||
* The new :meth:`FileUploadHandler.upload_interrupted()
|
||||
<django.core.files.uploadhandler.FileUploadHandler.upload_interrupted>`
|
||||
callback allows handling interrupted uploads.
|
||||
|
||||
Forms
|
||||
~~~~~
|
||||
|
|
|
@ -6,12 +6,13 @@ import sys
|
|||
import tempfile as sys_tempfile
|
||||
import unittest
|
||||
from io import BytesIO, StringIO
|
||||
from unittest import mock
|
||||
from urllib.parse import quote
|
||||
|
||||
from django.core.files import temp as tempfile
|
||||
from django.core.files.uploadedfile import SimpleUploadedFile
|
||||
from django.http.multipartparser import (
|
||||
MultiPartParser, MultiPartParserError, parse_header,
|
||||
FILE, MultiPartParser, MultiPartParserError, Parser, parse_header,
|
||||
)
|
||||
from django.test import SimpleTestCase, TestCase, client, override_settings
|
||||
|
||||
|
@ -443,6 +444,30 @@ class FileUploadTests(TestCase):
|
|||
temp_path = response.json()['temp_path']
|
||||
self.assertIs(os.path.exists(temp_path), False)
|
||||
|
||||
def test_upload_interrupted_temporary_file_handler(self):
|
||||
# Simulate an interrupted upload by omitting the closing boundary.
|
||||
class MockedParser(Parser):
|
||||
def __iter__(self):
|
||||
for item in super().__iter__():
|
||||
item_type, meta_data, field_stream = item
|
||||
yield item_type, meta_data, field_stream
|
||||
if item_type == FILE:
|
||||
return
|
||||
|
||||
with tempfile.NamedTemporaryFile() as temp_file:
|
||||
temp_file.write(b'a')
|
||||
temp_file.seek(0)
|
||||
with mock.patch(
|
||||
'django.http.multipartparser.Parser',
|
||||
MockedParser,
|
||||
):
|
||||
response = self.client.post(
|
||||
'/temp_file/upload_interrupted/',
|
||||
{'file': temp_file},
|
||||
)
|
||||
temp_path = response.json()['temp_path']
|
||||
self.assertIs(os.path.exists(temp_path), False)
|
||||
|
||||
def test_fileupload_getlist(self):
|
||||
file = tempfile.NamedTemporaryFile
|
||||
with file() as file1, file() as file2, file() as file2a:
|
||||
|
|
|
@ -14,6 +14,7 @@ urlpatterns = [
|
|||
path('getlist_count/', views.file_upload_getlist_count),
|
||||
path('upload_errors/', views.file_upload_errors),
|
||||
path('temp_file/stop_upload/', views.file_stop_upload_temporary_file),
|
||||
path('temp_file/upload_interrupted/', views.file_upload_interrupted_temporary_file),
|
||||
path('filename_case/', views.file_upload_filename_case_view),
|
||||
re_path(r'^fd_closing/(?P<access>t|f)/$', views.file_upload_fd_closing),
|
||||
]
|
||||
|
|
|
@ -2,6 +2,7 @@ import hashlib
|
|||
import os
|
||||
|
||||
from django.core.files.uploadedfile import UploadedFile
|
||||
from django.core.files.uploadhandler import TemporaryFileUploadHandler
|
||||
from django.http import HttpResponse, HttpResponseServerError, JsonResponse
|
||||
|
||||
from .models import FileModel
|
||||
|
@ -112,6 +113,15 @@ def file_stop_upload_temporary_file(request):
|
|||
)
|
||||
|
||||
|
||||
def file_upload_interrupted_temporary_file(request):
|
||||
request.upload_handlers.insert(0, TemporaryFileUploadHandler())
|
||||
request.upload_handlers.pop(2)
|
||||
request.FILES # Trigger file parsing.
|
||||
return JsonResponse(
|
||||
{'temp_path': request.upload_handlers[0].file.temporary_file_path()},
|
||||
)
|
||||
|
||||
|
||||
def file_upload_getlist_count(request):
|
||||
"""
|
||||
Check the .getlist() function to ensure we receive the correct number of files.
|
||||
|
|
Loading…
Reference in New Issue