選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

mixins.py 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  1. import logging
  2. from django.http import HttpResponseRedirect
  3. from django.core.urlresolvers import reverse
  4. from django.contrib.sites.models import Site
  5. from django.core.exceptions import ObjectDoesNotExist
  6. from django.db.models import get_model
  7. from oscar.core.loading import get_class
  8. OrderCreator = get_class('order.utils', 'OrderCreator')
  9. Dispatcher = get_class('customer.utils', 'Dispatcher')
  10. CheckoutSessionMixin = get_class('checkout.session', 'CheckoutSessionMixin')
  11. ShippingAddress = get_model('order', 'ShippingAddress')
  12. CommunicationEvent = get_model('order', 'CommunicationEvent')
  13. PaymentEventType = get_model('order', 'PaymentEventType')
  14. PaymentEvent = get_model('order', 'PaymentEvent')
  15. PaymentEventQuantity = get_model('order', 'PaymentEventQuantity')
  16. UserAddress = get_model('address', 'UserAddress')
  17. Basket = get_model('basket', 'Basket')
  18. CommunicationEventType = get_model('customer', 'CommunicationEventType')
  19. # Standard logger for checkout events
  20. logger = logging.getLogger('oscar.checkout')
  21. class OrderPlacementMixin(CheckoutSessionMixin):
  22. """
  23. Mixin which provides functionality for placing orders.
  24. """
  25. # Any payment sources should be added to this list as part of the
  26. # _handle_payment method. If the order is placed successfully, then
  27. # they will be persisted.
  28. _payment_sources = None
  29. _payment_events = None
  30. # Default code for the email to send after successful checkout
  31. communication_type_code = 'ORDER_PLACED'
  32. def handle_order_placement(self, order_number, basket, total_incl_tax,
  33. total_excl_tax, user=None, **kwargs):
  34. """
  35. Write out the order models and return the appropriate HTTP response
  36. We deliberately pass the basket in here as the one tied to the request
  37. isn't necessarily the correct one to use in placing the order. This
  38. can happen when a basket gets frozen.
  39. """
  40. order = self.place_order(order_number, basket, total_incl_tax,
  41. total_excl_tax, user, **kwargs)
  42. basket.set_as_submitted()
  43. return self.handle_successful_order(order)
  44. def add_payment_source(self, source):
  45. if self._payment_sources is None:
  46. self._payment_sources = []
  47. self._payment_sources.append(source)
  48. def add_payment_event(self, event_type_name, amount):
  49. event_type, __ = PaymentEventType.objects.get_or_create(
  50. name=event_type_name)
  51. if self._payment_events is None:
  52. self._payment_events = []
  53. event = PaymentEvent(event_type=event_type, amount=amount)
  54. self._payment_events.append(event)
  55. def handle_successful_order(self, order):
  56. """
  57. Handle the various steps required after an order has been successfully
  58. placed.
  59. Override this view if you want to perform custom actions when an
  60. order is submitted.
  61. """
  62. # Send confirmation message (normally an email)
  63. self.send_confirmation_message(order)
  64. # Flush all session data
  65. self.checkout_session.flush()
  66. # Save order id in session so thank-you page can load it
  67. self.request.session['checkout_order_id'] = order.id
  68. return HttpResponseRedirect(self.get_success_url())
  69. def get_success_url(self):
  70. return reverse('checkout:thank-you')
  71. def place_order(self, order_number, basket, total_incl_tax,
  72. total_excl_tax, user=None, **kwargs):
  73. """
  74. Writes the order out to the DB including the payment models
  75. """
  76. shipping_address = self.create_shipping_address(basket)
  77. shipping_method = self.get_shipping_method(basket)
  78. billing_address = self.create_billing_address(shipping_address)
  79. if 'status' not in kwargs:
  80. status = self.get_initial_order_status(basket)
  81. else:
  82. status = kwargs.pop('status')
  83. # We allow a user to be passed in to handle cases where the order is
  84. # being placed on behalf of someone else.
  85. if user is None:
  86. user = self.request.user
  87. # Set guest email address for anon checkout. Some libraries (eg
  88. # PayPal) will pass this explicitly so we take care not to clobber.
  89. if (not self.request.user.is_authenticated() and 'guest_email'
  90. not in kwargs):
  91. kwargs['guest_email'] = self.checkout_session.get_guest_email()
  92. order = OrderCreator().place_order(basket=basket,
  93. total_incl_tax=total_incl_tax,
  94. total_excl_tax=total_excl_tax,
  95. user=user,
  96. shipping_method=shipping_method,
  97. shipping_address=shipping_address,
  98. billing_address=billing_address,
  99. order_number=order_number,
  100. status=status,
  101. **kwargs)
  102. self.save_payment_details(order)
  103. return order
  104. def create_shipping_address(self, basket=None):
  105. """
  106. Create and returns the shipping address for the current order.
  107. If the shipping address was entered manually, then we simply
  108. write out a ShippingAddress model with the appropriate form data. If
  109. the user is authenticated, then we create a UserAddress from this data
  110. too so it can be re-used in the future.
  111. If the shipping address was selected from the user's address book,
  112. then we convert the UserAddress to a ShippingAddress.
  113. """
  114. if not basket:
  115. basket = self.request.basket
  116. if not basket.is_shipping_required():
  117. return None
  118. addr_data = self.checkout_session.new_shipping_address_fields()
  119. addr_id = self.checkout_session.user_address_id()
  120. if addr_data:
  121. addr = self.create_shipping_address_from_form_fields(addr_data)
  122. self.create_user_address(addr_data)
  123. elif addr_id:
  124. addr = self.create_shipping_address_from_user_address(addr_id)
  125. else:
  126. raise AttributeError("No shipping address data found")
  127. return addr
  128. def create_shipping_address_from_form_fields(self, addr_data):
  129. """Creates a shipping address model from the saved form fields"""
  130. shipping_addr = ShippingAddress(**addr_data)
  131. shipping_addr.save()
  132. return shipping_addr
  133. def create_user_address(self, addr_data):
  134. """
  135. For signed-in users, we create a user address model which will go
  136. into their address book.
  137. """
  138. if self.request.user.is_authenticated():
  139. addr_data['user_id'] = self.request.user.id
  140. user_addr = UserAddress(**addr_data)
  141. # Check that this address isn't already in the db as we don't want
  142. # to fill up the customer address book with duplicate addresses
  143. try:
  144. UserAddress._default_manager.get(
  145. hash=user_addr.generate_hash())
  146. except ObjectDoesNotExist:
  147. user_addr.save()
  148. def create_shipping_address_from_user_address(self, addr_id):
  149. """Creates a shipping address from a user address"""
  150. address = UserAddress._default_manager.get(pk=addr_id)
  151. # Increment the number of orders to help determine popularity of orders
  152. address.num_orders += 1
  153. address.save()
  154. shipping_addr = ShippingAddress()
  155. address.populate_alternative_model(shipping_addr)
  156. shipping_addr.save()
  157. return shipping_addr
  158. def create_billing_address(self, shipping_address=None):
  159. """
  160. Saves any relevant billing data (eg a billing address).
  161. """
  162. return None
  163. def save_payment_details(self, order):
  164. """
  165. Saves all payment-related details. This could include a billing
  166. address, payment sources and any order payment events.
  167. """
  168. self.save_payment_events(order)
  169. self.save_payment_sources(order)
  170. def save_payment_events(self, order):
  171. """
  172. Saves any relevant payment events for this order
  173. """
  174. if not self._payment_events:
  175. return
  176. for event in self._payment_events:
  177. event.order = order
  178. event.save()
  179. # We assume all lines are involved in the initial payment event
  180. for line in order.lines.all():
  181. PaymentEventQuantity.objects.create(
  182. event=event,
  183. line=line,
  184. quantity=line.quantity)
  185. def save_payment_sources(self, order):
  186. """
  187. Saves any payment sources used in this order.
  188. When the payment sources are created, the order model does not exist
  189. and so they need to have it set before saving.
  190. """
  191. if not self._payment_sources:
  192. return
  193. for source in self._payment_sources:
  194. source.order = order
  195. source.save()
  196. def get_initial_order_status(self, basket):
  197. return None
  198. def get_submitted_basket(self):
  199. basket_id = self.checkout_session.get_submitted_basket_id()
  200. return Basket._default_manager.get(pk=basket_id)
  201. def restore_frozen_basket(self):
  202. """
  203. Restores a frozen basket as the sole OPEN basket. Note that this also
  204. merges in any new products that have been added to a basket that has
  205. been created while payment.
  206. """
  207. try:
  208. fzn_basket = self.get_submitted_basket()
  209. except Basket.DoesNotExist:
  210. # Strange place. The previous basket stored in the session does
  211. # not exist.
  212. pass
  213. else:
  214. fzn_basket.thaw()
  215. if self.request.basket.id != fzn_basket.id:
  216. fzn_basket.merge(self.request.basket)
  217. self.request.basket = fzn_basket
  218. def send_confirmation_message(self, order, **kwargs):
  219. code = self.communication_type_code
  220. ctx = {'order': order,
  221. 'lines': order.lines.all()}
  222. if not self.request.user.is_authenticated():
  223. path = reverse('customer:anon-order',
  224. kwargs={'order_number': order.number,
  225. 'hash': order.verification_hash()})
  226. site = Site.objects.get_current()
  227. ctx['status_url'] = 'http://%s%s' % (site.domain, path)
  228. try:
  229. event_type = CommunicationEventType.objects.get(code=code)
  230. except CommunicationEventType.DoesNotExist:
  231. # No event-type in database, attempt to find templates for this
  232. # type and render them immediately to get the messages
  233. messages = CommunicationEventType.objects.get_and_render(code, ctx)
  234. event_type = None
  235. else:
  236. # Create order event
  237. CommunicationEvent._default_manager.create(order=order,
  238. event_type=event_type)
  239. messages = event_type.get_messages(ctx)
  240. if messages and messages['body']:
  241. logger.info("Order #%s - sending %s messages", order.number, code)
  242. dispatcher = Dispatcher(logger)
  243. dispatcher.dispatch_order_messages(order, messages,
  244. event_type, **kwargs)
  245. else:
  246. logger.warning("Order #%s - no %s communication event type",
  247. order.number, code)