Repository: fidals/shopelectro

Issue:
catalog.py:57-60: Render 404, that is recommending...
The puzzle `273-9b2b9d3f` from #273 has to be resolved:

https://github.com/fidals/shopelectro/blob/36a9dd50f8a4e0685485d57121cf47019dd5646a/shopelectro/views/catalog.py#L57-L60

The puzzle was created by Artemiy on 24-Jul-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).


Discussion:
@artemiyds2 @duker33 please pay attention to this new issue.

Resolving patch:
diff --git a/shopelectro/management/commands/_update_catalog/update_products.py b/shopelectro/management/commands/_update_catalog/update_products.py
--- a/shopelectro/management/commands/_update_catalog/update_products.py
+++ b/shopelectro/management/commands/_update_catalog/update_products.py
@@ -237,11 +237,11 @@ def report(recipients=None, message=None):
 @transaction.atomic
 def delete(data: Dict[UUID, Data]):
     uuids = list(data)
-    pages_to_deactive = ProductPage.objects.exclude(
+    pages_to_deactivate = ProductPage.objects.exclude(
         shopelectro_product__uuid__in=uuids)
-    pages_to_deactive.update(is_active=False)
-    deactive_count = pages_to_deactive.count()
-    logger.info(f'{deactive_count} products and {deactive_count} pages were deleted.')
+    pages_to_deactivate.update(is_active=False)
+    deactivated_count = pages_to_deactivate.count()
+    logger.info(f'{deactivated_count} products and {deactivated_count} pages were deleted.')
 
 
 @transaction.atomic
diff --git a/shopelectro/views/catalog.py b/shopelectro/views/catalog.py
--- a/shopelectro/views/catalog.py
+++ b/shopelectro/views/catalog.py
@@ -1,3 +1,4 @@
+import typing
 from functools import partial
 
 from django import http
@@ -54,16 +55,22 @@ def get(self, request, *args, **kwargs):
         try:
             self.object = self.get_object()
         except http.Http404 as error404:
-            # @todo #273 Render 404, that is recommending products for a deleted product.
-            #  1. Find a product with page__is_active=False
-            #  2. If the product exists then render 404 with products of its category
-            #  See the parent issue for details.
-            raise error404
+            response_404 = self.render_siblings_on_404(request, **kwargs)
+            if response_404:
+                return response_404
+            else:
+                raise error404
+
         context = self.get_context_data(object=self.object)
         return self.render_to_response(context)
 
     def get_context_data(self, **kwargs):
         context = super(ProductPage, self).get_context_data(**kwargs)
+        product = self.object
+        if not product.page.is_active:
+            # this context required to render 404 page
+            # with it's own logic
+            return context
 
         group_tags_pairs = (
             models.Tag.objects
@@ -77,6 +84,25 @@ def get_context_data(self, **kwargs):
             'group_tags_pairs': group_tags_pairs
         }
 
+    def render_siblings_on_404(
+        self, request, **url_kwargs
+    ) -> typing.Union[http.Http404, None]:
+        """Try to render removed product's siblings on it's 404 page."""
+        inactive_product = models.Product.objects.filter(
+            **{self.slug_field: url_kwargs['product_vendor_code']},
+            category__isnull=False,
+            page__is_active=False
+        ).first()
+        if inactive_product:
+            related_products = models.Product.objects.filter(
+                category=inactive_product.category,
+                page__is_active=True
+            )[:10]
+            self.object = inactive_product
+            context = self.get_context_data(object=inactive_product, **url_kwargs)
+            context.update(related_products=related_products)
+            return render(request, 'catalog/product_404.html', context, status=404)
+
 
 # SHOPELECTRO-SPECIFIC VIEWS
 @set_csrf_cookie


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
@@ -436,6 +436,20 @@ def test_orphan_product(self):
         response = self.client.get(product.url)
         self.assertEqual(response.status_code, 404)
 
+    def test_related_products_on_404(self):
+        """404 page of sometimes removed product should contain product's siblings."""
+        product = Product.objects.first()
+        product.page.is_active = False
+        product.save()  # saves product.page too
+
+        response = self.client.get(product.url)
+        # 404 page should show 10 siblings. We'll check the last one
+        sibling_product = product.category.products.all()[9]
+        self.assertEqual(response.status_code, 404)
+        self.assertTrue(
+            sibling_product.name in str(response.content)
+        )
+
 
 class ProductPageSchema(TestCase):