Coverage for src/orders/views.py: 71%
35 statements
« prev ^ index » next coverage.py v7.9.2, created at 2025-08-04 12:59 +0300
« prev ^ index » next coverage.py v7.9.2, created at 2025-08-04 12:59 +0300
1"""
2This module defines the API views for order management.
4Key features:
5- Allows users to view their orders (list, retrieve)
6- Provides a custom endpoint to create orders from the shopping bag using a POST request
7- Ensures all order creation steps are performed atomically (all succeed or all fail)
8"""
10from django.db import transaction
12from rest_framework import viewsets, status
13from rest_framework.decorators import action
14from rest_framework.response import Response
15from rest_framework.exceptions import ValidationError
17from src.orders.serializers import OrderCreateSerializer, OrderGroupSerializer
18from src.orders.services import OrderService
19from src.orders.constants import OrderStatusMessages
22class OrderViewSet(viewsets.ReadOnlyModelViewSet):
23 """
24 ViewSet for user order management.
26 Inherits from DRF's ReadOnlyModelViewSet, which provides only the 'list' and 'retrieve' actions (GET requests).
27 This means users can view their orders but cannot create, update, or delete them through the standard API endpoints.
28 This is appropriate because order creation is handled by a custom workflow (create_from_shopping_bag),
29 and we want to prevent direct modification of orders via the API.
31 - Allows users to view their orders (list, retrieve)
32 - Provides a custom action to create orders from the shopping bag
33 - Uses @action to add a custom endpoint (not standard CRUD)
34 - Uses @transaction.atomic to ensure all DB changes for order creation are all-or-nothing
35 """
37 serializer_class = OrderGroupSerializer
39 def get_queryset(self):
40 # Returns all orders for the current user
41 return OrderService.get_user_orders(self.request.user)
43 def list(self, request, *args, **kwargs):
44 # Returns a grouped list of all orders for the current user
45 grouped_orders = OrderService.get_user_orders_grouped(request.user)
47 serializer_data = []
48 # Each group is a set of products purchased together
49 for _, orders in grouped_orders.items():
50 serializer_data.append({'orders': orders})
52 serializer = self.get_serializer(serializer_data, many=True)
53 return Response(serializer.data)
55 @action(
56 detail=False, # This action is not for a single order, but for the collection
57 methods=['post'], # Only POST requests are allowed
58 url_path='create-from-bag', # URL will be /orders/create-from-bag/
59 )
60 # Ensures all DB operations succeed or fail together (no partial orders)
61 @transaction.atomic
62 def create_from_shopping_bag(self, request):
63 """
64 Custom endpoint to create an order from the user's shopping bag.
66 - Uses @action to expose this as a POST endpoint at /orders/create-from-bag/
67 - Uses @transaction.atomic to guarantee that all database changes (order creation, inventory updates, etc.)
68 are performed as a single transaction. If any step fails, all changes are rolled back, preventing partial orders.
69 - Handles validation, order processing, and returns a summary of the created order group.
70 """
71 # Handles order creation from the user's shopping bag
72 serializer = OrderCreateSerializer(data=request.data)
74 if serializer.is_valid():
75 try:
76 # Process the order using validated payment data
77 orders = OrderService.process_order_from_shopping_bag(
78 user=request.user,
79 )
81 if orders:
82 # If orders were created, calculate the total price for the group
83 order_group_str = str(orders[0].order_group)
84 total_price = OrderService.calculate_order_group_total(
85 order_group_str, request.user
86 )
88 group_serializer = OrderGroupSerializer({'orders': orders})
90 return Response(
91 {
92 'message': OrderStatusMessages.STATUS_CREATED,
93 'order': group_serializer.data,
94 'total_items': len(orders),
95 'total_price': total_price,
96 },
97 status=status.HTTP_201_CREATED,
98 )
100 else:
101 # No orders were created (should not happen in normal flow)
102 return Response(
103 {
104 'message': OrderStatusMessages.STATUS_NO_ORDERS,
105 },
106 status=status.HTTP_400_BAD_REQUEST,
107 )
109 except ValidationError as e:
110 # Handles validation errors (e.g., invalid payment, empty bag)
111 return Response(
112 {
113 'error': str(e),
114 },
115 status=status.HTTP_400_BAD_REQUEST,
116 )
118 else:
119 # Handles serializer validation errors (e.g., missing fields)
120 return Response(
121 serializer.errors, status=status.HTTP_400_BAD_REQUEST
122 )