Repository: fidals/shopelectro

Issue:
Integrate new Context classes
You can get these classes from https://github.com/fidals/refarm-site/issues/183 


Discussion:
We will be capable to do it after https://github.com/fidals/refarm-site/pull/238

Resolving patch:
diff --git a/shopelectro/context.py b/shopelectro/context.py
--- a/shopelectro/context.py
+++ b/shopelectro/context.py
@@ -4,6 +4,8 @@
 from catalog.models import ProductQuerySet
 from images.models import Image
 
+# @todo #683:60m Remove the ProductImages in favor of catalog.newcontext.ProductImages
+
 
 class ProductImages(AbstractProductsListContext):
 
diff --git a/shopelectro/views/catalog.py b/shopelectro/views/catalog.py
--- a/shopelectro/views/catalog.py
+++ b/shopelectro/views/catalog.py
@@ -1,14 +1,15 @@
 import typing
+from functools import partial
 
 from django import http
 from django.conf import settings
-from django.core.paginator import Paginator
 from django.shortcuts import render, get_object_or_404
 from django.views.decorators.http import require_POST
 from django_user_agents.utils import get_user_agent
 
-from catalog import context
+from catalog import newcontext
 from catalog.views import catalog
+from images.models import Image
 from pages import views as pages_views
 
 from shopelectro import context as se_context
@@ -25,6 +26,64 @@ def get_products_count(request):
     return PRODUCTS_ON_PAGE_MOB if mobile_view else PRODUCTS_ON_PAGE_PC
 
 
+def get_view_type(request):
+    view_type = request.session.get('view_type', 'tile')
+    assert view_type in ['list', 'tile']
+    return view_type
+
+
+# @todo #683:60m Create context class(es) for catalog page representation.
+#  Created class(es) will compose the context classes for catalog views.
+#  Remove get_catalog_context in favor of created class(es).
+def get_catalog_context(request, category, raw_tags, page_number, per_page, sorting_index):
+    all_tags = newcontext.Tags(models.Tag.objects.all())
+    selected_tags = newcontext.tags.ParsedTags(
+        tags=all_tags,
+        raw_tags=raw_tags,
+    )
+    if raw_tags:
+        selected_tags = newcontext.tags.Checked404Tags(selected_tags)
+
+    # @todo #683:30m Remove *Tags and *Products suffixes from catalog.newcontext classes.
+    #  Rename Checked404Tags to ExistingOr404.
+    products = newcontext.products.OrderedProducts(
+        sorting_index=sorting_index,
+        products=newcontext.products.TaggedProducts(
+            tags=selected_tags,
+            products=newcontext.products.ProductsByCategory(
+                category=category,
+                products=newcontext.products.ActiveProducts(
+                    newcontext.Products(
+                        models.Product.objects.all(),
+                    ),
+                ),
+            ),
+        ),
+    )
+
+    paginated_products = newcontext.products.PaginatedProducts(
+        products=products,
+        url=request.path,
+        page_number=page_number,
+        per_page=per_page,
+    )
+
+    images = newcontext.products.ProductImages(paginated_products, Image.objects.all())
+    brands = newcontext.products.ProductBrands(paginated_products, all_tags)
+    grouped_tags = newcontext.tags.GroupedTags(all_tags)
+
+    contexts = newcontext.Contexts(paginated_products, images, brands, grouped_tags)
+    # @todo #683:60m Create context class(es).
+    #  Remove optional_context from get_catalog_context and
+    #  patching of page.get_template_render_context in favor of created class(es).
+    optional_context = {
+        'skip_canonical': selected_tags.qs().exists(),
+        'total_products': products.qs().count(),
+        'selected_tags': selected_tags.qs(),
+    }
+    return contexts, optional_context
+
+
 # CATALOG VIEWS
 class CategoryTree(catalog.CategoryTree):
     category_model = models.Category
@@ -171,24 +230,41 @@ class CategoryPage(catalog.CategoryPage):
 
     def get_context_data(self, **kwargs):
         """Add sorting options and view_types in context."""
-        context_ = (
-            context.Category(
-                url_kwargs=self.kwargs,
-                request=self.request,
-                page=self.object,
-                products=models.Product.objects.all(),
-                product_pages=models.ProductPage.objects.all(),  # Ignore CPDBear
-            )
-            | context.TaggedCategory(tags=models.Tag.objects.all())
-            | context.SortingCategory()  # requires TaggedCategory
-            | context.PaginationCategory()  # requires SortingCategory
-            | context.ProductBrands()  # requires TaggedCategory
-            | se_context.ProductImages()
-            | context.DBTemplate()  # requires TaggedCategory
+        sorting_index = int(self.kwargs.get('sorting', 0))
+
+        contexts, optional_context = get_catalog_context(
+            request=self.request,
+            category=self.object.model,
+            raw_tags=self.kwargs.get('tags'),
+            page_number=int(self.request.GET.get('page', 1)),
+            per_page=int(self.request.GET.get(
+                'step', get_products_count(self.request),
+            )),
+            sorting_index=sorting_index,
         )
+
+        selected_tags = optional_context['selected_tags']
+        if selected_tags:
+            def template_context(page, tag_titles, tags):
+                return {
+                    'page': page,
+                    'tag_titles': tag_titles,
+                    'tags': tags,
+                }
+
+            page = self.object
+            page.get_template_render_context = partial(
+                template_context, page, selected_tags.as_title(), selected_tags
+            )
+
         return {
             **super().get_context_data(**kwargs),
-            **context_.get_context_data(),
+            **contexts.context(),
+            **optional_context,
+            'view_type': get_view_type(self.request),
+            'sorting_options': settings.CATEGORY_SORTING_OPTIONS.values(),
+            'limits': settings.CATEGORY_STEP_MULTIPLIERS,
+            'sort': sorting_index,
         }
 
 
@@ -208,83 +284,23 @@ def load_more(request, slug, offset=0, limit=0, sorting=0, tags=None):
         return http.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 http.HttpResponseBadRequest(
-            'The limit number is wrong. List of available numbers:'
-            f' {", ".join(map(str, settings.CATEGORY_STEP_MULTIPLIERS))}'
-        )
     # increment page number because:
     # 11 // 12 = 0, 0 // 12 = 0 but it should be the first page
     # 12 // 12 = 1, 23 // 12 = 1, but it should be the second page
     page_number = (offset // products_on_page) + 1
     category = get_object_or_404(models.CategoryPage, slug=slug).model
-    sorting_option = context.SortingOption(index=int(sorting))
-
-    all_products = (
-        models.Product.objects
-        .active()
-        .get_category_descendants(category)
-        .order_by(sorting_option.directed_field)
+    sorting_index = int(sorting)
+
+    contexts, _ = get_catalog_context(
+        request=request,
+        category=category,
+        raw_tags=tags,
+        page_number=page_number,
+        per_page=products_on_page,
+        sorting_index=sorting_index,
     )
 
-    if tags:
-        tag_entities = models.Tag.objects.filter(
-            slug__in=models.Tag.parse_url_tags(tags)
-        )
-
-        all_products = (
-            all_products
-            .filter(tags__in=tag_entities)
-            # Use distinct because filtering by QuerySet tags,
-            # that related with products by many-to-many relation.
-            # Add `id` because `sorting_option.directed_field` maybe not uniq.
-            .distinct('id', sorting_option.directed_field)
-        )
-
-    paginated = context.PaginatorLinks(
-        page_number,
-        request.path,
-        Paginator(all_products, products_on_page)
-    )
-    paginated_page = paginated.page()
-    products = paginated_page.object_list
-    view = request.session.get('view_type', 'tile')
-
-    # products list has been sliced.
-    # So it can't be context's arg.
-    # We should create not sliced list with the same data.
-    products_to_filter = (
-        models.Product.objects
-        .filter(id__in=[p.id for p in products])
-        .order_by(sorting_option.directed_field)
-    )
-    data_from_context = (
-        context.Category(
-            url_kwargs={},
-            request=request,
-            page=category.page,
-            products=products_to_filter,
-            product_pages=models.ProductPage.objects.filter(
-                shopelectro_product__in=products_to_filter
-            ),
-        )
-        | context.TaggedCategory(tags=models.Tag.objects.all())
-        | context.SortingCategory()  # requires TaggedCategory
-        | context.PaginationCategory()  # requires SortingCategory
-        | context.ProductBrands()  # requires TaggedCategory
-        | se_context.ProductImages()
-        | context.DBTemplate()  # requires TaggedCategory
-    ).get_context_data()
-
-    return render(request, 'catalog/category_products.html', {
-        'products': products,
-        'product_images': data_from_context['product_images'],
-        'product_brands': data_from_context['product_brands'],
-        'paginated': paginated,
-        'paginated_page': paginated_page,
-        'view_type': view,
-        'prods': products_on_page,
-    })
+    return render(request, 'catalog/category_products.html', contexts.context())
 
 
 @require_POST


Test patch:
diff --git a/shopelectro/tests/tests_views.py b/shopelectro/tests/tests_views.py
--- a/shopelectro/tests/tests_views.py
+++ b/shopelectro/tests/tests_views.py
@@ -31,7 +31,7 @@
 
 
 def get_page_number(response):
-    return response.context['paginated_page'].number
+    return response.context['paginated']['page'].number
 
 
 def json_to_dict(response: HttpResponse) -> dict:
@@ -236,7 +236,7 @@ def test_pagination_step(self):
         """Category page contains `pagination_step` count of products in list."""
         pagination_step = 25
         response = self.get_category_page(query_string={'step': pagination_step})
-        self.assertEqual(len(response.context['products']), pagination_step)
+        self.assertEqual(len(response.context['paginated']['page'].object_list), pagination_step)
 
     def test_pagination_404(self):
         """Category page returns 404 for a nonexistent page number."""