77from shop .order .models import Order , OrderProductOptionRelation , OrderProductRelation , SingleProductCart , TicketInfo
88from shop .payment_history .models import PaymentHistory , PaymentHistoryStatus , is_legal_payment_status_transition
99from shop .payment_history .tasks import send_payment_completed_notifications
10- from shop .product .models import Option , OptionGroup , Product , Tag
10+ from shop .product .models import Option , OptionGroup , Product , ProductTagRelation , Tag
1111from shop .serializers .cart_validation import (
1212 CartOrderableCheckSerializer ,
1313 OrderableCheckSerializerMode ,
1717FREE_CHECKOUT_PRICE_ERROR = "무료 주문은 결제 준비 금액이 0원이어야 합니다."
1818FREE_CHECKOUT_TARGET_ERROR = "무료 주문 대상을 찾을 수 없습니다."
1919FREE_CHECKOUT_TRANSITION_ERROR = "이미 처리된 주문이거나 무료 완료로 전환할 수 없습니다."
20+ _LOCKED_OPTIONS_ATTR = "_locked_checkout_options"
21+ _LOCKED_TICKET_INFO_ATTR = "_locked_checkout_ticket_info"
22+ _MISSING = object ()
2023
2124
2225def complete_free_checkout (cart_or_order : Order | SingleProductCart ) -> Order :
@@ -95,7 +98,10 @@ def _lock_order_allocation_rows(order: Order) -> list[OrderProductRelation]:
9598 return product_rels
9699
97100 list (Product .objects .select_for_update ().filter_active ().filter (id__in = product_ids ).order_by ("id" ))
98- list (Tag .objects .select_for_update ().filter_active ().filter (products__product_id__in = product_ids ).order_by ("id" ))
101+ active_tag_ids = (
102+ ProductTagRelation .objects .filter_active ().filter (product_id__in = product_ids ).order_by ().values ("tag_id" )
103+ )
104+ list (Tag .objects .select_for_update ().filter_active ().filter (id__in = active_tag_ids ).order_by ("id" ))
99105
100106 option_groups = list (
101107 OptionGroup .objects .select_for_update ().filter_active ().filter (product_id__in = product_ids ).order_by ("id" )
@@ -105,20 +111,37 @@ def _lock_order_allocation_rows(order: Order) -> list[OrderProductRelation]:
105111 if option_group_ids :
106112 list (Option .objects .select_for_update ().filter_active ().filter (group_id__in = option_group_ids ).order_by ("id" ))
107113
108- list (
114+ option_rels = list (
109115 OrderProductOptionRelation .objects .select_for_update ()
110116 .filter_active ()
111117 .filter (order_product_relation_id__in = product_rel_ids )
112118 .order_by ("id" )
113119 )
114- list (TicketInfo .objects .select_for_update ().filter_active ().filter (order_product_relation_id__in = product_rel_ids ))
120+ ticket_infos = list (
121+ TicketInfo .objects .select_for_update ().filter_active ().filter (order_product_relation_id__in = product_rel_ids )
122+ )
123+
124+ option_rels_by_product_rel_id : dict [int , list [OrderProductOptionRelation ]] = {}
125+ for option_rel in option_rels :
126+ option_rels_by_product_rel_id .setdefault (option_rel .order_product_relation_id , []).append (option_rel )
127+
128+ ticket_infos_by_product_rel_id = {
129+ ticket_info .order_product_relation_id : ticket_info for ticket_info in ticket_infos
130+ }
131+ for product_rel in product_rels :
132+ setattr (product_rel , _LOCKED_OPTIONS_ATTR , option_rels_by_product_rel_id .get (product_rel .id , []))
133+ setattr (product_rel , _LOCKED_TICKET_INFO_ATTR , ticket_infos_by_product_rel_id .get (product_rel .id ))
115134
116135 return product_rels
117136
118137
119138def _build_product_validation_payload (
120139 product_rel : OrderProductRelation , validation_mode : OrderableCheckSerializerMode
121140) -> dict :
141+ option_rels = getattr (product_rel , _LOCKED_OPTIONS_ATTR , _MISSING )
142+ if option_rels is _MISSING :
143+ option_rels = product_rel .options .filter_active ().order_by ("id" )
144+
122145 payload = {
123146 "product" : product_rel .product_id ,
124147 "donation_price" : product_rel .donation_price ,
@@ -128,15 +151,17 @@ def _build_product_validation_payload(
128151 "product_option" : option_rel .product_option_id ,
129152 "custom_response" : option_rel .custom_response ,
130153 }
131- for option_rel in product_rel . options . filter_active (). order_by ( "id" )
154+ for option_rel in option_rels
132155 ],
133156 }
134157
135158 if (
136159 validation_mode == OrderableCheckSerializerMode .CHECKOUT_SINGLE_PRODUCT
137160 and product_rel .product .category .is_ticket
138161 ):
139- ticket_info = getattr (product_rel , "ticket_info" , None )
162+ ticket_info = getattr (product_rel , _LOCKED_TICKET_INFO_ATTR , _MISSING )
163+ if ticket_info is _MISSING :
164+ ticket_info = getattr (product_rel , "ticket_info" , None )
140165 if ticket_info :
141166 payload ["ticket_info" ] = {
142167 "name" : ticket_info .name ,
0 commit comments