| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120 |
- from itertools import chain
- import logging
-
- from django.db.models import Q
- from django.utils.timezone import now
-
- from oscar.core.loading import get_model
- from oscar.apps.offer import results
-
- ConditionalOffer = get_model('offer', 'ConditionalOffer')
-
- logger = logging.getLogger('oscar.offers')
-
-
- class OfferApplicationError(Exception):
- pass
-
-
- class Applicator(object):
-
- def apply(self, request, basket):
- """
- Apply all relevant offers to the given basket.
-
- The request is passed too as sometimes the available offers
- are dependent on the user (eg session-based offers).
- """
- offers = self.get_offers(request, basket)
- self.apply_offers(basket, offers)
-
- def apply_offers(self, basket, offers):
- applications = results.OfferApplications()
- for offer in offers:
- num_applications = 0
- # Keep applying the offer until either
- # (a) We reach the max number of applications for the offer.
- # (b) The benefit can't be applied successfully.
- while num_applications < offer.get_max_applications(basket.owner):
- result = offer.apply_benefit(basket)
- num_applications += 1
- if not result.is_successful:
- break
- applications.add(offer, result)
- if result.is_final:
- break
-
- # Store this list of discounts with the basket so it can be
- # rendered in templates
- basket.offer_applications = applications
-
- def get_offers(self, request, basket):
- """
- Return all offers to apply to the basket.
-
- This method should be subclassed and extended to provide more
- sophisticated behaviour. For instance, you could load extra offers
- based on the session or the user type.
- """
- site_offers = self.get_site_offers()
- basket_offers = self.get_basket_offers(basket, request.user)
- user_offers = self.get_user_offers(request.user)
- session_offers = self.get_session_offers(request)
-
- return list(sorted(chain(
- session_offers, basket_offers, user_offers, site_offers),
- key=lambda o: o.priority, reverse=True))
-
- def get_site_offers(self):
- """
- Return site offers that are available to all users
- """
- cutoff = now()
- date_based = Q(
- Q(start_datetime__lte=cutoff),
- Q(end_datetime__gte=cutoff) | Q(end_datetime=None),
- )
-
- nondate_based = Q(start_datetime=None, end_datetime=None)
-
- qs = ConditionalOffer.objects.filter(
- date_based | nondate_based,
- offer_type=ConditionalOffer.SITE,
- status=ConditionalOffer.OPEN)
- # Using select_related with the condition/benefit ranges doesn't seem
- # to work. I think this is because both the related objects have the
- # FK to range with the same name.
- return qs.select_related('condition', 'benefit')
-
- def get_basket_offers(self, basket, user):
- """
- Return basket-linked offers such as those associated with a voucher
- code
- """
- offers = []
- if not basket.id:
- return offers
-
- for voucher in basket.vouchers.all():
- if voucher.is_active() and voucher.is_available_to_user(user):
- basket_offers = voucher.offers.all()
- for offer in basket_offers:
- offer.set_voucher(voucher)
- offers = list(chain(offers, basket_offers))
- return offers
-
- def get_user_offers(self, user):
- """
- Returns offers linked to this particular user.
-
- Eg: student users might get 25% off
- """
- return []
-
- def get_session_offers(self, request):
- """
- Returns temporary offers linked to the current session.
-
- Eg: visitors coming from an affiliate site get a 10% discount
- """
- return []
|