We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
docker-compose.yml:93: Resurrect coala eslint.
The puzzle `322-dbccf9a1` from #322 has to be resolved:

https://github.com/fidals/shopelectro/blob/c827fa74e382579bf43fa368be3549cd108f6050/docker/docker-compose.yml#L93-L93

The puzzle was created by Artemiy on 08-Jun-18. 

Estimate: 0 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/views/catalog.py`
Content:
```
1 from functools import partial
2 
3 from django.conf import settings
4 from django.core.paginator import Paginator
5 from django.http import Http404, HttpResponse, HttpResponseBadRequest, HttpResponseForbidden
6 from django.shortcuts import render, get_object_or_404
7 from django.views.decorators.http import require_POST
8 from django_user_agents.utils import get_user_agent
9 
10 from catalog.views import catalog
11 from images.models import Image
12 from pages import views as pages_views
13 
14 from shopelectro import config
15 from shopelectro import models
16 from shopelectro.views.helpers import set_csrf_cookie
17 
18 PRODUCTS_ON_PAGE_PC = 48
19 PRODUCTS_ON_PAGE_MOB = 12
20 
21 
22 def get_products_count(request):
23     """Calculate max products list size from request. List size depends on device type."""
24     mobile_view = get_user_agent(request).is_mobile
25     return PRODUCTS_ON_PAGE_MOB if mobile_view else PRODUCTS_ON_PAGE_PC
26 
27 
28 # CATALOG VIEWS
29 class CategoryTree(catalog.CategoryTree):
30     category_model = models.Category
31 
32 
33 @set_csrf_cookie
34 class ProductPage(catalog.ProductPage):
35     pk_url_kwarg = None
36     slug_url_kwarg = 'product_vendor_code'
37     slug_field = 'vendor_code'
38 
39     queryset = (
40         models.Product.objects
41         .filter(category__isnull=False)
42         .prefetch_related('product_feedbacks', 'page__images')
43         .select_related('page')
44     )
45 
46     def get_context_data(self, **kwargs):
47         context = super(ProductPage, self).get_context_data(**kwargs)
48 
49         group_tags_pairs = (
50             models.Tag.objects
51             .filter(products=self.object)
52             .get_group_tags_pairs()
53         )
54 
55         return {
56             **context,
57             'price_bounds': config.PRICE_BOUNDS,
58             'group_tags_pairs': group_tags_pairs
59         }
60 
61 
62 # SHOPELECTRO-SPECIFIC VIEWS
63 @set_csrf_cookie
64 class IndexPage(pages_views.CustomPageView):
65 
66     def get_context_data(self, **kwargs):
67         """Extended method. Add product's images to context."""
68         context = super(IndexPage, self).get_context_data(**kwargs)
69         mobile_view = get_user_agent(self.request).is_mobile
70 
71         top_products = (
72             models.Product.objects
73             .filter(id__in=settings.TOP_PRODUCTS)
74             .prefetch_related('category')
75             .select_related('page')
76         )
77 
78         images = Image.objects.get_main_images_by_pages(
79             models.ProductPage.objects.filter(
80                 shopelectro_product__in=top_products
81             )
82         )
83 
84         categories = models.Category.objects.get_root_categories_by_products(
85             top_products)
86 
87         prepared_top_products = []
88         if not mobile_view:
89             prepared_top_products = [
90                 (product, images.get(product.page), categories.get(product))
91                 for product in top_products
92             ]
93 
94         return {
95             **context,
96             'category_tile': config.MAIN_PAGE_TILE,
97             'prepared_top_products': prepared_top_products,
98         }
99 
100 
101 def merge_products_and_images(products):
102     images = Image.objects.get_main_images_by_pages(
103         models.ProductPage.objects.filter(shopelectro_product__in=products)
104     )
105 
106     return [
107         (product, images.get(product.page))
108         for product in products
109     ]
110 
111 
112 @set_csrf_cookie
113 class CategoryPage(catalog.CategoryPage):
114 
115     def get_context_data(self, **kwargs):
116         """Add sorting options and view_types in context."""
117         context = super().get_context_data(**kwargs)
118         products_on_page = int(self.request.GET.get(
119             'step', get_products_count(self.request),
120         ))
121         page_number = int(self.request.GET.get('page', 1))
122         view_type = self.request.session.get('view_type', 'tile')
123         sorting = int(self.kwargs.get('sorting', 0))
124         sorting_option = config.category_sorting(sorting)
125         category = context['category']
126         if (
127             page_number < 1 or
128             products_on_page not in settings.CATEGORY_STEP_MULTIPLIERS
129         ):
130             raise Http404('Page does not exist.')
131 
132         all_products = (
133             models.Product.objects
134             .prefetch_related('page__images')
135             .select_related('page')
136             .get_by_category(category, ordering=(sorting_option, ))
137         )
138 
139         group_tags_pairs = (
140             models.Tag.objects
141             .filter(products__in=all_products)
142             .get_group_tags_pairs()
143         )
144 
145         tags = self.kwargs.get('tags')
146 
147         tag_titles = ''
148         if tags:
149             slugs = models.Tag.parse_url_tags(tags)
150             tags = models.Tag.objects.filter(slug__in=slugs)
151 
152             all_products = (
153                 all_products
154                 .filter(tags__in=tags)
155                 # Use distinct because filtering by QuerySet tags,
156                 # that related with products by many-to-many relation.
157                 .distinct(sorting_option.lstrip('-'))
158             )
159 
160             tag_titles = models.serialize_tags_to_title(tags)
161 
162         def template_context(page, tag_titles, tags):
163             return {
164                 'page': page,
165                 'tag_titles': tag_titles,
166                 'tags': tags,
167             }
168 
169         page = context['page']
170         page.get_template_render_context = partial(
171             template_context, page, tag_titles, tags)
172 
173         paginated_page = Paginator(all_products, products_on_page).page(page_number)
174         total_products = all_products.count()
175         products = paginated_page.object_list
176         if not products:
177             raise Http404('Page without products does not exist.')
178 
179         return {
180             **context,
181             'product_image_pairs': merge_products_and_images(products),
182             'group_tags_pairs': group_tags_pairs,
183             'total_products': total_products,
184             'products_count': (page_number - 1) * products_on_page + products.count(),
185             'paginated_page': paginated_page,
186             'sorting_options': config.category_sorting(),
187             'limits': settings.CATEGORY_STEP_MULTIPLIERS,
188             'sort': sorting,
189             'tags': tags,
190             'view_type': view_type,
191             'skip_canonical': bool(tags),
192         }
193 
194 
195 def load_more(request, category_slug, offset=0, limit=0, sorting=0, tags=None):
196     """
197     Load more products of a given category.
198 
199     :param sorting: preferred sorting index from CATEGORY_SORTING tuple
200     :param request: HttpRequest object
201     :param category_slug: Slug for a given category
202     :param offset: used for slicing QuerySet.
203     :return: products list in html format
204     """
205     products_on_page = limit or get_products_count(request)
206     offset = int(offset)
207     if offset < 0:
208         return HttpResponseBadRequest('The offset is wrong. An offset should be greater than or equal to 0.')
209     if products_on_page not in settings.CATEGORY_STEP_MULTIPLIERS:
210         return HttpResponseBadRequest(
211             'The limit number is wrong. List of available numbers:'
212             f' {", ".join(map(str, settings.CATEGORY_STEP_MULTIPLIERS))}'
213         )
214     # increment page number because:
215     # 11 // 12 = 0, 0 // 12 = 0 but it should be the first page
216     # 12 // 12 = 1, 23 // 12 = 1, but it should be the second page
217     page_number = (offset // products_on_page) + 1
218     category = get_object_or_404(models.CategoryPage, slug=category_slug).model
219     sorting_option = config.category_sorting(int(sorting))
220 
221     all_products = (
222         models.Product.objects
223         .prefetch_related('page__images')
224         .select_related('page')
225         .get_by_category(category, ordering=(sorting_option,))
226     )
227 
228     if tags:
229         tag_entities = models.Tag.objects.filter(
230             slug__in=models.Tag.parse_url_tags(tags)
231         )
232 
233         all_products = (
234             all_products
235             .filter(tags__in=tag_entities)
236             # Use distinct because filtering by QuerySet tags,
237             # that related with products by many-to-many relation.
238             .distinct(sorting_option.lstrip('-'))
239         )
240 
241     paginated_page = Paginator(all_products, products_on_page).page(page_number)
242     products = paginated_page.object_list
243     view = request.session.get('view_type', 'tile')
244 
245     return render(request, 'catalog/category_products.html', {
246         'product_image_pairs': merge_products_and_images(products),
247         'paginated_page': paginated_page,
248         'view_type': view,
249         'prods': products_on_page,
250     })
251 
252 
253 @require_POST
254 def save_feedback(request):
255     def get_keys_from_post(*args):
256         return {arg: request.POST.get(arg, '') for arg in args}
257 
258     product_id = request.POST.get('id')
259     product = models.Product.objects.filter(id=product_id).first()
260     if not (product_id and product):
261         return HttpResponse(status=422)
262 
263     fields = ['rating', 'name', 'dignities', 'limitations', 'general']
264     feedback_data = get_keys_from_post(*fields)
265 
266     models.ProductFeedback.objects.create(product=product, **feedback_data)
267     return HttpResponse('ok')
268 
269 
270 @require_POST
271 def delete_feedback(request):
272     if not request.user.is_authenticated:
273         return HttpResponseForbidden('Not today, sly guy...')
274 
275     feedback_id = request.POST.get('id')
276     feedback = models.ProductFeedback.objects.filter(id=feedback_id).first()
277     if not (feedback_id and feedback):
278         return HttpResponse(status=422)
279 
280     feedback.delete()
281     return HttpResponse('Feedback with id={} was deleted.'.format(feedback_id))
282 
283 
284 class ProductsWithoutImages(catalog.ProductsWithoutImages):
285     model = models.Product
286 
287 
288 class ProductsWithoutText(catalog.ProductsWithoutText):
289     model = models.Product
290
```
--- 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/views/catalog.py b/shopelectro/views/catalog.py
--- a/shopelectro/views/catalog.py
+++ b/shopelectro/views/catalog.py
@@ -205,7 +205,9 @@
     products_on_page = limit or get_products_count(request)
     offset = int(offset)
     if offset < 0:
-        return HttpResponseBadRequest('The offset is wrong. An offset should be greater than or equal to 0.')
+        return HttpResponseBadRequest(
+            'The offset is wrong. An offset should be greater than or equal to 0.'
+        )
     if products_on_page not in settings.CATEGORY_STEP_MULTIPLIERS:
         return HttpResponseBadRequest(
             'The limit number is wrong. List of available numbers:'

{"golden_diff": "diff --git a/shopelectro/views/catalog.py b/shopelectro/views/catalog.py\n--- a/shopelectro/views/catalog.py\n+++ b/shopelectro/views/catalog.py\n@@ -205,7 +205,9 @@\n     products_on_page = limit or get_products_count(request)\n     offset = int(offset)\n     if offset < 0:\n-        return HttpResponseBadRequest('The offset is wrong. An offset should be greater than or equal to 0.')\n+        return HttpResponseBadRequest(\n+            'The offset is wrong. An offset should be greater than or equal to 0.'\n+        )\n     if products_on_page not in settings.CATEGORY_STEP_MULTIPLIERS:\n         return HttpResponseBadRequest(\n             'The limit number is wrong. List of available numbers:'\n", "issue": "docker-compose.yml:93: Resurrect coala eslint.\nThe puzzle `322-dbccf9a1` from #322 has to be resolved:\n\nhttps://github.com/fidals/shopelectro/blob/c827fa74e382579bf43fa368be3549cd108f6050/docker/docker-compose.yml#L93-L93\n\nThe puzzle was created by Artemiy on 08-Jun-18. \n\nEstimate: 0 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": "from functools import partial\n\nfrom django.conf import settings\nfrom django.core.paginator import Paginator\nfrom django.http import Http404, HttpResponse, HttpResponseBadRequest, HttpResponseForbidden\nfrom django.shortcuts import render, get_object_or_404\nfrom django.views.decorators.http import require_POST\nfrom django_user_agents.utils import get_user_agent\n\nfrom catalog.views import catalog\nfrom images.models import Image\nfrom pages import views as pages_views\n\nfrom shopelectro import config\nfrom shopelectro import models\nfrom shopelectro.views.helpers import set_csrf_cookie\n\nPRODUCTS_ON_PAGE_PC = 48\nPRODUCTS_ON_PAGE_MOB = 12\n\n\ndef get_products_count(request):\n    \"\"\"Calculate max products list size from request. List size depends on device type.\"\"\"\n    mobile_view = get_user_agent(request).is_mobile\n    return PRODUCTS_ON_PAGE_MOB if mobile_view else PRODUCTS_ON_PAGE_PC\n\n\n# CATALOG VIEWS\nclass CategoryTree(catalog.CategoryTree):\n    category_model = models.Category\n\n\n@set_csrf_cookie\nclass ProductPage(catalog.ProductPage):\n    pk_url_kwarg = None\n    slug_url_kwarg = 'product_vendor_code'\n    slug_field = 'vendor_code'\n\n    queryset = (\n        models.Product.objects\n        .filter(category__isnull=False)\n        .prefetch_related('product_feedbacks', 'page__images')\n        .select_related('page')\n    )\n\n    def get_context_data(self, **kwargs):\n        context = super(ProductPage, self).get_context_data(**kwargs)\n\n        group_tags_pairs = (\n            models.Tag.objects\n            .filter(products=self.object)\n            .get_group_tags_pairs()\n        )\n\n        return {\n            **context,\n            'price_bounds': config.PRICE_BOUNDS,\n            'group_tags_pairs': group_tags_pairs\n        }\n\n\n# SHOPELECTRO-SPECIFIC VIEWS\n@set_csrf_cookie\nclass IndexPage(pages_views.CustomPageView):\n\n    def get_context_data(self, **kwargs):\n        \"\"\"Extended method. Add product's images to context.\"\"\"\n        context = super(IndexPage, self).get_context_data(**kwargs)\n        mobile_view = get_user_agent(self.request).is_mobile\n\n        top_products = (\n            models.Product.objects\n            .filter(id__in=settings.TOP_PRODUCTS)\n            .prefetch_related('category')\n            .select_related('page')\n        )\n\n        images = Image.objects.get_main_images_by_pages(\n            models.ProductPage.objects.filter(\n                shopelectro_product__in=top_products\n            )\n        )\n\n        categories = models.Category.objects.get_root_categories_by_products(\n            top_products)\n\n        prepared_top_products = []\n        if not mobile_view:\n            prepared_top_products = [\n                (product, images.get(product.page), categories.get(product))\n                for product in top_products\n            ]\n\n        return {\n            **context,\n            'category_tile': config.MAIN_PAGE_TILE,\n            'prepared_top_products': prepared_top_products,\n        }\n\n\ndef merge_products_and_images(products):\n    images = Image.objects.get_main_images_by_pages(\n        models.ProductPage.objects.filter(shopelectro_product__in=products)\n    )\n\n    return [\n        (product, images.get(product.page))\n        for product in products\n    ]\n\n\n@set_csrf_cookie\nclass CategoryPage(catalog.CategoryPage):\n\n    def get_context_data(self, **kwargs):\n        \"\"\"Add sorting options and view_types in context.\"\"\"\n        context = super().get_context_data(**kwargs)\n        products_on_page = int(self.request.GET.get(\n            'step', get_products_count(self.request),\n        ))\n        page_number = int(self.request.GET.get('page', 1))\n        view_type = self.request.session.get('view_type', 'tile')\n        sorting = int(self.kwargs.get('sorting', 0))\n        sorting_option = config.category_sorting(sorting)\n        category = context['category']\n        if (\n            page_number < 1 or\n            products_on_page not in settings.CATEGORY_STEP_MULTIPLIERS\n        ):\n            raise Http404('Page does not exist.')\n\n        all_products = (\n            models.Product.objects\n            .prefetch_related('page__images')\n            .select_related('page')\n            .get_by_category(category, ordering=(sorting_option, ))\n        )\n\n        group_tags_pairs = (\n            models.Tag.objects\n            .filter(products__in=all_products)\n            .get_group_tags_pairs()\n        )\n\n        tags = self.kwargs.get('tags')\n\n        tag_titles = ''\n        if tags:\n            slugs = models.Tag.parse_url_tags(tags)\n            tags = models.Tag.objects.filter(slug__in=slugs)\n\n            all_products = (\n                all_products\n                .filter(tags__in=tags)\n                # Use distinct because filtering by QuerySet tags,\n                # that related with products by many-to-many relation.\n                .distinct(sorting_option.lstrip('-'))\n            )\n\n            tag_titles = models.serialize_tags_to_title(tags)\n\n        def template_context(page, tag_titles, tags):\n            return {\n                'page': page,\n                'tag_titles': tag_titles,\n                'tags': tags,\n            }\n\n        page = context['page']\n        page.get_template_render_context = partial(\n            template_context, page, tag_titles, tags)\n\n        paginated_page = Paginator(all_products, products_on_page).page(page_number)\n        total_products = all_products.count()\n        products = paginated_page.object_list\n        if not products:\n            raise Http404('Page without products does not exist.')\n\n        return {\n            **context,\n            'product_image_pairs': merge_products_and_images(products),\n            'group_tags_pairs': group_tags_pairs,\n            'total_products': total_products,\n            'products_count': (page_number - 1) * products_on_page + products.count(),\n            'paginated_page': paginated_page,\n            'sorting_options': config.category_sorting(),\n            'limits': settings.CATEGORY_STEP_MULTIPLIERS,\n            'sort': sorting,\n            'tags': tags,\n            'view_type': view_type,\n            'skip_canonical': bool(tags),\n        }\n\n\ndef load_more(request, category_slug, offset=0, limit=0, sorting=0, tags=None):\n    \"\"\"\n    Load more products of a given category.\n\n    :param sorting: preferred sorting index from CATEGORY_SORTING tuple\n    :param request: HttpRequest object\n    :param category_slug: Slug for a given category\n    :param offset: used for slicing QuerySet.\n    :return: products list in html format\n    \"\"\"\n    products_on_page = limit or get_products_count(request)\n    offset = int(offset)\n    if offset < 0:\n        return HttpResponseBadRequest('The offset is wrong. An offset should be greater than or equal to 0.')\n    if products_on_page not in settings.CATEGORY_STEP_MULTIPLIERS:\n        return HttpResponseBadRequest(\n            'The limit number is wrong. List of available numbers:'\n            f' {\", \".join(map(str, settings.CATEGORY_STEP_MULTIPLIERS))}'\n        )\n    # increment page number because:\n    # 11 // 12 = 0, 0 // 12 = 0 but it should be the first page\n    # 12 // 12 = 1, 23 // 12 = 1, but it should be the second page\n    page_number = (offset // products_on_page) + 1\n    category = get_object_or_404(models.CategoryPage, slug=category_slug).model\n    sorting_option = config.category_sorting(int(sorting))\n\n    all_products = (\n        models.Product.objects\n        .prefetch_related('page__images')\n        .select_related('page')\n        .get_by_category(category, ordering=(sorting_option,))\n    )\n\n    if tags:\n        tag_entities = models.Tag.objects.filter(\n            slug__in=models.Tag.parse_url_tags(tags)\n        )\n\n        all_products = (\n            all_products\n            .filter(tags__in=tag_entities)\n            # Use distinct because filtering by QuerySet tags,\n            # that related with products by many-to-many relation.\n            .distinct(sorting_option.lstrip('-'))\n        )\n\n    paginated_page = Paginator(all_products, products_on_page).page(page_number)\n    products = paginated_page.object_list\n    view = request.session.get('view_type', 'tile')\n\n    return render(request, 'catalog/category_products.html', {\n        'product_image_pairs': merge_products_and_images(products),\n        'paginated_page': paginated_page,\n        'view_type': view,\n        'prods': products_on_page,\n    })\n\n\n@require_POST\ndef save_feedback(request):\n    def get_keys_from_post(*args):\n        return {arg: request.POST.get(arg, '') for arg in args}\n\n    product_id = request.POST.get('id')\n    product = models.Product.objects.filter(id=product_id).first()\n    if not (product_id and product):\n        return HttpResponse(status=422)\n\n    fields = ['rating', 'name', 'dignities', 'limitations', 'general']\n    feedback_data = get_keys_from_post(*fields)\n\n    models.ProductFeedback.objects.create(product=product, **feedback_data)\n    return HttpResponse('ok')\n\n\n@require_POST\ndef delete_feedback(request):\n    if not request.user.is_authenticated:\n        return HttpResponseForbidden('Not today, sly guy...')\n\n    feedback_id = request.POST.get('id')\n    feedback = models.ProductFeedback.objects.filter(id=feedback_id).first()\n    if not (feedback_id and feedback):\n        return HttpResponse(status=422)\n\n    feedback.delete()\n    return HttpResponse('Feedback with id={} was deleted.'.format(feedback_id))\n\n\nclass ProductsWithoutImages(catalog.ProductsWithoutImages):\n    model = models.Product\n\n\nclass ProductsWithoutText(catalog.ProductsWithoutText):\n    model = models.Product\n", "path": "shopelectro/views/catalog.py"}], "after_files": [{"content": "from functools import partial\n\nfrom django.conf import settings\nfrom django.core.paginator import Paginator\nfrom django.http import Http404, HttpResponse, HttpResponseBadRequest, HttpResponseForbidden\nfrom django.shortcuts import render, get_object_or_404\nfrom django.views.decorators.http import require_POST\nfrom django_user_agents.utils import get_user_agent\n\nfrom catalog.views import catalog\nfrom images.models import Image\nfrom pages import views as pages_views\n\nfrom shopelectro import config\nfrom shopelectro import models\nfrom shopelectro.views.helpers import set_csrf_cookie\n\nPRODUCTS_ON_PAGE_PC = 48\nPRODUCTS_ON_PAGE_MOB = 12\n\n\ndef get_products_count(request):\n    \"\"\"Calculate max products list size from request. List size depends on device type.\"\"\"\n    mobile_view = get_user_agent(request).is_mobile\n    return PRODUCTS_ON_PAGE_MOB if mobile_view else PRODUCTS_ON_PAGE_PC\n\n\n# CATALOG VIEWS\nclass CategoryTree(catalog.CategoryTree):\n    category_model = models.Category\n\n\n@set_csrf_cookie\nclass ProductPage(catalog.ProductPage):\n    pk_url_kwarg = None\n    slug_url_kwarg = 'product_vendor_code'\n    slug_field = 'vendor_code'\n\n    queryset = (\n        models.Product.objects\n        .filter(category__isnull=False)\n        .prefetch_related('product_feedbacks', 'page__images')\n        .select_related('page')\n    )\n\n    def get_context_data(self, **kwargs):\n        context = super(ProductPage, self).get_context_data(**kwargs)\n\n        group_tags_pairs = (\n            models.Tag.objects\n            .filter(products=self.object)\n            .get_group_tags_pairs()\n        )\n\n        return {\n            **context,\n            'price_bounds': config.PRICE_BOUNDS,\n            'group_tags_pairs': group_tags_pairs\n        }\n\n\n# SHOPELECTRO-SPECIFIC VIEWS\n@set_csrf_cookie\nclass IndexPage(pages_views.CustomPageView):\n\n    def get_context_data(self, **kwargs):\n        \"\"\"Extended method. Add product's images to context.\"\"\"\n        context = super(IndexPage, self).get_context_data(**kwargs)\n        mobile_view = get_user_agent(self.request).is_mobile\n\n        top_products = (\n            models.Product.objects\n            .filter(id__in=settings.TOP_PRODUCTS)\n            .prefetch_related('category')\n            .select_related('page')\n        )\n\n        images = Image.objects.get_main_images_by_pages(\n            models.ProductPage.objects.filter(\n                shopelectro_product__in=top_products\n            )\n        )\n\n        categories = models.Category.objects.get_root_categories_by_products(\n            top_products)\n\n        prepared_top_products = []\n        if not mobile_view:\n            prepared_top_products = [\n                (product, images.get(product.page), categories.get(product))\n                for product in top_products\n            ]\n\n        return {\n            **context,\n            'category_tile': config.MAIN_PAGE_TILE,\n            'prepared_top_products': prepared_top_products,\n        }\n\n\ndef merge_products_and_images(products):\n    images = Image.objects.get_main_images_by_pages(\n        models.ProductPage.objects.filter(shopelectro_product__in=products)\n    )\n\n    return [\n        (product, images.get(product.page))\n        for product in products\n    ]\n\n\n@set_csrf_cookie\nclass CategoryPage(catalog.CategoryPage):\n\n    def get_context_data(self, **kwargs):\n        \"\"\"Add sorting options and view_types in context.\"\"\"\n        context = super().get_context_data(**kwargs)\n        products_on_page = int(self.request.GET.get(\n            'step', get_products_count(self.request),\n        ))\n        page_number = int(self.request.GET.get('page', 1))\n        view_type = self.request.session.get('view_type', 'tile')\n        sorting = int(self.kwargs.get('sorting', 0))\n        sorting_option = config.category_sorting(sorting)\n        category = context['category']\n        if (\n            page_number < 1 or\n            products_on_page not in settings.CATEGORY_STEP_MULTIPLIERS\n        ):\n            raise Http404('Page does not exist.')\n\n        all_products = (\n            models.Product.objects\n            .prefetch_related('page__images')\n            .select_related('page')\n            .get_by_category(category, ordering=(sorting_option, ))\n        )\n\n        group_tags_pairs = (\n            models.Tag.objects\n            .filter(products__in=all_products)\n            .get_group_tags_pairs()\n        )\n\n        tags = self.kwargs.get('tags')\n\n        tag_titles = ''\n        if tags:\n            slugs = models.Tag.parse_url_tags(tags)\n            tags = models.Tag.objects.filter(slug__in=slugs)\n\n            all_products = (\n                all_products\n                .filter(tags__in=tags)\n                # Use distinct because filtering by QuerySet tags,\n                # that related with products by many-to-many relation.\n                .distinct(sorting_option.lstrip('-'))\n            )\n\n            tag_titles = models.serialize_tags_to_title(tags)\n\n        def template_context(page, tag_titles, tags):\n            return {\n                'page': page,\n                'tag_titles': tag_titles,\n                'tags': tags,\n            }\n\n        page = context['page']\n        page.get_template_render_context = partial(\n            template_context, page, tag_titles, tags)\n\n        paginated_page = Paginator(all_products, products_on_page).page(page_number)\n        total_products = all_products.count()\n        products = paginated_page.object_list\n        if not products:\n            raise Http404('Page without products does not exist.')\n\n        return {\n            **context,\n            'product_image_pairs': merge_products_and_images(products),\n            'group_tags_pairs': group_tags_pairs,\n            'total_products': total_products,\n            'products_count': (page_number - 1) * products_on_page + products.count(),\n            'paginated_page': paginated_page,\n            'sorting_options': config.category_sorting(),\n            'limits': settings.CATEGORY_STEP_MULTIPLIERS,\n            'sort': sorting,\n            'tags': tags,\n            'view_type': view_type,\n            'skip_canonical': bool(tags),\n        }\n\n\ndef load_more(request, category_slug, offset=0, limit=0, sorting=0, tags=None):\n    \"\"\"\n    Load more products of a given category.\n\n    :param sorting: preferred sorting index from CATEGORY_SORTING tuple\n    :param request: HttpRequest object\n    :param category_slug: Slug for a given category\n    :param offset: used for slicing QuerySet.\n    :return: products list in html format\n    \"\"\"\n    products_on_page = limit or get_products_count(request)\n    offset = int(offset)\n    if offset < 0:\n        return HttpResponseBadRequest(\n            'The offset is wrong. An offset should be greater than or equal to 0.'\n        )\n    if products_on_page not in settings.CATEGORY_STEP_MULTIPLIERS:\n        return HttpResponseBadRequest(\n            'The limit number is wrong. List of available numbers:'\n            f' {\", \".join(map(str, settings.CATEGORY_STEP_MULTIPLIERS))}'\n        )\n    # increment page number because:\n    # 11 // 12 = 0, 0 // 12 = 0 but it should be the first page\n    # 12 // 12 = 1, 23 // 12 = 1, but it should be the second page\n    page_number = (offset // products_on_page) + 1\n    category = get_object_or_404(models.CategoryPage, slug=category_slug).model\n    sorting_option = config.category_sorting(int(sorting))\n\n    all_products = (\n        models.Product.objects\n        .prefetch_related('page__images')\n        .select_related('page')\n        .get_by_category(category, ordering=(sorting_option,))\n    )\n\n    if tags:\n        tag_entities = models.Tag.objects.filter(\n            slug__in=models.Tag.parse_url_tags(tags)\n        )\n\n        all_products = (\n            all_products\n            .filter(tags__in=tag_entities)\n            # Use distinct because filtering by QuerySet tags,\n            # that related with products by many-to-many relation.\n            .distinct(sorting_option.lstrip('-'))\n        )\n\n    paginated_page = Paginator(all_products, products_on_page).page(page_number)\n    products = paginated_page.object_list\n    view = request.session.get('view_type', 'tile')\n\n    return render(request, 'catalog/category_products.html', {\n        'product_image_pairs': merge_products_and_images(products),\n        'paginated_page': paginated_page,\n        'view_type': view,\n        'prods': products_on_page,\n    })\n\n\n@require_POST\ndef save_feedback(request):\n    def get_keys_from_post(*args):\n        return {arg: request.POST.get(arg, '') for arg in args}\n\n    product_id = request.POST.get('id')\n    product = models.Product.objects.filter(id=product_id).first()\n    if not (product_id and product):\n        return HttpResponse(status=422)\n\n    fields = ['rating', 'name', 'dignities', 'limitations', 'general']\n    feedback_data = get_keys_from_post(*fields)\n\n    models.ProductFeedback.objects.create(product=product, **feedback_data)\n    return HttpResponse('ok')\n\n\n@require_POST\ndef delete_feedback(request):\n    if not request.user.is_authenticated:\n        return HttpResponseForbidden('Not today, sly guy...')\n\n    feedback_id = request.POST.get('id')\n    feedback = models.ProductFeedback.objects.filter(id=feedback_id).first()\n    if not (feedback_id and feedback):\n        return HttpResponse(status=422)\n\n    feedback.delete()\n    return HttpResponse('Feedback with id={} was deleted.'.format(feedback_id))\n\n\nclass ProductsWithoutImages(catalog.ProductsWithoutImages):\n    model = models.Product\n\n\nclass ProductsWithoutText(catalog.ProductsWithoutText):\n    model = models.Product\n", "path": "shopelectro/views/catalog.py"}]}