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.

middleware.py 6.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. import zlib
  2. from django.conf import settings
  3. from django.db.models import get_model
  4. from oscar.core.loading import get_class
  5. Applicator = get_class('offer.utils', 'Applicator')
  6. Basket = get_model('basket', 'basket')
  7. Selector = get_class('partner.strategy', 'Selector')
  8. selector = Selector()
  9. class BasketMiddleware(object):
  10. def process_request(self, request):
  11. request.cookies_to_delete = []
  12. basket = self.get_basket(request)
  13. # Load stock/price strategy and assign to request and basket
  14. strategy = selector.strategy(
  15. request=request, user=request.user)
  16. request.strategy = basket.strategy = strategy
  17. self.ensure_basket_lines_have_stockrecord(basket)
  18. self.apply_offers_to_basket(request, basket)
  19. request.basket = basket
  20. def get_basket(self, request):
  21. """
  22. Return an open basket for this request
  23. """
  24. manager = Basket.open
  25. cookie_basket = self.get_cookie_basket(
  26. settings.OSCAR_BASKET_COOKIE_OPEN, request, manager)
  27. if hasattr(request, 'user') and request.user.is_authenticated():
  28. # Signed-in user: if they have a cookie basket too, it means
  29. # that they have just signed in and we need to merge their cookie
  30. # basket into their user basket, then delete the cookie
  31. try:
  32. basket, _ = manager.get_or_create(owner=request.user)
  33. except Basket.MultipleObjectsReturned:
  34. # Not sure quite how we end up here with multiple baskets
  35. # We merge them and create a fresh one
  36. old_baskets = list(manager.filter(owner=request.user))
  37. basket = old_baskets[0]
  38. for other_basket in old_baskets[1:]:
  39. self.merge_baskets(basket, other_basket)
  40. # Assign user onto basket to prevent further SQL queries when
  41. # basket.owner is accessed.
  42. basket.owner = request.user
  43. if cookie_basket:
  44. self.merge_baskets(basket, cookie_basket)
  45. request.cookies_to_delete.append(
  46. settings.OSCAR_BASKET_COOKIE_OPEN)
  47. elif cookie_basket:
  48. # Anonymous user with a basket tied to the cookie
  49. basket = cookie_basket
  50. else:
  51. # Anonymous user with no basket - we don't save the basket until
  52. # we need to.
  53. basket = Basket()
  54. return basket
  55. def merge_baskets(self, master, slave):
  56. """
  57. Merge one basket into another.
  58. This is its own method to allow it to be overridden
  59. """
  60. master.merge(slave, add_quantities=False)
  61. def process_response(self, request, response):
  62. # Delete any surplus cookies
  63. if hasattr(request, 'cookies_to_delete'):
  64. for cookie_key in request.cookies_to_delete:
  65. response.delete_cookie(cookie_key)
  66. # If a basket has had products added to it, but the user is anonymous
  67. # then we need to assign it to a cookie
  68. if (hasattr(request, 'basket') and request.basket.id > 0
  69. and not request.user.is_authenticated()
  70. and settings.OSCAR_BASKET_COOKIE_OPEN not in request.COOKIES):
  71. cookie = "%s_%s" % (
  72. request.basket.id, self.get_basket_hash(request.basket.id))
  73. response.set_cookie(
  74. settings.OSCAR_BASKET_COOKIE_OPEN, cookie,
  75. max_age=settings.OSCAR_BASKET_COOKIE_LIFETIME, httponly=True)
  76. return response
  77. def process_template_response(self, request, response):
  78. if hasattr(response, 'context_data'):
  79. if response.context_data is None:
  80. response.context_data = {}
  81. if 'basket' not in response.context_data:
  82. response.context_data['basket'] = request.basket
  83. else:
  84. # Occasionally, a view will want to pass an alternative basket
  85. # to be rendered. This can happen as part of checkout
  86. # processes where the submitted basket is frozen when the
  87. # customer is redirected to another site (eg PayPal). When the
  88. # customer returns and we want to show the order preview
  89. # template, we need to ensure that the frozen basket gets
  90. # rendered (not request.basket). We still keep a reference to
  91. # the request basket (just in case).
  92. response.context_data['request_basket'] = request.basket
  93. return response
  94. def get_cookie_basket(self, cookie_key, request, manager):
  95. """
  96. Looks for a basket which is referenced by a cookie.
  97. If a cookie key is found with no matching basket, then we add
  98. it to the list to be deleted.
  99. """
  100. basket = None
  101. if cookie_key in request.COOKIES:
  102. parts = request.COOKIES[cookie_key].split("_")
  103. if len(parts) != 2:
  104. return basket
  105. basket_id, basket_hash = parts
  106. if basket_hash == self.get_basket_hash(basket_id):
  107. try:
  108. basket = Basket.objects.get(pk=basket_id, owner=None,
  109. status=Basket.OPEN)
  110. except Basket.DoesNotExist:
  111. request.cookies_to_delete.append(cookie_key)
  112. else:
  113. request.cookies_to_delete.append(cookie_key)
  114. return basket
  115. def apply_offers_to_basket(self, request, basket):
  116. if not basket.is_empty:
  117. Applicator().apply(request, basket)
  118. def get_basket_hash(self, basket_id):
  119. return str(zlib.crc32(str(basket_id) + settings.SECRET_KEY))
  120. def ensure_basket_lines_have_stockrecord(self, basket):
  121. """
  122. Ensure each basket line has a stockrecord.
  123. This is to handle the backwards compatibility issue introduced in v0.6
  124. where basket lines began to require a stockrecord.
  125. """
  126. if not basket.id:
  127. return
  128. for line in basket.all_lines():
  129. if not line.stockrecord:
  130. self.ensure_line_has_stockrecord(basket, line)
  131. def ensure_line_has_stockrecord(self, basket, line):
  132. # Get details off line before deleting it
  133. product = line.product
  134. quantity = line.quantity
  135. options = []
  136. for attr in line.attributes.all():
  137. options.append({
  138. 'option': attr.option,
  139. 'value': attr.value})
  140. line.delete()
  141. # Attempt to re-add to basket with the appropriate stockrecord
  142. stock_info = basket.strategy.fetch(product)
  143. if stock_info.stockrecord:
  144. basket.add(product, stock_info, quantity,
  145. options=options)