Реализация корзины покупок — это базовый, но критически важный этап разработки любого e-commerce проекта. Главный архитектурный вопрос здесь заключается в выборе места хранения данных: в сессии пользователя (для неавторизованных гостей) или в базе данных (для постоянного хранения и синхронизации между устройствами).
В этой статье мы разберем гибридный подход, который позволит пользователям добавлять товары в корзину без регистрации, а после входа в систему — сохранять их выбор.
Архитектура хранения данных
Для реализации корзины мы будем использовать django.contrib.sessions. Это позволит нам хранить ID товаров и их количество в JSON-подобной структуре в сессии пользователя.
Почему сессии?
- Скорость: Не нужно делать запрос к БД при каждом обновлении страницы.
- UX: Пользователь может начать покупки, не проходя через форму регистрации.
- Снижение нагрузки: БД не забивается временными данными «брошенных корзин».
Пошаговая реализация
1. Создание класса Cart
Вместо того чтобы писать логику прямо во views, создадим отдельный класс-менеджер для работы с корзиной. Создайте файл cart.py в вашем приложении.
from decimal import Decimal
from django.conf import settings
class Cart:
def __init__(self, request):
self.session = request.session
cart = self.session.get('session_cart')
if not cart:
cart = self.session['session_cart'] = {}
self.cart = cart
def add(self, product, quantity=1, override_quantity=False):
product_id = str(product.id)
if product_id not in self.cart:
self.cart[product_id] = {'quantity': 0, 'price': str(product.price)}
if override_quantity:
self.cart[product_id]['quantity'] = quantity
else:
self.cart[product_id]['quantity'] += quantity
self.save()
def save(self):
self.session.modified = True
def remove(self, product):
product_id = str(product.id)
if product_id in self.cart:
del self.cart[product_id]
self.save()
def __iter__(self):
# Здесь логика получения объектов товаров из БД на основе ID в сессии
pass
def get_total_price(self):
return sum(Decimal(item['price']) * item['quantity'] for item in self.cart.values())
def clear(self):
del self.session['session_cart']
self.save()
2. Создание View для управления корзиной
Теперь создадим обработчики для добавления и удаления товаров.
from django.shortcuts import redirect, get_object_or_404, render
from .models import Product
from .cart import Cart
def cart_add(request, product_id):
cart = Cart(request)
product = get_object_or_404(Product, id=product_id)
cart.add(product=product, quantity=1)
return redirect('cart_detail')
def cart_remove(request, product_id):
cart = Cart(request)
product = get_object_or_404(Product, id=product_id)
cart.remove(product)
return redirect('cart_detail')
def cart_detail(request):
cart = Cart(request)
# Дополнительная логика для получения списка объектов товаров
return render(request, 'cart/detail.html', {'cart': cart})
3. Настройка URL-путей
Добавьте соответствующие пути в urls.py:
from django.urls import path
from . import views
urlpatterns = [
path('cart/add/<int:product_id>/', views.cart_add, name='cart_add'),
path('cart/remove/<int:product_id>/', views.cart_remove, name='cart_remove'),
path('cart/', views.cart_detail, name='cart_detail'),
]
Оптимизация и безопасность
Проблема N+1
При выводе корзины в шаблоне часто возникает проблема N+1 запросов к базе данных (один запрос на корзину + по одному запросу на каждый товар). Чтобы этого избежать, используйте метод __iter__ в классе Cart с применением .filter(id__in=self.cart.keys()).select_related(...).
Валидация остатков
Перед методом self.save() в классе Cart рекомендуется добавить проверку наличия товара на складе (Product.stock), чтобы пользователь не мог добавить в корзину больше единиц, чем доступно в реальности.
Заключение
Использование сессий для корзины в Django — это сбалансированное решение, которое обеспечивает высокую скорость работы и удобство для пользователя. Для масштабирования проекта (например, при переходе на Redis для хранения сессий) данный подход останется актуальным, так как логика инкапсулирована в отдельном классе.
Следующим шагом в развитии системы может стать создание модели Order и OrderItem, куда данные из сессионной корзины будут переноситься в момент оформления заказа.