Skip to content
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

Merged
merged 6 commits into from
Dec 31, 2018
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion catalog/newcontext/__init__.py
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
37 changes: 35 additions & 2 deletions catalog/newcontext/products.py
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:
Expand All @@ -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(
Expand Down Expand Up @@ -86,6 +89,7 @@ def context(self):
}

return {
**self._products.context(),
'product_brands': product_brands,
}

Expand All @@ -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):
Copy link
Collaborator

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

return self._pagination_context()['page'].object_list

def context(self):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

annotate it's returning value with dict plz

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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?

Copy link
Collaborator

Choose a reason for hiding this comment

The 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(),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe we should remove this string?
It seems to me, that every context class ancestor should return only it's part of context.
But maybe i don't fully understand how context instances should chain in a view finally.
So, it's up to you

Copy link
Contributor Author

@ArtemijRodionov ArtemijRodionov Dec 31, 2018

Choose a reason for hiding this comment

The 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 page instance in final context, but pagination has page too

Copy link
Collaborator

Choose a reason for hiding this comment

The 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:

In [1]: {**{1: 'one', 2: 'two'}, **{1: 'uno'}}
Out[1]: {1: 'uno', 2: 'two'}

'paginated': self._pagination_context(),
}
4 changes: 2 additions & 2 deletions catalog/newcontext/tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
File renamed without changes.
33 changes: 30 additions & 3 deletions tests/catalog/test_context.py
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
Expand All @@ -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())
Expand 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()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can call products_ctx as just the context. We are already in ProductsContext class

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It will raise name conflicts with module context, so i'd prefer to leave it as it is. Should i create a todo for it?

Copy link
Collaborator

Choose a reason for hiding this comment

The 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.
Create todo plz, y

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(
Expand All @@ -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()
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
Expand Down