All checks were successful
Build and Push Docker Image / build (push) Successful in 26s
245 lines
8.5 KiB
Python
245 lines
8.5 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, 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")))
|