summaryrefslogtreecommitdiff
path: root/tests/admin_views
diff options
context:
space:
mode:
authorTim Graham <timograham@gmail.com>2017-06-19 15:10:26 -0400
committerTim Graham <timograham@gmail.com>2017-06-19 16:00:06 -0400
commit7d52de31af40b031aa055f326bb334024c1ec0cc (patch)
tree789def5518fe4f238e3a57b7109290e7ef3a871f /tests/admin_views
parent57f4b3ce373c1c1fef794507a16410d40aebdda6 (diff)
Moved admin's action view tests to a separate file.
Diffstat (limited to 'tests/admin_views')
-rw-r--r--tests/admin_views/test_actions.py366
-rw-r--r--tests/admin_views/tests.py381
2 files changed, 374 insertions, 373 deletions
diff --git a/tests/admin_views/test_actions.py b/tests/admin_views/test_actions.py
new file mode 100644
index 0000000000..f869fc2cc2
--- /dev/null
+++ b/tests/admin_views/test_actions.py
@@ -0,0 +1,366 @@
+import json
+
+from django.contrib.admin.helpers import ACTION_CHECKBOX_NAME
+from django.contrib.admin.views.main import IS_POPUP_VAR
+from django.contrib.auth.models import User
+from django.core import mail
+from django.template.loader import render_to_string
+from django.template.response import TemplateResponse
+from django.test import TestCase, override_settings
+from django.urls import reverse
+
+from .forms import MediaActionForm
+from .models import (
+ Actor, Answer, ExternalSubscriber, Question, Subscriber,
+ UnchangeableObject,
+)
+
+
+@override_settings(ROOT_URLCONF='admin_views.urls')
+class AdminActionsTest(TestCase):
+
+ @classmethod
+ def setUpTestData(cls):
+ cls.superuser = User.objects.create_superuser(username='super', password='secret', email='super@example.com')
+ cls.s1 = ExternalSubscriber.objects.create(name='John Doe', email='john@example.org')
+ cls.s2 = Subscriber.objects.create(name='Max Mustermann', email='max@example.org')
+
+ def setUp(self):
+ self.client.force_login(self.superuser)
+
+ def test_model_admin_custom_action(self):
+ """A custom action defined in a ModelAdmin method."""
+ action_data = {
+ ACTION_CHECKBOX_NAME: [self.s1.pk],
+ 'action': 'mail_admin',
+ 'index': 0,
+ }
+ self.client.post(reverse('admin:admin_views_subscriber_changelist'), action_data)
+ self.assertEqual(len(mail.outbox), 1)
+ self.assertEqual(mail.outbox[0].subject, 'Greetings from a ModelAdmin action')
+
+ def test_model_admin_default_delete_action(self):
+ action_data = {
+ ACTION_CHECKBOX_NAME: [self.s1.pk, self.s2.pk],
+ 'action': 'delete_selected',
+ 'index': 0,
+ }
+ delete_confirmation_data = {
+ ACTION_CHECKBOX_NAME: [self.s1.pk, self.s2.pk],
+ 'action': 'delete_selected',
+ 'post': 'yes',
+ }
+ confirmation = self.client.post(reverse('admin:admin_views_subscriber_changelist'), action_data)
+ self.assertIsInstance(confirmation, TemplateResponse)
+ self.assertContains(confirmation, 'Are you sure you want to delete the selected subscribers?')
+ self.assertContains(confirmation, '<h2>Summary</h2>')
+ self.assertContains(confirmation, '<li>Subscribers: 2</li>')
+ self.assertContains(confirmation, '<li>External subscribers: 1</li>')
+ self.assertContains(confirmation, ACTION_CHECKBOX_NAME, count=2)
+ self.client.post(reverse('admin:admin_views_subscriber_changelist'), delete_confirmation_data)
+ self.assertEqual(Subscriber.objects.count(), 0)
+
+ @override_settings(USE_THOUSAND_SEPARATOR=True, USE_L10N=True)
+ def test_non_localized_pk(self):
+ """
+ If USE_THOUSAND_SEPARATOR is set, the ids for the objects selected for
+ deletion are rendered without separators.
+ """
+ s = ExternalSubscriber.objects.create(id=9999)
+ action_data = {
+ ACTION_CHECKBOX_NAME: [s.pk, self.s2.pk],
+ 'action': 'delete_selected',
+ 'index': 0,
+ }
+ response = self.client.post(reverse('admin:admin_views_subscriber_changelist'), action_data)
+ self.assertTemplateUsed(response, 'admin/delete_selected_confirmation.html')
+ self.assertContains(response, 'value="9999"') # Instead of 9,999
+ self.assertContains(response, 'value="%s"' % self.s2.pk)
+
+ def test_model_admin_default_delete_action_protected(self):
+ """
+ The default delete action where some related objects are protected
+ from deletion.
+ """
+ q1 = Question.objects.create(question='Why?')
+ a1 = Answer.objects.create(question=q1, answer='Because.')
+ a2 = Answer.objects.create(question=q1, answer='Yes.')
+ q2 = Question.objects.create(question='Wherefore?')
+ action_data = {
+ ACTION_CHECKBOX_NAME: [q1.pk, q2.pk],
+ 'action': 'delete_selected',
+ 'index': 0,
+ }
+ delete_confirmation_data = action_data.copy()
+ delete_confirmation_data['post'] = 'yes'
+ response = self.client.post(reverse('admin:admin_views_question_changelist'), action_data)
+ self.assertContains(response, 'would require deleting the following protected related objects')
+ self.assertContains(
+ response,
+ '<li>Answer: <a href="%s">Because.</a></li>' % reverse('admin:admin_views_answer_change', args=(a1.pk,)),
+ html=True
+ )
+ self.assertContains(
+ response,
+ '<li>Answer: <a href="%s">Yes.</a></li>' % reverse('admin:admin_views_answer_change', args=(a2.pk,)),
+ html=True
+ )
+ # A POST request to delete protected objects displays the page which
+ # says the deletion is prohibited.
+ response = self.client.post(reverse('admin:admin_views_question_changelist'), delete_confirmation_data)
+ self.assertContains(response, 'would require deleting the following protected related objects')
+ self.assertEqual(Question.objects.count(), 2)
+
+ def test_model_admin_default_delete_action_no_change_url(self):
+ """
+ The default delete action doesn't break if a ModelAdmin removes the
+ change_view URL (#20640).
+ """
+ obj = UnchangeableObject.objects.create()
+ action_data = {
+ ACTION_CHECKBOX_NAME: obj.pk,
+ 'action': 'delete_selected',
+ 'index': '0',
+ }
+ response = self.client.post(reverse('admin:admin_views_unchangeableobject_changelist'), action_data)
+ # No 500 caused by NoReverseMatch
+ self.assertEqual(response.status_code, 200)
+ # The page doesn't display a link to the nonexistent change page.
+ self.assertContains(response, '<li>Unchangeable object: %s</li>' % obj, 1, html=True)
+
+ def test_custom_function_mail_action(self):
+ """A custom action may be defined in a function."""
+ action_data = {
+ ACTION_CHECKBOX_NAME: [self.s1.pk],
+ 'action': 'external_mail',
+ 'index': 0,
+ }
+ self.client.post(reverse('admin:admin_views_externalsubscriber_changelist'), action_data)
+ self.assertEqual(len(mail.outbox), 1)
+ self.assertEqual(mail.outbox[0].subject, 'Greetings from a function action')
+
+ def test_custom_function_action_with_redirect(self):
+ """Another custom action defined in a function."""
+ action_data = {
+ ACTION_CHECKBOX_NAME: [self.s1.pk],
+ 'action': 'redirect_to',
+ 'index': 0,
+ }
+ response = self.client.post(reverse('admin:admin_views_externalsubscriber_changelist'), action_data)
+ self.assertEqual(response.status_code, 302)
+
+ def test_default_redirect(self):
+ """
+ Actions which don't return an HttpResponse are redirected to the same
+ page, retaining the querystring (which may contain changelist info).
+ """
+ action_data = {
+ ACTION_CHECKBOX_NAME: [self.s1.pk],
+ 'action': 'external_mail',
+ 'index': 0,
+ }
+ url = reverse('admin:admin_views_externalsubscriber_changelist') + '?o=1'
+ response = self.client.post(url, action_data)
+ self.assertRedirects(response, url)
+
+ def test_custom_function_action_streaming_response(self):
+ """A custom action may return a StreamingHttpResponse."""
+ action_data = {
+ ACTION_CHECKBOX_NAME: [self.s1.pk],
+ 'action': 'download',
+ 'index': 0,
+ }
+ response = self.client.post(reverse('admin:admin_views_externalsubscriber_changelist'), 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):
+ """A custom action may returns an HttpResponse with a 403 code."""
+ action_data = {
+ ACTION_CHECKBOX_NAME: [self.s1.pk],
+ 'action': 'no_perm',
+ 'index': 0,
+ }
+ response = self.client.post(reverse('admin:admin_views_externalsubscriber_changelist'), action_data)
+ self.assertEqual(response.status_code, 403)
+ self.assertEqual(response.content, b'No permission to perform this action')
+
+ def test_actions_ordering(self):
+ """Actions are ordered as expected."""
+ response = self.client.get(reverse('admin:admin_views_externalsubscriber_changelist'))
+ self.assertContains(response, '''<label>Action: <select name="action" required>
+<option value="" selected>---------</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="download">Download subscription</option>
+<option value="no_perm">No permission to run</option>
+</select>''', html=True)
+
+ def test_model_without_action(self):
+ """A ModelAdmin might not have any actions."""
+ response = self.client.get(reverse('admin:admin_views_oldsubscriber_changelist'))
+ self.assertIsNone(response.context['action_form'])
+ self.assertNotContains(
+ response, '<input type="checkbox" class="action-select"',
+ msg_prefix='Found an unexpected action toggle checkboxbox in response'
+ )
+ self.assertNotContains(response, '<input type="checkbox" class="action-select"')
+
+ def test_model_without_action_still_has_jquery(self):
+ """
+ A ModelAdmin without any actions still has jQuery included on the page.
+ """
+ response = self.client.get(reverse('admin:admin_views_oldsubscriber_changelist'))
+ self.assertIsNone(response.context['action_form'])
+ self.assertContains(
+ response, 'jquery.min.js',
+ msg_prefix='jQuery missing from admin pages for model with no admin actions'
+ )
+
+ def test_action_column_class(self):
+ """The checkbox column class is present in the response."""
+ response = self.client.get(reverse('admin:admin_views_subscriber_changelist'))
+ self.assertIsNotNone(response.context['action_form'])
+ self.assertContains(response, 'action-checkbox-column')
+
+ def test_multiple_actions_form(self):
+ """
+ Actions come from the form whose submit button was pressed (#10618).
+ """
+ action_data = {
+ ACTION_CHECKBOX_NAME: [self.s1.pk],
+ # Two different actions selected on the two forms...
+ 'action': ['external_mail', 'delete_selected'],
+ # ...but "go" was clicked on the top form.
+ 'index': 0
+ }
+ self.client.post(reverse('admin:admin_views_externalsubscriber_changelist'), action_data)
+ # The action sends mail rather than deletes.
+ self.assertEqual(len(mail.outbox), 1)
+ self.assertEqual(mail.outbox[0].subject, 'Greetings from a function action')
+
+ def test_media_from_actions_form(self):
+ """
+ The action form's media is included in the changelist view's media.
+ """
+ response = self.client.get(reverse('admin:admin_views_subscriber_changelist'))
+ media_path = MediaActionForm.Media.js[0]
+ self.assertIsInstance(response.context['action_form'], MediaActionForm)
+ self.assertIn('media', response.context)
+ self.assertIn(media_path, response.context['media']._js)
+ self.assertContains(response, media_path)
+
+ def test_user_message_on_none_selected(self):
+ """
+ User sees a warning when 'Go' is pressed and no items are selected.
+ """
+ action_data = {
+ ACTION_CHECKBOX_NAME: [],
+ 'action': 'delete_selected',
+ 'index': 0,
+ }
+ url = reverse('admin:admin_views_subscriber_changelist')
+ response = self.client.post(url, action_data)
+ self.assertRedirects(response, url, fetch_redirect_response=False)
+ response = self.client.get(response.url)
+ msg = 'Items must be selected in order to perform actions on them. No items have been changed.'
+ self.assertContains(response, msg)
+ self.assertEqual(Subscriber.objects.count(), 2)
+
+ def test_user_message_on_no_action(self):
+ """
+ User sees a warning when 'Go' is pressed and no action is selected.
+ """
+ action_data = {
+ ACTION_CHECKBOX_NAME: [self.s1.pk, self.s2.pk],
+ 'action': '',
+ 'index': 0,
+ }
+ url = reverse('admin:admin_views_subscriber_changelist')
+ response = self.client.post(url, action_data)
+ self.assertRedirects(response, url, fetch_redirect_response=False)
+ response = self.client.get(response.url)
+ self.assertContains(response, 'No action selected.')
+ self.assertEqual(Subscriber.objects.count(), 2)
+
+ def test_selection_counter(self):
+ """The selection counter is there."""
+ response = self.client.get(reverse('admin:admin_views_subscriber_changelist'))
+ self.assertContains(response, '0 of 2 selected')
+
+ def test_popup_actions(self):
+ """ Actions aren't shown in popups."""
+ changelist_url = reverse('admin:admin_views_subscriber_changelist')
+ response = self.client.get(changelist_url)
+ self.assertIsNotNone(response.context['action_form'])
+ response = self.client.get(changelist_url + '?%s' % IS_POPUP_VAR)
+ self.assertIsNone(response.context['action_form'])
+
+ def test_popup_template_response_on_add(self):
+ """
+ Success on popups shall be rendered from template in order to allow
+ easy customization.
+ """
+ response = self.client.post(
+ reverse('admin:admin_views_actor_add') + '?%s=1' % IS_POPUP_VAR,
+ {'name': 'Troy McClure', 'age': '55', IS_POPUP_VAR: '1'}
+ )
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.template_name, [
+ 'admin/admin_views/actor/popup_response.html',
+ 'admin/admin_views/popup_response.html',
+ 'admin/popup_response.html',
+ ])
+ self.assertTemplateUsed(response, 'admin/popup_response.html')
+
+ def test_popup_template_response_on_change(self):
+ instance = Actor.objects.create(name='David Tennant', age=45)
+ response = self.client.post(
+ reverse('admin:admin_views_actor_change', args=(instance.pk,)) + '?%s=1' % IS_POPUP_VAR,
+ {'name': 'David Tennant', 'age': '46', IS_POPUP_VAR: '1'}
+ )
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.template_name, [
+ 'admin/admin_views/actor/popup_response.html',
+ 'admin/admin_views/popup_response.html',
+ 'admin/popup_response.html',
+ ])
+ self.assertTemplateUsed(response, 'admin/popup_response.html')
+
+ def test_popup_template_response_on_delete(self):
+ instance = Actor.objects.create(name='David Tennant', age=45)
+ response = self.client.post(
+ reverse('admin:admin_views_actor_delete', args=(instance.pk,)) + '?%s=1' % IS_POPUP_VAR,
+ {IS_POPUP_VAR: '1'}
+ )
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.template_name, [
+ 'admin/admin_views/actor/popup_response.html',
+ 'admin/admin_views/popup_response.html',
+ 'admin/popup_response.html',
+ ])
+ self.assertTemplateUsed(response, 'admin/popup_response.html')
+
+ def test_popup_template_escaping(self):
+ popup_response_data = json.dumps({
+ 'new_value': 'new_value\\',
+ 'obj': 'obj\\',
+ 'value': 'value\\',
+ })
+ context = {
+ 'popup_response_data': popup_response_data,
+ }
+ output = render_to_string('admin/popup_response.html', context)
+ self.assertIn(
+ r'&quot;value\\&quot;', output
+ )
+ self.assertIn(
+ r'&quot;new_value\\&quot;', output
+ )
+ self.assertIn(
+ r'&quot;obj\\&quot;', output
+ )
diff --git a/tests/admin_views/tests.py b/tests/admin_views/tests.py
index eee375e9f6..d259e58e58 100644
--- a/tests/admin_views/tests.py
+++ b/tests/admin_views/tests.py
@@ -1,5 +1,4 @@
import datetime
-import json
import os
import re
import unittest
@@ -24,7 +23,6 @@ from django.core import mail
from django.core.checks import Error
from django.core.files import temp as tempfile
from django.forms.utils import ErrorList
-from django.template.loader import render_to_string
from django.template.response import TemplateResponse
from django.test import (
SimpleTestCase, TestCase, ignore_warnings, modify_settings,
@@ -41,26 +39,23 @@ from django.utils.http import urlencode
from . import customadmin
from .admin import CityAdmin, site, site2
-from .forms import MediaActionForm
from .models import (
Actor, AdminOrderedAdminMethod, AdminOrderedCallable, AdminOrderedField,
AdminOrderedModelMethod, Answer, Answer2, Article, BarAccount, Book,
Bookmark, Category, Chapter, ChapterXtra1, ChapterXtra2, Character, Child,
Choice, City, Collector, Color, ComplexSortedPerson, CoverLetter,
CustomArticle, CyclicOne, CyclicTwo, DooHickey, Employee, EmptyModel,
- ExternalSubscriber, Fabric, FancyDoodad, FieldOverridePost,
- FilteredManager, FooAccount, FoodDelivery, FunkyTag, Gallery, Grommet,
- Inquisition, Language, Link, MainPrepopulated, Media,
- ModelWithStringPrimaryKey, OtherStory, Paper, Parent,
- ParentWithDependentChildren, ParentWithUUIDPK, Person, Persona, Picture,
- Pizza, Plot, PlotDetails, PluggableSearchPerson, Podcast, Post,
+ Fabric, FancyDoodad, FieldOverridePost, FilteredManager, FooAccount,
+ FoodDelivery, FunkyTag, Gallery, Grommet, Inquisition, Language, Link,
+ MainPrepopulated, Media, ModelWithStringPrimaryKey, OtherStory, Paper,
+ Parent, ParentWithDependentChildren, ParentWithUUIDPK, Person, Persona,
+ Picture, Pizza, Plot, PlotDetails, PluggableSearchPerson, Podcast, Post,
PrePopulatedPost, Promo, Question, ReadablePizza, Recommendation,
Recommender, RelatedPrepopulated, RelatedWithUUIDPKModel, Report,
Restaurant, RowLevelChangePermissionModel, SecretHideout, Section,
- ShortMessage, Simple, State, Story, Subscriber, SuperSecretHideout,
- SuperVillain, Telegram, TitleTranslation, Topping, UnchangeableObject,
- UndeletableObject, UnorderedObject, Villain, Vodcast, Whatsit, Widget,
- Worker, WorkHour,
+ ShortMessage, Simple, State, Story, SuperSecretHideout, SuperVillain,
+ Telegram, TitleTranslation, Topping, UnchangeableObject, UndeletableObject,
+ UnorderedObject, Villain, Vodcast, Whatsit, Widget, Worker, WorkHour,
)
@@ -3203,366 +3198,6 @@ class AdminInheritedInlinesTest(TestCase):
@override_settings(ROOT_URLCONF='admin_views.urls')
-class AdminActionsTest(TestCase):
-
- @classmethod
- def setUpTestData(cls):
- cls.superuser = User.objects.create_superuser(username='super', password='secret', email='super@example.com')
- cls.s1 = ExternalSubscriber.objects.create(name='John Doe', email='john@example.org')
- cls.s2 = Subscriber.objects.create(name='Max Mustermann', email='max@example.org')
-
- def setUp(self):
- self.client.force_login(self.superuser)
-
- def test_model_admin_custom_action(self):
- "Tests a custom action defined in a ModelAdmin method"
- action_data = {
- ACTION_CHECKBOX_NAME: [self.s1.pk],
- 'action': 'mail_admin',
- 'index': 0,
- }
- self.client.post(reverse('admin:admin_views_subscriber_changelist'), action_data)
- self.assertEqual(len(mail.outbox), 1)
- self.assertEqual(mail.outbox[0].subject, 'Greetings from a ModelAdmin action')
-
- def test_model_admin_default_delete_action(self):
- "Tests the default delete action defined as a ModelAdmin method"
- action_data = {
- ACTION_CHECKBOX_NAME: [self.s1.pk, self.s2.pk],
- 'action': 'delete_selected',
- 'index': 0,
- }
- delete_confirmation_data = {
- ACTION_CHECKBOX_NAME: [self.s1.pk, self.s2.pk],
- 'action': 'delete_selected',
- 'post': 'yes',
- }
- confirmation = self.client.post(reverse('admin:admin_views_subscriber_changelist'), action_data)
- self.assertIsInstance(confirmation, TemplateResponse)
- self.assertContains(confirmation, "Are you sure you want to delete the selected subscribers?")
- self.assertContains(confirmation, "<h2>Summary</h2>")
- self.assertContains(confirmation, "<li>Subscribers: 2</li>")
- self.assertContains(confirmation, "<li>External subscribers: 1</li>")
- self.assertContains(confirmation, ACTION_CHECKBOX_NAME, count=2)
- self.client.post(reverse('admin:admin_views_subscriber_changelist'), delete_confirmation_data)
- self.assertEqual(Subscriber.objects.count(), 0)
-
- @override_settings(USE_THOUSAND_SEPARATOR=True, USE_L10N=True)
- def test_non_localized_pk(self):
- """If USE_THOUSAND_SEPARATOR is set, make sure that the ids for
- the objects selected for deletion are rendered without separators.
- Refs #14895.
- """
- s = ExternalSubscriber.objects.create(id=9999)
- action_data = {
- ACTION_CHECKBOX_NAME: [s.pk, self.s2.pk],
- 'action': 'delete_selected',
- 'index': 0,
- }
- response = self.client.post(reverse('admin:admin_views_subscriber_changelist'), action_data)
- self.assertTemplateUsed(response, 'admin/delete_selected_confirmation.html')
- self.assertContains(response, 'value="9999"') # Instead of 9,999
- self.assertContains(response, 'value="%s"' % self.s2.pk)
-
- def test_model_admin_default_delete_action_protected(self):
- """
- Tests the default delete action defined as a ModelAdmin method in the
- case where some related objects are protected from deletion.
- """
- q1 = Question.objects.create(question="Why?")
- a1 = Answer.objects.create(question=q1, answer="Because.")
- a2 = Answer.objects.create(question=q1, answer="Yes.")
- q2 = Question.objects.create(question="Wherefore?")
-
- action_data = {
- ACTION_CHECKBOX_NAME: [q1.pk, q2.pk],
- 'action': 'delete_selected',
- 'index': 0,
- }
- delete_confirmation_data = action_data.copy()
- delete_confirmation_data['post'] = 'yes'
-
- response = self.client.post(reverse('admin:admin_views_question_changelist'), action_data)
-
- self.assertContains(response, "would require deleting the following protected related objects")
- self.assertContains(
- response,
- '<li>Answer: <a href="%s">Because.</a></li>' % reverse('admin:admin_views_answer_change', args=(a1.pk,)),
- html=True
- )
- self.assertContains(
- response,
- '<li>Answer: <a href="%s">Yes.</a></li>' % reverse('admin:admin_views_answer_change', args=(a2.pk,)),
- html=True
- )
-
- # A POST request to delete protected objects should display the page
- # which says the deletion is prohibited.
- response = self.client.post(reverse('admin:admin_views_question_changelist'), delete_confirmation_data)
- self.assertContains(response, "would require deleting the following protected related objects")
- self.assertEqual(Question.objects.count(), 2)
-
- def test_model_admin_default_delete_action_no_change_url(self):
- """
- Default delete action shouldn't break if a user's ModelAdmin removes the url for change_view.
-
- Regression test for #20640
- """
- obj = UnchangeableObject.objects.create()
- action_data = {
- ACTION_CHECKBOX_NAME: obj.pk,
- "action": "delete_selected",
- "index": "0",
- }
- response = self.client.post(reverse('admin:admin_views_unchangeableobject_changelist'), action_data)
- # No 500 caused by NoReverseMatch
- self.assertEqual(response.status_code, 200)
- # The page shouldn't display a link to the nonexistent change page
- self.assertContains(response, "<li>Unchangeable object: %s</li>" % obj, 1, html=True)
-
- def test_custom_function_mail_action(self):
- "Tests a custom action defined in a function"
- action_data = {
- ACTION_CHECKBOX_NAME: [self.s1.pk],
- 'action': 'external_mail',
- 'index': 0,
- }
- self.client.post(reverse('admin:admin_views_externalsubscriber_changelist'), action_data)
- self.assertEqual(len(mail.outbox), 1)
- self.assertEqual(mail.outbox[0].subject, 'Greetings from a function action')
-
- def test_custom_function_action_with_redirect(self):
- "Tests a custom action defined in a function"
- action_data = {
- ACTION_CHECKBOX_NAME: [self.s1.pk],
- 'action': 'redirect_to',
- 'index': 0,
- }
- response = self.client.post(reverse('admin:admin_views_externalsubscriber_changelist'), action_data)
- self.assertEqual(response.status_code, 302)
-
- def test_default_redirect(self):
- """
- Actions which don't return an HttpResponse are redirected to the same
- page, retaining the querystring (which may contain changelist
- information).
- """
- action_data = {
- ACTION_CHECKBOX_NAME: [self.s1.pk],
- 'action': 'external_mail',
- 'index': 0,
- }
- url = reverse('admin:admin_views_externalsubscriber_changelist') + '?o=1'
- 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: [self.s1.pk],
- 'action': 'download',
- 'index': 0,
- }
- response = self.client.post(reverse('admin:admin_views_externalsubscriber_changelist'), 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: [self.s1.pk],
- 'action': 'no_perm',
- 'index': 0,
- }
- response = self.client.post(reverse('admin:admin_views_externalsubscriber_changelist'), action_data)
- self.assertEqual(response.status_code, 403)
- self.assertEqual(response.content, b'No permission to perform this action')
-
- def test_actions_ordering(self):
- """
- Actions are ordered as expected.
- """
- response = self.client.get(reverse('admin:admin_views_externalsubscriber_changelist'))
- self.assertContains(response, '''<label>Action: <select name="action" required>
-<option value="" selected>---------</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="download">Download subscription</option>
-<option value="no_perm">No permission to run</option>
-</select>''', html=True)
-
- def test_model_without_action(self):
- "Tests a ModelAdmin without any action"
- response = self.client.get(reverse('admin:admin_views_oldsubscriber_changelist'))
- self.assertIsNone(response.context["action_form"])
- self.assertNotContains(
- response, '<input type="checkbox" class="action-select"',
- msg_prefix="Found an unexpected action toggle checkboxbox in response"
- )
- self.assertNotContains(response, '<input type="checkbox" class="action-select"')
-
- def test_model_without_action_still_has_jquery(self):
- "A ModelAdmin without any actions still gets jQuery included in page"
- response = self.client.get(reverse('admin:admin_views_oldsubscriber_changelist'))
- self.assertIsNone(response.context["action_form"])
- self.assertContains(
- response, 'jquery.min.js',
- msg_prefix="jQuery missing from admin pages for model with no admin actions"
- )
-
- def test_action_column_class(self):
- "The checkbox column class is present in the response"
- response = self.client.get(reverse('admin:admin_views_subscriber_changelist'))
- self.assertIsNotNone(response.context["action_form"])
- self.assertContains(response, 'action-checkbox-column')
-
- def test_multiple_actions_form(self):
- """
- Actions come from the form whose submit button was pressed (#10618).
- """
- action_data = {
- ACTION_CHECKBOX_NAME: [self.s1.pk],
- # Two different actions selected on the two forms...
- 'action': ['external_mail', 'delete_selected'],
- # ...but we clicked "go" on the top form.
- 'index': 0
- }
- self.client.post(reverse('admin:admin_views_externalsubscriber_changelist'), action_data)
-
- # Send mail, don't delete.
- self.assertEqual(len(mail.outbox), 1)
- self.assertEqual(mail.outbox[0].subject, 'Greetings from a function action')
-
- def test_media_from_actions_form(self):
- """
- The action form's media is included in changelist view's media.
- """
- response = self.client.get(reverse('admin:admin_views_subscriber_changelist'))
- media_path = MediaActionForm.Media.js[0]
- self.assertIsInstance(response.context['action_form'], MediaActionForm)
- self.assertIn('media', response.context)
- self.assertIn(media_path, response.context['media']._js)
- self.assertContains(response, media_path)
-
- def test_user_message_on_none_selected(self):
- """
- User should see a warning when 'Go' is pressed and no items are selected.
- """
- action_data = {
- ACTION_CHECKBOX_NAME: [],
- 'action': 'delete_selected',
- 'index': 0,
- }
- url = reverse('admin:admin_views_subscriber_changelist')
- response = self.client.post(url, action_data)
- self.assertRedirects(response, url, fetch_redirect_response=False)
- response = self.client.get(response.url)
- msg = """Items must be selected in order to perform actions on them. No items have been changed."""
- self.assertContains(response, msg)
- self.assertEqual(Subscriber.objects.count(), 2)
-
- def test_user_message_on_no_action(self):
- """
- User should see a warning when 'Go' is pressed and no action is selected.
- """
- action_data = {
- ACTION_CHECKBOX_NAME: [self.s1.pk, self.s2.pk],
- 'action': '',
- 'index': 0,
- }
- url = reverse('admin:admin_views_subscriber_changelist')
- response = self.client.post(url, action_data)
- self.assertRedirects(response, url, fetch_redirect_response=False)
- response = self.client.get(response.url)
- msg = """No action selected."""
- self.assertContains(response, msg)
- self.assertEqual(Subscriber.objects.count(), 2)
-
- def test_selection_counter(self):
- """
- Check if the selection counter is there.
- """
- response = self.client.get(reverse('admin:admin_views_subscriber_changelist'))
- self.assertContains(response, '0 of 2 selected')
-
- def test_popup_actions(self):
- """ Actions should not be shown in popups. """
- response = self.client.get(reverse('admin:admin_views_subscriber_changelist'))
- self.assertIsNotNone(response.context["action_form"])
- response = self.client.get(
- reverse('admin:admin_views_subscriber_changelist') + '?%s' % IS_POPUP_VAR)
- self.assertIsNone(response.context["action_form"])
-
- def test_popup_template_response_on_add(self):
- """
- Success on popups shall be rendered from template in order to allow
- easy customization.
- """
- response = self.client.post(
- reverse('admin:admin_views_actor_add') + '?%s=1' % IS_POPUP_VAR,
- {'name': 'Troy McClure', 'age': '55', IS_POPUP_VAR: '1'})
- self.assertEqual(response.status_code, 200)
- self.assertEqual(response.template_name, [
- 'admin/admin_views/actor/popup_response.html',
- 'admin/admin_views/popup_response.html',
- 'admin/popup_response.html',
- ])
- self.assertTemplateUsed(response, 'admin/popup_response.html')
-
- def test_popup_template_response_on_change(self):
- instance = Actor.objects.create(name='David Tennant', age=45)
- response = self.client.post(
- reverse('admin:admin_views_actor_change', args=(instance.pk,)) + '?%s=1' % IS_POPUP_VAR,
- {'name': 'David Tennant', 'age': '46', IS_POPUP_VAR: '1'}
- )
- self.assertEqual(response.status_code, 200)
- self.assertEqual(response.template_name, [
- 'admin/admin_views/actor/popup_response.html',
- 'admin/admin_views/popup_response.html',
- 'admin/popup_response.html',
- ])
- self.assertTemplateUsed(response, 'admin/popup_response.html')
-
- def test_popup_template_response_on_delete(self):
- instance = Actor.objects.create(name='David Tennant', age=45)
- response = self.client.post(
- reverse('admin:admin_views_actor_delete', args=(instance.pk,)) + '?%s=1' % IS_POPUP_VAR,
- {IS_POPUP_VAR: '1'}
- )
- self.assertEqual(response.status_code, 200)
- self.assertEqual(response.template_name, [
- 'admin/admin_views/actor/popup_response.html',
- 'admin/admin_views/popup_response.html',
- 'admin/popup_response.html',
- ])
- self.assertTemplateUsed(response, 'admin/popup_response.html')
-
- def test_popup_template_escaping(self):
- popup_response_data = json.dumps({
- 'new_value': 'new_value\\',
- 'obj': 'obj\\',
- 'value': 'value\\',
- })
- context = {
- 'popup_response_data': popup_response_data,
- }
- output = render_to_string('admin/popup_response.html', context)
- self.assertIn(
- r'&quot;value\\&quot;', output
- )
- self.assertIn(
- r'&quot;new_value\\&quot;', output
- )
- self.assertIn(
- r'&quot;obj\\&quot;', output
- )
-
-
-@override_settings(ROOT_URLCONF='admin_views.urls')
class TestCustomChangeList(TestCase):
@classmethod