Хранение корзины покупок в сессиях
Необходимо создать простую структуру, которая может быть сериализована в JSON для хранения элементов корзины в сессии. Корзина должна включать следующие данные для каждого содержащегося в ней элемента:
- id экземпляра Product
- Количество товара, выбранное для данного продукта
- Цена единицы для данного продукта
Поскольку цены на продукцию могут различаться, мы приближаемся к сохранению цены продукта вместе с продуктом, когда он добавляется в корзину. Таким образом, мы будем сохранять ту же цену, которую пользователи увидели при добавлении товара в корзину, даже если цена продукта изменится после этого.
Теперь необходимо управлять созданием корзин и связывать их с сеансами. Корзина покупок должна работать следующим образом:
- Когда требуется корзина, мы проверяем, установлен ли пользовательский session key. Если в сессии не задана корзина, мы создадим новую корзину и сохраним ее в session key корзины.
- Для последовательных запросов мы выполняем одну и ту же проверку и получая номенклатуры корзины из session key корзины. Мы извлекаем элементы корзины из базы данных и связанные с ними объекты продукта.
Измените файл settings.py проекта и добавьте в него следующий параметр:
CART_SESSION_ID = 'cart'
Это ключ, который мы собираемся использовать для хранения корзины в сессии пользователя.
Давайте создадим приложение для управления корзинами покупок. Откройте терминал и создайте новое приложение, запустив следующую команду из каталога проекта:
python manage.py startapp cart
Затем отредактируйте файл settings.py проекта и добавьте "cart" к параметру INSTALLED_APPS следующим образом:
INSTALLED_APPS = (
# ...
'shop',
'cart',
)
Создайте новый файл в каталоге приложения cart и назовите его cart.py. Добавьте в него следующий код:
from decimal import Decimal
from django.conf import settings
from shop.models import Product
class Cart(object):
def __init__(self, request):
"""
Инициализируем корзину
"""
self.session = request.session
cart = self.session.get(settings.CART_SESSION_ID)
if not cart:
# save an empty cart in the session
cart = self.session[settings.CART_SESSION_ID] = {}
self.cart = cart
Это класс Cart, который позволит нам управлять корзиной для покупок. Требуется инициализация корзины с помощью объекта request. Мы храним текущую сессию с помощью self.session = request.session, чтобы сделать его доступным для других методов класса Cart. Во-первых, мы пытаемся получить корзину с текущей сессии с помощью self.session.get(settings.CART_SESSION_ID). Если в сессии отсутствует корзина, то мы создадим сессию с пустой корзиной, установив пустой словарь в сессии. Мы ожидаем, что наш словарь корзины будет использовать коды продуктов в качестве ключей и словарь с количеством и ценой в качестве значения для каждого ключа. Таким образом, мы можем гарантировать, что продукт не будет добавлен в корзину более одного раза; можно также упростить доступ к данным элементов корзины.
Создадим метод для добавления продуктов в корзину или обновления их количества. Добавьте следующие методы add() и save() в класс Cart:
def add(self, product, quantity=1, update_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 update_quantity:
self.cart[product_id]['quantity'] = quantity
else:
self.cart[product_id]['quantity'] += quantity
self.save()
def save(self):
# Обновление сессии cart
self.session[settings.CART_SESSION_ID] = self.cart
# Отметить сеанс как "измененный", чтобы убедиться, что он сохранен
self.session.modified = True
Метод add() принимает следующие параметры:
- product : Экземпляр Product для добавления или обновления в корзине
- quantity : Необязательное целое число для количества продукта. По умолчанию используется значение 1 .
- update_quantity : Это логическое значение, которое указывает, требуется ли обновление количества с заданным количеством (True), или же новое количество должно быть добавлено к существующему количеству (False).
id продукта используется в качестве ключа в словаре содержимого корзины. id продукта преобразуется в строку, так как Джанго использует JSON для сериализации данных сессии, а JSON разрешает только имена строк. id продукта — это ключ, а значение, которое мы сохраняем, — словарь с количеством и ценой для продукта. Цена продукта преобразуется из десятичного разделителя в строку, чтобы сериализовать его. Наконец, мы вызываем метод save(), чтобы сохранить корзину в сессии.
Метод save() сохраняет все изменения в корзине в сессии и помечает сессию как modified с помощью session.modified = True. Это говорит о том, что сессия modified и должна быть сохранена.
Нам также нужен метод для удаления продуктов из корзины. Добавьте следующий метод в класс Cart:
def remove(self, product):
"""
Удаление товара из корзины.
"""
product_id = str(product.id)
if product_id in self.cart:
del self.cart[product_id]
self.save()
Метод remove() удаляет заданный продукт из словаря корзины и вызывает метод save() для обновления корзины в сессии.
Нам придется перебрать элементы, содержащихся в корзине, и получить доступ к соответствующим экземплярам Product. Для этого в классе можно определить метод __iter__(). Добавьте следующий метод в класс Cart:
def __iter__(self):
"""
Перебор элементов в корзине и получение продуктов из базы данных.
"""
product_ids = self.cart.keys()
# получение объектов product и добавление их в корзину
products = Product.objects.filter(id__in=product_ids)
for product in products:
self.cart[str(product.id)]['product'] = product
for item in self.cart.values():
item['price'] = Decimal(item['price'])
item['total_price'] = item['price'] * item['quantity']
yield item
В методе __iter__() мы извлекаем экземпляры продукта, присутствующие в корзине, чтобы включить их в номенклатуры корзины. Наконец, мы проходим по элементам корзины, преобразуя цену номенклатуры обратно в десятичное число и добавляя атрибут total_price к каждому элементу. Теперь можно легко выполнить итерацию по товарам в корзине.
Нам также нужен способ вернуть общее количество товаров в корзине. Когда функция len() выполняется на объекте, Python вызывает метод __len__() для извлечения ее длины. Мы собираемся определить пользовательский метод __len__(), чтобы вернуть общее количество элементов, хранящихся в корзине. Добавьте следующий метод __len__() в класс Cart:
def __len__(self):
"""
Подсчет всех товаров в корзине.
"""
return sum(item['quantity'] for item in self.cart.values())
Мы возвращаем сумму количества всех товаров.
Добавьте следующий метод для расчета общей стоимости товаров в корзине:
def get_total_price(self):
"""
Подсчет стоимости товаров в корзине.
"""
return sum(Decimal(item['price']) * item['quantity'] for item in
self.cart.values())
И, наконец, добавьте метод для очистки сеанса корзины:
def clear(self):
# удаление корзины из сессии
del self.session[settings.CART_SESSION_ID]
self.session.modified = True
Теперь наш класс Cart готов к управлению корзиной для покупок.