We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Page-product rows duplication on search page
![image](https://user-images.githubusercontent.com/3526796/40539273-e0321bfc-601c-11e8-80e5-9889e18be685.png)


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `shopelectro/models.py`
Content:
```
1 from itertools import chain, groupby
2 from operator import attrgetter
3 from typing import List, Tuple
4 from uuid import uuid4
5 
6 from django.conf import settings
7 from django.db import models
8 from django.urls import reverse
9 from django.utils.text import slugify
10 from django.utils.translation import ugettext_lazy as _
11 from mptt.querysets import TreeQuerySet
12 from unidecode import unidecode
13 
14 from catalog.models import (
15     AbstractCategory,
16     AbstractProduct,
17     CategoryManager,
18 )
19 from ecommerce.models import Order as ecOrder
20 from pages.models import CustomPage, ModelPage, SyncPageMixin
21 
22 
23 class SECategoryQuerySet(TreeQuerySet):
24     def get_categories_tree_with_pictures(self) -> 'SECategoryQuerySet':
25         categories_with_pictures = (
26             self
27             .filter(products__page__images__isnull=False)
28             .distinct()
29         )
30 
31         return categories_with_pictures.get_ancestors(include_self=True)
32 
33 
34 class SECategoryManager(CategoryManager.from_queryset(SECategoryQuerySet)):
35     pass
36 
37 
38 class Category(AbstractCategory, SyncPageMixin):
39 
40     objects = SECategoryManager()
41     uuid = models.UUIDField(default=uuid4, editable=False)
42 
43     @classmethod
44     def get_default_parent(cls):
45         return CustomPage.objects.filter(slug='catalog').first()
46 
47     @property
48     def image(self):
49         products = self.products.all()
50         return products[0].image if products else None
51 
52     def get_absolute_url(self):
53         return reverse('category', args=(self.page.slug,))
54 
55 
56 class Product(AbstractProduct, SyncPageMixin):
57 
58     category = models.ForeignKey(
59         Category,
60         on_delete=models.CASCADE,
61         null=True,
62         related_name='products',
63         verbose_name=_('category'),
64     )
65 
66     tags = models.ManyToManyField(
67         'Tag',
68         related_name='products',
69         blank=True,
70         verbose_name=_('tags'),
71     )
72 
73     vendor_code = models.SmallIntegerField(verbose_name=_('vendor_code'))
74     uuid = models.UUIDField(default=uuid4, editable=False)
75     purchase_price = models.FloatField(
76         default=0, verbose_name=_('purchase_price'))
77     wholesale_small = models.FloatField(
78         default=0, verbose_name=_('wholesale_small'))
79     wholesale_medium = models.FloatField(
80         default=0, verbose_name=_('wholesale_medium'))
81     wholesale_large = models.FloatField(
82         default=0, verbose_name=_('wholesale_large'))
83 
84     def get_absolute_url(self):
85         return reverse('product', args=(self.vendor_code,))
86 
87     @property
88     def average_rate(self):
89         """Return rounded to first decimal averaged rating."""
90         rating = self.product_feedbacks.aggregate(
91             avg=models.Avg('rating')).get('avg', 0)
92         return round(rating, 1)
93 
94     @property
95     def feedback_count(self):
96         return self.product_feedbacks.count()
97 
98     @property
99     def feedback(self):
100         return self.product_feedbacks.all().order_by('-date')
101 
102     @property
103     def params(self):
104         return Tag.objects.filter(products=self).get_group_tags_pairs()
105 
106 
107 class ProductFeedback(models.Model):
108     product = models.ForeignKey(
109         Product, on_delete=models.CASCADE, null=True,
110         related_name='product_feedbacks'
111     )
112 
113     date = models.DateTimeField(
114         auto_now=True, db_index=True, verbose_name=_('date'))
115     name = models.CharField(
116         max_length=255, db_index=True, verbose_name=_('name'))
117     rating = models.PositiveSmallIntegerField(
118         default=1, db_index=True, verbose_name=_('rating'))
119     dignities = models.TextField(
120         default='', blank=True, verbose_name=_('dignities'))
121     limitations = models.TextField(
122         default='', blank=True, verbose_name=_('limitations'))
123     general = models.TextField(
124         default='', blank=True, verbose_name=_('limitations'))
125 
126 
127 def _default_payment():
128     """Default payment option is first element of first tuple in options."""
129     assert settings.PAYMENT_OPTIONS[0][0], 'No payment options!'
130     return settings.PAYMENT_OPTIONS[0][0]
131 
132 
133 class Order(ecOrder):
134     address = models.TextField(blank=True, default='')
135     payment_type = models.CharField(
136         max_length=255,
137         choices=settings.PAYMENT_OPTIONS,
138         default=_default_payment()
139     )
140     comment = models.TextField(blank=True, default='')
141 
142     @property
143     def payment_type_name(self):
144         """Return name for an order's payment option."""
145         return next(
146             name for option, name in settings.PAYMENT_OPTIONS
147             if self.payment_type == option
148         )
149 
150     def set_positions(self, cart):
151         """Save cart's state into Order instance."""
152         self.save()
153         for id_, position in cart:
154             self.positions.create(
155                 order=self,
156                 product_id=id_,
157                 vendor_code=position['vendor_code'],
158                 name=position['name'],
159                 price=position['price'],
160                 quantity=position['quantity']
161             )
162         return self
163 
164 
165 class CategoryPage(ModelPage):
166     """Create proxy model for Admin."""
167 
168     class Meta(ModelPage.Meta):  # Ignore PycodestyleBear (E303)
169         proxy = True
170 
171     objects = ModelPage.create_model_page_managers(Category)
172 
173 
174 class ProductPage(ModelPage):
175     """Create proxy model for Admin."""
176 
177     class Meta(ModelPage.Meta):  # Ignore PycodestyleBear (E303)
178         proxy = True
179 
180     objects = ModelPage.create_model_page_managers(Product)
181 
182 
183 class TagGroup(models.Model):
184 
185     uuid = models.UUIDField(default=uuid4, editable=False)  # Ignore CPDBear
186     name = models.CharField(
187         max_length=100, db_index=True, verbose_name=_('name'))
188     position = models.PositiveSmallIntegerField(
189         default=0, blank=True, db_index=True, verbose_name=_('position'),
190     )
191 
192     def __str__(self):
193         return self.name
194 
195 
196 class TagQuerySet(models.QuerySet):
197 
198     def get_group_tags_pairs(self) -> List[Tuple[TagGroup, List['Tag']]]:
199         ordering = settings.TAGS_ORDER
200         distinct = [order.lstrip('-') for order in ordering]
201 
202         tags = (
203             self
204             .all()
205             .prefetch_related('group')
206             .order_by(*ordering)
207             .distinct(*distinct, 'id')
208         )
209 
210         group_tags_pair = [
211             (group, list(tags_))
212             for group, tags_ in groupby(tags, key=attrgetter('group'))
213         ]
214 
215         return group_tags_pair
216 
217 
218 class TagManager(models.Manager.from_queryset(TagQuerySet)):
219 
220     def get_queryset(self):
221         return (
222             super().get_queryset()
223             .order_by(*settings.TAGS_ORDER)
224         )
225 
226     def get_group_tags_pairs(self):
227         return self.get_queryset().get_group_tags_pairs()
228 
229 
230 class Tag(models.Model):
231 
232     objects = TagManager()
233 
234     uuid = models.UUIDField(default=uuid4, editable=False)
235     name = models.CharField(
236         max_length=100, db_index=True, verbose_name=_('name'))
237     position = models.PositiveSmallIntegerField(
238         default=0, blank=True, db_index=True, verbose_name=_('position'),
239     )
240 
241     slug = models.SlugField(default='')
242 
243     group = models.ForeignKey(
244         TagGroup, on_delete=models.CASCADE, null=True, related_name='tags',
245     )
246 
247     def __str__(self):
248         return self.name
249 
250     def save(self, *args, **kwargs):
251         if not self.slug:
252             # same slugify code used in PageMixin object
253             self.slug = slugify(
254                 unidecode(self.name.replace('.', '-').replace('+', '-'))
255             )
256         super(Tag, self).save(*args, **kwargs)
257 
258     @staticmethod
259     def parse_url_tags(tags: str) -> list:
260         groups = tags.split(settings.TAGS_URL_DELIMITER)
261         return set(chain.from_iterable(
262             group.split(settings.TAG_GROUPS_URL_DELIMITER) for group in groups
263         ))
264 
265 
266 def serialize_tags(
267     tags: TagQuerySet,
268     field_name: str,
269     type_delimiter: str,
270     group_delimiter: str,
271 ) -> str:
272     group_tags_map = tags.get_group_tags_pairs()
273 
274     _, tags_by_group = zip(*group_tags_map)
275 
276     return group_delimiter.join(
277         type_delimiter.join(getattr(tag, field_name) for tag in tags_list)
278         for tags_list in tags_by_group
279     )
280 
281 
282 def serialize_tags_to_url(tags: TagQuerySet) -> str:
283     return serialize_tags(
284         tags=tags,
285         field_name='slug',
286         type_delimiter=settings.TAGS_URL_DELIMITER,
287         group_delimiter=settings.TAG_GROUPS_URL_DELIMITER
288     )
289 
290 
291 def serialize_tags_to_title(tags: TagQuerySet) -> str:
292     return serialize_tags(
293         tags=tags,
294         field_name='name',
295         type_delimiter=settings.TAGS_TITLE_DELIMITER,
296         group_delimiter=settings.TAG_GROUPS_TITLE_DELIMITER
297     )
298
```

Path: `shopelectro/views/search.py`
Content:
```
1 from django.conf import settings
2 
3 from search import views as search_views, search as search_engine
4 from pages.models import Page
5 
6 from shopelectro.models import Product, Category
7 
8 
9 class Search(search_views.SearchView):
10     def get_redirect_search_entity(self):
11         return next(s for s in self.search_entities if s.name == 'product')
12 
13     # ignore CPDBear
14     search_entities = [
15         search_engine.Search(
16             name='category',
17             qs=Category.objects.all(),
18             fields=['name'],
19             min_similarity=settings.TRIGRAM_MIN_SIMILARITY,
20         ),
21         search_engine.Search(
22             name='product',
23             qs=Product.objects.all(),
24             fields=['name'],
25             redirect_field='vendor_code',
26             min_similarity=settings.TRIGRAM_MIN_SIMILARITY,
27         ),
28         search_engine.Search(
29             name='page',
30             qs=Page.objects.all(),
31             fields=['name'],
32             min_similarity=settings.TRIGRAM_MIN_SIMILARITY,
33         )
34     ]
35 
36     redirect_field = 'vendor_code'
37 
38 
39 class Autocomplete(search_views.AutocompleteView):
40 
41     # ignore CPDBear
42     search_entities = [
43         search_engine.Search(
44             name='category',
45             qs=Category.objects.all(),
46             fields=['name', 'id'],
47             template_fields=['name', 'url'],
48             min_similarity=settings.TRIGRAM_MIN_SIMILARITY,
49         ),
50         search_engine.Search(
51             name='product',
52             qs=Product.objects.all(),
53             fields=['name', 'id', 'vendor_code'],
54             template_fields=['name', 'price', 'url'],
55             min_similarity=settings.TRIGRAM_MIN_SIMILARITY,
56         ),
57         search_engine.Search(
58             name='pages',
59             qs=Page.objects.all(),
60             fields=['name'],
61             template_fields=['name', 'url'],
62             min_similarity=settings.TRIGRAM_MIN_SIMILARITY,
63         )
64     ]
65 
66     see_all_label = settings.SEARCH_SEE_ALL_LABEL
67 
68 
69 class AdminAutocomplete(search_views.AdminAutocompleteView):
70 
71     # ignore CPDBear
72     search_entities = [
73         search_engine.Search(
74             name='category',
75             qs=Category.objects.all(),
76             fields=['name'],
77             min_similarity=settings.TRIGRAM_MIN_SIMILARITY,
78         ),
79         search_engine.Search(
80             name='product',
81             qs=Product.objects.all(),
82             fields=['name'],
83             min_similarity=settings.TRIGRAM_MIN_SIMILARITY,
84         ),
85         search_engine.Search(
86             name='pages',
87             qs=Page.objects.all(),
88             fields=['name'],
89             min_similarity=settings.TRIGRAM_MIN_SIMILARITY,
90         )
91     ]
92
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

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 @@
         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 @@
         ),
         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 @@
         ),
         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 @@
         ),
         search_engine.Search(
             name='pages',
-            qs=Page.objects.all(),
+            qs=ExcludedModelTPage.objects.all(),
             fields=['name'],
             min_similarity=settings.TRIGRAM_MIN_SIMILARITY,
         )

{"golden_diff": "diff --git a/shopelectro/models.py b/shopelectro/models.py\n--- a/shopelectro/models.py\n+++ b/shopelectro/models.py\n@@ -8,6 +8,7 @@\n from django.urls import reverse\n from django.utils.text import slugify\n from django.utils.translation import ugettext_lazy as _\n+from mptt.models import TreeManager\n from mptt.querysets import TreeQuerySet\n from unidecode import unidecode\n \n@@ -17,7 +18,7 @@\n     CategoryManager,\n )\n from ecommerce.models import Order as ecOrder\n-from pages.models import CustomPage, ModelPage, SyncPageMixin\n+from pages.models import CustomPage, ModelPage, Page, SyncPageMixin\n \n \n class SECategoryQuerySet(TreeQuerySet):\n@@ -295,3 +296,17 @@\n         type_delimiter=settings.TAGS_TITLE_DELIMITER,\n         group_delimiter=settings.TAG_GROUPS_TITLE_DELIMITER\n     )\n+\n+\n+class ExcludedModelTPageManager(TreeManager):\n+\n+    def get_queryset(self):\n+        return super().get_queryset().exclude(type=Page.MODEL_TYPE)\n+\n+\n+class ExcludedModelTPage(Page):\n+\n+    class Meta(Page.Meta):  # Ignore PycodestyleBear (E303)\n+        proxy = True\n+\n+    objects = ExcludedModelTPageManager()\ndiff --git a/shopelectro/views/search.py b/shopelectro/views/search.py\n--- a/shopelectro/views/search.py\n+++ b/shopelectro/views/search.py\n@@ -3,7 +3,7 @@\n from search import views as search_views, search as search_engine\n from pages.models import Page\n \n-from shopelectro.models import Product, Category\n+from shopelectro.models import Category, Product, ExcludedModelTPage\n \n \n class Search(search_views.SearchView):\n@@ -27,7 +27,7 @@\n         ),\n         search_engine.Search(\n             name='page',\n-            qs=Page.objects.all(),\n+            qs=ExcludedModelTPage.objects.all(),\n             fields=['name'],\n             min_similarity=settings.TRIGRAM_MIN_SIMILARITY,\n         )\n@@ -56,7 +56,7 @@\n         ),\n         search_engine.Search(\n             name='pages',\n-            qs=Page.objects.all(),\n+            qs=ExcludedModelTPage.objects.all(),\n             fields=['name'],\n             template_fields=['name', 'url'],\n             min_similarity=settings.TRIGRAM_MIN_SIMILARITY,\n@@ -84,7 +84,7 @@\n         ),\n         search_engine.Search(\n             name='pages',\n-            qs=Page.objects.all(),\n+            qs=ExcludedModelTPage.objects.all(),\n             fields=['name'],\n             min_similarity=settings.TRIGRAM_MIN_SIMILARITY,\n         )\n", "issue": "Page-product rows duplication on search page\n![image](https://user-images.githubusercontent.com/3526796/40539273-e0321bfc-601c-11e8-80e5-9889e18be685.png)\r\n\n", "before_files": [{"content": "from itertools import chain, groupby\nfrom operator import attrgetter\nfrom typing import List, Tuple\nfrom uuid import uuid4\n\nfrom django.conf import settings\nfrom django.db import models\nfrom django.urls import reverse\nfrom django.utils.text import slugify\nfrom django.utils.translation import ugettext_lazy as _\nfrom mptt.querysets import TreeQuerySet\nfrom unidecode import unidecode\n\nfrom catalog.models import (\n    AbstractCategory,\n    AbstractProduct,\n    CategoryManager,\n)\nfrom ecommerce.models import Order as ecOrder\nfrom pages.models import CustomPage, ModelPage, SyncPageMixin\n\n\nclass SECategoryQuerySet(TreeQuerySet):\n    def get_categories_tree_with_pictures(self) -> 'SECategoryQuerySet':\n        categories_with_pictures = (\n            self\n            .filter(products__page__images__isnull=False)\n            .distinct()\n        )\n\n        return categories_with_pictures.get_ancestors(include_self=True)\n\n\nclass SECategoryManager(CategoryManager.from_queryset(SECategoryQuerySet)):\n    pass\n\n\nclass Category(AbstractCategory, SyncPageMixin):\n\n    objects = SECategoryManager()\n    uuid = models.UUIDField(default=uuid4, editable=False)\n\n    @classmethod\n    def get_default_parent(cls):\n        return CustomPage.objects.filter(slug='catalog').first()\n\n    @property\n    def image(self):\n        products = self.products.all()\n        return products[0].image if products else None\n\n    def get_absolute_url(self):\n        return reverse('category', args=(self.page.slug,))\n\n\nclass Product(AbstractProduct, SyncPageMixin):\n\n    category = models.ForeignKey(\n        Category,\n        on_delete=models.CASCADE,\n        null=True,\n        related_name='products',\n        verbose_name=_('category'),\n    )\n\n    tags = models.ManyToManyField(\n        'Tag',\n        related_name='products',\n        blank=True,\n        verbose_name=_('tags'),\n    )\n\n    vendor_code = models.SmallIntegerField(verbose_name=_('vendor_code'))\n    uuid = models.UUIDField(default=uuid4, editable=False)\n    purchase_price = models.FloatField(\n        default=0, verbose_name=_('purchase_price'))\n    wholesale_small = models.FloatField(\n        default=0, verbose_name=_('wholesale_small'))\n    wholesale_medium = models.FloatField(\n        default=0, verbose_name=_('wholesale_medium'))\n    wholesale_large = models.FloatField(\n        default=0, verbose_name=_('wholesale_large'))\n\n    def get_absolute_url(self):\n        return reverse('product', args=(self.vendor_code,))\n\n    @property\n    def average_rate(self):\n        \"\"\"Return rounded to first decimal averaged rating.\"\"\"\n        rating = self.product_feedbacks.aggregate(\n            avg=models.Avg('rating')).get('avg', 0)\n        return round(rating, 1)\n\n    @property\n    def feedback_count(self):\n        return self.product_feedbacks.count()\n\n    @property\n    def feedback(self):\n        return self.product_feedbacks.all().order_by('-date')\n\n    @property\n    def params(self):\n        return Tag.objects.filter(products=self).get_group_tags_pairs()\n\n\nclass ProductFeedback(models.Model):\n    product = models.ForeignKey(\n        Product, on_delete=models.CASCADE, null=True,\n        related_name='product_feedbacks'\n    )\n\n    date = models.DateTimeField(\n        auto_now=True, db_index=True, verbose_name=_('date'))\n    name = models.CharField(\n        max_length=255, db_index=True, verbose_name=_('name'))\n    rating = models.PositiveSmallIntegerField(\n        default=1, db_index=True, verbose_name=_('rating'))\n    dignities = models.TextField(\n        default='', blank=True, verbose_name=_('dignities'))\n    limitations = models.TextField(\n        default='', blank=True, verbose_name=_('limitations'))\n    general = models.TextField(\n        default='', blank=True, verbose_name=_('limitations'))\n\n\ndef _default_payment():\n    \"\"\"Default payment option is first element of first tuple in options.\"\"\"\n    assert settings.PAYMENT_OPTIONS[0][0], 'No payment options!'\n    return settings.PAYMENT_OPTIONS[0][0]\n\n\nclass Order(ecOrder):\n    address = models.TextField(blank=True, default='')\n    payment_type = models.CharField(\n        max_length=255,\n        choices=settings.PAYMENT_OPTIONS,\n        default=_default_payment()\n    )\n    comment = models.TextField(blank=True, default='')\n\n    @property\n    def payment_type_name(self):\n        \"\"\"Return name for an order's payment option.\"\"\"\n        return next(\n            name for option, name in settings.PAYMENT_OPTIONS\n            if self.payment_type == option\n        )\n\n    def set_positions(self, cart):\n        \"\"\"Save cart's state into Order instance.\"\"\"\n        self.save()\n        for id_, position in cart:\n            self.positions.create(\n                order=self,\n                product_id=id_,\n                vendor_code=position['vendor_code'],\n                name=position['name'],\n                price=position['price'],\n                quantity=position['quantity']\n            )\n        return self\n\n\nclass CategoryPage(ModelPage):\n    \"\"\"Create proxy model for Admin.\"\"\"\n\n    class Meta(ModelPage.Meta):  # Ignore PycodestyleBear (E303)\n        proxy = True\n\n    objects = ModelPage.create_model_page_managers(Category)\n\n\nclass ProductPage(ModelPage):\n    \"\"\"Create proxy model for Admin.\"\"\"\n\n    class Meta(ModelPage.Meta):  # Ignore PycodestyleBear (E303)\n        proxy = True\n\n    objects = ModelPage.create_model_page_managers(Product)\n\n\nclass TagGroup(models.Model):\n\n    uuid = models.UUIDField(default=uuid4, editable=False)  # Ignore CPDBear\n    name = models.CharField(\n        max_length=100, db_index=True, verbose_name=_('name'))\n    position = models.PositiveSmallIntegerField(\n        default=0, blank=True, db_index=True, verbose_name=_('position'),\n    )\n\n    def __str__(self):\n        return self.name\n\n\nclass TagQuerySet(models.QuerySet):\n\n    def get_group_tags_pairs(self) -> List[Tuple[TagGroup, List['Tag']]]:\n        ordering = settings.TAGS_ORDER\n        distinct = [order.lstrip('-') for order in ordering]\n\n        tags = (\n            self\n            .all()\n            .prefetch_related('group')\n            .order_by(*ordering)\n            .distinct(*distinct, 'id')\n        )\n\n        group_tags_pair = [\n            (group, list(tags_))\n            for group, tags_ in groupby(tags, key=attrgetter('group'))\n        ]\n\n        return group_tags_pair\n\n\nclass TagManager(models.Manager.from_queryset(TagQuerySet)):\n\n    def get_queryset(self):\n        return (\n            super().get_queryset()\n            .order_by(*settings.TAGS_ORDER)\n        )\n\n    def get_group_tags_pairs(self):\n        return self.get_queryset().get_group_tags_pairs()\n\n\nclass Tag(models.Model):\n\n    objects = TagManager()\n\n    uuid = models.UUIDField(default=uuid4, editable=False)\n    name = models.CharField(\n        max_length=100, db_index=True, verbose_name=_('name'))\n    position = models.PositiveSmallIntegerField(\n        default=0, blank=True, db_index=True, verbose_name=_('position'),\n    )\n\n    slug = models.SlugField(default='')\n\n    group = models.ForeignKey(\n        TagGroup, on_delete=models.CASCADE, null=True, related_name='tags',\n    )\n\n    def __str__(self):\n        return self.name\n\n    def save(self, *args, **kwargs):\n        if not self.slug:\n            # same slugify code used in PageMixin object\n            self.slug = slugify(\n                unidecode(self.name.replace('.', '-').replace('+', '-'))\n            )\n        super(Tag, self).save(*args, **kwargs)\n\n    @staticmethod\n    def parse_url_tags(tags: str) -> list:\n        groups = tags.split(settings.TAGS_URL_DELIMITER)\n        return set(chain.from_iterable(\n            group.split(settings.TAG_GROUPS_URL_DELIMITER) for group in groups\n        ))\n\n\ndef serialize_tags(\n    tags: TagQuerySet,\n    field_name: str,\n    type_delimiter: str,\n    group_delimiter: str,\n) -> str:\n    group_tags_map = tags.get_group_tags_pairs()\n\n    _, tags_by_group = zip(*group_tags_map)\n\n    return group_delimiter.join(\n        type_delimiter.join(getattr(tag, field_name) for tag in tags_list)\n        for tags_list in tags_by_group\n    )\n\n\ndef serialize_tags_to_url(tags: TagQuerySet) -> str:\n    return serialize_tags(\n        tags=tags,\n        field_name='slug',\n        type_delimiter=settings.TAGS_URL_DELIMITER,\n        group_delimiter=settings.TAG_GROUPS_URL_DELIMITER\n    )\n\n\ndef serialize_tags_to_title(tags: TagQuerySet) -> str:\n    return serialize_tags(\n        tags=tags,\n        field_name='name',\n        type_delimiter=settings.TAGS_TITLE_DELIMITER,\n        group_delimiter=settings.TAG_GROUPS_TITLE_DELIMITER\n    )\n", "path": "shopelectro/models.py"}, {"content": "from django.conf import settings\n\nfrom search import views as search_views, search as search_engine\nfrom pages.models import Page\n\nfrom shopelectro.models import Product, Category\n\n\nclass Search(search_views.SearchView):\n    def get_redirect_search_entity(self):\n        return next(s for s in self.search_entities if s.name == 'product')\n\n    # ignore CPDBear\n    search_entities = [\n        search_engine.Search(\n            name='category',\n            qs=Category.objects.all(),\n            fields=['name'],\n            min_similarity=settings.TRIGRAM_MIN_SIMILARITY,\n        ),\n        search_engine.Search(\n            name='product',\n            qs=Product.objects.all(),\n            fields=['name'],\n            redirect_field='vendor_code',\n            min_similarity=settings.TRIGRAM_MIN_SIMILARITY,\n        ),\n        search_engine.Search(\n            name='page',\n            qs=Page.objects.all(),\n            fields=['name'],\n            min_similarity=settings.TRIGRAM_MIN_SIMILARITY,\n        )\n    ]\n\n    redirect_field = 'vendor_code'\n\n\nclass Autocomplete(search_views.AutocompleteView):\n\n    # ignore CPDBear\n    search_entities = [\n        search_engine.Search(\n            name='category',\n            qs=Category.objects.all(),\n            fields=['name', 'id'],\n            template_fields=['name', 'url'],\n            min_similarity=settings.TRIGRAM_MIN_SIMILARITY,\n        ),\n        search_engine.Search(\n            name='product',\n            qs=Product.objects.all(),\n            fields=['name', 'id', 'vendor_code'],\n            template_fields=['name', 'price', 'url'],\n            min_similarity=settings.TRIGRAM_MIN_SIMILARITY,\n        ),\n        search_engine.Search(\n            name='pages',\n            qs=Page.objects.all(),\n            fields=['name'],\n            template_fields=['name', 'url'],\n            min_similarity=settings.TRIGRAM_MIN_SIMILARITY,\n        )\n    ]\n\n    see_all_label = settings.SEARCH_SEE_ALL_LABEL\n\n\nclass AdminAutocomplete(search_views.AdminAutocompleteView):\n\n    # ignore CPDBear\n    search_entities = [\n        search_engine.Search(\n            name='category',\n            qs=Category.objects.all(),\n            fields=['name'],\n            min_similarity=settings.TRIGRAM_MIN_SIMILARITY,\n        ),\n        search_engine.Search(\n            name='product',\n            qs=Product.objects.all(),\n            fields=['name'],\n            min_similarity=settings.TRIGRAM_MIN_SIMILARITY,\n        ),\n        search_engine.Search(\n            name='pages',\n            qs=Page.objects.all(),\n            fields=['name'],\n            min_similarity=settings.TRIGRAM_MIN_SIMILARITY,\n        )\n    ]\n", "path": "shopelectro/views/search.py"}], "after_files": [{"content": "from itertools import chain, groupby\nfrom operator import attrgetter\nfrom typing import List, Tuple\nfrom uuid import uuid4\n\nfrom django.conf import settings\nfrom django.db import models\nfrom django.urls import reverse\nfrom django.utils.text import slugify\nfrom django.utils.translation import ugettext_lazy as _\nfrom mptt.models import TreeManager\nfrom mptt.querysets import TreeQuerySet\nfrom unidecode import unidecode\n\nfrom catalog.models import (\n    AbstractCategory,\n    AbstractProduct,\n    CategoryManager,\n)\nfrom ecommerce.models import Order as ecOrder\nfrom pages.models import CustomPage, ModelPage, Page, SyncPageMixin\n\n\nclass SECategoryQuerySet(TreeQuerySet):\n    def get_categories_tree_with_pictures(self) -> 'SECategoryQuerySet':\n        categories_with_pictures = (\n            self\n            .filter(products__page__images__isnull=False)\n            .distinct()\n        )\n\n        return categories_with_pictures.get_ancestors(include_self=True)\n\n\nclass SECategoryManager(CategoryManager.from_queryset(SECategoryQuerySet)):\n    pass\n\n\nclass Category(AbstractCategory, SyncPageMixin):\n\n    objects = SECategoryManager()\n    uuid = models.UUIDField(default=uuid4, editable=False)\n\n    @classmethod\n    def get_default_parent(cls):\n        return CustomPage.objects.filter(slug='catalog').first()\n\n    @property\n    def image(self):\n        products = self.products.all()\n        return products[0].image if products else None\n\n    def get_absolute_url(self):\n        return reverse('category', args=(self.page.slug,))\n\n\nclass Product(AbstractProduct, SyncPageMixin):\n\n    category = models.ForeignKey(\n        Category,\n        on_delete=models.CASCADE,\n        null=True,\n        related_name='products',\n        verbose_name=_('category'),\n    )\n\n    tags = models.ManyToManyField(\n        'Tag',\n        related_name='products',\n        blank=True,\n        verbose_name=_('tags'),\n    )\n\n    vendor_code = models.SmallIntegerField(verbose_name=_('vendor_code'))\n    uuid = models.UUIDField(default=uuid4, editable=False)\n    purchase_price = models.FloatField(\n        default=0, verbose_name=_('purchase_price'))\n    wholesale_small = models.FloatField(\n        default=0, verbose_name=_('wholesale_small'))\n    wholesale_medium = models.FloatField(\n        default=0, verbose_name=_('wholesale_medium'))\n    wholesale_large = models.FloatField(\n        default=0, verbose_name=_('wholesale_large'))\n\n    def get_absolute_url(self):\n        return reverse('product', args=(self.vendor_code,))\n\n    @property\n    def average_rate(self):\n        \"\"\"Return rounded to first decimal averaged rating.\"\"\"\n        rating = self.product_feedbacks.aggregate(\n            avg=models.Avg('rating')).get('avg', 0)\n        return round(rating, 1)\n\n    @property\n    def feedback_count(self):\n        return self.product_feedbacks.count()\n\n    @property\n    def feedback(self):\n        return self.product_feedbacks.all().order_by('-date')\n\n    @property\n    def params(self):\n        return Tag.objects.filter(products=self).get_group_tags_pairs()\n\n\nclass ProductFeedback(models.Model):\n    product = models.ForeignKey(\n        Product, on_delete=models.CASCADE, null=True,\n        related_name='product_feedbacks'\n    )\n\n    date = models.DateTimeField(\n        auto_now=True, db_index=True, verbose_name=_('date'))\n    name = models.CharField(\n        max_length=255, db_index=True, verbose_name=_('name'))\n    rating = models.PositiveSmallIntegerField(\n        default=1, db_index=True, verbose_name=_('rating'))\n    dignities = models.TextField(\n        default='', blank=True, verbose_name=_('dignities'))\n    limitations = models.TextField(\n        default='', blank=True, verbose_name=_('limitations'))\n    general = models.TextField(\n        default='', blank=True, verbose_name=_('limitations'))\n\n\ndef _default_payment():\n    \"\"\"Default payment option is first element of first tuple in options.\"\"\"\n    assert settings.PAYMENT_OPTIONS[0][0], 'No payment options!'\n    return settings.PAYMENT_OPTIONS[0][0]\n\n\nclass Order(ecOrder):\n    address = models.TextField(blank=True, default='')\n    payment_type = models.CharField(\n        max_length=255,\n        choices=settings.PAYMENT_OPTIONS,\n        default=_default_payment()\n    )\n    comment = models.TextField(blank=True, default='')\n\n    @property\n    def payment_type_name(self):\n        \"\"\"Return name for an order's payment option.\"\"\"\n        return next(\n            name for option, name in settings.PAYMENT_OPTIONS\n            if self.payment_type == option\n        )\n\n    def set_positions(self, cart):\n        \"\"\"Save cart's state into Order instance.\"\"\"\n        self.save()\n        for id_, position in cart:\n            self.positions.create(\n                order=self,\n                product_id=id_,\n                vendor_code=position['vendor_code'],\n                name=position['name'],\n                price=position['price'],\n                quantity=position['quantity']\n            )\n        return self\n\n\nclass CategoryPage(ModelPage):\n    \"\"\"Create proxy model for Admin.\"\"\"\n\n    class Meta(ModelPage.Meta):  # Ignore PycodestyleBear (E303)\n        proxy = True\n\n    objects = ModelPage.create_model_page_managers(Category)\n\n\nclass ProductPage(ModelPage):\n    \"\"\"Create proxy model for Admin.\"\"\"\n\n    class Meta(ModelPage.Meta):  # Ignore PycodestyleBear (E303)\n        proxy = True\n\n    objects = ModelPage.create_model_page_managers(Product)\n\n\nclass TagGroup(models.Model):\n\n    uuid = models.UUIDField(default=uuid4, editable=False)  # Ignore CPDBear\n    name = models.CharField(\n        max_length=100, db_index=True, verbose_name=_('name'))\n    position = models.PositiveSmallIntegerField(\n        default=0, blank=True, db_index=True, verbose_name=_('position'),\n    )\n\n    def __str__(self):\n        return self.name\n\n\nclass TagQuerySet(models.QuerySet):\n\n    def get_group_tags_pairs(self) -> List[Tuple[TagGroup, List['Tag']]]:\n        ordering = settings.TAGS_ORDER\n        distinct = [order.lstrip('-') for order in ordering]\n\n        tags = (\n            self\n            .all()\n            .prefetch_related('group')\n            .order_by(*ordering)\n            .distinct(*distinct, 'id')\n        )\n\n        group_tags_pair = [\n            (group, list(tags_))\n            for group, tags_ in groupby(tags, key=attrgetter('group'))\n        ]\n\n        return group_tags_pair\n\n\nclass TagManager(models.Manager.from_queryset(TagQuerySet)):\n\n    def get_queryset(self):\n        return (\n            super().get_queryset()\n            .order_by(*settings.TAGS_ORDER)\n        )\n\n    def get_group_tags_pairs(self):\n        return self.get_queryset().get_group_tags_pairs()\n\n\nclass Tag(models.Model):\n\n    objects = TagManager()\n\n    uuid = models.UUIDField(default=uuid4, editable=False)\n    name = models.CharField(\n        max_length=100, db_index=True, verbose_name=_('name'))\n    position = models.PositiveSmallIntegerField(\n        default=0, blank=True, db_index=True, verbose_name=_('position'),\n    )\n\n    slug = models.SlugField(default='')\n\n    group = models.ForeignKey(\n        TagGroup, on_delete=models.CASCADE, null=True, related_name='tags',\n    )\n\n    def __str__(self):\n        return self.name\n\n    def save(self, *args, **kwargs):\n        if not self.slug:\n            # same slugify code used in PageMixin object\n            self.slug = slugify(\n                unidecode(self.name.replace('.', '-').replace('+', '-'))\n            )\n        super(Tag, self).save(*args, **kwargs)\n\n    @staticmethod\n    def parse_url_tags(tags: str) -> list:\n        groups = tags.split(settings.TAGS_URL_DELIMITER)\n        return set(chain.from_iterable(\n            group.split(settings.TAG_GROUPS_URL_DELIMITER) for group in groups\n        ))\n\n\ndef serialize_tags(\n    tags: TagQuerySet,\n    field_name: str,\n    type_delimiter: str,\n    group_delimiter: str,\n) -> str:\n    group_tags_map = tags.get_group_tags_pairs()\n\n    _, tags_by_group = zip(*group_tags_map)\n\n    return group_delimiter.join(\n        type_delimiter.join(getattr(tag, field_name) for tag in tags_list)\n        for tags_list in tags_by_group\n    )\n\n\ndef serialize_tags_to_url(tags: TagQuerySet) -> str:\n    return serialize_tags(\n        tags=tags,\n        field_name='slug',\n        type_delimiter=settings.TAGS_URL_DELIMITER,\n        group_delimiter=settings.TAG_GROUPS_URL_DELIMITER\n    )\n\n\ndef serialize_tags_to_title(tags: TagQuerySet) -> str:\n    return serialize_tags(\n        tags=tags,\n        field_name='name',\n        type_delimiter=settings.TAGS_TITLE_DELIMITER,\n        group_delimiter=settings.TAG_GROUPS_TITLE_DELIMITER\n    )\n\n\nclass ExcludedModelTPageManager(TreeManager):\n\n    def get_queryset(self):\n        return super().get_queryset().exclude(type=Page.MODEL_TYPE)\n\n\nclass ExcludedModelTPage(Page):\n\n    class Meta(Page.Meta):  # Ignore PycodestyleBear (E303)\n        proxy = True\n\n    objects = ExcludedModelTPageManager()\n", "path": "shopelectro/models.py"}, {"content": "from django.conf import settings\n\nfrom search import views as search_views, search as search_engine\nfrom pages.models import Page\n\nfrom shopelectro.models import Category, Product, ExcludedModelTPage\n\n\nclass Search(search_views.SearchView):\n    def get_redirect_search_entity(self):\n        return next(s for s in self.search_entities if s.name == 'product')\n\n    # ignore CPDBear\n    search_entities = [\n        search_engine.Search(\n            name='category',\n            qs=Category.objects.all(),\n            fields=['name'],\n            min_similarity=settings.TRIGRAM_MIN_SIMILARITY,\n        ),\n        search_engine.Search(\n            name='product',\n            qs=Product.objects.all(),\n            fields=['name'],\n            redirect_field='vendor_code',\n            min_similarity=settings.TRIGRAM_MIN_SIMILARITY,\n        ),\n        search_engine.Search(\n            name='page',\n            qs=ExcludedModelTPage.objects.all(),\n            fields=['name'],\n            min_similarity=settings.TRIGRAM_MIN_SIMILARITY,\n        )\n    ]\n\n    redirect_field = 'vendor_code'\n\n\nclass Autocomplete(search_views.AutocompleteView):\n\n    # ignore CPDBear\n    search_entities = [\n        search_engine.Search(\n            name='category',\n            qs=Category.objects.all(),\n            fields=['name', 'id'],\n            template_fields=['name', 'url'],\n            min_similarity=settings.TRIGRAM_MIN_SIMILARITY,\n        ),\n        search_engine.Search(\n            name='product',\n            qs=Product.objects.all(),\n            fields=['name', 'id', 'vendor_code'],\n            template_fields=['name', 'price', 'url'],\n            min_similarity=settings.TRIGRAM_MIN_SIMILARITY,\n        ),\n        search_engine.Search(\n            name='pages',\n            qs=ExcludedModelTPage.objects.all(),\n            fields=['name'],\n            template_fields=['name', 'url'],\n            min_similarity=settings.TRIGRAM_MIN_SIMILARITY,\n        )\n    ]\n\n    see_all_label = settings.SEARCH_SEE_ALL_LABEL\n\n\nclass AdminAutocomplete(search_views.AdminAutocompleteView):\n\n    # ignore CPDBear\n    search_entities = [\n        search_engine.Search(\n            name='category',\n            qs=Category.objects.all(),\n            fields=['name'],\n            min_similarity=settings.TRIGRAM_MIN_SIMILARITY,\n        ),\n        search_engine.Search(\n            name='product',\n            qs=Product.objects.all(),\n            fields=['name'],\n            min_similarity=settings.TRIGRAM_MIN_SIMILARITY,\n        ),\n        search_engine.Search(\n            name='pages',\n            qs=ExcludedModelTPage.objects.all(),\n            fields=['name'],\n            min_similarity=settings.TRIGRAM_MIN_SIMILARITY,\n        )\n    ]\n", "path": "shopelectro/views/search.py"}]}