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 4.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. from decimal import Decimal
  2. from itertools import chain
  3. import logging
  4. from oscar.core.loading import import_module
  5. import_module('offer.models', ['ConditionalOffer'], locals())
  6. logger = logging.getLogger('oscar.offers')
  7. class OfferApplicationError(Exception):
  8. pass
  9. # This needs hooking into the offer application system.
  10. class Discount(object):
  11. def __init__(self, offer):
  12. self.offer = offer
  13. self.discount = Decimal('0.00')
  14. self.frequency = 0
  15. def discount(self, discount):
  16. self.discount += discount
  17. self.frequency += 1
  18. def is_voucher_discount(self):
  19. return bool(self.offer.get_voucher())
  20. def get_voucher(self):
  21. return self.offer.get_voucher()
  22. class Applicator(object):
  23. """
  24. For applying offers to a basket.
  25. """
  26. max_applications = 10000
  27. def apply(self, request, basket):
  28. """
  29. Apply all relevant offers to the given basket. The request and user is passed
  30. too as sometimes the available offers are dependent on the user (eg session-based
  31. offers).
  32. """
  33. offers = self.get_offers(request, basket)
  34. logger.debug("Found %d offers to apply to basket %d", len(offers), basket.id)
  35. discounts = self.get_basket_discounts(basket, offers)
  36. # Store this list of discounts with the basket so it can be
  37. # rendered in templates
  38. basket.set_discounts(list(discounts.values()))
  39. def get_basket_discounts(self, basket, offers):
  40. discounts = {}
  41. for offer in offers:
  42. # For each offer, we keep trying to apply it until the
  43. # discount is 0
  44. applications = 0
  45. while True:
  46. discount = offer.apply_benefit(basket)
  47. applications += 1
  48. logger.debug("Found discount %.2f for basket %d from offer %d", discount, basket.id, offer.id)
  49. if discount > 0:
  50. if offer.id not in discounts:
  51. discounts[offer.id] = {'name': offer.name,
  52. 'offer': offer,
  53. 'voucher': offer.get_voucher(),
  54. 'freq': 0,
  55. 'discount': Decimal('0.00')}
  56. discounts[offer.id]['discount'] += discount
  57. discounts[offer.id]['freq'] += 1
  58. else:
  59. break
  60. if applications > self.max_applications:
  61. logger.error("Exceeded %d applications for offer %d on basket %d", self.max_applications, offer.id, basket.id)
  62. raise OfferApplicationError("Exceeded %d applications for offer %d on basket %d" % (self.max_applications, offer.id, basket.id))
  63. logger.debug("Finished applying offers to basket %d", basket.id)
  64. return discounts
  65. def get_offers(self, request, basket):
  66. """
  67. Return all offers to apply to the basket.
  68. This method should be subclassed and extended to provide more sophisticated
  69. behaviour. For instance, you could load extra offers based on the session or
  70. the user type.
  71. """
  72. site_offers = self.get_site_offers()
  73. basket_offers = self.get_basket_offers(basket, request.user)
  74. user_offers = self.get_user_offers(request.user)
  75. session_offers = self.get_session_offers(request)
  76. return list(chain(session_offers, basket_offers, user_offers, site_offers))
  77. def get_site_offers(self):
  78. """
  79. Return site offers that are available to all users
  80. """
  81. return ConditionalOffer.active.filter(offer_type="Site")
  82. def get_basket_offers(self, basket, user):
  83. """
  84. Return basket-linked offers such as those associated with a voucher code"""
  85. offers = []
  86. if not basket.id:
  87. return offers
  88. for voucher in basket.vouchers.all():
  89. if voucher.is_active() and voucher.is_available_to_user(user):
  90. basket_offers = voucher.offers.all()
  91. for offer in basket_offers:
  92. offer.set_voucher(voucher)
  93. offers = list(chain(offers, basket_offers))
  94. return offers
  95. def get_user_offers(self, user):
  96. """
  97. Returns offers linked to this particular user.
  98. Eg: student users might get 25% off
  99. """
  100. return []
  101. def get_session_offers(self, request):
  102. """
  103. Returns temporary offers linked to the current session.
  104. Eg: visitors coming from an affiliate site get a 10% discount
  105. """
  106. return []