From 415a34467977d38c20729d01ce88c2c6fad25198 Mon Sep 17 00:00:00 2001 From: duker Date: Thu, 6 Sep 2018 09:42:05 +0300 Subject: [PATCH] #550 Merge fixes after hell --- shopelectro/context.py | 49 ++++++++++++- shopelectro/models.py | 24 +++++-- shopelectro/views/catalog.py | 131 ++++------------------------------- 3 files changed, 78 insertions(+), 126 deletions(-) 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 4e2e8da5..71663af4 100644 --- a/shopelectro/models.py +++ b/shopelectro/models.py @@ -1,11 +1,17 @@ -from typing import Optional +import random +import string +from itertools import chain, groupby +from operator import attrgetter +import typing from uuid import uuid4 from django.conf import settings from django.db import models from django.urls import reverse +from django.utils.text import slugify from django.utils.translation import ugettext_lazy as _ from mptt.querysets import TreeQuerySet +from unidecode import unidecode from catalog.models import ( AbstractCategory, @@ -13,13 +19,19 @@ CategoryManager, ProductActiveManager, ProductManager, - Tag as caTag, TagGroup as caTagGroup, ) from ecommerce.models import Order as ecOrder from pages.models import CustomPage, ModelPage, Page, SyncPageMixin, PageManager +def randomize_slug(slug: str) -> str: + slug_hash = ''.join( + random.choices(string.ascii_lowercase, k=settings.SLUG_HASH_SIZE) + ) + return f'{slug}_{slug_hash}' + + class SECategoryQuerySet(TreeQuerySet): def get_categories_tree_with_pictures(self) -> 'SECategoryQuerySet': categories_with_pictures = ( @@ -109,7 +121,7 @@ def get_params(self): return Tag.objects.filter_by_products([self]).get_group_tags_pairs() 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 '' @@ -199,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] @@ -210,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') diff --git a/shopelectro/views/catalog.py b/shopelectro/views/catalog.py index b5efcae3..a40842f3 100644 --- a/shopelectro/views/catalog.py +++ b/shopelectro/views/catalog.py @@ -1,5 +1,5 @@ import typing -from functools import partial +from functools import lru_cache from django import http from django.conf import settings @@ -8,12 +8,11 @@ from django.views.decorators.http import require_POST from django_user_agents.utils import get_user_agent -from catalog import models as ca_models from catalog.views import catalog from images.models import Image from pages import views as pages_views -from shopelectro import models +from shopelectro import context, models from shopelectro.views.helpers import set_csrf_cookie PRODUCTS_ON_PAGE_PC = 48 @@ -26,42 +25,6 @@ def get_products_count(request): return PRODUCTS_ON_PAGE_MOB if mobile_view else PRODUCTS_ON_PAGE_PC -# @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} - - class SortingOption: def __init__(self, index=0): options = settings.CATEGORY_SORTING_OPTIONS[index] @@ -188,6 +151,7 @@ def get_context_data(self, **kwargs): } +@lru_cache(maxsize=64) def merge_products_context(products): images = Image.objects.get_main_images_by_pages( models.ProductPage.objects.filter(shopelectro_product__in=products) @@ -210,87 +174,16 @@ class CategoryPage(catalog.CategoryPage): def get_context_data(self, **kwargs): """Add sorting options and view_types in context.""" - context = super().get_context_data(**kwargs) - products_on_page = int(self.request.GET.get( - 'step', get_products_count(self.request), - )) - page_number = int(self.request.GET.get('page', 1)) - view_type = self.request.session.get('view_type', 'tile') - sorting_index = int(self.kwargs.get('sorting', 0)) - sorting_option = SortingOption(index=sorting_index) - category = context['category'] - if ( - page_number < 1 or - products_on_page not in settings.CATEGORY_STEP_MULTIPLIERS - ): - raise http.Http404('Page does not exist.') # Ignore CPDBear - - all_products = models.Product.actives.get_category_descendants( - category, ordering=(sorting_option.directed_field, ) - ) - - group_tags_pairs = ( - models.Tag.objects - .filter_by_products(all_products) - .get_group_tags_pairs() + context_ = ( + context.Category(self.kwargs, self.request) + | context.TaggedCategory() + | context.SortingCategory() + | context.PaginationCategory() # requires SortingCategory + | context.DBTemplate() # requires TaggedCategory ) - - tags = self.kwargs.get('tags') - - tag_titles = '' - if tags: - slugs = models.Tag.parse_url_tags(tags) - tags = models.Tag.objects.filter(slug__in=slugs) - - if not tags: - raise http.Http404('No such tag.') - - all_products = ( - all_products - .filter(tags__in=tags) - # Use distinct because filtering by QuerySet tags, - # that related with products by many-to-many relation. - .distinct(sorting_option.field) - ) - - tag_titles = ca_models.serialize_tags_to_title(tags) - - def template_context(page, tag_titles, tags): - return { - 'page': page, - 'tag_titles': tag_titles, - 'tags': tags, - } - - page = context['page'] - page.get_template_render_context = partial( - template_context, page, tag_titles, tags) - - paginated = PaginatorLinks( - page_number, - self.request.path, - Paginator(all_products, products_on_page) - ) - paginated_page = paginated.page() - - total_products = all_products.count() - products_on_page = paginated_page.object_list - if not products_on_page: - raise http.Http404('Page without products does not exist.') - return { - **context, - 'products_data': merge_products_context(products_on_page), - 'group_tags_pairs': group_tags_pairs, - 'total_products': total_products, - 'paginated': paginated, - 'paginated_page': paginated_page, - 'sorting_options': settings.CATEGORY_SORTING_OPTIONS.values(), - 'limits': settings.CATEGORY_STEP_MULTIPLIERS, - 'sort': sorting_index, - 'tags': tags, - 'view_type': view_type, - 'skip_canonical': bool(tags), + **super().get_context_data(**kwargs), + **context_.get_context_data(), } @@ -339,7 +232,7 @@ def load_more(request, category_slug, offset=0, limit=0, sorting=0, tags=None): .distinct(sorting_option.field) ) - paginated = PaginatorLinks( + paginated = context.PaginatorLinks( page_number, request.path, Paginator(all_products, products_on_page)