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")))