All checks were successful
Build and Push Docker Image / build (push) Successful in 50s
- Added `Order` and `OrderItem` models with migrations for managing orders and their details - Created `CheckoutForm` for validating order data and customer information - Built templates for order confirmation, checkout success, and emails (HTML and plain text) - Added logic for email notifications upon successful order placement - Enhanced shopping cart template with checkout panel integration - Updated styles and JavaScript for streamlined checkout UX - Refactored cart view to support item summaries and final totals during checkout
158 lines
6.1 KiB
Python
158 lines
6.1 KiB
Python
from django.contrib import messages
|
||
from django.contrib.auth.decorators import login_required
|
||
from django.core.exceptions import ValidationError
|
||
from django.http import Http404, HttpRequest, HttpResponse
|
||
from django.shortcuts import get_object_or_404, redirect, render
|
||
from django.urls import reverse
|
||
from django.utils.http import url_has_allowed_host_and_scheme
|
||
from django.views.decorators.http import require_POST
|
||
|
||
from items.models import Item
|
||
from orders.forms import CheckoutForm
|
||
from orders.models import CartItem, Order
|
||
from orders.services import (
|
||
add_item_to_cart,
|
||
calculate_checkout_totals,
|
||
create_order_from_cart,
|
||
format_currency,
|
||
get_user_cart_items,
|
||
remove_item_from_cart,
|
||
send_order_confirmation_email,
|
||
update_cart_item_quantity,
|
||
)
|
||
|
||
|
||
def resolve_next_url(request: HttpRequest, fallback: str) -> str:
|
||
next_url = request.POST.get("next") or fallback
|
||
if not url_has_allowed_host_and_scheme(
|
||
next_url,
|
||
allowed_hosts={request.get_host()},
|
||
require_https=request.is_secure(),
|
||
):
|
||
return fallback
|
||
return next_url
|
||
|
||
|
||
@login_required
|
||
def cart(request: HttpRequest) -> HttpResponse:
|
||
cart_items = get_user_cart_items(request.user)
|
||
total_quantity = sum(cart_item.quantity for cart_item in cart_items)
|
||
totals = calculate_checkout_totals(cart_items, Order.DeliveryMethod.COURIER)
|
||
form = CheckoutForm(initial={
|
||
"customer_name": request.user.get_full_name() or request.user.get_username(),
|
||
"email": request.user.email,
|
||
})
|
||
|
||
return render(request, "orders/cart.html", {
|
||
"cart_items": cart_items,
|
||
"checkout_form": form,
|
||
"total_quantity": total_quantity,
|
||
"products_total_value": totals["products_total"],
|
||
"products_total": format_currency(totals["products_total"]),
|
||
"discount_total": format_currency(totals["discount_total"]),
|
||
"delivery_total": format_currency(totals["delivery_total"]),
|
||
"floor_lift_total": format_currency(totals["floor_lift_total"]),
|
||
"total_amount": format_currency(totals["total"]),
|
||
})
|
||
|
||
|
||
@login_required
|
||
@require_POST
|
||
def checkout(request: HttpRequest) -> HttpResponse:
|
||
cart_items = get_user_cart_items(request.user)
|
||
if not cart_items:
|
||
messages.error(request, "Корзина пуста. Добавьте товары перед оформлением заказа.")
|
||
return redirect("orders:cart")
|
||
|
||
form = CheckoutForm(request.POST)
|
||
if not form.is_valid():
|
||
total_quantity = sum(cart_item.quantity for cart_item in cart_items)
|
||
delivery_method = request.POST.get("delivery_method") or Order.DeliveryMethod.COURIER
|
||
totals = calculate_checkout_totals(cart_items, delivery_method)
|
||
return render(request, "orders/cart.html", {
|
||
"cart_items": cart_items,
|
||
"checkout_form": form,
|
||
"total_quantity": total_quantity,
|
||
"products_total_value": totals["products_total"],
|
||
"products_total": format_currency(totals["products_total"]),
|
||
"discount_total": format_currency(totals["discount_total"]),
|
||
"delivery_total": format_currency(totals["delivery_total"]),
|
||
"floor_lift_total": format_currency(totals["floor_lift_total"]),
|
||
"total_amount": format_currency(totals["total"]),
|
||
}, status=400)
|
||
|
||
try:
|
||
order = create_order_from_cart(request.user, form.cleaned_data)
|
||
except ValidationError as error:
|
||
messages.error(request, error.message)
|
||
return redirect("orders:cart")
|
||
|
||
try:
|
||
send_order_confirmation_email(order)
|
||
except Exception:
|
||
messages.error(request, "Заказ создан, но письмо с деталями не удалось отправить. Мы свяжемся с вами.")
|
||
else:
|
||
messages.success(request, "Оплата прошла успешно. Детали заказа отправлены на почту.")
|
||
|
||
return redirect("orders:success", order_id=order.id)
|
||
|
||
|
||
@login_required
|
||
def success(request: HttpRequest, order_id: int) -> HttpResponse:
|
||
order = get_object_or_404(
|
||
Order.objects.prefetch_related("items"),
|
||
pk=order_id,
|
||
user=request.user,
|
||
)
|
||
return render(request, "orders/success.html", {"order": order})
|
||
|
||
|
||
@login_required
|
||
@require_POST
|
||
def add(request: HttpRequest) -> HttpResponse:
|
||
item = get_object_or_404(Item, pk=request.POST.get("item_id"))
|
||
|
||
try:
|
||
_, created = add_item_to_cart(request.user, item)
|
||
if created:
|
||
messages.success(request, f"Товар «{item.name}» добавлен в корзину.")
|
||
else:
|
||
messages.info(request, f"Товар «{item.name}» уже есть в корзине.")
|
||
except ValidationError as error:
|
||
messages.error(request, error.message)
|
||
|
||
return redirect(resolve_next_url(request, reverse("orders:cart")))
|
||
|
||
|
||
@login_required
|
||
@require_POST
|
||
def remove(request: HttpRequest) -> HttpResponse:
|
||
item = get_object_or_404(Item, pk=request.POST.get("item_id"))
|
||
removed = remove_item_from_cart(request.user, item.id)
|
||
|
||
if removed:
|
||
messages.success(request, f"Товар «{item.name}» удалён из корзины.")
|
||
else:
|
||
messages.info(request, f"Товар «{item.name}» уже отсутствует в корзине.")
|
||
|
||
return redirect(resolve_next_url(request, reverse("orders:cart")))
|
||
|
||
|
||
@login_required
|
||
@require_POST
|
||
def update_quantity(request: HttpRequest) -> HttpResponse:
|
||
item = get_object_or_404(Item, pk=request.POST.get("item_id"))
|
||
|
||
try:
|
||
quantity = int(request.POST.get("quantity"))
|
||
update_cart_item_quantity(request.user, item.id, quantity)
|
||
messages.success(request, f"Количество товара «{item.name}» обновлено.")
|
||
except (TypeError, ValueError):
|
||
messages.error(request, "Некорректное количество товара.")
|
||
except CartItem.DoesNotExist as error:
|
||
raise Http404() from error
|
||
except ValidationError as error:
|
||
messages.error(request, error.message)
|
||
|
||
return redirect(resolve_next_url(request, reverse("orders:cart")))
|