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)
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()
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)
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
# ...
SESSION_ENGINE = 'django.contrib.sessions.backends.signed_cookies'
MIDDLEWARE_CLASSES = [
# ...
'orders.middleware.BasketMiddleware',
]
# ...
@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)