| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180 |
- 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
- Solr's index.
- """
- def strategy(self, request=None, user=None, **kwargs):
- # Default to the backwards-compatible strategy of picking the first
- # stockrecord.
- return Default(request)
-
-
- class Base(object):
- """
- The interface for a strategy. Only has to implement the fetch method,
- which is responsible for returning a StockInfo 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):
- raise NotImplementedError(
- "A strategy class must define a fetch method "
- "for returning the availability and pricing "
- "information."
- )
-
-
- class Structured(Base):
- """
- An intermediate class which should be sufficient for most use cases
- """
-
- def fetch(self, product, stockrecord=None):
- 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 to go with the passed product
- """
- 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):
- """
- Always use the first (normally only) stock record for a product
- """
-
- def select_stockrecord(self, product):
- try:
- return product.stockrecords.all()[0]
- except IndexError:
- return None
-
-
- class StockRequired(object):
-
- 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):
- """
- Prices are the same as the price_excl_tax field on the
- stockrecord with zero tax.
- """
-
- 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):
- """
- Prices are the same as the price_excl_tax field on the
- stockrecord with zero tax.
- """
- 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):
- """
- For when taxes aren't known until the shipping details are entered. Like
- in the USA
- """
-
- 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)
- """
|