Repository: wagtail/wagtail

Issue:
DBSearchResults doesn't use original queryset but ElasticSearchResults does
The `DBSearchResults` class used by the database search backend to retrieve results constructs a new queryset from scratch on each query. The `ElasticSearchResults` filters the original queryset to only show the results returned by Elasticsearch.

This causes things like `prefetch_related` and `defer` to be forgotten and also makes it difficult to search a `ValuesQuerySet` or the new `SpecificQuerySet` implemented in #1251.



Discussion:

Resolving patch:
diff --git a/wagtail/wagtailcore/models.py b/wagtail/wagtailcore/models.py
--- a/wagtail/wagtailcore/models.py
+++ b/wagtail/wagtailcore/models.py
@@ -301,6 +301,7 @@ class Page(six.with_metaclass(PageBase, MP_Node, ClusterableModel, index.Indexed
         index.FilterField('path'),
         index.FilterField('depth'),
         index.FilterField('locked'),
+        index.FilterField('show_in_menus'),
     )
 
     def __init__(self, *args, **kwargs):
diff --git a/wagtail/wagtailsearch/backends/db.py b/wagtail/wagtailsearch/backends/db.py
--- a/wagtail/wagtailsearch/backends/db.py
+++ b/wagtail/wagtailsearch/backends/db.py
@@ -22,10 +22,12 @@ def _connect_filters(self, filters, connector, negated):
 
         return q
 
-    def get_q(self):
-        # Get filters as a q object
-        q = self._get_filters_from_queryset()
+    def get_extra_q(self):
+        # Run _get_filters_from_queryset to test that no fields that are not
+        # a FilterField have been used in the query.
+        self._get_filters_from_queryset()
 
+        q = models.Q()
         model = self.queryset.model
 
         if self.query_string is not None:
@@ -57,10 +59,10 @@ def get_q(self):
 
 class DBSearchResults(BaseSearchResults):
     def get_queryset(self):
-        model = self.query.queryset.model
-        q = self.query.get_q()
+        queryset = self.query.queryset
+        q = self.query.get_extra_q()
 
-        return model.objects.filter(q).distinct()[self.start:self.stop]
+        return queryset.filter(q).distinct()[self.start:self.stop]
 
     def _do_search(self):
         return self.get_queryset()


Test patch:
diff --git a/wagtail/wagtailcore/tests/test_page_queryset.py b/wagtail/wagtailcore/tests/test_page_queryset.py
--- a/wagtail/wagtailcore/tests/test_page_queryset.py
+++ b/wagtail/wagtailcore/tests/test_page_queryset.py
@@ -410,3 +410,17 @@ def test_filtering_after_specific(self):
             Page.objects.get(url_path='/home/events/christmas/').specific,
             Page.objects.get(url_path='/home/events/').specific,
             Page.objects.get(url_path='/home/about-us/').specific])
+
+    def test_specific_query_with_search(self):
+        # 1276 - The database search backend didn't return results with the
+        # specific type when searching a specific queryset.
+
+        pages = list(Page.objects.specific().live().in_menu().search(None, backend='wagtail.wagtailsearch.backends.db'))
+
+        # Check that each page is in the queryset with the correct type.
+        # We don't care about order here
+        self.assertEqual(len(pages), 4)
+        self.assertIn(Page.objects.get(url_path='/home/other/').specific, pages)
+        self.assertIn(Page.objects.get(url_path='/home/events/christmas/').specific, pages)
+        self.assertIn(Page.objects.get(url_path='/home/events/').specific, pages)
+        self.assertIn(Page.objects.get(url_path='/home/about-us/').specific, pages)