您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

middleware.py 7.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. from django.conf import settings
  2. from django.core.signing import Signer, BadSignature
  3. from django.utils.functional import SimpleLazyObject, empty
  4. from oscar.core.loading import get_model
  5. from oscar.core.loading import get_class
  6. Applicator = get_class('offer.utils', 'Applicator')
  7. Basket = get_model('basket', 'basket')
  8. Selector = get_class('partner.strategy', 'Selector')
  9. selector = Selector()
  10. class BasketMiddleware(object):
  11. # Middleware interface methods
  12. def process_request(self, request):
  13. # Keep track of cookies that need to be deleted (which can only be done
  14. # when we're processing the response instance).
  15. request.cookies_to_delete = []
  16. # Load stock/price strategy and assign to request (it will later be
  17. # assigned to the basket too).
  18. strategy = selector.strategy(request=request, user=request.user)
  19. request.strategy = strategy
  20. # We lazily load the basket so use a private variable to hold the
  21. # cached instance.
  22. request._basket_cache = None
  23. def load_full_basket():
  24. """
  25. Return the basket after applying offers.
  26. """
  27. basket = self.get_basket(request)
  28. basket.strategy = request.strategy
  29. self.apply_offers_to_basket(request, basket)
  30. return basket
  31. def load_basket_hash():
  32. """
  33. Load the basket and return the basket hash
  34. Note that we don't apply offers or check that every line has a
  35. stockrecord here.
  36. """
  37. basket = self.get_basket(request)
  38. if basket.id:
  39. return self.get_basket_hash(basket.id)
  40. # Use Django's SimpleLazyObject to only perform the loading work
  41. # when the attribute is accessed.
  42. request.basket = SimpleLazyObject(load_full_basket)
  43. request.basket_hash = SimpleLazyObject(load_basket_hash)
  44. def process_response(self, request, response):
  45. # Delete any surplus cookies
  46. cookies_to_delete = getattr(request, 'cookies_to_delete', [])
  47. for cookie_key in cookies_to_delete:
  48. response.delete_cookie(cookie_key)
  49. if not hasattr(request, 'basket'):
  50. return response
  51. # If the basket was never initialized we can safely return
  52. if (isinstance(request.basket, SimpleLazyObject)
  53. and request.basket._wrapped is empty):
  54. return response
  55. cookie_key = self.get_cookie_key(request)
  56. # Check if we need to set a cookie. If the cookies is already available
  57. # but is set in the cookies_to_delete list then we need to re-set it.
  58. has_basket_cookie = (
  59. cookie_key in request.COOKIES
  60. and cookie_key not in cookies_to_delete)
  61. # If a basket has had products added to it, but the user is anonymous
  62. # then we need to assign it to a cookie
  63. if (request.basket.id and not request.user.is_authenticated()
  64. and not has_basket_cookie):
  65. cookie = self.get_basket_hash(request.basket.id)
  66. response.set_cookie(
  67. cookie_key, cookie,
  68. max_age=settings.OSCAR_BASKET_COOKIE_LIFETIME, httponly=True)
  69. return response
  70. def get_cookie_key(self, request):
  71. """
  72. Returns the cookie name to use for storing a cookie basket.
  73. The method serves as a useful hook in multi-site scenarios where
  74. different baskets might be needed.
  75. """
  76. return settings.OSCAR_BASKET_COOKIE_OPEN
  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. # Helper methods
  95. def get_basket(self, request):
  96. """
  97. Return the open basket for this request
  98. """
  99. if request._basket_cache is not None:
  100. return request._basket_cache
  101. manager = Basket.open
  102. cookie_key = self.get_cookie_key(request)
  103. cookie_basket = self.get_cookie_basket(cookie_key, request, manager)
  104. if hasattr(request, 'user') and request.user.is_authenticated():
  105. # Signed-in user: if they have a cookie basket too, it means
  106. # that they have just signed in and we need to merge their cookie
  107. # basket into their user basket, then delete the cookie.
  108. try:
  109. basket, __ = manager.get_or_create(owner=request.user)
  110. except Basket.MultipleObjectsReturned:
  111. # Not sure quite how we end up here with multiple baskets.
  112. # We merge them and create a fresh one
  113. old_baskets = list(manager.filter(owner=request.user))
  114. basket = old_baskets[0]
  115. for other_basket in old_baskets[1:]:
  116. self.merge_baskets(basket, other_basket)
  117. # Assign user onto basket to prevent further SQL queries when
  118. # basket.owner is accessed.
  119. basket.owner = request.user
  120. if cookie_basket:
  121. self.merge_baskets(basket, cookie_basket)
  122. request.cookies_to_delete.append(cookie_key)
  123. elif cookie_basket:
  124. # Anonymous user with a basket tied to the cookie
  125. basket = cookie_basket
  126. else:
  127. # Anonymous user with no basket - instantiate a new basket
  128. # instance. No need to save yet.
  129. # we need to.
  130. basket = Basket()
  131. # Cache basket instance for the during of this request
  132. request._basket_cache = basket
  133. return basket
  134. def merge_baskets(self, master, slave):
  135. """
  136. Merge one basket into another.
  137. This is its own method to allow it to be overridden
  138. """
  139. master.merge(slave, add_quantities=False)
  140. def get_cookie_basket(self, cookie_key, request, manager):
  141. """
  142. Looks for a basket which is referenced by a cookie.
  143. If a cookie key is found with no matching basket, then we add
  144. it to the list to be deleted.
  145. """
  146. basket = None
  147. if cookie_key in request.COOKIES:
  148. basket_hash = request.COOKIES[cookie_key]
  149. try:
  150. basket_id = Signer().unsign(basket_hash)
  151. basket = Basket.objects.get(pk=basket_id, owner=None,
  152. status=Basket.OPEN)
  153. except (BadSignature, Basket.DoesNotExist):
  154. request.cookies_to_delete.append(cookie_key)
  155. return basket
  156. def apply_offers_to_basket(self, request, basket):
  157. if not basket.is_empty:
  158. Applicator().apply(request, basket)
  159. def get_basket_hash(self, basket_id):
  160. return Signer().sign(basket_id)