Files
santeh-ray/orders/views.py
Azimkin a84b9af926
All checks were successful
Build and Push Docker Image / build (push) Successful in 26s
Implement guest buy now checkout
2026-06-15 23:16:10 +02:00

245 lines
8.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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, require_http_methods
from items.models import Color, Item
from orders.forms import CheckoutForm
from orders.models import CartItem, Order
from orders.services import (
CheckoutLine,
add_item_to_cart,
calculate_checkout_totals,
create_order_from_item,
create_order_from_cart,
format_currency,
get_user_cart_items,
remove_item_from_cart,
send_order_confirmation_email,
update_cart_item_quantity,
)
GUEST_ORDER_SESSION_KEY = "guest_order_ids"
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
def build_checkout_context(
checkout_items: list,
form: CheckoutForm,
*,
direct_checkout: bool = False,
direct_item: Item | None = None,
) -> dict:
total_quantity = sum(checkout_item.quantity for checkout_item in checkout_items)
delivery_method = form.data.get("delivery_method") if form.is_bound else Order.DeliveryMethod.COURIER
totals = calculate_checkout_totals(checkout_items, delivery_method or Order.DeliveryMethod.COURIER)
return {
"cart_items": checkout_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"]),
"direct_checkout": direct_checkout,
"direct_item": direct_item,
}
def get_checkout_initial(user) -> dict:
if not getattr(user, "is_authenticated", False):
return {}
return {
"customer_name": user.get_full_name() or user.get_username(),
"email": user.email,
}
def remember_guest_order(request: HttpRequest, order: Order) -> None:
if request.user.is_authenticated:
return
guest_order_ids = request.session.get(GUEST_ORDER_SESSION_KEY, [])
guest_order_ids.append(order.id)
request.session[GUEST_ORDER_SESSION_KEY] = guest_order_ids
@login_required
def cart(request: HttpRequest) -> HttpResponse:
cart_items = get_user_cart_items(request.user)
form = CheckoutForm(initial=get_checkout_initial(request.user))
return render(request, "orders/cart.html", build_checkout_context(cart_items, form))
@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():
return render(request, "orders/cart.html", build_checkout_context(cart_items, form), 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)
def success(request: HttpRequest, order_id: int) -> HttpResponse:
orders = Order.objects.prefetch_related("items")
if request.user.is_authenticated:
order = get_object_or_404(orders, pk=order_id, user=request.user)
else:
guest_order_ids = request.session.get(GUEST_ORDER_SESSION_KEY, [])
if order_id not in guest_order_ids:
raise Http404()
order = get_object_or_404(orders, pk=order_id, user__isnull=True)
return render(request, "orders/success.html", {"order": order})
@require_http_methods(["GET", "POST"])
def buy_now(request: HttpRequest, item_id: int) -> HttpResponse:
item = get_object_or_404(
Item.objects.select_related("category", "subcategory").prefetch_related("images"),
pk=item_id,
is_available=True,
)
checkout_items = [CheckoutLine(item=item)]
if request.method == "POST":
form = CheckoutForm(request.POST)
if not form.is_valid():
return render(
request,
"orders/cart.html",
build_checkout_context(
checkout_items,
form,
direct_checkout=True,
direct_item=item,
),
status=400,
)
try:
order = create_order_from_item(request.user, item, form.cleaned_data)
except ValidationError as error:
messages.error(request, error.message)
return redirect("detail", category=item.category.slug, slug=item.slug)
remember_guest_order(request, order)
try:
send_order_confirmation_email(order)
except Exception:
messages.error(request, "Заказ создан, но письмо с деталями не удалось отправить. Мы свяжемся с вами.")
else:
messages.success(request, "Заказ успешно оформлен. Детали отправлены на почту.")
return redirect("orders:success", order_id=order.id)
form = CheckoutForm(initial=get_checkout_initial(request.user))
return render(
request,
"orders/cart.html",
build_checkout_context(
checkout_items,
form,
direct_checkout=True,
direct_item=item,
),
)
@login_required
@require_POST
def add(request: HttpRequest) -> HttpResponse:
item = get_object_or_404(Item, pk=request.POST.get("item_id"))
color_id = request.POST.get("color_id")
color = None
if color_id:
try:
color = Color.objects.get(pk=color_id)
except Color.DoesNotExist:
pass
try:
_, created = add_item_to_cart(request.user, item, color=color)
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")))