Repository: fidals/shopelectro

Issue:
category_products.html:5: Implement pagination buttons...
The puzzle `302-bdb9bbef` from #302 has to be resolved:

https://github.com/fidals/shopelectro/blob/96e14747c3d7da9dd7db50f01bbb987147e4e2cb/templates/catalog/category_products.html#L5-L5

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

Estimate: 60 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.
@duker33 , should hidden buttons either reload (by link) or update (by ajax) the state of a category page?
@artemiy312 , page full updating will be enough. But choose the most convenient way for realization.

The main requirement here is to load right products set by direct link `/my_prod/?page_num=3`

Resolving patch:
diff --git a/shopelectro/views/catalog.py b/shopelectro/views/catalog.py
--- a/shopelectro/views/catalog.py
+++ b/shopelectro/views/catalog.py
@@ -1,7 +1,7 @@
 from functools import partial
 
 from django.conf import settings
-from django.core.paginator import Paginator
+from django.core.paginator import Paginator, InvalidPage
 from django.http import Http404, HttpResponse, HttpResponseBadRequest, HttpResponseForbidden
 from django.shortcuts import render, get_object_or_404
 from django.views.decorators.http import require_POST
@@ -25,6 +25,13 @@ def get_products_count(request):
     return PRODUCTS_ON_PAGE_MOB if mobile_view else PRODUCTS_ON_PAGE_PC
 
 
+def get_paginated_page_or_404(objects, per_page, page_number):
+    try:
+        return Paginator(objects, per_page).page(page_number)
+    except InvalidPage:
+        raise Http404('Page does not exist')
+
+
 # CATALOG VIEWS
 class CategoryTree(catalog.CategoryTree):
     category_model = models.Category
@@ -170,7 +177,7 @@ def template_context(page, tag_titles, tags):
         page.get_template_render_context = partial(
             template_context, page, tag_titles, tags)
 
-        paginated_page = Paginator(all_products, products_on_page).page(page_number)
+        paginated_page = get_paginated_page_or_404(all_products, products_on_page, page_number)
         total_products = all_products.count()
         products = paginated_page.object_list
         if not products:
@@ -240,7 +247,7 @@ def load_more(request, category_slug, offset=0, limit=0, sorting=0, tags=None):
             .distinct(sorting_option.lstrip('-'))
         )
 
-    paginated_page = Paginator(all_products, products_on_page).page(page_number)
+    paginated_page = get_paginated_page_or_404(all_products, products_on_page, page_number)
     products = paginated_page.object_list
     view = request.session.get('view_type', 'tile')
 


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
@@ -15,7 +15,7 @@
 from django.conf import settings
 from django.db.models import Q
 from django.http import HttpResponse
-from django.test import TestCase
+from django.test import override_settings, TestCase
 from django.urls import reverse
 from django.utils.translation import ugettext as _
 
@@ -216,6 +216,48 @@ def test_pagination_step(self):
         response = self.get_category_page(query_string={'step': pagination_step})
         self.assertEqual(len(response.context['product_image_pairs']), pagination_step)
 
+    def test_pagination_404(self):
+        """Category page returns 404 for a nonexistent page number."""
+        self.assertEqual(
+            self.get_category_page(query_string={'page': 1000}).status_code,
+            404,
+        )
+
+    def assert_pagination_links(self, next_, prev, page_number):
+        self.assertEqual(
+            get_page_number(self.client.get(next_['href'])),
+            page_number + 1,
+        )
+        self.assertEqual(
+            get_page_number(self.client.get(prev['href'])),
+            page_number - 1,
+        )
+
+    @override_settings(DEBUG=True, INTERNAL_IPS=[])
+    def test_pagination_buttons(self):
+        """Each button forward to a previous and a next pagination pages."""
+        page_number = 3
+        prev, next_ = BeautifulSoup(
+            self.get_category_page(query_string={'page': page_number}).content.decode('utf-8'),
+            'html.parser'
+        ).find(class_='catalog-pagination').find_all('a')
+
+        self.assert_pagination_links(next_, prev, page_number)
+
+    def test_pagination_canonical(self):
+        """Canonical links forward to a previous and a next pagination pages."""
+        page_number = 3
+        soup = BeautifulSoup(
+            self.get_category_page(query_string={'page': page_number}).content.decode('utf-8'),
+            'html.parser'
+        )
+
+        self.assert_pagination_links(
+            next_=soup.find('link', attrs={'rel': 'next'}),
+            prev=soup.find('link', attrs={'rel': 'prev'}),
+            page_number=page_number,
+        )
+
 
 class LoadMore(TestCase):