| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172 |
- import zlib
-
- from django.conf import settings
- from django.db.models import get_model
-
- from oscar.core.loading import get_class
-
- Applicator = get_class('offer.utils', 'Applicator')
- Basket = get_model('basket', 'basket')
- Selector = get_class('partner.strategy', 'Selector')
-
- selector = Selector()
-
-
- class BasketMiddleware(object):
-
- def process_request(self, request):
- request.cookies_to_delete = []
- basket = self.get_basket(request)
-
- # Load stock/price strategy and assign to request and basket
- strategy = selector.strategy(
- request=request, user=request.user)
- request.strategy = basket.strategy = strategy
-
- self.ensure_basket_lines_have_stockrecord(basket)
-
- self.apply_offers_to_basket(request, basket)
- request.basket = basket
-
- def get_basket(self, request):
- """
- Return an open basket for this request
- """
- manager = Basket.open
- cookie_basket = self.get_cookie_basket(
- settings.OSCAR_BASKET_COOKIE_OPEN, request, manager)
-
- if hasattr(request, 'user') and request.user.is_authenticated():
- # Signed-in user: if they have a cookie basket too, it means
- # that they have just signed in and we need to merge their cookie
- # basket into their user basket, then delete the cookie
- try:
- basket, _ = manager.get_or_create(owner=request.user)
- except Basket.MultipleObjectsReturned:
- # Not sure quite how we end up here with multiple baskets
- # We merge them and create a fresh one
- old_baskets = list(manager.filter(owner=request.user))
- basket = old_baskets[0]
- for other_basket in old_baskets[1:]:
- self.merge_baskets(basket, other_basket)
-
- # Assign user onto basket to prevent further SQL queries when
- # basket.owner is accessed.
- basket.owner = request.user
-
- if cookie_basket:
- self.merge_baskets(basket, cookie_basket)
- request.cookies_to_delete.append(
- settings.OSCAR_BASKET_COOKIE_OPEN)
- elif cookie_basket:
- # Anonymous user with a basket tied to the cookie
- basket = cookie_basket
- else:
- # Anonymous user with no basket - we don't save the basket until
- # we need to.
- basket = Basket()
- return basket
-
- def merge_baskets(self, master, slave):
- """
- Merge one basket into another.
-
- This is its own method to allow it to be overridden
- """
- master.merge(slave, add_quantities=False)
-
- def process_response(self, request, response):
- # Delete any surplus cookies
- if hasattr(request, 'cookies_to_delete'):
- for cookie_key in request.cookies_to_delete:
- response.delete_cookie(cookie_key)
-
- # If a basket has had products added to it, but the user is anonymous
- # then we need to assign it to a cookie
- if (hasattr(request, 'basket') and request.basket.id > 0
- and not request.user.is_authenticated()
- and settings.OSCAR_BASKET_COOKIE_OPEN not in request.COOKIES):
- cookie = "%s_%s" % (
- request.basket.id, self.get_basket_hash(request.basket.id))
- response.set_cookie(
- settings.OSCAR_BASKET_COOKIE_OPEN, cookie,
- max_age=settings.OSCAR_BASKET_COOKIE_LIFETIME, httponly=True)
- return response
-
- def process_template_response(self, request, response):
- if hasattr(response, 'context_data'):
- if response.context_data is None:
- response.context_data = {}
- if 'basket' not in response.context_data:
- response.context_data['basket'] = request.basket
- else:
- # Occasionally, a view will want to pass an alternative basket
- # to be rendered. This can happen as part of checkout
- # processes where the submitted basket is frozen when the
- # customer is redirected to another site (eg PayPal). When the
- # customer returns and we want to show the order preview
- # template, we need to ensure that the frozen basket gets
- # rendered (not request.basket). We still keep a reference to
- # the request basket (just in case).
- response.context_data['request_basket'] = request.basket
- return response
-
- def get_cookie_basket(self, cookie_key, request, manager):
- """
- Looks for a basket which is referenced by a cookie.
-
- If a cookie key is found with no matching basket, then we add
- it to the list to be deleted.
- """
- basket = None
- if cookie_key in request.COOKIES:
- parts = request.COOKIES[cookie_key].split("_")
- if len(parts) != 2:
- return basket
- basket_id, basket_hash = parts
- if basket_hash == self.get_basket_hash(basket_id):
- try:
- basket = Basket.objects.get(pk=basket_id, owner=None,
- status=Basket.OPEN)
- except Basket.DoesNotExist:
- request.cookies_to_delete.append(cookie_key)
- else:
- request.cookies_to_delete.append(cookie_key)
- return basket
-
- def apply_offers_to_basket(self, request, basket):
- if not basket.is_empty:
- Applicator().apply(request, basket)
-
- def get_basket_hash(self, basket_id):
- return str(zlib.crc32(str(basket_id) + settings.SECRET_KEY))
-
- def ensure_basket_lines_have_stockrecord(self, basket):
- """
- Ensure each basket line has a stockrecord.
-
- This is to handle the backwards compatibility issue introduced in v0.6
- where basket lines began to require a stockrecord.
- """
- if not basket.id:
- return
- for line in basket.all_lines():
- if not line.stockrecord:
- self.ensure_line_has_stockrecord(basket, line)
-
- def ensure_line_has_stockrecord(self, basket, line):
- # Get details off line before deleting it
- product = line.product
- quantity = line.quantity
- options = []
- for attr in line.attributes.all():
- options.append({
- 'option': attr.option,
- 'value': attr.value})
- line.delete()
-
- # Attempt to re-add to basket with the appropriate stockrecord
- stock_info = basket.strategy.fetch(product)
- if stock_info.stockrecord:
- basket.add(product, stock_info, quantity,
- options=options)
|