Fixed #20331 -- Allowed admin actions to serve StreamingHttpResponses

Thanks Edwin.
This commit is contained in:
Tim Graham 2013-05-29 10:55:56 -04:00
parent 8010289ea2
commit d727518ad6
3 changed files with 50 additions and 8 deletions

View File

@ -24,6 +24,7 @@ from django.db.models.related import RelatedObject
from django.db.models.fields import BLANK_CHOICE_DASH, FieldDoesNotExist from django.db.models.fields import BLANK_CHOICE_DASH, FieldDoesNotExist
from django.db.models.sql.constants import QUERY_TERMS from django.db.models.sql.constants import QUERY_TERMS
from django.http import Http404, HttpResponse, HttpResponseRedirect from django.http import Http404, HttpResponse, HttpResponseRedirect
from django.http.response import HttpResponseBase
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.template.response import SimpleTemplateResponse, TemplateResponse from django.template.response import SimpleTemplateResponse, TemplateResponse
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
@ -1026,10 +1027,10 @@ class ModelAdmin(BaseModelAdmin):
response = func(self, request, queryset) response = func(self, request, queryset)
# Actions may return an HttpResponse, which will be used as the # Actions may return an HttpResponse-like object, which will be
# response from the POST. If not, we'll be a good little HTTP # used as the response from the POST. If not, we'll be a good
# citizen and redirect back to the changelist page. # little HTTP citizen and redirect back to the changelist page.
if isinstance(response, HttpResponse): if isinstance(response, HttpResponseBase):
return response return response
else: else:
return HttpResponseRedirect(request.get_full_path()) return HttpResponseRedirect(request.get_full_path())

View File

@ -9,11 +9,13 @@ from django.contrib import admin
from django.contrib.admin.views.main import ChangeList from django.contrib.admin.views.main import ChangeList
from django.core.files.storage import FileSystemStorage from django.core.files.storage import FileSystemStorage
from django.core.mail import EmailMessage from django.core.mail import EmailMessage
from django.core.servers.basehttp import FileWrapper
from django.conf.urls import patterns, url from django.conf.urls import patterns, url
from django.db import models from django.db import models
from django.forms.models import BaseModelFormSet 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.contrib.admin import BooleanFieldListFilter
from django.utils.six import StringIO
from .models import (Article, Chapter, Account, Media, Child, Parent, Picture, from .models import (Article, Chapter, Account, Media, Child, Parent, Picture,
Widget, DooHickey, Grommet, Whatsit, FancyDoodad, Category, Link, 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)' 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): class ExternalSubscriberAdmin(admin.ModelAdmin):
actions = [redirect_to, external_mail] actions = [redirect_to, external_mail, download, no_perm]
class Podcast(Media): class Podcast(Media):

View File

@ -2432,6 +2432,29 @@ class AdminActionsTest(TestCase):
response = self.client.post(url, action_data) response = self.client.post(url, action_data)
self.assertRedirects(response, url) 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 = 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_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, b'No permission to perform this action')
def test_actions_ordering(self): def test_actions_ordering(self):
""" """
Ensure that actions are ordered as expected. Ensure that actions are ordered as expected.
@ -2440,9 +2463,13 @@ class AdminActionsTest(TestCase):
response = self.client.get('/test_admin/admin/admin_views/externalsubscriber/') response = self.client.get('/test_admin/admin/admin_views/externalsubscriber/')
self.assertContains(response, '''<label>Action: <select name="action"> self.assertContains(response, '''<label>Action: <select name="action">
<option value="" selected="selected">---------</option> <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="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) </select>''', html=True)
def test_model_without_action(self): def test_model_without_action(self):