class Image(AbstractPromotion): """ An image promotion is simply a named image which has an optional link to another part of the site (or another site). This can be used to model both banners and pods. """ _type = 'Image' name = models.CharField(_("Name"), max_length=128) link_url = ExtendedURLField( _('Link URL'), blank=True, help_text=_('This is where this promotion links to')) image = models.ImageField(_('Image'), upload_to=settings.OSCAR_PROMOTION_FOLDER, max_length=255) date_created = models.DateTimeField(auto_now_add=True) def __str__(self): return self.name class Meta: app_label = 'promotions' verbose_name = _("Image") verbose_name_plural = _("Image")
class AbstractProductList(AbstractPromotion): """ Abstract superclass for promotions which are essentially a list of products. 促销的抽象超类本质上是产品列表。 """ name = models.CharField(pgettext_lazy("Promotion product list title", "Title"), max_length=255) description = models.TextField(_("Description"), blank=True) link_url = ExtendedURLField(_('Link URL'), blank=True) link_text = models.CharField(_("Link text"), max_length=255, blank=True) date_created = models.DateTimeField(auto_now_add=True) class Meta: abstract = True app_label = 'promotions' verbose_name = _("Product list") verbose_name_plural = _("Product lists") def __str__(self): return self.name def template_context(self, request): return {'products': self.get_products()}
class PagePromotion(LinkedPromotion): """ A promotion embedded on a particular page. """ page_url = ExtendedURLField(max_length=128, db_index=True) def __unicode__(self): return u"%s on %s" % (self.content_object, self.page_url) def get_link(self): return reverse('promotions:page-click', kwargs={'page_promotion_id': self.id})
class AbstractProductList(AbstractPromotion): """ Abstract superclass for promotions which are essentially a list of products. """ name = models.CharField(_("Title"), max_length=255) description = models.TextField(null=True, blank=True) link_url = ExtendedURLField(blank=True, null=True) date_created = models.DateTimeField(auto_now_add=True) class Meta: abstract = True def __unicode__(self): return self.name
class PagePromotion(LinkedPromotion): """ A promotion embedded on a particular page. """ page_url = ExtendedURLField(_('Page URL'), max_length=128, db_index=True) def __str__(self): return u"%s on %s" % (self.content_object, self.page_url) def get_link(self): return reverse('promotions:page-click', kwargs={'page_promotion_id': self.id}) class Meta(LinkedPromotion.Meta): verbose_name = _("Page Promotion") verbose_name_plural = _("Page Promotions")
class Image(AbstractPromotion): """ An image promotion is simply a named image which has an optional link to another part of the site (or another site). This can be used to model both banners and pods. """ _type = 'Image' name = models.CharField(_("Name"), max_length=128) link_url = ExtendedURLField(blank=True, null=True, help_text="""This is where this promotion links to""") image = models.ImageField(upload_to=settings.OSCAR_PROMOTION_FOLDER) date_created = models.DateTimeField(auto_now_add=True) def __unicode__(self): return self.name
class CarouselImage(AbstractPromotion): """ """ _type = 'CarouselImage' name = models.CharField(_("Name"), max_length=128) url = ExtendedURLField(_('url'), max_length=128, db_index=True, verify_exists=True) image = models.ImageField(_('Image'), upload_to=settings.OSCAR_PROMOTION_FOLDER, max_length=255) date_created = models.DateTimeField(auto_now_add=True) message = models.TextField(_("HTML message")) def __unicode__(self): return self.name class Meta: verbose_name = _("Carousel Image") verbose_name_plural = _("Carousel Images")
class Slide(AbstractPromotion): """ Slide for bxslider """ _type = 'Slide' name = models.CharField(_("Name"), max_length=128) image = models.ImageField(_('Image'), upload_to=settings.OSCAR_PROMOTION_FOLDER, max_length=255) link_url = ExtendedURLField( _('Link URL'), blank=True, help_text=_('This is where this promotion links to')) body = models.TextField(_("BxSlider text block in HTML")) date_created = models.DateTimeField(auto_now_add=True) def __str__(self): return self.name class Meta: app_label = 'promotions' verbose_name = _("Slide") verbose_name_plural = _("Slide")
class ConditionalOffer(models.Model): """ A conditional offer (eg buy 1, get 10% off) """ name = models.CharField( max_length=128, unique=True, help_text="""This is displayed within the customer's basket""") slug = models.SlugField(max_length=128, unique=True, null=True) description = models.TextField(blank=True, null=True) # Offers come in a few different types: # (a) Offers that are available to all customers on the site. Eg a # 3-for-2 offer. # (b) Offers that are linked to a voucher, and only become available once # that voucher has been applied to the basket # (c) Offers that are linked to a user. Eg, all students get 10% off. The code # to apply this offer needs to be coded # (d) Session offers - these are temporarily available to a user after some trigger # event. Eg, users coming from some affiliate site get 10% off. SITE, VOUCHER, USER, SESSION = ("Site", "Voucher", "User", "Session") TYPE_CHOICES = ( (SITE, "Site offer - available to all users"), (VOUCHER, "Voucher offer - only available after entering the appropriate voucher code" ), (USER, "User offer - available to certain types of user"), (SESSION, "Session offer - temporary offer, available for a user for the duration of their session" ), ) offer_type = models.CharField(_("Type"), choices=TYPE_CHOICES, default=SITE, max_length=128) condition = models.ForeignKey('offer.Condition') benefit = models.ForeignKey('offer.Benefit') # Range of availability. Note that if this is a voucher offer, then these # dates are ignored and only the dates from the voucher are used to determine # availability. start_date = models.DateField(blank=True, null=True) end_date = models.DateField(blank=True, null=True, help_text="""Offers are not active on their end date, only the days preceding""") # Some complicated situations require offers to be applied in a set order. priority = models.IntegerField( default=0, help_text="The highest priority offers are applied first") # We track some information on usage total_discount = models.DecimalField(decimal_places=2, max_digits=12, default=Decimal('0.00')) num_orders = models.PositiveIntegerField(default=0) date_created = models.DateTimeField(auto_now_add=True) objects = models.Manager() active = ActiveOfferManager() redirect_url = ExtendedURLField(_('URL redirect (optional)'), blank=True) # We need to track the voucher that this offer came from (if it is a voucher offer) _voucher = None class Meta: ordering = ['-priority'] def save(self, *args, **kwargs): if not self.slug: self.slug = slugify(self.name) return super(ConditionalOffer, self).save(*args, **kwargs) def get_absolute_url(self): return reverse('offer:detail', kwargs={'slug': self.slug}) def __unicode__(self): return self.name def clean(self): if self.start_date and self.end_date and self.start_date > self.end_date: raise exceptions.ValidationError( 'End date should be later than start date') def is_active(self, test_date=None): if not test_date: test_date = datetime.date.today() return self.start_date <= test_date and test_date < self.end_date def is_condition_satisfied(self, basket): return self._proxy_condition().is_satisfied(basket) def is_condition_partially_satisfied(self, basket): return self._proxy_condition().is_partially_satisfied(basket) def get_upsell_message(self, basket): return self._proxy_condition().get_upsell_message(basket) def apply_benefit(self, basket): """ Applies the benefit to the given basket and returns the discount. """ if not self.is_condition_satisfied(basket): return Decimal('0.00') return self._proxy_benefit().apply(basket, self._proxy_condition()) def set_voucher(self, voucher): self._voucher = voucher def get_voucher(self): return self._voucher def _proxy_condition(self): """ Returns the appropriate proxy model for the condition """ field_dict = dict(self.condition.__dict__) if '_state' in field_dict: del field_dict['_state'] if '_range_cache' in field_dict: del field_dict['_range_cache'] if self.condition.type == self.condition.COUNT: return CountCondition(**field_dict) elif self.condition.type == self.condition.VALUE: return ValueCondition(**field_dict) elif self.condition.type == self.condition.COVERAGE: return CoverageCondition(**field_dict) return self.condition def _proxy_benefit(self): """ Returns the appropriate proxy model for the condition """ field_dict = dict(self.benefit.__dict__) if '_state' in field_dict: del field_dict['_state'] if self.benefit.type == self.benefit.PERCENTAGE: return PercentageDiscountBenefit(**field_dict) elif self.benefit.type == self.benefit.FIXED: return AbsoluteDiscountBenefit(**field_dict) elif self.benefit.type == self.benefit.MULTIBUY: return MultibuyDiscountBenefit(**field_dict) elif self.benefit.type == self.benefit.FIXED_PRICE: return FixedPriceBenefit(**field_dict) return self.benefit def record_usage(self, discount): self.num_orders += 1 self.total_discount += discount self.save()
class ConditionalOffer(models.Model): """ A conditional offer (eg buy 1, get 10% off) """ name = models.CharField( _("Name"), max_length=128, unique=True, help_text=_("This is displayed within the customer's basket")) slug = models.SlugField(_("Slug"), max_length=128, unique=True, null=True) description = models.TextField( _("Description"), blank=True, null=True, help_text=_("This is displayed on the offer browsing page")) # Offers come in a few different types: # (a) Offers that are available to all customers on the site. Eg a # 3-for-2 offer. # (b) Offers that are linked to a voucher, and only become available once # that voucher has been applied to the basket # (c) Offers that are linked to a user. Eg, all students get 10% off. The # code to apply this offer needs to be coded # (d) Session offers - these are temporarily available to a user after some # trigger event. Eg, users coming from some affiliate site get 10% # off. SITE, VOUCHER, USER, SESSION = ("Site", "Voucher", "User", "Session") TYPE_CHOICES = ( (SITE, _("Site offer - available to all users")), (VOUCHER, _("Voucher offer - only available after entering " "the appropriate voucher code")), (USER, _("User offer - available to certain types of user")), (SESSION, _("Session offer - temporary offer, available for " "a user for the duration of their session")), ) offer_type = models.CharField(_("Type"), choices=TYPE_CHOICES, default=SITE, max_length=128) # We track a status variable so it's easier to load offers that are # 'available' in some sense. OPEN, SUSPENDED, CONSUMED = "Open", "Suspended", "Consumed" status = models.CharField(_("Status"), max_length=64, default=OPEN) condition = models.ForeignKey('offer.Condition', verbose_name=_("Condition")) benefit = models.ForeignKey('offer.Benefit', verbose_name=_("Benefit")) # Some complicated situations require offers to be applied in a set order. priority = models.IntegerField( _("Priority"), default=0, help_text=_("The highest priority offers are applied first")) # AVAILABILITY # Range of availability. Note that if this is a voucher offer, then these # dates are ignored and only the dates from the voucher are used to # determine availability. start_datetime = models.DateTimeField(_("Start date"), blank=True, null=True) end_datetime = models.DateTimeField( _("End date"), blank=True, null=True, help_text=_("Offers are active until the end of the 'end date'")) # Use this field to limit the number of times this offer can be applied in # total. Note that a single order can apply an offer multiple times so # this is not the same as the number of orders that can use it. max_global_applications = models.PositiveIntegerField( _("Max global applications"), help_text=_("The number of times this offer can be used before it " "is unavailable"), blank=True, null=True) # Use this field to limit the number of times this offer can be used by a # single user. This only works for signed-in users - it doesn't really # make sense for sites that allow anonymous checkout. max_user_applications = models.PositiveIntegerField( _("Max user applications"), help_text=_("The number of times a single user can use this offer"), blank=True, null=True) # Use this field to limit the number of times this offer can be applied to # a basket (and hence a single order). max_basket_applications = models.PositiveIntegerField( blank=True, null=True, help_text=_("The number of times this offer can be applied to a " "basket (and order)")) # Use this field to limit the amount of discount an offer can lead to. # This can be helpful with budgeting. max_discount = models.DecimalField( _("Max discount"), decimal_places=2, max_digits=12, null=True, blank=True, help_text=_("When an offer has given more discount to orders " "than this threshold, then the offer becomes " "unavailable")) # TRACKING total_discount = models.DecimalField(_("Total Discount"), decimal_places=2, max_digits=12, default=D('0.00')) num_applications = models.PositiveIntegerField(_("Number of applications"), default=0) num_orders = models.PositiveIntegerField(_("Number of Orders"), default=0) redirect_url = ExtendedURLField(_("URL redirect (optional)"), blank=True) date_created = models.DateTimeField(_("Date Created"), auto_now_add=True) objects = models.Manager() active = ActiveOfferManager() # We need to track the voucher that this offer came from (if it is a # voucher offer) _voucher = None class Meta: ordering = ['-priority'] verbose_name = _("Conditional offer") verbose_name_plural = _("Conditional offers") # The way offers are looked up involves the fields # (offer_type, status, start_datetime, end_datetime). Ideally, you want # a DB index that covers these 4 fields (will add support for this in # Django 1.5) def save(self, *args, **kwargs): if not self.slug: self.slug = slugify(self.name) # Check to see if consumption thresholds have been broken if not self.is_suspended: if self.get_max_applications() == 0: self.status = self.CONSUMED else: self.status = self.OPEN return super(ConditionalOffer, self).save(*args, **kwargs) def get_absolute_url(self): return reverse('offer:detail', kwargs={'slug': self.slug}) def __unicode__(self): return self.name def clean(self): if (self.start_datetime and self.end_datetime and self.start_datetime > self.end_datetime): raise exceptions.ValidationError( _('End date should be later than start date')) @property def is_open(self): return self.status == self.OPEN @property def is_suspended(self): return self.status == self.SUSPENDED def suspend(self): self.status = self.SUSPENDED self.save() suspend.alters_data = True def unsuspend(self): self.status = self.OPEN self.save() suspend.alters_data = True def is_available(self, user=None, test_date=None): """ Test whether this offer is available to be used """ if self.is_suspended: return False if test_date is None: test_date = now() predicates = [] if self.start_datetime: predicates.append(self.start_datetime > test_date) if self.end_datetime: predicates.append(test_date > self.end_datetime) if any(predicates): return 0 return self.get_max_applications(user) > 0 def is_condition_satisfied(self, basket): return self.condition.proxy().is_satisfied(basket) def is_condition_partially_satisfied(self, basket): return self.condition.proxy().is_partially_satisfied(basket) def get_upsell_message(self, basket): return self.condition.proxy().get_upsell_message(basket) def apply_benefit(self, basket): """ Applies the benefit to the given basket and returns the discount. """ if not self.is_condition_satisfied(basket): return D('0.00') return self.benefit.proxy().apply(basket, self.condition.proxy(), self) def set_voucher(self, voucher): self._voucher = voucher def get_voucher(self): return self._voucher def get_max_applications(self, user=None): """ Return the number of times this offer can be applied to a basket for a given user. """ if self.max_discount and self.total_discount >= self.max_discount: return 0 # Hard-code a maximum value as we need some sensible upper limit for # when there are not other caps. limits = [10000] if self.max_user_applications and user: limits.append( max( 0, self.max_user_applications - self.get_num_user_applications(user))) if self.max_basket_applications: limits.append(self.max_basket_applications) if self.max_global_applications: limits.append( max(0, self.max_global_applications - self.num_applications)) return min(limits) def get_num_user_applications(self, user): OrderDiscount = models.get_model('order', 'OrderDiscount') aggregates = OrderDiscount.objects.filter( offer_id=self.id, order__user=user).aggregate(total=models.Sum('frequency')) return aggregates['total'] if aggregates['total'] is not None else 0 def shipping_discount(self, charge): return self.benefit.proxy().shipping_discount(charge) def record_usage(self, discount): self.num_applications += discount['freq'] self.total_discount += discount['discount'] self.num_orders += 1 self.save() record_usage.alters_data = True def availability_description(self): """ Return a description of when this offer is available """ restrictions = self.availability_restrictions() descriptions = [r['description'] for r in restrictions] return "<br/>".join(descriptions) def availability_restrictions(self): restrictions = [] if self.is_suspended: restrictions.append({ 'description': _("Offer is suspended"), 'is_satisfied': False }) if self.max_global_applications: remaining = self.max_global_applications - self.num_applications desc = _("Limited to %(total)d uses " "(%(remainder)d remaining)") % { 'total': self.max_global_applications, 'remainder': remaining } restrictions.append({ 'description': desc, 'is_satisfied': remaining > 0 }) if self.max_user_applications: if self.max_user_applications == 1: desc = _("Limited to 1 use per user") else: desc = _("Limited to %(total)d uses per user") % { 'total': self.max_user_applications } restrictions.append({'description': desc, 'is_satisfied': True}) if self.max_basket_applications: if self.max_user_applications == 1: desc = _("Limited to 1 use per basket") else: desc = _("Limited to %(total)d uses per basket") % { 'total': self.max_basket_applications } restrictions.append({'description': desc, 'is_satisfied': True}) def format_datetime(dt): # Only show hours/minutes if they have been specified if dt.hour == 0 and dt.minute == 0: return date(dt, settings.DATE_FORMAT) return date(dt, settings.DATETIME_FORMAT) if self.start_datetime or self.end_datetime: today = now() if self.start_datetime and self.end_datetime: desc = _("Available between %(start)s and %(end)s") % { 'start': format_datetime(self.start_datetime), 'end': format_datetime(self.end_datetime) } is_satisfied = self.start_datetime <= today <= self.end_datetime elif self.start_datetime: desc = _("Available from %(start)s") % { 'start': format_datetime(self.start_datetime) } is_satisfied = today >= self.start_datetime elif self.end_datetime: desc = _("Available until %(end)s") % { 'end': format_datetime(self.end_datetime) } is_satisfied = today <= self.end_datetime restrictions.append({ 'description': desc, 'is_satisfied': is_satisfied }) if self.max_discount: desc = _("Limited to a cost of %(max)s") % { 'max': currency(self.max_discount) } restrictions.append({ 'description': desc, 'is_satisfied': self.total_discount < self.max_discount }) return restrictions
class ConditionalOffer(models.Model): """ A conditional offer (eg buy 1, get 10% off) """ name = models.CharField( _("Name"), max_length=128, unique=True, help_text=_("This is displayed within the customer's basket")) slug = models.SlugField(_("Slug"), max_length=128, unique=True, null=True) description = models.TextField(_("Description"), blank=True, null=True) # Offers come in a few different types: # (a) Offers that are available to all customers on the site. Eg a # 3-for-2 offer. # (b) Offers that are linked to a voucher, and only become available once # that voucher has been applied to the basket # (c) Offers that are linked to a user. Eg, all students get 10% off. The # code to apply this offer needs to be coded # (d) Session offers - these are temporarily available to a user after some # trigger event. Eg, users coming from some affiliate site get 10% off. SITE, VOUCHER, USER, SESSION = ("Site", "Voucher", "User", "Session") TYPE_CHOICES = ( (SITE, _("Site offer - available to all users")), (VOUCHER, _("Voucher offer - only available after entering the appropriate voucher code" )), (USER, _("User offer - available to certain types of user")), (SESSION, _("Session offer - temporary offer, available for a user for the duration of their session" )), ) offer_type = models.CharField(_("Type"), choices=TYPE_CHOICES, default=SITE, max_length=128) condition = models.ForeignKey('offer.Condition', verbose_name=_("Condition")) benefit = models.ForeignKey('offer.Benefit', verbose_name=_("Benefit")) # Some complicated situations require offers to be applied in a set order. priority = models.IntegerField( _("Priority"), default=0, help_text=_("The highest priority offers are applied first")) # AVAILABILITY # Range of availability. Note that if this is a voucher offer, then these # dates are ignored and only the dates from the voucher are used to # determine availability. start_date = models.DateField(_("Start Date"), blank=True, null=True) end_date = models.DateField( _("End Date"), blank=True, null=True, help_text=_("Offers are not active on their end date, only " "the days preceding")) # Use this field to limit the number of times this offer can be applied in # total. Note that a single order can apply an offer multiple times so # this is not the same as the number of orders that can use it. max_global_applications = models.PositiveIntegerField( _("Max global applications"), help_text=_("The number of times this offer can be used before it " "is unavailable"), blank=True, null=True) # Use this field to limit the number of times this offer can be used by a # single user. This only works for signed-in users - it doesn't really # make sense for sites that allow anonymous checkout. max_user_applications = models.PositiveIntegerField( _("Max user applications"), help_text=_("The number of times a single user can use this offer"), blank=True, null=True) # Use this field to limit the number of times this offer can be applied to # a basket (and hence a single order). max_basket_applications = models.PositiveIntegerField( blank=True, null=True, help_text=_("The number of times this offer can be applied to a " "basket (and order)")) # Use this field to limit the amount of discount an offer can lead to. # This can be helpful with budgeting. max_discount = models.DecimalField( _("Max discount"), decimal_places=2, max_digits=12, null=True, blank=True, help_text=_("When an offer has given more discount to orders " "than this threshold, then the offer becomes " "unavailable")) # TRACKING total_discount = models.DecimalField(_("Total Discount"), decimal_places=2, max_digits=12, default=D('0.00')) num_applications = models.PositiveIntegerField(_("Number of applications"), default=0) num_orders = models.PositiveIntegerField(_("Number of Orders"), default=0) redirect_url = ExtendedURLField(_("URL redirect (optional)"), blank=True) date_created = models.DateTimeField(_("Date Created"), auto_now_add=True) objects = models.Manager() active = ActiveOfferManager() # We need to track the voucher that this offer came from (if it is a # voucher offer) _voucher = None class Meta: ordering = ['-priority'] verbose_name = _("Conditional Offer") verbose_name_plural = _("Conditional Offers") def save(self, *args, **kwargs): if not self.slug: self.slug = slugify(self.name) return super(ConditionalOffer, self).save(*args, **kwargs) def get_absolute_url(self): return reverse('offer:detail', kwargs={'slug': self.slug}) def __unicode__(self): return self.name def clean(self): if self.start_date and self.end_date and self.start_date > self.end_date: raise exceptions.ValidationError( _('End date should be later than start date')) def is_active(self, test_date=None): """ Test whether this offer is active and can be used by customers """ if test_date is None: test_date = datetime.date.today() predicates = [self.get_max_applications() > 0] if self.start_date: predicates.append(self.start_date <= test_date) if self.end_date: predicates.append(test_date < self.end_date) if self.max_discount: predicates.append(self.total_discount < self.max_discount) return all(predicates) def is_condition_satisfied(self, basket): return self._proxy_condition().is_satisfied(basket) def is_condition_partially_satisfied(self, basket): return self._proxy_condition().is_partially_satisfied(basket) def get_upsell_message(self, basket): return self._proxy_condition().get_upsell_message(basket) def apply_benefit(self, basket): """ Applies the benefit to the given basket and returns the discount. """ if not self.is_condition_satisfied(basket): return D('0.00') return self._proxy_benefit().apply(basket, self._proxy_condition(), self) def set_voucher(self, voucher): self._voucher = voucher def get_voucher(self): return self._voucher def get_max_applications(self, user=None): """ Return the number of times this offer can be applied to a basket """ limits = [10000] if self.max_user_applications and user: limits.append( max( 0, self.max_user_applications - self.get_num_user_applications(user))) if self.max_basket_applications: limits.append(self.max_basket_applications) if self.max_global_applications: limits.append( max(0, self.max_global_applications - self.num_applications)) return min(limits) def get_num_user_applications(self, user): OrderDiscount = models.get_model('order', 'OrderDiscount') aggregates = OrderDiscount.objects.filter( offer_id=self.id, order__user=user).aggregate(total=models.Sum('frequency')) return aggregates['total'] if aggregates['total'] is not None else 0 def shipping_discount(self, charge): return self._proxy_benefit().shipping_discount(charge) def _proxy_condition(self): """ Returns the appropriate proxy model for the condition """ field_dict = dict(self.condition.__dict__) for field in field_dict.keys(): if field.startswith('_'): del field_dict[field] if self.condition.proxy_class: klass = load_proxy(self.condition.proxy_class) return klass(**field_dict) klassmap = { self.condition.COUNT: CountCondition, self.condition.VALUE: ValueCondition, self.condition.COVERAGE: CoverageCondition } if self.condition.type in klassmap: return klassmap[self.condition.type](**field_dict) return self.condition def _proxy_benefit(self): """ Returns the appropriate proxy model for the benefit """ field_dict = dict(self.benefit.__dict__) for field in field_dict.keys(): if field.startswith('_'): del field_dict[field] klassmap = { self.benefit.PERCENTAGE: PercentageDiscountBenefit, self.benefit.FIXED: AbsoluteDiscountBenefit, self.benefit.MULTIBUY: MultibuyDiscountBenefit, self.benefit.FIXED_PRICE: FixedPriceBenefit, self.benefit.SHIPPING_ABSOLUTE: ShippingAbsoluteDiscountBenefit, self.benefit.SHIPPING_FIXED_PRICE: ShippingFixedPriceBenefit, self.benefit.SHIPPING_PERCENTAGE: ShippingPercentageDiscountBenefit } if self.benefit.type in klassmap: return klassmap[self.benefit.type](**field_dict) return self.benefit def record_usage(self, discount): self.num_applications += discount['freq'] self.total_discount += discount['discount'] self.num_orders += 1 self.save() record_usage.alters_data = True def availability_description(self): """ Return a description of when this offer is available """ sentences = [] if self.max_global_applications: desc = _("Can be used %(total)d times " "(%(remainder)d remaining)") % { 'total': self.max_global_applications, 'remainder': self.max_global_applications - self.num_applications } sentences.append(desc) if self.max_user_applications: if self.max_user_applications == 1: desc = _("Can be used once per user") else: desc = _("Can be used %(total)d times per user") % { 'total': self.max_user_applications } sentences.append(desc) if self.max_basket_applications: if self.max_user_applications == 1: desc = _("Can be used once per basket") else: desc = _("Can be used %(total)d times per basket") % { 'total': self.max_basket_applications } sentences.append(desc) if self.start_date and self.end_date: desc = _("Available between %(start)s and %(end)s") % { 'start': self.start_date, 'end': self.end_date } sentences.append(desc) elif self.start_date: sentences.append( _("Available until %(start)s") % {'start': self.start_date}) elif self.end_date: sentences.append( _("Available until %(end)s") % {'end': self.end_date}) if self.max_discount: sentences.append( _("Available until a discount of %(max)s " "has been awarded") % {'max': currency(self.max_discount)}) return "<br/>".join(sentences)