diff --git a/rest_framework/response.py b/rest_framework/response.py index bf0663255..c7d2ce5b6 100644 --- a/rest_framework/response.py +++ b/rest_framework/response.py @@ -13,6 +13,29 @@ from django.utils.six.moves.http_client import responses from rest_framework.serializers import Serializer +def cleanup(response): + """ Cleanup circular reference between view/request/response object + + This reduce load on GC and help to keep low memory usage even + if some response are larger. + """ + view = response.renderer_context.get('view') + request = response.renderer_context.get('request') + + if view: + view.response = None + view.request = None + + if request: + request.parser_context.clear() + + response.renderer_context.clear() + + # Re-add request in renderer_context. It does not seem to cause + # circular reference and is needed for tests. + response.renderer_context['request'] = request + + class Response(SimpleTemplateResponse): """ An HttpResponse that allows its data to be rendered into @@ -48,6 +71,8 @@ class Response(SimpleTemplateResponse): for name, value in six.iteritems(headers): self[name] = value + self.add_post_render_callback(cleanup) + @property def rendered_content(self): renderer = getattr(self, 'accepted_renderer', None) diff --git a/rest_framework/viewsets.py b/rest_framework/viewsets.py index 9a85049bc..a7f1b9aad 100644 --- a/rest_framework/viewsets.py +++ b/rest_framework/viewsets.py @@ -100,7 +100,16 @@ class ViewSetMixin(object): self.kwargs = kwargs # And continue as usual - return self.dispatch(request, *args, **kwargs) + result = self.dispatch(request, *args, **kwargs) + + # break our circular reference before finishing + for method in actions: + delattr(self, method) + + if hasattr(self, 'head'): + delattr(self, 'head') + + return result # take name and docstring from class update_wrapper(view, cls, updated=()) diff --git a/tests/test_generics.py b/tests/test_generics.py index c0ff1c5c4..b60ff77e3 100644 --- a/tests/test_generics.py +++ b/tests/test_generics.py @@ -167,7 +167,7 @@ class TestRootView(TestCase): request = factory.post('/', data, HTTP_ACCEPT='text/html') response = self.view(request).render() expected_error = 'Ensure this field has no more than 100 characters.' - assert expected_error in response.rendered_content.decode('utf-8') + assert expected_error in response.content.decode('utf-8') EXPECTED_QUERIES_FOR_PUT = 2 @@ -314,7 +314,7 @@ class TestInstanceView(TestCase): request = factory.put('/', data, HTTP_ACCEPT='text/html') response = self.view(request, pk=1).render() expected_error = 'Ensure this field has no more than 100 characters.' - assert expected_error in response.rendered_content.decode('utf-8') + assert expected_error in response.content.decode('utf-8') class TestFKInstanceView(TestCase):