Files
santeh-ray/items/views.py
Azimkin 068e5190c2
All checks were successful
Build and Push Docker Image / build (push) Successful in 29s
Small fixes
2026-06-24 21:27:50 +02:00

361 lines
12 KiB
Python

from dataclasses import dataclass
from decimal import Decimal, InvalidOperation, ROUND_HALF_UP
from urllib.parse import urlencode
from django.core.paginator import Paginator
from django.http import HttpRequest, HttpResponse, Http404
from django.shortcuts import redirect, render
from favorites.services import get_user_favorite_item_ids
from items.models import Category, Color, Item, Subcategory
from orders.models import CartItem
from orders.services import get_user_cart_item_ids
@dataclass
class SelectedCategory:
name: str | None
id: str | None
@dataclass
class SelectedSubcategory:
name: str | None
id: str | None
def _parse_price(value: str | None) -> Decimal | None:
if value is None:
return None
normalized_value = value.strip()
if not normalized_value:
return None
try:
return Decimal(normalized_value)
except InvalidOperation:
return None
def _build_catalog_url(
category_slug: str | None = None,
subcategory_slug: str | None = None,
*,
search_query: str = "",
min_price: str = "",
max_price: str = "",
sale: bool = False,
new: bool = False,
color: str = "",
) -> str:
base_url = "/catalog/"
if category_slug:
base_url = f"/catalog/{category_slug}/"
query_params: dict[str, str] = {}
if subcategory_slug:
query_params["subcategory"] = subcategory_slug
if search_query:
query_params["q"] = search_query
if min_price:
query_params["min_price"] = min_price
if max_price:
query_params["max_price"] = max_price
if sale:
query_params["sale"] = "1"
if new:
query_params["new"] = "1"
if color:
query_params["color"] = color
if not query_params:
return base_url
return f"{base_url}?{urlencode(query_params)}"
def _resolve_catalog_scope(scope_value: str | None) -> tuple[str | None, str | None]:
if not scope_value:
return None, None
parts = [part.strip().lower() for part in scope_value.split(":")]
if len(parts) == 2 and parts[0] == "category" and parts[1]:
return parts[1], None
if len(parts) == 3 and parts[0] == "subcategory" and parts[1] and parts[2]:
return parts[1], parts[2]
return None, None
def _format_price_value(value: Decimal) -> str:
return format(value.quantize(Decimal("1"), rounding=ROUND_HALF_UP), "f")
def index(request: HttpRequest, category: str | None = None) -> HttpResponse:
selected_category = SelectedCategory(name=None, id=None)
selected_subcategory = SelectedSubcategory(name=None, id=None)
subcategories = Subcategory.objects.none()
subcategory_slug = request.GET.get('subcategory')
search_query = request.GET.get('q', '').strip()
min_price_raw = request.GET.get('min_price', '').strip()
max_price_raw = request.GET.get('max_price', '').strip()
selected_scope = request.GET.get('category', '').strip()
filter_sale = request.GET.get('sale') == '1'
filter_new = request.GET.get('new') == '1'
selected_color_id_raw = request.GET.get('color', '').strip()
if category is None and selected_scope:
category_slug, selected_subcategory_slug = _resolve_catalog_scope(selected_scope)
if category_slug:
return redirect(
_build_catalog_url(
category_slug,
selected_subcategory_slug,
search_query=search_query,
min_price=min_price_raw,
max_price=max_price_raw,
sale=filter_sale,
new=filter_new,
color=selected_color_id_raw,
)
)
items = (
Item.objects.select_related('category', 'subcategory')
.prefetch_related('images', 'colors')
.order_by('category__name', 'name')
)
if category is None and subcategory_slug:
raise Http404()
if category is not None:
try:
cat = Category.objects.get(slug=category.lower())
selected_category = SelectedCategory(name=cat.name, id=cat.slug)
except Category.DoesNotExist:
raise Http404()
subcategories = Subcategory.objects.filter(category=cat).order_by('name')
items = items.filter(category=cat)
if subcategory_slug:
try:
subcategory_obj = subcategories.get(slug=subcategory_slug.lower())
selected_subcategory = SelectedSubcategory(name=subcategory_obj.name, id=subcategory_obj.slug)
except Subcategory.DoesNotExist:
raise Http404()
items = items.filter(subcategory=subcategory_obj)
items = list(items)
if search_query:
normalized_search_query = search_query.casefold()
items = [
item
for item in items
if normalized_search_query in item.name.casefold()
]
if filter_sale:
items = [item for item in items if item.discount_price is not None]
if filter_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))
any_color_url = _build_catalog_url(
selected_category.id,
selected_subcategory.id,
search_query=search_query,
min_price=min_price_raw,
max_price=max_price_raw,
sale=filter_sale,
new=filter_new,
)
for color in available_colors:
color.catalog_url = _build_catalog_url(
selected_category.id,
selected_subcategory.id,
search_query=search_query,
min_price=min_price_raw,
max_price=max_price_raw,
sale=filter_sale,
new=filter_new,
color=str(color.id),
)
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]
slider_min_bound = min(item_prices, default=Decimal('0'))
slider_max_bound = max(item_prices, default=slider_min_bound)
min_price = _parse_price(min_price_raw)
if min_price is not None:
items = [item for item in items if item.display_price >= min_price]
max_price = _parse_price(max_price_raw)
if max_price is not None:
items = [item for item in items if item.display_price <= max_price]
slider_selected_min = min_price if min_price is not None else slider_min_bound
slider_selected_max = max_price if max_price is not None else 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)
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'))
all_items_url = _build_catalog_url(
search_query=search_query,
min_price=min_price_raw,
max_price=max_price_raw,
sale=filter_sale,
new=filter_new,
color=selected_color_id_raw,
)
for catalog_category in categories:
catalog_category.catalog_url = _build_catalog_url(
catalog_category.slug,
search_query=search_query,
min_price=min_price_raw,
max_price=max_price_raw,
sale=filter_sale,
new=filter_new,
color=selected_color_id_raw,
)
subcategories = list(subcategories)
selected_category_url = None
if selected_category.id is not None:
selected_category_url = _build_catalog_url(
selected_category.id,
search_query=search_query,
min_price=min_price_raw,
max_price=max_price_raw,
sale=filter_sale,
new=filter_new,
color=selected_color_id_raw,
)
for catalog_subcategory in subcategories:
catalog_subcategory.catalog_url = _build_catalog_url(
selected_category.id,
catalog_subcategory.slug,
search_query=search_query,
min_price=min_price_raw,
max_price=max_price_raw,
sale=filter_sale,
new=filter_new,
color=selected_color_id_raw,
)
price_filter_action = _build_catalog_url(selected_category.id)
filters_active = any([
search_query,
min_price_raw,
max_price_raw,
filter_sale,
filter_new,
selected_color_id_raw,
])
reset_filters_url = _build_catalog_url(selected_category.id, selected_subcategory.id)
sidebar_sale_items = (
Item.objects.filter(discount_price__isnull=False, is_available=True)
.prefetch_related('images')
.order_by('-created_at')[:3]
)
sidebar_new_items = (
Item.objects.filter(is_new=True, is_available=True)
.prefetch_related('images')
.order_by('-created_at')[:3]
)
sale_all_url = _build_catalog_url(sale=True)
new_all_url = _build_catalog_url(new=True)
return render(request, 'catalog/index.html', {
'categories': categories,
'subcategories': subcategories,
'items': items,
'page_obj': page_obj,
'paginator': paginator,
'pagination_query_string': pagination_query_string,
'available_colors': available_colors,
'any_color_url': any_color_url,
'selected_color_id': selected_color_id_raw,
'selected_category': selected_category,
'selected_subcategory': selected_subcategory,
'all_items_url': all_items_url,
'selected_category_url': selected_category_url,
'price_filter_action': price_filter_action,
'filters_active': filters_active,
'reset_filters_url': reset_filters_url,
'current_search_query': search_query,
'current_min_price': min_price_raw,
'current_max_price': max_price_raw,
'price_slider_min_bound': _format_price_value(slider_min_bound),
'price_slider_max_bound': _format_price_value(slider_max_bound),
'price_slider_selected_min': _format_price_value(slider_selected_min),
'price_slider_selected_max': _format_price_value(slider_selected_max),
'favorite_item_ids': get_user_favorite_item_ids(request.user),
'filter_sale': filter_sale,
'filter_new': filter_new,
'sidebar_sale_items': sidebar_sale_items,
'sidebar_new_items': sidebar_new_items,
'sale_all_url': sale_all_url,
'new_all_url': new_all_url,
})
def detail(request: HttpRequest, category: str, slug: str) -> HttpResponse:
try:
item = (
Item.objects.select_related('category', 'subcategory')
.prefetch_related('images', 'colors')
.get(category__slug=category.lower(), slug=slug)
)
except Item.DoesNotExist:
raise Http404()
images = list(item.images.all())
colors = list(item.colors.all())
cart_color_id = None
if request.user.is_authenticated:
cart_item = CartItem.objects.filter(
cart__user=request.user, item=item
).select_related('selected_color').first()
if cart_item and cart_item.selected_color_id:
cart_color_id = cart_item.selected_color_id
selected_color_id = cart_color_id
if selected_color_id is None and colors:
selected_color_id = colors[0].id
return render(request, 'catalog/detail.html', {
'categories': Category.objects.order_by('name'),
'item': item,
'images': images,
'colors': colors,
'primary_image': images[0] if images else None,
'cart_item_ids': get_user_cart_item_ids(request.user),
'selected_color_id': selected_color_id,
'favorite_item_ids': get_user_favorite_item_ids(request.user),
})