Repository: encode/django-rest-framework

Issue:
request.FILES  close
Django closes transparently the elements of requests.FILES since https://github.com/django/django/pull/2747/ , but Django Rest Framework forgets doing so.  In particular it leaves  /tmp/*.upload files.


Discussion:
Django REST framework system is built on top of Django's. Moreover, I've seen cases where test failed because the file was closed during the API call.
I'm closing the issue due to the lack of evidences.
Will reconsider with a failing test case we can reproduce.
[demo.zip](https://github.com/encode/django-rest-framework/files/1068133/demo.zip)

Here `curl -F file=@/tmp/fff http://localhost:8080/A/` does  not leave /tmp/.....upload file, but `curl -F file=@/tmp/fff http://localhost:8080/Z/` leaves.

Note  `FILE_UPLOAD_HANDLERS = ['django.core.files.uploadhandler.TemporaryFileUploadHandler']`
 
Hi @dilyanpalauzov - the demo definitely demonstrates that temporary files remain after the request has been processed by DRF. DRF's stream processing is preempting Django's own data/files handling, so the underlying Django request object doesn't have any `_files` to close. 

Would you be willing to open a PR with a failing test case? 
I do not have the time to delve how to write test cases.

Resolving patch:
diff --git a/rest_framework/request.py b/rest_framework/request.py
--- a/rest_framework/request.py
+++ b/rest_framework/request.py
@@ -250,6 +250,10 @@ def _load_data_and_files(self):
             else:
                 self._full_data = self._data
 
+            # copy files refs to the underlying request so that closable
+            # objects are handled appropriately.
+            self._request._files = self._files
+
     def _load_stream(self):
         """
         Return the content body of the request, as a stream.


Test patch:
diff --git a/tests/test_fields.py b/tests/test_fields.py
--- a/tests/test_fields.py
+++ b/tests/test_fields.py
@@ -1144,7 +1144,7 @@ class TestDateTimeField(FieldValues):
     valid_inputs = {
         '2001-01-01 13:00': datetime.datetime(2001, 1, 1, 13, 00, tzinfo=utc),
         '2001-01-01T13:00': datetime.datetime(2001, 1, 1, 13, 00, tzinfo=utc),
-        '2001-01-01T13:00Z': datetime.datetime(2001, 1, 1, 13, 00, tzinfo=utc)
+        '2001-01-01T13:00Z': datetime.datetime(2001, 1, 1, 13, 00, tzinfo=utc),
         datetime.datetime(2001, 1, 1, 13, 00): datetime.datetime(2001, 1, 1, 13, 00, tzinfo=utc),
         datetime.datetime(2001, 1, 1, 13, 00, tzinfo=utc): datetime.datetime(2001, 1, 1, 13, 00, tzinfo=utc),
     }
diff --git a/tests/test_request.py b/tests/test_request.py
--- a/tests/test_request.py
+++ b/tests/test_request.py
@@ -3,6 +3,9 @@
 """
 from __future__ import unicode_literals
 
+import os.path
+import tempfile
+
 from django.conf.urls import url
 from django.contrib.auth import authenticate, login, logout
 from django.contrib.auth.models import User
@@ -120,11 +123,39 @@ def post(self, request):
 
         return Response(status=status.HTTP_500_INTERNAL_SERVER_ERROR)
 
+
+class FileUploadView(APIView):
+    def post(self, request):
+        filenames = [file.temporary_file_path() for file in request.FILES.values()]
+
+        for filename in filenames:
+            assert os.path.exists(filename)
+
+        return Response(status=status.HTTP_200_OK, data=filenames)
+
+
 urlpatterns = [
     url(r'^$', MockView.as_view()),
+    url(r'^upload/$', FileUploadView.as_view())
 ]
 
 
+@override_settings(
+    ROOT_URLCONF='tests.test_request',
+    FILE_UPLOAD_HANDLERS=['django.core.files.uploadhandler.TemporaryFileUploadHandler'])
+class FileUploadTests(TestCase):
+
+    def test_fileuploads_closed_at_request_end(self):
+        with tempfile.NamedTemporaryFile() as f:
+            response = self.client.post('/upload/', {'file': f})
+
+        # sanity check that file was processed
+        assert len(response.data) == 1
+
+        for file in response.data:
+            assert not os.path.exists(file)
+
+
 @override_settings(ROOT_URLCONF='tests.test_request')
 class TestContentParsingWithAuthentication(TestCase):
     def setUp(self):