diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py
index b35f100..812a24f 100644
--- a/django/contrib/admin/options.py
+++ b/django/contrib/admin/options.py
@@ -22,6 +22,7 @@ from django.db.models.related import RelatedObject
 from django.db.models.fields import BLANK_CHOICE_DASH, FieldDoesNotExist
 from django.db.models.sql.constants import QUERY_TERMS
 from django.http import Http404, HttpResponse, HttpResponseRedirect
+from django.http.response import HttpResponseBase
 from django.shortcuts import get_object_or_404
 from django.template.response import SimpleTemplateResponse, TemplateResponse
 from django.utils.decorators import method_decorator
@@ -973,10 +974,10 @@ class ModelAdmin(BaseModelAdmin):
 
             response = func(self, request, queryset)
 
-            # Actions may return an HttpResponse, which will be used as the
-            # response from the POST. If not, we'll be a good little HTTP
-            # citizen and redirect back to the changelist page.
-            if isinstance(response, HttpResponse):
+            # Actions may return an HttpResponse-like object, which will be
+            # used as the response from the POST. If not, we'll be a good
+            # little HTTP citizen and redirect back to the changelist page.
+            if isinstance(response, HttpResponseBase):
                 return response
             else:
                 return HttpResponseRedirect(request.get_full_path())
diff --git a/docs/releases/1.6.txt b/docs/releases/1.6.txt
index 3e15f5b..3f6c453 100644
--- a/docs/releases/1.6.txt
+++ b/docs/releases/1.6.txt
@@ -202,6 +202,10 @@ Minor features
 * Added ``BCryptSHA256PasswordHasher`` to resolve the password truncation issue
   with bcrypt.
 
+* :doc:`Admin action </ref/contrib/admin/actions>` can now return any response
+  that is a subclass of ``HttpResponseBase``, including
+  :class:`~django.http.StreamingHttpResponse`.
+
 Backwards incompatible changes in 1.6
 =====================================
 
diff --git a/tests/admin_views/admin.py b/tests/admin_views/admin.py
index cc7585c..e1f2ef2 100644
--- a/tests/admin_views/admin.py
+++ b/tests/admin_views/admin.py
@@ -12,7 +12,7 @@ from django.core.mail import EmailMessage
 from django.conf.urls import patterns, url
 from django.db import models
 from django.forms.models import BaseModelFormSet
-from django.http import HttpResponse
+from django.http import HttpResponse, StreamingHttpResponse
 from django.contrib.admin import BooleanFieldListFilter
 
 from .models import (Article, Chapter, Account, Media, Child, Parent, Picture,
@@ -238,8 +238,22 @@ def redirect_to(modeladmin, request, selected):
 redirect_to.short_description = 'Redirect to (Awesome action)'
 
 
+def download(modeladmin, request, selected):
+    import StringIO
+    from django.core.servers.basehttp import FileWrapper
+    buf = StringIO.StringIO('This is the content of the file')
+    return StreamingHttpResponse(FileWrapper(buf))
+download.short_description = 'Download subscription'
+
+
+def no_perm(modeladmin, request, selected):
+    return HttpResponse(content='No permission to perform this action',
+                        status=403)
+no_perm.short_description = 'No permission to run'
+
+
 class ExternalSubscriberAdmin(admin.ModelAdmin):
-    actions = [redirect_to, external_mail]
+    actions = [redirect_to, external_mail, download, no_perm]
 
 
 class Podcast(Media):
diff --git a/tests/admin_views/tests.py b/tests/admin_views/tests.py
index e0000ff..1c78b60 100644
--- a/tests/admin_views/tests.py
+++ b/tests/admin_views/tests.py
@@ -2413,6 +2413,29 @@ class AdminActionsTest(TestCase):
         response = self.client.post(url, action_data)
         self.assertRedirects(response, url)
 
+    def test_custom_function_action_streaming_response(self):
+        """Tests a custom action that returns a StreamingHttpResponse."""
+        action_data = {
+            ACTION_CHECKBOX_NAME: [1],
+            'action': 'download',
+            'index': 0,
+        }
+        response = self.client.post('/test_admin/admin/admin_views/externalsubscriber/', action_data)
+        content = ''.join([b for b in response.streaming_content])
+        self.assertEqual(content, 'This is the content of the file')
+        self.assertEqual(response.status_code, 200)
+
+    def test_custom_function_action_no_perm_response(self):
+        """Tests a custom action that returns an HttpResponse with 403 code."""
+        action_data = {
+            ACTION_CHECKBOX_NAME: [1],
+            'action': 'no_perm',
+            'index': 0,
+        }
+        response = self.client.post('/test_admin/admin/admin_views/externalsubscriber/', action_data)
+        self.assertEqual(response.status_code, 403)
+        self.assertEqual(response.content, 'No permission to perform this action')
+
     def test_actions_ordering(self):
         """
         Ensure that actions are ordered as expected.
@@ -2421,9 +2444,13 @@ class AdminActionsTest(TestCase):
         response = self.client.get('/test_admin/admin/admin_views/externalsubscriber/')
         self.assertContains(response, '''<label>Action: <select name="action">
 <option value="" selected="selected">---------</option>
-<option value="delete_selected">Delete selected external subscribers</option>
+<option value="delete_selected">Delete selected external
+subscribers</option>
 <option value="redirect_to">Redirect to (Awesome action)</option>
-<option value="external_mail">External mail (Another awesome action)</option>
+<option value="external_mail">External mail (Another awesome
+action)</option>
+<option value="download">Download subscription</option>
+<option value="no_perm">No permission to run</option>
 </select>''', html=True)
 
     def test_model_without_action(self):
