diff --git a/shopelectro/context.py b/shopelectro/context.py index 0b77f94b..a243911f 100644 --- a/shopelectro/context.py +++ b/shopelectro/context.py @@ -45,6 +45,42 @@ def directed_field(self): return self.direction + self.field +# @todo #539:60m Move PaginatorLinks to refarm-site. +class PaginatorLinks: + + def __init__(self, number, path, paginated: Paginator): + self.paginated = paginated + self.number = number + self.path = path + + self.index = number - 1 + self.neighbor_bounds = settings.PAGINATION_NEIGHBORS // 2 + self.neighbor_range = list(self.paginated.page_range) + + def page(self): + try: + return self.paginated.page(self.number) + except InvalidPage: + raise http.Http404('Page does not exist') + + def showed_number(self): + return self.index * self.paginated.per_page + self.page().object_list.count() + + def _url(self, number): + self.paginated.validate_number(number) + return self.path if number == 1 else f'{self.path}?page={number}' + + def prev_numbers(self): + return self.neighbor_range[:self.index][-self.neighbor_bounds:] + + def next_numbers(self): + return self.neighbor_range[self.index + 1:][:self.neighbor_bounds] + + def number_url_map(self): + numbers = self.prev_numbers() + self.next_numbers() + return {number: self._url(number) for number in numbers} + + # @todo #550:30m Split to ProductImagesContext and ProductBrandContext @lru_cache(maxsize=64) def merge_products_context(products: QuerySet): @@ -194,7 +230,10 @@ def get_tags(request_tags_: str): request_tags = self.url_kwargs.get('tags') if not request_tags: return None - return get_tags(request_tags) + tags = get_tags(request_tags) + if not tags: + raise http.Http404('No such tag.') + return tags @property def products(self): @@ -317,11 +356,19 @@ def get_context_data(self): if not products: raise http.Http404('Page without products does not exist.') + paginated = PaginatorLinks( + page_number, + self.request.path, + Paginator(self.products, products_on_page) + ) + paginated_page = paginated.page() + return { **context, 'products_data': merge_products_context(products), 'total_products': total_products, 'products_count': (page_number - 1) * products_on_page + products.count(), + 'paginated': paginated, 'paginated_page': paginated_page, 'sorting_options': settings.CATEGORY_SORTING_OPTIONS.values(), 'limits': settings.CATEGORY_STEP_MULTIPLIERS, diff --git a/shopelectro/models.py b/shopelectro/models.py index 9b8c057f..71663af4 100644 --- a/shopelectro/models.py +++ b/shopelectro/models.py @@ -2,7 +2,7 @@ import string from itertools import chain, groupby from operator import attrgetter -from typing import Dict, List, Optional, Tuple +import typing from uuid import uuid4 from django.conf import settings @@ -19,6 +19,7 @@ CategoryManager, ProductActiveManager, ProductManager, + TagGroup as caTagGroup, ) from ecommerce.models import Order as ecOrder from pages.models import CustomPage, ModelPage, Page, SyncPageMixin, PageManager @@ -119,18 +120,8 @@ def feedback(self): def get_params(self): return Tag.objects.filter_by_products([self]).get_group_tags_pairs() - # @todo #388:30m Move Product.get_siblings method to refarm-site - # And reuse it on STB. - def get_siblings(self, offset): - return ( - self.__class__.actives - .filter(category=self.category) - .prefetch_related('category') - .select_related('page')[:offset] - ) - def get_brand_name(self) -> str: - brand: Optional['Tag'] = Tag.objects.get_brands([self]).get(self) + brand: typing.Optional['Tag'] = Tag.objects.get_brands([self]).get(self) return brand.name if brand else '' @@ -210,14 +201,9 @@ class Meta(ModelPage.Meta): # Ignore PycodestyleBear (E303) objects = ModelPage.create_model_page_managers(Product) -class TagGroup(models.Model): +class TagGroup(caTagGroup): + pass - uuid = models.UUIDField(default=uuid4, editable=False) # Ignore CPDBear - name = models.CharField( - max_length=100, db_index=True, verbose_name=_('name')) - position = models.PositiveSmallIntegerField( - default=0, blank=True, db_index=True, verbose_name=_('position'), - ) def __str__(self): return self.name @@ -225,7 +211,7 @@ def __str__(self): class TagQuerySet(models.QuerySet): - def filter_by_products(self, products: List[Product]): + def filter_by_products(self, products: typing.List[Product]): ordering = settings.TAGS_ORDER distinct = [order.lstrip('-') for order in ordering] @@ -236,14 +222,14 @@ def filter_by_products(self, products: List[Product]): .distinct(*distinct, 'id') ) - def get_group_tags_pairs(self) -> List[Tuple[TagGroup, List['Tag']]]: + def get_group_tags_pairs(self) -> typing.List[typing.Tuple[TagGroup, typing.List['Tag']]]: grouped_tags = groupby(self.prefetch_related('group'), key=attrgetter('group')) return [ (group, list(tags_)) for group, tags_ in grouped_tags ] - def get_brands(self, products: List[Product]) -> Dict[Product, 'Tag']: + def get_brands(self, products: typing.List[Product]) -> typing.Dict[Product, 'Tag']: brand_tags = ( self.filter(group__name=settings.BRAND_TAG_GROUP_NAME) .prefetch_related('products') @@ -336,8 +322,6 @@ class Tag(models.Model): TagGroup, on_delete=models.CASCADE, null=True, related_name='tags', ) - def __str__(self): - return self.name def save(self, *args, **kwargs): if not self.slug: diff --git a/shopelectro/views/catalog.py b/shopelectro/views/catalog.py index 358c3ea6..a40842f3 100644 --- a/shopelectro/views/catalog.py +++ b/shopelectro/views/catalog.py @@ -1,8 +1,7 @@ import typing -from functools import lru_cache, partial +from functools import lru_cache from django import http -from django.db.models import QuerySet from django.conf import settings from django.core.paginator import Paginator, InvalidPage from django.shortcuts import render, get_object_or_404 @@ -26,13 +25,6 @@ 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 http.Http404('Page does not exist') - - class SortingOption: def __init__(self, index=0): options = settings.CATEGORY_SORTING_OPTIONS[index] @@ -180,11 +172,6 @@ def merge_products_context(products): @set_csrf_cookie class CategoryPage(catalog.CategoryPage): - def get_products(self) -> QuerySet: - return models.Product.actives.get_category_descendants( - self.object.model - ) - def get_context_data(self, **kwargs): """Add sorting options and view_types in context.""" context_ = ( @@ -200,7 +187,6 @@ def get_context_data(self, **kwargs): } -# @todo #550:60m Make `catalog.views.load_more` one of the context classes def load_more(request, category_slug, offset=0, limit=0, sorting=0, tags=None): """ Load more products of a given category. @@ -246,12 +232,18 @@ def load_more(request, category_slug, offset=0, limit=0, sorting=0, tags=None): .distinct(sorting_option.field) ) - paginated_page = get_paginated_page_or_404(all_products, products_on_page, page_number) + paginated = context.PaginatorLinks( + page_number, + request.path, + Paginator(all_products, products_on_page) + ) + paginated_page = paginated.page() products = paginated_page.object_list view = request.session.get('view_type', 'tile') return render(request, 'catalog/category_products.html', { 'products_data': merge_products_context(products), + 'paginated': paginated, 'paginated_page': paginated_page, 'view_type': view, 'prods': products_on_page,