Ticket #21241: 881.patch
File 881.patch, 5.5 KB (added by , 11 years ago) |
---|
-
django/contrib/admin/options.py
From d4b88f068571cb5e5e4da48533b1f06aa2cf7618 Mon Sep 17 00:00:00 2001 From: Chris Adams <chris@improbable.org> Date: Mon, 7 Oct 2013 10:48:19 -0400 Subject: [PATCH 1/2] Admin: optimize search filter construction Using the database this way is inherently slow but the previous implementation would call QuerySet.filter() once per whitespace-separated term in the query text, causing an extra set of JOINs for each term. With the MySQL backend, this actually causes database errors once you hit the server limit of 61. This commit logically ANDs each of the query components together so `queryset.filter()` can be called a single time. --- django/contrib/admin/options.py | 10 +++++++--- tests/admin_changelist/tests.py | 19 +++++++++++++++++++ 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index 3b02ac0..f366b5d 100644
a b def construct_search(field_name): 849 849 if search_fields and search_term: 850 850 orm_lookups = [construct_search(str(search_field)) 851 851 for search_field in search_fields] 852 853 query_parts = [] 852 854 for bit in search_term.split(): 853 or_queries = [models.Q(**{orm_lookup: bit}) 854 for orm_lookup in orm_lookups] 855 queryset = queryset.filter(reduce(operator.or_, or_queries)) 855 or_queries = (models.Q(**{orm_lookup: bit}) 856 for orm_lookup in orm_lookups) 857 query_parts.append(reduce(operator.or_, or_queries)) 858 queryset = queryset.filter(reduce(operator.and_, query_parts)) 859 856 860 if not use_distinct: 857 861 for search_spec in orm_lookups: 858 862 if lookup_needs_distinct(self.opts, search_spec): -
tests/admin_changelist/tests.py
diff --git a/tests/admin_changelist/tests.py b/tests/admin_changelist/tests.py index f9ef079..e49145f 100644
a b def test_pagination_page_range(self): 643 643 list(real_page_range), 644 644 ) 645 645 646 def test_search_query_efficiency(self): 647 """Ensure that search queries only add one ORM filter rather than one per term""" 648 new_parent = Parent.objects.create(name='parent') 649 for i in range(200): 650 Child.objects.create(name='foo bar baz qux quux corge %s' % i, 651 parent=new_parent) 652 653 m = ParentAdmin(Parent, admin.site) 654 655 request = self.factory.get('/parent/', data={'q': 'foo bar baz'}) 656 657 cl = ChangeList(request, Parent, m.list_display, m.list_display_links, 658 m.list_filter, m.date_hierarchy, m.search_fields, 659 m.list_select_related, m.list_per_page, 660 m.list_max_show_all, m.list_editable, m) 661 662 self.assertEqual(2, cl.queryset.query.count_active_tables(), 663 "ChangeList search filters should not cause duplicate JOINs") 664 646 665 647 666 class AdminLogNodeTestCase(TestCase): 648 667 -
tests/admin_changelist/tests.py
-- 1.8.4 From 76ea5651266d0749701a238fb844333a6a0fc418 Mon Sep 17 00:00:00 2001 From: Chris Adams <chris@improbable.org> Date: Mon, 7 Oct 2013 11:50:11 -0400 Subject: [PATCH 2/2] Tests: confirm that admin changelist search terms are ANDed Performance work on #881 inadvertently demonstrated that there wasn't a test for the documented behaviour of admin changelist terms being evaluated as logical ANDs --- tests/admin_changelist/tests.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/tests/admin_changelist/tests.py b/tests/admin_changelist/tests.py index e49145f..f63de41 100644
a b def test_search_query_efficiency(self): 662 662 self.assertEqual(2, cl.queryset.query.count_active_tables(), 663 663 "ChangeList search filters should not cause duplicate JOINs") 664 664 665 def test_search_query_logic(self): 666 """Changelist search terms should be ANDed""" 667 668 parent1 = Parent.objects.create(name='parent 1') 669 parent2 = Parent.objects.create(name='parent 2') 670 671 Child.objects.create(name='foo bar baz', parent=parent1) 672 Child.objects.create(name='bar baz qux', parent=parent2) 673 674 m = ParentAdmin(Parent, admin.site) 675 676 request = self.factory.get('/parent/', data={'q': 'foo bar baz'}) 677 678 cl = ChangeList(request, Parent, m.list_display, m.list_display_links, 679 m.list_filter, m.date_hierarchy, m.search_fields, 680 m.list_select_related, m.list_per_page, 681 m.list_max_show_all, m.list_editable, m) 682 683 cl.get_results(request) 684 self.assertListEqual(["parent 1"], list(cl.queryset.values_list("name", flat=True))) 685 686 687 request2 = self.factory.get('/parent/', data={'q': 'bar baz'}) 688 cl2 = ChangeList(request2, Parent, m.list_display, m.list_display_links, 689 m.list_filter, m.date_hierarchy, m.search_fields, 690 m.list_select_related, m.list_per_page, 691 m.list_max_show_all, m.list_editable, m) 692 cl2.get_results(request2) 693 self.assertListEqual(['parent 1', 'parent 2'], 694 list(cl2.queryset.order_by("name").values_list("name", flat=True))) 695 665 696 666 697 class AdminLogNodeTestCase(TestCase): 667 698