Loading django/contrib/admin/options.py +5 −4 Original line number Diff line number Diff line Loading @@ -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.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 Loading Loading @@ -1026,10 +1027,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()) Loading tests/admin_views/admin.py +16 −2 Original line number Diff line number Diff line Loading @@ -9,11 +9,13 @@ from django.contrib import admin from django.contrib.admin.views.main import ChangeList from django.core.files.storage 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, Loading Loading @@ -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): Loading tests/admin_views/tests.py +29 −2 Original line number Diff line number Diff line Loading @@ -2432,6 +2432,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 = 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): """ Ensure that actions are ordered as expected. Loading @@ -2440,9 +2463,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): Loading Loading
django/contrib/admin/options.py +5 −4 Original line number Diff line number Diff line Loading @@ -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.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 Loading Loading @@ -1026,10 +1027,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()) Loading
tests/admin_views/admin.py +16 −2 Original line number Diff line number Diff line Loading @@ -9,11 +9,13 @@ from django.contrib import admin from django.contrib.admin.views.main import ChangeList from django.core.files.storage 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, Loading Loading @@ -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): Loading
tests/admin_views/tests.py +29 −2 Original line number Diff line number Diff line Loading @@ -2432,6 +2432,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 = 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): """ Ensure that actions are ordered as expected. Loading @@ -2440,9 +2463,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): Loading