Skip to content


Subversion checkout URL

You can clone with
Download ZIP


Fixed #20331 -- Allowed admin actions to serve StreamingHttpResponses #1230

wants to merge 1 commit into from

1 participant

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on May 29, 2013
  1. @timgraham
This page is out of date. Refresh to see the latest.
9 django/contrib/admin/
@@ -24,6 +24,7 @@
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
@@ -1026,10 +1027,10 @@ def response_action(self, request, queryset):
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
return HttpResponseRedirect(request.get_full_path())
18 tests/admin_views/
@@ -9,11 +9,13 @@
from django.contrib.admin.views.main import ChangeList
from import FileSystemStorage
from django.core.mail import EmailMessage
+from django.core.servers.basehttp import FileWrapper
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 django.utils.six import StringIO
from .models import (Article, Chapter, Account, Media, Child, Parent, Picture,
Widget, DooHickey, Grommet, Whatsit, FancyDoodad, Category, Link,
@@ -238,8 +240,20 @@ def redirect_to(modeladmin, request, selected):
redirect_to.short_description = 'Redirect to (Awesome action)'
+def download(modeladmin, request, selected):
+ buf = 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):
31 tests/admin_views/
@@ -2432,6 +2432,29 @@ def test_default_redirect(self):
response =, 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': 'download',
+ 'index': 0,
+ }
+ response ='/test_admin/admin/admin_views/externalsubscriber/', action_data)
+ content = b''.join(response.streaming_content)
+ self.assertEqual(content, b'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': 'no_perm',
+ 'index': 0,
+ }
+ response ='/test_admin/admin/admin_views/externalsubscriber/', action_data)
+ self.assertEqual(response.status_code, 403)
+ self.assertEqual(response.content, b'No permission to perform this action')
def test_actions_ordering(self):
Ensure that actions are ordered as expected.
@@ -2440,9 +2463,13 @@ def test_actions_ordering(self):
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
<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
+<option value="download">Download subscription</option>
+<option value="no_perm">No permission to run</option>
</select>''', html=True)
def test_model_without_action(self):
Something went wrong with that request. Please try again.