summaryrefslogtreecommitdiff
path: root/tests/admin_views/test_autocomplete_view.py
diff options
context:
space:
mode:
authorJohannes Hoppe <info@johanneshoppe.com>2017-05-10 14:48:57 +0200
committerTim Graham <timograham@gmail.com>2017-09-18 13:48:02 -0400
commit94cd8efc50c717cd00244f4b2233f971a53b205e (patch)
treeab1f13a12121907a561962181eaceeab793cb838 /tests/admin_views/test_autocomplete_view.py
parent01a294f8f014a32e288958701540ea47dcb9fc14 (diff)
Fixed #14370 -- Allowed using a Select2 widget for ForeignKey and ManyToManyField in the admin.
Thanks Florian Apolloner and Tim Graham for review and contributing to the patch.
Diffstat (limited to 'tests/admin_views/test_autocomplete_view.py')
-rw-r--r--tests/admin_views/test_autocomplete_view.py231
1 files changed, 231 insertions, 0 deletions
diff --git a/tests/admin_views/test_autocomplete_view.py b/tests/admin_views/test_autocomplete_view.py
new file mode 100644
index 0000000000..8396ceb5d1
--- /dev/null
+++ b/tests/admin_views/test_autocomplete_view.py
@@ -0,0 +1,231 @@
+import json
+
+from django.contrib import admin
+from django.contrib.admin import site
+from django.contrib.admin.tests import AdminSeleniumTestCase
+from django.contrib.admin.views.autocomplete import AutocompleteJsonView
+from django.contrib.auth.models import Permission, User
+from django.contrib.contenttypes.models import ContentType
+from django.http import Http404
+from django.test import RequestFactory, override_settings
+from django.urls import reverse, reverse_lazy
+
+from .admin import AnswerAdmin, QuestionAdmin
+from .models import Answer, Author, Authorship, Book, Question
+from .tests import AdminViewBasicTestCase
+
+PAGINATOR_SIZE = AutocompleteJsonView.paginate_by
+
+
+class AuthorAdmin(admin.ModelAdmin):
+ search_fields = ['id']
+
+
+class AuthorshipInline(admin.TabularInline):
+ model = Authorship
+ autocomplete_fields = ['author']
+
+
+class BookAdmin(admin.ModelAdmin):
+ inlines = [AuthorshipInline]
+
+
+site.register(Question, QuestionAdmin)
+site.register(Answer, AnswerAdmin)
+site.register(Author, AuthorAdmin)
+site.register(Book, BookAdmin)
+
+
+class AutocompleteJsonViewTests(AdminViewBasicTestCase):
+ as_view_args = {'model_admin': QuestionAdmin(Question, site)}
+ factory = RequestFactory()
+ url = reverse_lazy('admin:admin_views_question_autocomplete')
+
+ @classmethod
+ def setUpTestData(cls):
+ cls.user = User.objects.create_user(
+ username='user', password='secret',
+ email='user@example.com', is_staff=True,
+ )
+ super().setUpTestData()
+
+ def test_success(self):
+ q = Question.objects.create(question='Is this a question?')
+ request = self.factory.get(self.url, {'term': 'is'})
+ request.user = self.superuser
+ response = AutocompleteJsonView.as_view(**self.as_view_args)(request)
+ self.assertEqual(response.status_code, 200)
+ data = json.loads(response.content.decode('utf-8'))
+ self.assertEqual(data, {
+ 'results': [{'id': str(q.pk), 'text': q.question}],
+ 'pagination': {'more': False},
+ })
+
+ def test_must_be_logged_in(self):
+ response = self.client.get(self.url, {'term': ''})
+ self.assertEqual(response.status_code, 200)
+ self.client.logout()
+ response = self.client.get(self.url, {'term': ''})
+ self.assertEqual(response.status_code, 302)
+
+ def test_has_change_permission_required(self):
+ """
+ Users require the change permission for the related model to the
+ autocomplete view for it.
+ """
+ request = self.factory.get(self.url, {'term': 'is'})
+ self.user.is_staff = True
+ self.user.save()
+ request.user = self.user
+ response = AutocompleteJsonView.as_view(**self.as_view_args)(request)
+ self.assertEqual(response.status_code, 403)
+ self.assertJSONEqual(response.content.decode('utf-8'), {'error': '403 Forbidden'})
+ # Add the change permission and retry.
+ p = Permission.objects.get(
+ content_type=ContentType.objects.get_for_model(Question),
+ codename='change_question',
+ )
+ self.user.user_permissions.add(p)
+ request.user = User.objects.get(pk=self.user.pk)
+ response = AutocompleteJsonView.as_view(**self.as_view_args)(request)
+ self.assertEqual(response.status_code, 200)
+
+ def test_search_use_distinct(self):
+ """
+ Searching across model relations use QuerySet.distinct() to avoid
+ duplicates.
+ """
+ q1 = Question.objects.create(question='question 1')
+ q2 = Question.objects.create(question='question 2')
+ q2.related_questions.add(q1)
+ q3 = Question.objects.create(question='question 3')
+ q3.related_questions.add(q1)
+ request = self.factory.get(self.url, {'term': 'question'})
+ request.user = self.superuser
+
+ class DistinctQuestionAdmin(QuestionAdmin):
+ search_fields = ['related_questions__question', 'question']
+
+ model_admin = DistinctQuestionAdmin(Question, site)
+ response = AutocompleteJsonView.as_view(model_admin=model_admin)(request)
+ self.assertEqual(response.status_code, 200)
+ data = json.loads(response.content.decode('utf-8'))
+ self.assertEqual(len(data['results']), 3)
+
+ def test_missing_search_fields(self):
+ class EmptySearchAdmin(QuestionAdmin):
+ search_fields = []
+
+ model_admin = EmptySearchAdmin(Question, site)
+ msg = 'EmptySearchAdmin must have search_fields for the autocomplete_view.'
+ with self.assertRaisesMessage(Http404, msg):
+ model_admin.autocomplete_view(self.factory.get(self.url))
+
+ def test_get_paginator(self):
+ """Search results are paginated."""
+ Question.objects.bulk_create(Question(question=str(i)) for i in range(PAGINATOR_SIZE + 10))
+ model_admin = QuestionAdmin(Question, site)
+ model_admin.ordering = ['pk']
+ # The first page of results.
+ request = self.factory.get(self.url, {'term': ''})
+ request.user = self.superuser
+ response = AutocompleteJsonView.as_view(model_admin=model_admin)(request)
+ self.assertEqual(response.status_code, 200)
+ data = json.loads(response.content.decode('utf-8'))
+ self.assertEqual(data, {
+ 'results': [{'id': str(q.pk), 'text': q.question} for q in Question.objects.all()[:PAGINATOR_SIZE]],
+ 'pagination': {'more': True},
+ })
+ # The second page of results.
+ request = self.factory.get(self.url, {'term': '', 'page': '2'})
+ request.user = self.superuser
+ response = AutocompleteJsonView.as_view(model_admin=model_admin)(request)
+ self.assertEqual(response.status_code, 200)
+ data = json.loads(response.content.decode('utf-8'))
+ self.assertEqual(data, {
+ 'results': [{'id': str(q.pk), 'text': q.question} for q in Question.objects.all()[PAGINATOR_SIZE:]],
+ 'pagination': {'more': False},
+ })
+
+
+@override_settings(ROOT_URLCONF='admin_views.urls')
+class SeleniumTests(AdminSeleniumTestCase):
+ available_apps = ['admin_views'] + AdminSeleniumTestCase.available_apps
+
+ def setUp(self):
+ self.superuser = User.objects.create_superuser(
+ username='super', password='secret', email='super@example.com',
+ )
+ self.admin_login(username='super', password='secret', login_url=reverse('admin:index'))
+
+ def test_select(self):
+ from selenium.webdriver.common.keys import Keys
+ from selenium.webdriver.support.ui import Select
+ self.selenium.get(self.live_server_url + reverse('admin:admin_views_answer_add'))
+ elem = self.selenium.find_element_by_css_selector('.select2-selection')
+ elem.click() # Open the autocomplete dropdown.
+ results = self.selenium.find_element_by_css_selector('.select2-results')
+ self.assertTrue(results.is_displayed())
+ option = self.selenium.find_element_by_css_selector('.select2-results__option')
+ self.assertEqual(option.text, 'No results found')
+ elem.click() # Close the autocomplete dropdown.
+ q1 = Question.objects.create(question='Who am I?')
+ Question.objects.bulk_create(Question(question=str(i)) for i in range(PAGINATOR_SIZE + 10))
+ elem.click() # Reopen the dropdown now that some objects exist.
+ result_container = self.selenium.find_element_by_css_selector('.select2-results')
+ self.assertTrue(result_container.is_displayed())
+ results = result_container.find_elements_by_css_selector('.select2-results__option')
+ # PAGINATOR_SIZE results and "Loading more results".
+ self.assertEqual(len(results), PAGINATOR_SIZE + 1)
+ search = self.selenium.find_element_by_css_selector('.select2-search__field')
+ # Load next page of results by scrolling to the bottom of the list.
+ for _ in range(len(results)):
+ search.send_keys(Keys.ARROW_DOWN)
+ results = result_container.find_elements_by_css_selector('.select2-results__option')
+ # All objects and "Loading more results".
+ self.assertEqual(len(results), PAGINATOR_SIZE + 11)
+ # Limit the results with the search field.
+ search.send_keys('Who')
+ results = result_container.find_elements_by_css_selector('.select2-results__option')
+ self.assertEqual(len(results), 1)
+ # Select the result.
+ search.send_keys(Keys.RETURN)
+ select = Select(self.selenium.find_element_by_id('id_question'))
+ self.assertEqual(select.first_selected_option.get_attribute('value'), str(q1.pk))
+
+ def test_select_multiple(self):
+ from selenium.webdriver.common.keys import Keys
+ from selenium.webdriver.support.ui import Select
+ self.selenium.get(self.live_server_url + reverse('admin:admin_views_question_add'))
+ elem = self.selenium.find_element_by_css_selector('.select2-selection')
+ elem.click() # Open the autocomplete dropdown.
+ results = self.selenium.find_element_by_css_selector('.select2-results')
+ self.assertTrue(results.is_displayed())
+ option = self.selenium.find_element_by_css_selector('.select2-results__option')
+ self.assertEqual(option.text, 'No results found')
+ elem.click() # Close the autocomplete dropdown.
+ Question.objects.create(question='Who am I?')
+ Question.objects.bulk_create(Question(question=str(i)) for i in range(PAGINATOR_SIZE + 10))
+ elem.click() # Reopen the dropdown now that some objects exist.
+ result_container = self.selenium.find_element_by_css_selector('.select2-results')
+ self.assertTrue(result_container.is_displayed())
+ results = result_container.find_elements_by_css_selector('.select2-results__option')
+ self.assertEqual(len(results), PAGINATOR_SIZE + 1)
+ search = self.selenium.find_element_by_css_selector('.select2-search__field')
+ # Load next page of results by scrolling to the bottom of the list.
+ for _ in range(len(results)):
+ search.send_keys(Keys.ARROW_DOWN)
+ results = result_container.find_elements_by_css_selector('.select2-results__option')
+ self.assertEqual(len(results), 31)
+ # Limit the results with the search field.
+ search.send_keys('Who')
+ results = result_container.find_elements_by_css_selector('.select2-results__option')
+ self.assertEqual(len(results), 1)
+ # Select the result.
+ search.send_keys(Keys.RETURN)
+ # Reopen the dropdown and add the first result to the selection.
+ elem.click()
+ search.send_keys(Keys.ARROW_DOWN)
+ search.send_keys(Keys.RETURN)
+ select = Select(self.selenium.find_element_by_id('id_related_questions'))
+ self.assertEqual(len(select.all_selected_options), 2)