feat: add pagination and color filter to catalog view and templates
All checks were successful
Build and Push Docker Image / build (push) Successful in 24s
All checks were successful
Build and Push Docker Image / build (push) Successful in 24s
- Implemented pagination for catalog items with Django's Paginator - Updated `item-list.html` to include pagination controls - Added color filtering logic to catalog view with support for `Color` model - Enhanced `side-bar.html` with a dynamic color dropdown filter - Adjusted filtering URL logic to accommodate color filters and pagination parameters - Prefetched `colors` in item queries to optimize performance
This commit is contained in:
@@ -2,11 +2,12 @@ from dataclasses import dataclass
|
|||||||
from decimal import Decimal, InvalidOperation
|
from decimal import Decimal, InvalidOperation
|
||||||
from urllib.parse import urlencode
|
from urllib.parse import urlencode
|
||||||
|
|
||||||
|
from django.core.paginator import Paginator
|
||||||
from django.http import HttpRequest, HttpResponse, Http404
|
from django.http import HttpRequest, HttpResponse, Http404
|
||||||
from django.shortcuts import redirect, render
|
from django.shortcuts import redirect, render
|
||||||
|
|
||||||
from favorites.services import get_user_favorite_item_ids
|
from favorites.services import get_user_favorite_item_ids
|
||||||
from items.models import Category, Item, Subcategory
|
from items.models import Category, Color, Item, Subcategory
|
||||||
from orders.services import get_user_cart_item_ids
|
from orders.services import get_user_cart_item_ids
|
||||||
|
|
||||||
|
|
||||||
@@ -45,6 +46,7 @@ def _build_catalog_url(
|
|||||||
max_price: str = "",
|
max_price: str = "",
|
||||||
sale: bool = False,
|
sale: bool = False,
|
||||||
new: bool = False,
|
new: bool = False,
|
||||||
|
color: str = "",
|
||||||
) -> str:
|
) -> str:
|
||||||
base_url = "/catalog/"
|
base_url = "/catalog/"
|
||||||
if category_slug:
|
if category_slug:
|
||||||
@@ -63,6 +65,8 @@ def _build_catalog_url(
|
|||||||
query_params["sale"] = "1"
|
query_params["sale"] = "1"
|
||||||
if new:
|
if new:
|
||||||
query_params["new"] = "1"
|
query_params["new"] = "1"
|
||||||
|
if color:
|
||||||
|
query_params["color"] = color
|
||||||
|
|
||||||
if not query_params:
|
if not query_params:
|
||||||
return base_url
|
return base_url
|
||||||
@@ -100,6 +104,7 @@ def index(request: HttpRequest, category: str | None = None) -> HttpResponse:
|
|||||||
selected_scope = request.GET.get('category', '').strip()
|
selected_scope = request.GET.get('category', '').strip()
|
||||||
filter_sale = request.GET.get('sale') == '1'
|
filter_sale = request.GET.get('sale') == '1'
|
||||||
filter_new = request.GET.get('new') == '1'
|
filter_new = request.GET.get('new') == '1'
|
||||||
|
selected_color_id_raw = request.GET.get('color', '').strip()
|
||||||
|
|
||||||
if category is None and selected_scope:
|
if category is None and selected_scope:
|
||||||
category_slug, selected_subcategory_slug = _resolve_catalog_scope(selected_scope)
|
category_slug, selected_subcategory_slug = _resolve_catalog_scope(selected_scope)
|
||||||
@@ -113,12 +118,13 @@ def index(request: HttpRequest, category: str | None = None) -> HttpResponse:
|
|||||||
max_price=max_price_raw,
|
max_price=max_price_raw,
|
||||||
sale=filter_sale,
|
sale=filter_sale,
|
||||||
new=filter_new,
|
new=filter_new,
|
||||||
|
color=selected_color_id_raw,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
items = (
|
items = (
|
||||||
Item.objects.select_related('category', 'subcategory')
|
Item.objects.select_related('category', 'subcategory')
|
||||||
.prefetch_related('images')
|
.prefetch_related('images', 'colors')
|
||||||
.order_by('category__name', 'name')
|
.order_by('category__name', 'name')
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -159,6 +165,16 @@ def index(request: HttpRequest, category: str | None = None) -> HttpResponse:
|
|||||||
if filter_new:
|
if filter_new:
|
||||||
items = [item for item in items if item.is_new]
|
items = [item for item in items if item.is_new]
|
||||||
|
|
||||||
|
color_item_ids = {c.id for item in items for c in item.colors.all()}
|
||||||
|
available_colors = list(Color.objects.filter(id__in=color_item_ids))
|
||||||
|
|
||||||
|
if selected_color_id_raw:
|
||||||
|
try:
|
||||||
|
selected_color_id_int = int(selected_color_id_raw)
|
||||||
|
items = [item for item in items if any(c.id == selected_color_id_int for c in item.colors.all())]
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
item_prices = [item.display_price for item in items]
|
item_prices = [item.display_price for item in items]
|
||||||
slider_min_bound = min(item_prices, default=Decimal('0'))
|
slider_min_bound = min(item_prices, default=Decimal('0'))
|
||||||
slider_max_bound = max(item_prices, default=slider_min_bound)
|
slider_max_bound = max(item_prices, default=slider_min_bound)
|
||||||
@@ -176,6 +192,13 @@ def index(request: HttpRequest, category: str | None = None) -> HttpResponse:
|
|||||||
slider_selected_min = min(max(slider_selected_min, slider_min_bound), slider_max_bound)
|
slider_selected_min = min(max(slider_selected_min, slider_min_bound), slider_max_bound)
|
||||||
slider_selected_max = min(max(slider_selected_max, slider_min_bound), slider_max_bound)
|
slider_selected_max = min(max(slider_selected_max, slider_min_bound), slider_max_bound)
|
||||||
|
|
||||||
|
page_number = request.GET.get('page', 1)
|
||||||
|
paginator = Paginator(items, 24)
|
||||||
|
page_obj = paginator.get_page(page_number)
|
||||||
|
|
||||||
|
pagination_params = {k: v for k, v in request.GET.items() if k != 'page'}
|
||||||
|
pagination_query_string = urlencode(pagination_params)
|
||||||
|
|
||||||
categories = list(Category.objects.order_by('name'))
|
categories = list(Category.objects.order_by('name'))
|
||||||
all_items_url = _build_catalog_url(
|
all_items_url = _build_catalog_url(
|
||||||
search_query=search_query,
|
search_query=search_query,
|
||||||
@@ -183,6 +206,7 @@ def index(request: HttpRequest, category: str | None = None) -> HttpResponse:
|
|||||||
max_price=max_price_raw,
|
max_price=max_price_raw,
|
||||||
sale=filter_sale,
|
sale=filter_sale,
|
||||||
new=filter_new,
|
new=filter_new,
|
||||||
|
color=selected_color_id_raw,
|
||||||
)
|
)
|
||||||
for catalog_category in categories:
|
for catalog_category in categories:
|
||||||
catalog_category.catalog_url = _build_catalog_url(
|
catalog_category.catalog_url = _build_catalog_url(
|
||||||
@@ -192,6 +216,7 @@ def index(request: HttpRequest, category: str | None = None) -> HttpResponse:
|
|||||||
max_price=max_price_raw,
|
max_price=max_price_raw,
|
||||||
sale=filter_sale,
|
sale=filter_sale,
|
||||||
new=filter_new,
|
new=filter_new,
|
||||||
|
color=selected_color_id_raw,
|
||||||
)
|
)
|
||||||
|
|
||||||
subcategories = list(subcategories)
|
subcategories = list(subcategories)
|
||||||
@@ -204,6 +229,7 @@ def index(request: HttpRequest, category: str | None = None) -> HttpResponse:
|
|||||||
max_price=max_price_raw,
|
max_price=max_price_raw,
|
||||||
sale=filter_sale,
|
sale=filter_sale,
|
||||||
new=filter_new,
|
new=filter_new,
|
||||||
|
color=selected_color_id_raw,
|
||||||
)
|
)
|
||||||
for catalog_subcategory in subcategories:
|
for catalog_subcategory in subcategories:
|
||||||
catalog_subcategory.catalog_url = _build_catalog_url(
|
catalog_subcategory.catalog_url = _build_catalog_url(
|
||||||
@@ -214,6 +240,7 @@ def index(request: HttpRequest, category: str | None = None) -> HttpResponse:
|
|||||||
max_price=max_price_raw,
|
max_price=max_price_raw,
|
||||||
sale=filter_sale,
|
sale=filter_sale,
|
||||||
new=filter_new,
|
new=filter_new,
|
||||||
|
color=selected_color_id_raw,
|
||||||
)
|
)
|
||||||
|
|
||||||
price_filter_action = _build_catalog_url(selected_category.id)
|
price_filter_action = _build_catalog_url(selected_category.id)
|
||||||
@@ -235,6 +262,11 @@ def index(request: HttpRequest, category: str | None = None) -> HttpResponse:
|
|||||||
'categories': categories,
|
'categories': categories,
|
||||||
'subcategories': subcategories,
|
'subcategories': subcategories,
|
||||||
'items': items,
|
'items': items,
|
||||||
|
'page_obj': page_obj,
|
||||||
|
'paginator': paginator,
|
||||||
|
'pagination_query_string': pagination_query_string,
|
||||||
|
'available_colors': available_colors,
|
||||||
|
'selected_color_id': selected_color_id_raw,
|
||||||
'selected_category': selected_category,
|
'selected_category': selected_category,
|
||||||
'selected_subcategory': selected_subcategory,
|
'selected_subcategory': selected_subcategory,
|
||||||
'all_items_url': all_items_url,
|
'all_items_url': all_items_url,
|
||||||
|
|||||||
@@ -1,3 +1,18 @@
|
|||||||
<main class="item-list">
|
<main class="item-list">
|
||||||
{% include "_fragments/item-grid.html" with items=items empty_message="В этой категории пока нет товаров." %}
|
{% include "_fragments/item-grid.html" with items=page_obj empty_message="В этой категории пока нет товаров." %}
|
||||||
|
|
||||||
|
{% if paginator.num_pages > 1 %}
|
||||||
|
<nav class="catalog-pagination">
|
||||||
|
{% if page_obj.has_previous %}
|
||||||
|
<a href="?{{ pagination_query_string }}&page={{ page_obj.previous_page_number }}" class="catalog-pagination__btn">«</a>
|
||||||
|
{% endif %}
|
||||||
|
{% for i in paginator.page_range %}
|
||||||
|
<a href="?{{ pagination_query_string }}&page={{ i }}"
|
||||||
|
class="catalog-pagination__btn {% if i == page_obj.number %}active{% endif %}">{{ i }}</a>
|
||||||
|
{% endfor %}
|
||||||
|
{% if page_obj.has_next %}
|
||||||
|
<a href="?{{ pagination_query_string }}&page={{ page_obj.next_page_number }}" class="catalog-pagination__btn">»</a>
|
||||||
|
{% endif %}
|
||||||
|
</nav>
|
||||||
|
{% endif %}
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@@ -85,13 +85,19 @@
|
|||||||
<button type="submit" class="catalog-filter-submit">Применить</button>
|
<button type="submit" class="catalog-filter-submit">Применить</button>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
{% if available_colors %}
|
||||||
<section class="a-section color-section">
|
<section class="a-section color-section">
|
||||||
<h2>Цвет</h2>
|
<h2>Цвет</h2>
|
||||||
|
<select name="color" onchange="this.form.submit()">
|
||||||
|
<option value="">Любой</option>
|
||||||
|
{% for color in available_colors %}
|
||||||
|
<option value="{{ color.id }}" {% if selected_color_id == color.id|stringformat:"i" %}selected{% endif %}>
|
||||||
|
{{ color.name }}
|
||||||
|
</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
</section>
|
</section>
|
||||||
|
{% endif %}
|
||||||
<section class="a-section material-section">
|
|
||||||
<h2>Материал</h2>
|
|
||||||
</section>
|
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<section class="a-section promotions-section">
|
<section class="a-section promotions-section">
|
||||||
|
|||||||
Reference in New Issue
Block a user