Вопрос: Как реализовать корзину на django?

Лучший ответ: 1. Оформи код соответственно
2. Модель херня. Научись О2М связям
3. Стоит ли хранить корзины на бэкенде? Их хранят на бэкенде, когда любят спамить людей, бросивших корзины
4. В коде basket = Basket.objects.filter(user=request.user, book=book).first()
    if not basket:
        basket = Basket(user=request.user, book=book)
        basket.quantity += 1
        basket.save()
Ошибка
Вот пример одной корзины, которую я делал несколько лет назад.basket.pyfrom collections import UserDict

from core.models import ProductOption, Product
from .models import Item


class Basket(UserDict):
    changed = False

    def add(self, quantity=0, option=None, set_=False):
        self.changed = True
        id_ = str(option.product.id)
        option = str(option.id)
        self.setdefault(id_, {})
        self[id_].setdefault(option, 0)

        if set_:
            self[id_][option] = quantity
        else:
            self[id_][option] += quantity

        if self[id_][option] <= 0:
            del self[id_][option]
            if not self[id_]:
                del self[id_]
            return 0
        else:
            return self[id_][option]

    @property
    def total_count(self):
        return sum(x for product, options in self.items() for _, x in options.items())

    @property
    def total_price(self):
        prices = {str(id_): price for id_, price in
                  Product.objects.filter(id__in=self.keys()).values_list('id', 'price')}
        return sum(x * prices[product] for product, options in self.items() for _, x in options.items())

    def cost(self, option):
        price = option.product.price
        return self.count_option(option) * price

    def count_option(self, option):
        product_id = str(option.product.id)
        option_id = str(option.id)
        return self.get(product_id, {}).get(option_id, 0)

    def flush(self):
        self.changed = True
        for key in list(self):
            del self[key]

    def build_order(self, order):
        items = []
        for product_id, data in self.items():
            product = Product.objects.get(id=product_id)

            for option_id, quantity in data.items():
                if quantity == 0:
                    continue
                option = None
                if option_id != '0':
                    option = ProductOption.objects.get(id=option_id)
                items.append(
                    Item(order=order, option=option, quantity=quantity, product=product)
                )
        order.items.bulk_create(items)
        self.flush()
        return order

    def fix(self):
        """Фиксит корзину на случай, если опции удалили, а они находятся в корзине"""
        ids = self.keys()
        exist_in_db = (Product.objects
                       .filter(id__in=ids, options__in_stock=True, options__show=True)
                       .values_list('id', flat=True))
        to_remove = set(ids) - set(str(x) for x in exist_in_db)
        for id_ in to_remove:
            del self[id_]
        if to_remove:
            self.changed = True

    def to_dict(self):
        return dict(self)middleware.pyfrom django.utils.deprecation import MiddlewareMixin
from .basket import Basket


class BasketMiddleware(MiddlewareMixin):
    def process_request(self, request):
        request.basket = Basket(request.session.get('basket', {}))

    def process_response(self, request, response):
        if getattr(request, 'basket', None) is None:
            return response
        if request.basket.changed:
            request.session['basket'] = request.basket.to_dict()
            request.session.save()
        return responsesettings.py# ...
SESSION_ENGINE = 'django.contrib.sessions.backends.signed_cookies'
MIDDLEWARE_CLASSES = [
    # ...
    'orders.middleware.BasketMiddleware',
]
# ...views.py@method_decorator(csrf_exempt, name='dispatch')
class ChangeBasketOption(View):
    def post(self, request):
        change = int(request.POST.get('change'))
        pk = int(request.POST.get('id'))
        set_ = bool(request.POST.get('set', 0))
        option = get_object_or_404(ProductOption, pk=pk)
        value = request.basket.add(option=option, quantity=change, set_=set_)
        cost = request.basket.cost(option)
        return JsonResponse({
            'value': value,
            'cost': cost,
            'total': request.basket.total_price
        })


class Basket(FormView):
    template_name = 'orders/basket.html'
    form_class = OrderForm
    success_url = reverse_lazy('ordered')

    def get(self, request, *args, **kwargs):
        request.basket.fix()
        return super().get(request, *args, **kwargs)

    def get_context_data(self, **kwargs):
        kwargs['products'] = Product.objects.filter(id__in=self.request.basket.keys())
        kwargs['can_order'] = self.request.basket.total_price >= min_order_cost()
        return super().get_context_data(**kwargs)

    def get_form_kwargs(self):
        kwargs = super().get_form_kwargs()
        kwargs['user'] = self.request.user
        return kwargs

    def form_valid(self, form):
        order = form.save()
        order = self.request.basket.build_order(order)
        mail_new_order(order)
        return super().form_valid(form)
Смысл в том, что все корзины хранятся только в куках (из-за SESSION_ENGINE) у самих пользователей. Это означает, что хоть миллиард юзеров зайдёт и добавят по миллиону товаров — они не прибавят ни байта занимаемого места на HDD, пока не сделают заказ. К тому же, куки — очень быстрое хранилище и изменения в корзине происходят моментально. К примеру, AJAX запросы в ChangeBasketOption в среднем занимают 30мс в браузере.

Худший ответ: Можете посмотреть код моего интернет-магазина. Там хорошо реализована корзина.
https://github.com/UlugbekMuslitdinov/exizmat