-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
#213 paginated products #238
Changes from 5 commits
478fa6f
8716a8b
e2af71e
d208423
4b62488
cbaf28b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
# @todo #207:120m Implement Page, PaginatedProducts context classes. | ||
# @todo #207:60m Implement Page context class. | ||
|
||
from . import products, tags | ||
from .context import Context, Contexts, ModelContext, Products, Tags |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. annotate it's returning value with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is implementation of base class' abstract method, that already have the annotation and it won't be changed. Should we duplicate this information? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @artemiy312 , i think we should. Because when we read code, we don't walk throw ancestors of class we are reading |
||
return { | ||
**self._products.context(), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe we should remove this string? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It just helps to avoid key conflicts, that would be occurred. For example we already have a copy of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @artemiy312 , so similar keys should be merged. Python dict already has such strategy and it looks clear:
|
||
'paginated': self._pagination_context(), | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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,6 +23,7 @@ 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: | ||
return context.context.Products(qs or catalog_models.MockProduct.objects.all()) | ||
|
@@ -30,9 +36,30 @@ def test_ordered_products(self): | |
products_ctx = self.products_ctx() | ||
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.products_ctx() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we can call There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It will raise name conflicts with module There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @artemiy312 , it'll not lead to conflict, because it's method but not a function. |
||
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() | ||
) | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
annotate it's with QuerySet plz