| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224 |
- from collections import namedtuple
- from decimal import Decimal as D
-
- from . import availability, prices
-
-
- # A container for policies
- StockInfo = namedtuple('StockInfo', ['price', 'availability', 'stockrecord'])
-
-
- class Selector(object):
- """
- Responsible for returning the appropriate strategy class for a given
- user/session.
-
- This can be called in three ways:
-
- 1. Passing a request and user. This is for determining
- prices/availability for a normal user browsing the site.
-
- 2. Passing just the user. This is for offline processes that don't
- have a request instance but do know which user to determine prices for.
-
- 3. Passing nothing. This is for offline processes that don't
- correspond to a specific user. Eg, determining a price to store in
- a search index.
- """
-
- def strategy(self, request=None, user=None, **kwargs):
- """
- Return an instanticated strategy instance
- """
- # Default to the backwards-compatible strategy of picking the first
- # stockrecord.
- return Default(request)
-
-
- class Base(object):
- """
- The base strategy class
-
- Given a product, strategies are responsible for returning a ``StockInfo``
- instance which contains:
-
- - The appropriate stockrecord for this customer
- - A pricing policy instance
- - An availability policy instance
- """
-
- def __init__(self, request=None):
- self.request = request
- self.user = None
- if request and request.user.is_authenticated():
- self.user = request.user
-
- def fetch(self, product, stockrecord=None):
- """
- Given a product, return a ``StockInfo`` instance.
-
- The ``StockInfo`` class is a named tuple with attributes:
-
- - ``price``: a pricing policy object.
- - ``availability``: an availability policy object.
- - ``stockrecord``: the stockrecord that is being used to calculate prices and
-
- If a stockrecord is passed, return the appropriate ``StockInfo``
- instance for that product and stockrecord is returned.
- """
- raise NotImplementedError(
- "A strategy class must define a fetch method "
- "for returning the availability and pricing "
- "information."
- )
-
-
- class Structured(Base):
- """
- A strategy class which provides separate, overridable methods for
- determining the 3 things that a ``StockInfo`` instance requires:
-
- #) A stockrecord
- #) A pricing policy
- #) An availability policy
- """
-
- def fetch(self, product, stockrecord=None):
- """
- Return the appropriate stockinfo instance.
-
- This method is not intended to be overridden.
- """
- if stockrecord is None:
- stockrecord = self.select_stockrecord(product)
- return StockInfo(
- price=self.pricing_policy(product, stockrecord),
- availability=self.availability_policy(product, stockrecord),
- stockrecord=stockrecord)
-
- def select_stockrecord(self, product):
- """
- Select the appropriate stockrecord
- """
- raise NotImplementedError(
- "A structured strategy class must define a "
- "'select_stockrecord' method")
-
- def pricing_policy(self, product, stockrecord):
- """
- Return the appropriate pricing policy
- """
- raise NotImplementedError(
- "A structured strategy class must define a "
- "'pricing_policy' method")
-
- def availability_policy(self, product, stockrecord):
- """
- Return the appropriate availability policy
- """
- raise NotImplementedError(
- "A structured strategy class must define a "
- "'availability_policy' method")
-
-
- # Mixins - these can be used to construct the appropriate strategy class
-
-
- class UseFirstStockRecord(object):
- """
- Stockrecord selection mixin for use with the ``Structured`` base strategy.
- This mixin picks the first (normally only) stockrecord to fulfil a product.
-
- This is backwards compatible with Oscar<0.6 where only one stockrecord per
- product was permitted.
- """
-
- def select_stockrecord(self, product):
- try:
- return product.stockrecords.all()[0]
- except IndexError:
- return None
-
-
- class StockRequired(object):
- """
- Availability policy mixin for use with the ``Structured`` base strategy.
- This mixin ensures that a product can only be bought if it has stock
- available (if stock is being tracked).
- """
-
- def availability_policy(self, product, stockrecord):
- if not stockrecord:
- return availability.Unavailable()
- if not product.get_product_class().track_stock:
- return availability.Available()
- else:
- return availability.StockRequired(
- stockrecord.net_stock_level)
-
-
- class NoTax(object):
- """
- Pricing policy mixin for use with the ``Structured`` base strategy.
- This mixin specifies zero tax and uses the ``price_excl_tax`` from the
- stockrecord.
- """
-
- def pricing_policy(self, product, stockrecord):
- if not stockrecord:
- return prices.Unavailable()
- return prices.FixedPrice(
- currency=stockrecord.price_currency,
- excl_tax=stockrecord.price_excl_tax,
- tax=D('0.00'))
-
-
- class FixedRateTax(object):
- """
- Pricing policy mixin for use with the ``Structured`` base strategy.
- This mixin applies a fixed rate tax to the base price from the product's
- stockrecord.
- """
- rate = D('0.20')
-
- def pricing_policy(self, product, stockrecord):
- if not stockrecord:
- return prices.Unavailable()
- return prices.FixedPrice(
- currency=stockrecord.price_currency,
- excl_tax=stockrecord.price_excl_tax,
- tax=stockrecord.price_excl_tax * self.rate)
-
-
- class DeferredTax(object):
- """
- Pricing policy mixin for use with the ``Structured`` base strategy.
- This mixin does not specify the product tax and is suitable to territories
- where tax isn't known until late in the checkout process.
- """
-
- def pricing_policy(self, product, stockrecord):
- if not stockrecord:
- return prices.Unavailable()
- return prices.FixedPrice(
- currency=stockrecord.price_currency,
- excl_tax=stockrecord.price_excl_tax)
-
-
- # Example strategy composed of above mixins. For real projects, it's likely
- # you'll want to use a different pricing mixin as you'll probably want to
- # charge tax!
-
-
- class Default(UseFirstStockRecord, StockRequired, NoTax, Structured):
- """
- Default stock/price strategy that uses the first found stockrecord for a
- product, ensures that stock is available (unless the product class
- indicates that we don't need to track stock) and charges zero tax.
- """
-
-
- class US(UseFirstStockRecord, StockRequired, DeferredTax, Structured):
- """
- Default strategy for the USA (just for testing really)
- """
|