diff --git a/catalog/newcontext/__init__.py b/catalog/newcontext/__init__.py index 2f79330..180bc8b 100644 --- a/catalog/newcontext/__init__.py +++ b/catalog/newcontext/__init__.py @@ -1,3 +1,6 @@ -# @todo #207:120m Implement Page, PaginatedProducts context classes. +# @todo #207:60m Implement Page context class. + +# @todo #207:15m Annotate methods of Context implementations. + from . import products, tags from .context import Context, Contexts, ModelContext, Products, Tags diff --git a/catalog/newcontext/products.py b/catalog/newcontext/products.py index 05b0246..2f595f0 100644 --- a/catalog/newcontext/products.py +++ b/catalog/newcontext/products.py @@ -1,10 +1,13 @@ import typing +from functools import lru_cache +from django import http from django.conf import settings from django.db.models import QuerySet from catalog.newcontext.context import Context, Products, Tags from catalog.models import AbstractCategory +from refarm_pagination.context import PaginationContext class SortingOption: @@ -30,9 +33,9 @@ def qs(self): class OrderedProducts(Products): - def __init__(self, products: Products, req_kwargs): + def __init__(self, products: Products, sorting_index=0): self._products = products - self._sorting_index = req_kwargs.get('sorting', 0) + self._sorting_index = sorting_index def qs(self): return self._products.qs().order_by( @@ -86,6 +89,7 @@ def context(self): } return { + **self._products.context(), 'product_brands': product_brands, } @@ -108,5 +112,34 @@ def context(self): } return { + **self._products.context(), 'product_images': product_images, } + + +class PaginatedProducts(Products): + """Slice products and add pagination data to a context.""" + + def __init__(self, products: Products, url: str, page_number: int, per_page: int): + if ( + page_number < 1 or + per_page not in settings.CATEGORY_STEP_MULTIPLIERS + ): + raise http.Http404('Page does not exist.') + + self._products = products + self._page_number = page_number + self._pagination = PaginationContext(url, page_number, per_page, self._products.qs()) + + @lru_cache() + def _pagination_context(self): + return self._pagination.context() + + def qs(self): + return self._pagination_context()['page'].object_list + + def context(self): + return { + **self._products.context(), + 'paginated': self._pagination_context(), + } diff --git a/catalog/newcontext/tags.py b/catalog/newcontext/tags.py index 0148ad3..624b71e 100644 --- a/catalog/newcontext/tags.py +++ b/catalog/newcontext/tags.py @@ -17,9 +17,9 @@ def context(self): class ParsedTags(Tags): - def __init__(self, tags: Tags, req_kwargs): + def __init__(self, tags: Tags, raw_tags=''): self._tags = tags - self._raw_tags = req_kwargs.get('tags') + self._raw_tags = raw_tags def qs(self): tags = self._tags.qs() diff --git a/refarm_pagination/views.py b/refarm_pagination/context.py similarity index 100% rename from refarm_pagination/views.py rename to refarm_pagination/context.py diff --git a/tests/catalog/test_context.py b/tests/catalog/test_context.py index bd9a352..a38b3a9 100644 --- a/tests/catalog/test_context.py +++ b/tests/catalog/test_context.py @@ -1,3 +1,8 @@ +""" +@todo #213:30m Remove mocked Context classes. + Wait for fixtures of Tag models to implement this. +""" + import unittest from django.test import TestCase, override_settings @@ -18,8 +23,9 @@ def mocked_ctx(qs_attrs=None, context_attrs=None): class ProductsContext(TestCase): fixtures = ['catalog.json'] + per_page = 30 - def products_ctx(self, qs=None) -> context.context.Products: + def context(self, qs=None) -> context.context.Products: return context.context.Products(qs or catalog_models.MockProduct.objects.all()) def test_ordered_products(self): @@ -27,12 +33,33 @@ def test_ordered_products(self): with override_settings(CATEGORY_SORTING_OPTIONS={ 1: {'label': order_by, 'field': order_by, 'direction': ''} }): - products_ctx = self.products_ctx() + products_ctx = self.context() self.assertEqual( list(products_ctx.qs().order_by(order_by)), - list(context.products.OrderedProducts(products_ctx, {'sorting': 1}).qs()), + list(context.products.OrderedProducts(products_ctx, 1).qs()), + ) + + def test_paginated_qs(self): + with override_settings(CATEGORY_STEP_MULTIPLIERS=[self.per_page]): + products = self.context() + self.assertEqual( + list(products.qs()[:self.per_page]), + list(context.products.PaginatedProducts( + products, '', 1, self.per_page, + ).qs()), ) + def test_paginated_404(self): + page_number = 1 + with override_settings(CATEGORY_STEP_MULTIPLIERS=[self.per_page]): + with self.assertRaises(Http404): + # per_page not in CATEGORY_STEP_MULTIPLIERS + context.products.PaginatedProducts(None, '', page_number, self.per_page-1) + + with self.assertRaises(Http404): + # page number doesn't exist + context.products.PaginatedProducts(None, '', page_number-1, self.per_page) + def test_tagged_products(self): products_ctx = mocked_ctx() context.products.TaggedProducts( @@ -55,13 +82,13 @@ class TagsContext(TestCase): def test_parsed_tags(self): tags_ctx = mocked_ctx() - context.tags.ParsedTags(tags_ctx, {'tags': 'test'}).qs() + context.tags.ParsedTags(tags_ctx, raw_tags='test1=test2').qs() self.assertTrue(tags_ctx.qs().parsed.called) def test_unparsed_tags(self): self.assertFalse( context.tags.ParsedTags( - mocked_ctx(qs_attrs={'none.return_value': []}), {}, + mocked_ctx(qs_attrs={'none.return_value': []}), '', ).qs() ) diff --git a/tests/refarm_pagination/test_views.py b/tests/refarm_pagination/test_context.py similarity index 88% rename from tests/refarm_pagination/test_views.py rename to tests/refarm_pagination/test_context.py index 40cf742..3f1364c 100644 --- a/tests/refarm_pagination/test_views.py +++ b/tests/refarm_pagination/test_context.py @@ -3,17 +3,17 @@ from django.conf import settings from django.test import TestCase -from refarm_pagination import views, pagination +from refarm_pagination.context import PaginationContext from tests.catalog.models import MockProduct -class PaginationContext(TestCase): +class TestPaginationContext(TestCase): fixtures = ['catalog.json'] def context(self, **kwargs): - return views.PaginationContext(**{ + return PaginationContext(**{ 'url': '', 'number': 1, 'per_page': 1, @@ -23,7 +23,7 @@ def context(self, **kwargs): @contextmanager def mock_neighbor_pairs(self, url, number): - with mock.patch('refarm_pagination.views.NeighborPages') as mocked_pages: + with mock.patch('refarm_pagination.context.NeighborPages') as mocked_pages: mocked_page = mock.Mock() mocked_page.url = lambda _: url mocked_page.number = number