Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

strategy.py 6.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. from collections import namedtuple
  2. from decimal import Decimal as D
  3. from . import availability, prices
  4. # A container for policies
  5. StockInfo = namedtuple('StockInfo', ['price', 'availability', 'stockrecord'])
  6. class Selector(object):
  7. """
  8. Responsible for returning the appropriate strategy class for a given
  9. user/session.
  10. This can be called in three ways:
  11. 1. Passing a request and user. This is for determining
  12. prices/availability for a normal user browsing the site.
  13. 2. Passing just the user. This is for offline processes that don't
  14. have a request instance but do know which user to determine prices for.
  15. 3. Passing nothing. This is for offline processes that don't
  16. correspond to a specific user. Eg, determining a price to store in
  17. a search index.
  18. """
  19. def strategy(self, request=None, user=None, **kwargs):
  20. """
  21. Return an instanticated strategy instance
  22. """
  23. # Default to the backwards-compatible strategy of picking the first
  24. # stockrecord.
  25. return Default(request)
  26. class Base(object):
  27. """
  28. The base strategy class
  29. Given a product, strategies are responsible for returning a ``StockInfo``
  30. instance which contains:
  31. - The appropriate stockrecord for this customer
  32. - A pricing policy instance
  33. - An availability policy instance
  34. """
  35. def __init__(self, request=None):
  36. self.request = request
  37. self.user = None
  38. if request and request.user.is_authenticated():
  39. self.user = request.user
  40. def fetch(self, product, stockrecord=None):
  41. """
  42. Given a product, return a ``StockInfo`` instance.
  43. The ``StockInfo`` class is a named tuple with attributes:
  44. - ``price``: a pricing policy object.
  45. - ``availability``: an availability policy object.
  46. - ``stockrecord``: the stockrecord that is being used to calculate prices and
  47. If a stockrecord is passed, return the appropriate ``StockInfo``
  48. instance for that product and stockrecord is returned.
  49. """
  50. raise NotImplementedError(
  51. "A strategy class must define a fetch method "
  52. "for returning the availability and pricing "
  53. "information."
  54. )
  55. class Structured(Base):
  56. """
  57. A strategy class which provides separate, overridable methods for
  58. determining the 3 things that a ``StockInfo`` instance requires:
  59. #) A stockrecord
  60. #) A pricing policy
  61. #) An availability policy
  62. """
  63. def fetch(self, product, stockrecord=None):
  64. """
  65. Return the appropriate stockinfo instance.
  66. This method is not intended to be overridden.
  67. """
  68. if stockrecord is None:
  69. stockrecord = self.select_stockrecord(product)
  70. return StockInfo(
  71. price=self.pricing_policy(product, stockrecord),
  72. availability=self.availability_policy(product, stockrecord),
  73. stockrecord=stockrecord)
  74. def select_stockrecord(self, product):
  75. """
  76. Select the appropriate stockrecord
  77. """
  78. raise NotImplementedError(
  79. "A structured strategy class must define a "
  80. "'select_stockrecord' method")
  81. def pricing_policy(self, product, stockrecord):
  82. """
  83. Return the appropriate pricing policy
  84. """
  85. raise NotImplementedError(
  86. "A structured strategy class must define a "
  87. "'pricing_policy' method")
  88. def availability_policy(self, product, stockrecord):
  89. """
  90. Return the appropriate availability policy
  91. """
  92. raise NotImplementedError(
  93. "A structured strategy class must define a "
  94. "'availability_policy' method")
  95. # Mixins - these can be used to construct the appropriate strategy class
  96. class UseFirstStockRecord(object):
  97. """
  98. Stockrecord selection mixin for use with the ``Structured`` base strategy.
  99. This mixin picks the first (normally only) stockrecord to fulfil a product.
  100. This is backwards compatible with Oscar<0.6 where only one stockrecord per
  101. product was permitted.
  102. """
  103. def select_stockrecord(self, product):
  104. try:
  105. return product.stockrecords.all()[0]
  106. except IndexError:
  107. return None
  108. class StockRequired(object):
  109. """
  110. Availability policy mixin for use with the ``Structured`` base strategy.
  111. This mixin ensures that a product can only be bought if it has stock
  112. available (if stock is being tracked).
  113. """
  114. def availability_policy(self, product, stockrecord):
  115. if not stockrecord:
  116. return availability.Unavailable()
  117. if not product.get_product_class().track_stock:
  118. return availability.Available()
  119. else:
  120. return availability.StockRequired(
  121. stockrecord.net_stock_level)
  122. class NoTax(object):
  123. """
  124. Pricing policy mixin for use with the ``Structured`` base strategy.
  125. This mixin specifies zero tax and uses the ``price_excl_tax`` from the
  126. stockrecord.
  127. """
  128. def pricing_policy(self, product, stockrecord):
  129. if not stockrecord:
  130. return prices.Unavailable()
  131. return prices.FixedPrice(
  132. currency=stockrecord.price_currency,
  133. excl_tax=stockrecord.price_excl_tax,
  134. tax=D('0.00'))
  135. class FixedRateTax(object):
  136. """
  137. Pricing policy mixin for use with the ``Structured`` base strategy.
  138. This mixin applies a fixed rate tax to the base price from the product's
  139. stockrecord.
  140. """
  141. rate = D('0.20')
  142. def pricing_policy(self, product, stockrecord):
  143. if not stockrecord:
  144. return prices.Unavailable()
  145. return prices.FixedPrice(
  146. currency=stockrecord.price_currency,
  147. excl_tax=stockrecord.price_excl_tax,
  148. tax=stockrecord.price_excl_tax * self.rate)
  149. class DeferredTax(object):
  150. """
  151. Pricing policy mixin for use with the ``Structured`` base strategy.
  152. This mixin does not specify the product tax and is suitable to territories
  153. where tax isn't known until late in the checkout process.
  154. """
  155. def pricing_policy(self, product, stockrecord):
  156. if not stockrecord:
  157. return prices.Unavailable()
  158. return prices.FixedPrice(
  159. currency=stockrecord.price_currency,
  160. excl_tax=stockrecord.price_excl_tax)
  161. # Example strategy composed of above mixins. For real projects, it's likely
  162. # you'll want to use a different pricing mixin as you'll probably want to
  163. # charge tax!
  164. class Default(UseFirstStockRecord, StockRequired, NoTax, Structured):
  165. """
  166. Default stock/price strategy that uses the first found stockrecord for a
  167. product, ensures that stock is available (unless the product class
  168. indicates that we don't need to track stock) and charges zero tax.
  169. """
  170. class US(UseFirstStockRecord, StockRequired, DeferredTax, Structured):
  171. """
  172. Default strategy for the USA (just for testing really)
  173. """