You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

mixins.py 12KB

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