Repository: fidals/shopelectro

Issue:
Page-product rows duplication on search page
![image](https://user-images.githubusercontent.com/3526796/40539273-e0321bfc-601c-11e8-80e5-9889e18be685.png)



Discussion:

Resolving patch:
diff --git a/shopelectro/models.py b/shopelectro/models.py
--- a/shopelectro/models.py
+++ b/shopelectro/models.py
@@ -8,6 +8,7 @@
 from django.urls import reverse
 from django.utils.text import slugify
 from django.utils.translation import ugettext_lazy as _
+from mptt.models import TreeManager
 from mptt.querysets import TreeQuerySet
 from unidecode import unidecode
 
@@ -17,7 +18,7 @@
     CategoryManager,
 )
 from ecommerce.models import Order as ecOrder
-from pages.models import CustomPage, ModelPage, SyncPageMixin
+from pages.models import CustomPage, ModelPage, Page, SyncPageMixin
 
 
 class SECategoryQuerySet(TreeQuerySet):
@@ -295,3 +296,17 @@ def serialize_tags_to_title(tags: TagQuerySet) -> str:
         type_delimiter=settings.TAGS_TITLE_DELIMITER,
         group_delimiter=settings.TAG_GROUPS_TITLE_DELIMITER
     )
+
+
+class ExcludedModelTPageManager(TreeManager):
+
+    def get_queryset(self):
+        return super().get_queryset().exclude(type=Page.MODEL_TYPE)
+
+
+class ExcludedModelTPage(Page):
+
+    class Meta(Page.Meta):  # Ignore PycodestyleBear (E303)
+        proxy = True
+
+    objects = ExcludedModelTPageManager()
diff --git a/shopelectro/views/search.py b/shopelectro/views/search.py
--- a/shopelectro/views/search.py
+++ b/shopelectro/views/search.py
@@ -3,7 +3,7 @@
 from search import views as search_views, search as search_engine
 from pages.models import Page
 
-from shopelectro.models import Product, Category
+from shopelectro.models import Category, Product, ExcludedModelTPage
 
 
 class Search(search_views.SearchView):
@@ -27,7 +27,7 @@ def get_redirect_search_entity(self):
         ),
         search_engine.Search(
             name='page',
-            qs=Page.objects.all(),
+            qs=ExcludedModelTPage.objects.all(),
             fields=['name'],
             min_similarity=settings.TRIGRAM_MIN_SIMILARITY,
         )
@@ -56,7 +56,7 @@ class Autocomplete(search_views.AutocompleteView):
         ),
         search_engine.Search(
             name='pages',
-            qs=Page.objects.all(),
+            qs=ExcludedModelTPage.objects.all(),
             fields=['name'],
             template_fields=['name', 'url'],
             min_similarity=settings.TRIGRAM_MIN_SIMILARITY,
@@ -84,7 +84,7 @@ class AdminAutocomplete(search_views.AdminAutocompleteView):
         ),
         search_engine.Search(
             name='pages',
-            qs=Page.objects.all(),
+            qs=ExcludedModelTPage.objects.all(),
             fields=['name'],
             min_similarity=settings.TRIGRAM_MIN_SIMILARITY,
         )


Test patch:
diff --git a/shopelectro/tests/tests_views.py b/shopelectro/tests/tests_views.py
--- a/shopelectro/tests/tests_views.py
+++ b/shopelectro/tests/tests_views.py
@@ -9,8 +9,9 @@
 from itertools import chain
 from operator import attrgetter
 from xml.etree import ElementTree as ET
-from urllib.parse import urlparse
+from urllib.parse import urlparse, quote
 
+from bs4 import BeautifulSoup
 from django.conf import settings
 from django.db.models import Q
 from django.http import HttpResponse
@@ -330,6 +331,8 @@ class TestSearch(TestCase):
 
     fixtures = ['dump.json']
     TERM = 'Prod'
+    SIGNLE_RESULT_TERM = '#0'
+    QUOTED_SIGNLE_RESULT_TERM = quote(SIGNLE_RESULT_TERM)
     WRONG_TERM = 'Bugaga'  # it's short for trigram search testing
 
     def test_search_has_results(self):
@@ -395,3 +398,34 @@ def test_admin_autocomplete_no_results(self):
         self.assertEqual(response.status_code, 200)
         self.assertFalse(json_to_dict(response))
         self.assertNotContains(response, term)
+
+    def test_search_has_no_model_pages(self):
+        """Search page does not contain page with type=MODEL_TYPE and duplicated content."""
+        response = self.client.get(
+            f'/search/?term={self.QUOTED_SIGNLE_RESULT_TERM}',
+            follow=True,
+        )
+        result_links = BeautifulSoup(
+            response.content.decode('utf-8'), 'html.parser'
+        ).find_all(class_='search-result-link')
+        self.assertTrue(len(result_links) == 1)
+
+    def test_autocomplete_has_no_model_pages(self):
+        """Autocomplete does not contain page with type=MODEL_TYPE and duplicated content."""
+        response = self.client.get(
+            f'{reverse("autocomplete")}?term={self.QUOTED_SIGNLE_RESULT_TERM}'
+        )
+        self.assertFalse(
+            list(filter(
+                lambda r: r['type'] not in ['category', 'see_all'],
+                json_to_dict(response),
+            ))
+        )
+
+    def test_admin_autocomplete_has_no_model_pages(self):
+        """Admin autocomplete does not contain page with type=MODEL_TYPE and duplicated content."""
+        response = self.client.get(
+            f'{reverse("admin_autocomplete")}'
+            f'?term={self.QUOTED_SIGNLE_RESULT_TERM}&pageType=category'
+        )
+        self.assertTrue(len(json_to_dict(response)) == 1)