@vadimbespruzhny

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

Только начал изучать django, строго не судите. Есть модель книги и модель корзины куда надо эту книгу и закинуть.
Для корзины создал отдельное приложение. На странице подробного описания книги создал ссылки для добавления ее в корзину.

a href="{% url 'basket:add' book.pk %}" Добавить книгу в корзину


По ссылке переходит, все работает, но объект книги в корзину не добавляет.
Что здесь не так?

Шаблон корзины:

{% extends "master.html" %}
{% block title %}<title>Basket</title>{% endblock %}
{% block content %}
    <h1>Basket</h1>
    {% if basket %}
    
    <ul>
        {% for book in basket %}
        <li>
            <a href="{{ book.get_absolute_url }}">{{ book.title }}</a> ({{book.author}})
        </li>
        {% endfor %}
    </ul>
    <hr>
    {% else %}
        <p>There are no books in the basket.</p>
    {% endif %}
{% endblock %}


Модель книги:

class Book(models.Model):
    title = models.CharField(max_length=200)
    author = models.ForeignKey('Author', on_delete=models.SET_NULL, null=True)
    summary = models.TextField(
        max_length=1000,
        null=True, blank=True,
        help_text="Enter a brief description of the book"
        )
    isbn = models.CharField(
        max_length=13,
        blank=True,
        help_text='13 Character <a href="https://www.isbn-international.org/content/what-isbn">ISBN number</a>')
    genre = models.ManyToManyField(
        Genre,
        help_text="Select a genre for this book", blank=True)

    def display_genre(self):
        return ','.join(genre.name for genre in self.genre.all()[:3])
    display_genre.short_description = 'Genre'

    def __str__(self):
        return self.title

    def get_absolute_url(self):
        return reverse("book_detail", args=[str(self.id)])


Модель корзины:

class Basket(models.Model):
    user = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
        related_name='basket')
    book = models.ForeignKey(
        Book, on_delete=models.CASCADE)
    quantity = models.PositiveIntegerField(
        verbose_name='количество', default=0)
    add_datetime = models.DateTimeField(
        verbose_name='время', auto_now_add=True)


Вьюха корзины:

def basket(request):
    content = {}
    return render(request, 'basketapp/basket.html', content)


def basket_add(request, pk):
    book = get_object_or_404(Book, pk=pk)
    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()
    return HttpResponseRedirect(reverse('basketapp:basket'))


def basket_remove(request, pk):
    content = {}
    return render(request, 'basketapp/basket.html', content)
  • Вопрос задан
  • 10324 просмотра
Пригласить эксперта
Ответы на вопрос 2
@deliro
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.py
from 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.py
from 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 response

settings.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мс в браузере.
Ответ написан
Комментировать
@UlugbekMuslitdinov
Можете посмотреть код моего интернет-магазина. Там хорошо реализована корзина.
https://github.com/UlugbekMuslitdinov/exizmat
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы