We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
models.py:122-123: Move Product.get_siblings method to...
The puzzle `388-509fa51e` from #388 has to be resolved:

https://github.com/fidals/shopelectro/blob/5d0c5e97622abce3a922eda62c1f38810d7da5f8/shopelectro/models.py#L122-L123

The puzzle was created by duker33 on 12-Aug-18. 

Estimate: 30 minutes,     

If you have any technical questions, don't ask me, submit new tickets instead. The task will be "done" when the problem is fixed and the text of the puzzle is _removed_ from the source code. Here is more about [PDD](http://www.yegor256.com/2009/03/04/pdd.html) and [about me](http://www.yegor256.com/2017/04/05/pdd-in-action.html).

--- 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 import random
2 import string
3 from itertools import chain, groupby
4 from operator import attrgetter
5 from typing import Dict, List, Optional, Tuple
6 from uuid import uuid4
7 
8 from django.conf import settings
9 from django.db import models
10 from django.urls import reverse
11 from django.utils.text import slugify
12 from django.utils.translation import ugettext_lazy as _
13 from mptt.querysets import TreeQuerySet
14 from unidecode import unidecode
15 
16 from catalog.models import (
17     AbstractCategory,
18     AbstractProduct,
19     CategoryManager,
20     ProductActiveManager,
21     ProductManager,
22 )
23 from ecommerce.models import Order as ecOrder
24 from pages.models import CustomPage, ModelPage, Page, SyncPageMixin, PageManager
25 
26 
27 def randomize_slug(slug: str) -> str:
28     slug_hash = ''.join(
29         random.choices(string.ascii_lowercase, k=settings.SLUG_HASH_SIZE)
30     )
31     return f'{slug}_{slug_hash}'
32 
33 
34 class SECategoryQuerySet(TreeQuerySet):
35     def get_categories_tree_with_pictures(self) -> 'SECategoryQuerySet':
36         categories_with_pictures = (
37             self
38             .filter(products__page__images__isnull=False)
39             .distinct()
40         )
41 
42         return categories_with_pictures.get_ancestors(include_self=True)
43 
44 
45 class SECategoryManager(CategoryManager.from_queryset(SECategoryQuerySet)):
46     pass
47 
48 
49 class Category(AbstractCategory, SyncPageMixin):
50 
51     objects = SECategoryManager()
52     uuid = models.UUIDField(default=uuid4, editable=False)
53 
54     @classmethod
55     def get_default_parent(cls):
56         return CustomPage.objects.filter(slug='catalog').first()
57 
58     @property
59     def image(self):
60         products = self.products.all()
61         return products[0].image if products else None
62 
63     def get_absolute_url(self):
64         return reverse('category', args=(self.page.slug,))
65 
66 
67 class Product(AbstractProduct, SyncPageMixin):
68 
69     # That's why we are needed to explicitly add objects manager here
70     # because of Django special managers behaviour.
71     # Se se#480 for details.
72     objects = ProductManager()
73     actives = ProductActiveManager()
74 
75     category = models.ForeignKey(
76         Category,
77         on_delete=models.CASCADE,
78         null=True,
79         related_name='products',
80         verbose_name=_('category'),
81     )
82 
83     tags = models.ManyToManyField(
84         'Tag',
85         related_name='products',
86         blank=True,
87         verbose_name=_('tags'),
88     )
89 
90     vendor_code = models.SmallIntegerField(verbose_name=_('vendor_code'))
91     uuid = models.UUIDField(default=uuid4, editable=False)
92     purchase_price = models.FloatField(
93         default=0, verbose_name=_('purchase_price'))
94     wholesale_small = models.FloatField(
95         default=0, verbose_name=_('wholesale_small'))
96     wholesale_medium = models.FloatField(
97         default=0, verbose_name=_('wholesale_medium'))
98     wholesale_large = models.FloatField(
99         default=0, verbose_name=_('wholesale_large'))
100 
101     def get_absolute_url(self):
102         return reverse('product', args=(self.vendor_code,))
103 
104     @property
105     def average_rate(self):
106         """Return rounded to first decimal averaged rating."""
107         rating = self.product_feedbacks.aggregate(
108             avg=models.Avg('rating')).get('avg', 0)
109         return round(rating, 1)
110 
111     @property
112     def feedback_count(self):
113         return self.product_feedbacks.count()
114 
115     @property
116     def feedback(self):
117         return self.product_feedbacks.all().order_by('-date')
118 
119     def get_params(self):
120         return Tag.objects.filter_by_products([self]).get_group_tags_pairs()
121 
122     # @todo #388:30m Move Product.get_siblings method to refarm-site
123     #  And reuse it on STB.
124     def get_siblings(self, offset):
125         return (
126             self.__class__.actives
127             .filter(category=self.category)
128             .prefetch_related('category')
129             .select_related('page')[:offset]
130         )
131 
132     def get_brand_name(self) -> str:
133         brand: Optional['Tag'] = Tag.objects.get_brands([self]).get(self)
134         return brand.name if brand else ''
135 
136 
137 class ProductFeedback(models.Model):
138     product = models.ForeignKey(
139         Product, on_delete=models.CASCADE, null=True,
140         related_name='product_feedbacks'
141     )
142 
143     date = models.DateTimeField(
144         auto_now=True, db_index=True, verbose_name=_('date'))
145     name = models.CharField(
146         max_length=255, db_index=True, verbose_name=_('name'))
147     rating = models.PositiveSmallIntegerField(
148         default=1, db_index=True, verbose_name=_('rating'))
149     dignities = models.TextField(
150         default='', blank=True, verbose_name=_('dignities'))
151     limitations = models.TextField(
152         default='', blank=True, verbose_name=_('limitations'))
153     general = models.TextField(
154         default='', blank=True, verbose_name=_('limitations'))
155 
156 
157 def _default_payment():
158     """Default payment option is first element of first tuple in options."""
159     assert settings.PAYMENT_OPTIONS[0][0], 'No payment options!'
160     return settings.PAYMENT_OPTIONS[0][0]
161 
162 
163 class Order(ecOrder):
164     address = models.TextField(blank=True, default='')
165     payment_type = models.CharField(
166         max_length=255,
167         choices=settings.PAYMENT_OPTIONS,
168         default=_default_payment()
169     )
170     comment = models.TextField(blank=True, default='')
171 
172     @property
173     def payment_type_name(self):
174         """Return name for an order's payment option."""
175         return next(
176             name for option, name in settings.PAYMENT_OPTIONS
177             if self.payment_type == option
178         )
179 
180     def set_positions(self, cart):
181         """Save cart's state into Order instance."""
182         self.save()
183         for id_, position in cart:
184             self.positions.create(
185                 order=self,
186                 product_id=id_,
187                 vendor_code=position['vendor_code'],
188                 name=position['name'],
189                 price=position['price'],
190                 quantity=position['quantity']
191             )
192         return self
193 
194 
195 class CategoryPage(ModelPage):
196     """Create proxy model for Admin."""
197 
198     class Meta(ModelPage.Meta):  # Ignore PycodestyleBear (E303)
199         proxy = True
200 
201     objects = ModelPage.create_model_page_managers(Category)
202 
203 
204 class ProductPage(ModelPage):
205     """Create proxy model for Admin."""
206 
207     class Meta(ModelPage.Meta):  # Ignore PycodestyleBear (E303)
208         proxy = True
209 
210     objects = ModelPage.create_model_page_managers(Product)
211 
212 
213 class TagGroup(models.Model):
214 
215     uuid = models.UUIDField(default=uuid4, editable=False)  # Ignore CPDBear
216     name = models.CharField(
217         max_length=100, db_index=True, verbose_name=_('name'))
218     position = models.PositiveSmallIntegerField(
219         default=0, blank=True, db_index=True, verbose_name=_('position'),
220     )
221 
222     def __str__(self):
223         return self.name
224 
225 
226 class TagQuerySet(models.QuerySet):
227 
228     def filter_by_products(self, products: List[Product]):
229         ordering = settings.TAGS_ORDER
230         distinct = [order.lstrip('-') for order in ordering]
231 
232         return (
233             self
234             .filter(products__in=products)
235             .order_by(*ordering)
236             .distinct(*distinct, 'id')
237         )
238 
239     def get_group_tags_pairs(self) -> List[Tuple[TagGroup, List['Tag']]]:
240         grouped_tags = groupby(self.prefetch_related('group'), key=attrgetter('group'))
241         return [
242             (group, list(tags_))
243             for group, tags_ in grouped_tags
244         ]
245 
246     def get_brands(self, products: List[Product]) -> Dict[Product, 'Tag']:
247         brand_tags = (
248             self.filter(group__name=settings.BRAND_TAG_GROUP_NAME)
249             .prefetch_related('products')
250             .select_related('group')
251         )
252 
253         return {
254             product: brand
255             for brand in brand_tags for product in products
256             if product in brand.products.all()
257         }
258 
259 
260 class TagManager(models.Manager.from_queryset(TagQuerySet)):
261 
262     def get_queryset(self):
263         return (
264             super().get_queryset()
265             .order_by(*settings.TAGS_ORDER)
266         )
267 
268     def get_group_tags_pairs(self):
269         return self.get_queryset().get_group_tags_pairs()
270 
271     def filter_by_products(self, products):
272         return self.get_queryset().filter_by_products(products)
273 
274     def get_brands(self, products):
275         """Get a batch of products' brands."""
276         return self.get_queryset().get_brands(products)
277 
278 
279 class Tag(models.Model):
280 
281     # Uncomment it after moving to refarm with rf#162
282     # class Meta:
283     #     unique_together = ('name', 'group')
284 
285     objects = TagManager()
286 
287     uuid = models.UUIDField(default=uuid4, editable=False)
288     name = models.CharField(
289         max_length=100, db_index=True, verbose_name=_('name'))
290     position = models.PositiveSmallIntegerField(
291         default=0, blank=True, db_index=True, verbose_name=_('position'),
292     )
293 
294     # Set it as unique with rf#162
295     slug = models.SlugField(default='')
296 
297     group = models.ForeignKey(
298         TagGroup, on_delete=models.CASCADE, null=True, related_name='tags',
299     )
300 
301     def __str__(self):
302         return self.name
303 
304     def save(self, *args, **kwargs):
305         if not self.slug:
306             # same slugify code used in PageMixin object
307             self.slug = slugify(
308                 unidecode(self.name.replace('.', '-').replace('+', '-'))
309             )
310         doubled_tag_qs = self.__class__.objects.filter(slug=self.slug)
311         if doubled_tag_qs:
312             self.slug = randomize_slug(self.slug)
313         super(Tag, self).save(*args, **kwargs)
314 
315     @staticmethod
316     def parse_url_tags(tags: str) -> list:
317         groups = tags.split(settings.TAGS_URL_DELIMITER)
318         return set(chain.from_iterable(
319             group.split(settings.TAG_GROUPS_URL_DELIMITER) for group in groups
320         ))
321 
322 
323 def serialize_tags(
324     tags: TagQuerySet,
325     field_name: str,
326     type_delimiter: str,
327     group_delimiter: str,
328 ) -> str:
329     group_tags_map = tags.get_group_tags_pairs()
330 
331     _, tags_by_group = zip(*group_tags_map)
332 
333     return group_delimiter.join(
334         type_delimiter.join(getattr(tag, field_name) for tag in tags_list)
335         for tags_list in tags_by_group
336     )
337 
338 
339 def serialize_tags_to_url(tags: TagQuerySet) -> str:
340     return serialize_tags(
341         tags=tags,
342         field_name='slug',
343         type_delimiter=settings.TAGS_URL_DELIMITER,
344         group_delimiter=settings.TAG_GROUPS_URL_DELIMITER
345     )
346 
347 
348 def serialize_tags_to_title(tags: TagQuerySet) -> str:
349     return serialize_tags(
350         tags=tags,
351         field_name='name',
352         type_delimiter=settings.TAGS_TITLE_DELIMITER,
353         group_delimiter=settings.TAG_GROUPS_TITLE_DELIMITER
354     )
355 
356 
357 class ExcludedModelTPageManager(PageManager):
358 
359     def get_queryset(self):
360         return super().get_queryset().exclude(type=Page.MODEL_TYPE)
361 
362 
363 class ExcludedModelTPage(Page):
364 
365     class Meta(Page.Meta):  # Ignore PycodestyleBear (E303)
366         proxy = True
367 
368     objects = ExcludedModelTPageManager()
369
```
--- 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
@@ -119,16 +119,6 @@
     def get_params(self):
         return Tag.objects.filter_by_products([self]).get_group_tags_pairs()
 
-    # @todo #388:30m Move Product.get_siblings method to refarm-site
-    #  And reuse it on STB.
-    def get_siblings(self, offset):
-        return (
-            self.__class__.actives
-            .filter(category=self.category)
-            .prefetch_related('category')
-            .select_related('page')[:offset]
-        )
-
     def get_brand_name(self) -> str:
         brand: Optional['Tag'] = Tag.objects.get_brands([self]).get(self)
         return brand.name if brand else ''

{"golden_diff": "diff --git a/shopelectro/models.py b/shopelectro/models.py\n--- a/shopelectro/models.py\n+++ b/shopelectro/models.py\n@@ -119,16 +119,6 @@\n     def get_params(self):\n         return Tag.objects.filter_by_products([self]).get_group_tags_pairs()\n \n-    # @todo #388:30m Move Product.get_siblings method to refarm-site\n-    #  And reuse it on STB.\n-    def get_siblings(self, offset):\n-        return (\n-            self.__class__.actives\n-            .filter(category=self.category)\n-            .prefetch_related('category')\n-            .select_related('page')[:offset]\n-        )\n-\n     def get_brand_name(self) -> str:\n         brand: Optional['Tag'] = Tag.objects.get_brands([self]).get(self)\n         return brand.name if brand else ''\n", "issue": "models.py:122-123: Move Product.get_siblings method to...\nThe puzzle `388-509fa51e` from #388 has to be resolved:\n\nhttps://github.com/fidals/shopelectro/blob/5d0c5e97622abce3a922eda62c1f38810d7da5f8/shopelectro/models.py#L122-L123\n\nThe puzzle was created by duker33 on 12-Aug-18. \n\nEstimate: 30 minutes,     \n\nIf you have any technical questions, don't ask me, submit new tickets instead. The task will be \"done\" when the problem is fixed and the text of the puzzle is _removed_ from the source code. Here is more about [PDD](http://www.yegor256.com/2009/03/04/pdd.html) and [about me](http://www.yegor256.com/2017/04/05/pdd-in-action.html).\n", "before_files": [{"content": "import random\nimport string\nfrom itertools import chain, groupby\nfrom operator import attrgetter\nfrom typing import Dict, List, Optional, 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    ProductActiveManager,\n    ProductManager,\n)\nfrom ecommerce.models import Order as ecOrder\nfrom pages.models import CustomPage, ModelPage, Page, SyncPageMixin, PageManager\n\n\ndef randomize_slug(slug: str) -> str:\n    slug_hash = ''.join(\n        random.choices(string.ascii_lowercase, k=settings.SLUG_HASH_SIZE)\n    )\n    return f'{slug}_{slug_hash}'\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    # That's why we are needed to explicitly add objects manager here\n    # because of Django special managers behaviour.\n    # Se se#480 for details.\n    objects = ProductManager()\n    actives = ProductActiveManager()\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    def get_params(self):\n        return Tag.objects.filter_by_products([self]).get_group_tags_pairs()\n\n    # @todo #388:30m Move Product.get_siblings method to refarm-site\n    #  And reuse it on STB.\n    def get_siblings(self, offset):\n        return (\n            self.__class__.actives\n            .filter(category=self.category)\n            .prefetch_related('category')\n            .select_related('page')[:offset]\n        )\n\n    def get_brand_name(self) -> str:\n        brand: Optional['Tag'] = Tag.objects.get_brands([self]).get(self)\n        return brand.name if brand else ''\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 filter_by_products(self, products: List[Product]):\n        ordering = settings.TAGS_ORDER\n        distinct = [order.lstrip('-') for order in ordering]\n\n        return (\n            self\n            .filter(products__in=products)\n            .order_by(*ordering)\n            .distinct(*distinct, 'id')\n        )\n\n    def get_group_tags_pairs(self) -> List[Tuple[TagGroup, List['Tag']]]:\n        grouped_tags = groupby(self.prefetch_related('group'), key=attrgetter('group'))\n        return [\n            (group, list(tags_))\n            for group, tags_ in grouped_tags\n        ]\n\n    def get_brands(self, products: List[Product]) -> Dict[Product, 'Tag']:\n        brand_tags = (\n            self.filter(group__name=settings.BRAND_TAG_GROUP_NAME)\n            .prefetch_related('products')\n            .select_related('group')\n        )\n\n        return {\n            product: brand\n            for brand in brand_tags for product in products\n            if product in brand.products.all()\n        }\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    def filter_by_products(self, products):\n        return self.get_queryset().filter_by_products(products)\n\n    def get_brands(self, products):\n        \"\"\"Get a batch of products' brands.\"\"\"\n        return self.get_queryset().get_brands(products)\n\n\nclass Tag(models.Model):\n\n    # Uncomment it after moving to refarm with rf#162\n    # class Meta:\n    #     unique_together = ('name', 'group')\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    # Set it as unique with rf#162\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        doubled_tag_qs = self.__class__.objects.filter(slug=self.slug)\n        if doubled_tag_qs:\n            self.slug = randomize_slug(self.slug)\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(PageManager):\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"}], "after_files": [{"content": "import random\nimport string\nfrom itertools import chain, groupby\nfrom operator import attrgetter\nfrom typing import Dict, List, Optional, 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    ProductActiveManager,\n    ProductManager,\n)\nfrom ecommerce.models import Order as ecOrder\nfrom pages.models import CustomPage, ModelPage, Page, SyncPageMixin, PageManager\n\n\ndef randomize_slug(slug: str) -> str:\n    slug_hash = ''.join(\n        random.choices(string.ascii_lowercase, k=settings.SLUG_HASH_SIZE)\n    )\n    return f'{slug}_{slug_hash}'\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    # That's why we are needed to explicitly add objects manager here\n    # because of Django special managers behaviour.\n    # Se se#480 for details.\n    objects = ProductManager()\n    actives = ProductActiveManager()\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    def get_params(self):\n        return Tag.objects.filter_by_products([self]).get_group_tags_pairs()\n\n    def get_brand_name(self) -> str:\n        brand: Optional['Tag'] = Tag.objects.get_brands([self]).get(self)\n        return brand.name if brand else ''\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 filter_by_products(self, products: List[Product]):\n        ordering = settings.TAGS_ORDER\n        distinct = [order.lstrip('-') for order in ordering]\n\n        return (\n            self\n            .filter(products__in=products)\n            .order_by(*ordering)\n            .distinct(*distinct, 'id')\n        )\n\n    def get_group_tags_pairs(self) -> List[Tuple[TagGroup, List['Tag']]]:\n        grouped_tags = groupby(self.prefetch_related('group'), key=attrgetter('group'))\n        return [\n            (group, list(tags_))\n            for group, tags_ in grouped_tags\n        ]\n\n    def get_brands(self, products: List[Product]) -> Dict[Product, 'Tag']:\n        brand_tags = (\n            self.filter(group__name=settings.BRAND_TAG_GROUP_NAME)\n            .prefetch_related('products')\n            .select_related('group')\n        )\n\n        return {\n            product: brand\n            for brand in brand_tags for product in products\n            if product in brand.products.all()\n        }\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    def filter_by_products(self, products):\n        return self.get_queryset().filter_by_products(products)\n\n    def get_brands(self, products):\n        \"\"\"Get a batch of products' brands.\"\"\"\n        return self.get_queryset().get_brands(products)\n\n\nclass Tag(models.Model):\n\n    # Uncomment it after moving to refarm with rf#162\n    # class Meta:\n    #     unique_together = ('name', 'group')\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    # Set it as unique with rf#162\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        doubled_tag_qs = self.__class__.objects.filter(slug=self.slug)\n        if doubled_tag_qs:\n            self.slug = randomize_slug(self.slug)\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(PageManager):\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"}]}