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

- 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:
2026-05-26 03:57:38 +02:00
parent 56af404dc9
commit 4a7f520037
3 changed files with 60 additions and 7 deletions

View File

@@ -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,

View File

@@ -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">&laquo;</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">&raquo;</a>
{% endif %}
</nav>
{% endif %}
</main> </main>

View File

@@ -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">