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.

utils.py 9.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. from django.contrib.sites.models import Site
  2. from django.conf import settings
  3. from oscar.apps.shipping.methods import Free
  4. from oscar.core.loading import import_module
  5. import_module('order.models', ['ShippingAddress', 'Order', 'Line',
  6. 'LinePrice', 'LineAttribute', 'OrderDiscount'], locals())
  7. import_module('order.signals', ['order_placed'], locals())
  8. class OrderNumberGenerator(object):
  9. """
  10. Simple object for generating order numbers.
  11. We need this as the order number is often required for payment
  12. which takes place before the order model has been created.
  13. """
  14. def order_number(self, basket):
  15. """
  16. Return an order number for a given basket
  17. """
  18. return 100000 + basket.id
  19. class OrderCreator(object):
  20. """
  21. Places the order by writing out the various models
  22. """
  23. def place_order(self, basket, total_incl_tax=None, total_excl_tax=None,
  24. user=None, shipping_method=None, shipping_address=None,
  25. billing_address=None, order_number=None, status=None, **kwargs):
  26. """
  27. Placing an order involves creating all the relevant models based on the
  28. basket and session data.
  29. """
  30. # Only a basket instance is required to place an order - everything else can be set
  31. # to defaults
  32. if basket.is_empty:
  33. raise ValueError("Empty baskets cannot be submitted")
  34. if not shipping_method:
  35. shipping_method = Free()
  36. if total_incl_tax is None or total_excl_tax is None:
  37. total_incl_tax = basket.total_incl_tax + shipping_method.basket_charge_incl_tax()
  38. total_excl_tax = basket.total_excl_tax + shipping_method.basket_charge_excl_tax()
  39. if not order_number:
  40. generator = OrderNumberGenerator()
  41. order_number = generator.order_number(basket)
  42. if not status and hasattr(settings, 'OSCAR_INITIAL_ORDER_STATUS'):
  43. status = getattr(settings, 'OSCAR_INITIAL_ORDER_STATUS')
  44. try:
  45. Order._default_manager.get(number=order_number)
  46. except Order.DoesNotExist:
  47. pass
  48. else:
  49. raise ValueError("There is already an order with number %s" % order_number)
  50. # Ok - everything seems to be in order, let's place the order
  51. order = self.create_order_model(user, basket, shipping_address,
  52. shipping_method, billing_address, total_incl_tax,
  53. total_excl_tax, order_number, status, **kwargs)
  54. for line in basket.all_lines():
  55. self.create_line_models(order, line)
  56. self.update_stock_records(line)
  57. for discount in basket.get_discounts():
  58. self.create_discount_model(order, discount)
  59. for voucher in basket.vouchers.all():
  60. self.record_voucher_usage(order, voucher, user)
  61. # Send signal for analytics to pick up
  62. order_placed.send(sender=self, order=order, user=user)
  63. return order
  64. def create_order_model(self, user, basket, shipping_address, shipping_method,
  65. billing_address, total_incl_tax, total_excl_tax,
  66. order_number, status, **extra_order_fields):
  67. """
  68. Creates an order model.
  69. """
  70. order_data = {'basket': basket,
  71. 'number': order_number,
  72. 'site': Site._default_manager.get_current(),
  73. 'total_incl_tax': total_incl_tax,
  74. 'total_excl_tax': total_excl_tax,
  75. 'shipping_incl_tax': shipping_method.basket_charge_incl_tax(),
  76. 'shipping_excl_tax': shipping_method.basket_charge_excl_tax(),
  77. 'shipping_method': shipping_method.name}
  78. if shipping_address:
  79. order_data['shipping_address'] = shipping_address
  80. if billing_address:
  81. order_data['billing_address'] = billing_address
  82. if user and user.is_authenticated():
  83. order_data['user_id'] = user.id
  84. if status:
  85. order_data['status'] = status
  86. if extra_order_fields:
  87. order_data.update(extra_order_fields)
  88. order = Order(**order_data)
  89. order.save()
  90. return order
  91. def get_partner_for_product(self, product):
  92. """
  93. Return the partner for a product
  94. """
  95. if product.has_stockrecord:
  96. return product.stockrecord.partner
  97. raise AttributeError("No partner found for product '%s'" % product)
  98. def create_line_models(self, order, basket_line, extra_line_fields=None):
  99. """
  100. Create the batch line model.
  101. You can set extra fields by passing a dictionary as the extra_line_fields value
  102. """
  103. partner = self.get_partner_for_product(basket_line.product)
  104. stockrecord = basket_line.product.stockrecord
  105. line_data = {'order': order,
  106. # Partner details
  107. 'partner': partner,
  108. 'partner_name': partner.name,
  109. 'partner_sku': stockrecord.partner_sku,
  110. # Product details
  111. 'product': basket_line.product,
  112. 'title': basket_line.product.get_title(),
  113. 'quantity': basket_line.quantity,
  114. # Price details
  115. 'line_price_excl_tax': basket_line.line_price_excl_tax_and_discounts,
  116. 'line_price_incl_tax': basket_line.line_price_incl_tax_and_discounts,
  117. 'line_price_before_discounts_excl_tax': basket_line.line_price_excl_tax,
  118. 'line_price_before_discounts_incl_tax': basket_line.line_price_incl_tax,
  119. # Reporting details
  120. 'unit_cost_price': stockrecord.cost_price,
  121. 'unit_price_incl_tax': basket_line.unit_price_incl_tax,
  122. 'unit_price_excl_tax': basket_line.unit_price_excl_tax,
  123. 'unit_retail_price': stockrecord.price_retail,
  124. # Shipping details
  125. 'est_dispatch_date': basket_line.product.stockrecord.dispatch_date
  126. }
  127. extra_line_fields = extra_line_fields or {}
  128. if hasattr(settings, 'OSCAR_INITIAL_LINE_STATUS'):
  129. if not (extra_line_fields and 'status' in extra_line_fields):
  130. extra_line_fields['status'] = getattr(settings, 'OSCAR_INITIAL_LINE_STATUS')
  131. if extra_line_fields:
  132. line_data.update(extra_line_fields)
  133. order_line = Line._default_manager.create(**line_data)
  134. self.create_line_price_models(order, order_line, basket_line)
  135. self.create_line_attributes(order, order_line, basket_line)
  136. self.create_additional_line_models(order, order_line, basket_line)
  137. return order_line
  138. def update_stock_records(self, line):
  139. line.product.stockrecord.allocate(line.quantity)
  140. def create_additional_line_models(self, order, order_line, basket_line):
  141. """
  142. Empty method designed to be overridden.
  143. Some applications require additional information about lines, this
  144. method provides a clean place to create additional models that
  145. relate to a given line.
  146. """
  147. pass
  148. def create_line_price_models(self, order, order_line, basket_line):
  149. """
  150. Creates the batch line price models
  151. """
  152. breakdown = basket_line.get_price_breakdown()
  153. for price_incl_tax, price_excl_tax, quantity in breakdown:
  154. LinePrice._default_manager.create(order=order,
  155. line=order_line,
  156. quantity=quantity,
  157. price_incl_tax=price_incl_tax,
  158. price_excl_tax=price_excl_tax)
  159. def create_line_attributes(self, order, order_line, basket_line):
  160. """
  161. Creates the batch line attributes.
  162. """
  163. for attr in basket_line.attributes.all():
  164. LineAttribute._default_manager.create(line=order_line,
  165. option=attr.option,
  166. type=attr.option.code,
  167. value=attr.value)
  168. def create_discount_model(self, order, discount):
  169. """
  170. Creates an order discount model for each discount attached to the basket.
  171. """
  172. order_discount = OrderDiscount(order=order, offer=discount['offer'], amount=discount['discount'])
  173. if discount['voucher']:
  174. order_discount.voucher = discount['voucher']
  175. order_discount.voucher_code = discount['voucher'].code
  176. order_discount.save()
  177. def record_voucher_usage(self, order, voucher, user):
  178. """
  179. Updates the models that care about this voucher.
  180. """
  181. voucher.record_usage(order, user)
  182. voucher.num_orders += 1
  183. voucher.save()