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

1""" 

2This module defines the API views for order management. 

3 

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

9 

10from django.db import transaction 

11 

12from rest_framework import viewsets, status 

13from rest_framework.decorators import action 

14from rest_framework.response import Response 

15from rest_framework.exceptions import ValidationError 

16 

17from src.orders.serializers import OrderCreateSerializer, OrderGroupSerializer 

18from src.orders.services import OrderService 

19from src.orders.constants import OrderStatusMessages 

20 

21 

22class OrderViewSet(viewsets.ReadOnlyModelViewSet): 

23 """ 

24 ViewSet for user order management. 

25 

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. 

30 

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

36 

37 serializer_class = OrderGroupSerializer 

38 

39 def get_queryset(self): 

40 # Returns all orders for the current user 

41 return OrderService.get_user_orders(self.request.user) 

42 

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) 

46 

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

51 

52 serializer = self.get_serializer(serializer_data, many=True) 

53 return Response(serializer.data) 

54 

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. 

65 

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) 

73 

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 ) 

80 

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 ) 

87 

88 group_serializer = OrderGroupSerializer({'orders': orders}) 

89 

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 ) 

99 

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 ) 

108 

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 ) 

117 

118 else: 

119 # Handles serializer validation errors (e.g., missing fields) 

120 return Response( 

121 serializer.errors, status=status.HTTP_400_BAD_REQUEST 

122 )