Offers

Oscar ships with a powerful and flexible offers engine which is contained in the offers app. It is based around the concept of ‘conditional offers’ - that is, a basket must satisfy some condition in order to qualify for a benefit.

Oscar’s dashboard can be used to administer offers.

Structure

A conditional offer is composed of several components:

  • Customer-facing information - this is the name, description and type of an offer. These will be visible on offer-browsing pages as well as within the basket and checkout pages.

  • Availability - this determines when an offer is available.

  • Condition - this determines when a customer qualifies for the offer (e.g. spend £20 on DVDs). There are various condition types available.

  • Benefit - this determines the discount a customer receives. The discount can be against the basket cost or the shipping for an order.

Availability

An offer’s availability can be controlled by several settings which can be used in isolation or combination:

  • Date range - a date can be set, outside of which the offer is unavailable.

  • Max global applications - the number of times an offer can be used can be capped. Note that an offer can be used multiple times within the same order so this isn’t the same as limiting the number of orders that can use an offer.

  • Max user applications - the number of times a particular user can use an offer. This makes most sense to use in sites that don’t allow anonymous checkout as it could be circumvented by submitting multiple anonymous orders.

  • Max basket applications - the number of times an offer can be used for a single basket/order.

  • Max discount - the maximum amount of discount an offer can give across all orders. For instance, you might have a marketing budget of £10000 and so you could set the max discount to this value to ensure that once £10000 worth of benefit had been awarded, the offer would no longer be available. Note that the total discount would exceed £10000 as it would have to cross this threshold to disable the offer.

Conditions

There are 3 built-in condition types that can be created via the dashboard. Each needs to be linked with a range object, which is subset of the product catalogue. Ranges are created independently in the dashboard.

  • Count-based - i.e. a customer must buy X products from the condition range

  • Coverage-based - i.e. a customer must buy X DISTINCT products from the condition range. This can be used to create “bundle” offers.

  • Value-based - i.e. a customer must spend X on products from the condition range

It is also possible to create custom conditions in Python and register these so they are available to be selected within the dashboard. For instance, you could create a condition that specifies that the user must have been registered for over a year to qualify for the offer.

Under the hood, conditions are defined by 3 attributes: a range, a type and a value.

Benefits

There are several types of built-in benefit, which fall into one of two categories: benefits that give a basket discount, and those that give a shipping discount.

Basket benefits:

  • Fixed discount - i.e. get £5 off DVDs

  • Percentage discount - i.e. get 25% off books

  • Fixed price - i.e. get any DVD for £8

  • Multi-buy - i.e. get the cheapest product that meets the condition for free

Shipping benefits (these largely mirror the basket benefits):

  • Fixed discount - i.e. £5 off shipping

  • Percentage discount - i.e. get 25% off shipping

  • Fixed price - i.e. get shipping for £8

Like conditions, it is possible to create a custom benefit. An example might be to allow customers to earn extra credits/points when they qualify for some offer. For example, spend £100 on perfume, get 500 credits (note credits don’t exist in core Oscar but can be implemented using the ‘accounts’ plugin).

Under the hood, benefits are modelled by 4 attributes: a range, a type, a value and a setting for the maximum number of basket items that can be affected by a benefit. This last settings is useful for limiting the scope of an offer. For instance, you can create a benefit that gives 40% off ONE products from a given range by setting the max affected items to 1. Without this setting, the benefit would give 40% off ALL products from the range.

Benefits are slightly tricky in that some types don’t require a range and ignore the value of the max items setting.

Examples

Here’s some example offers:

3 for 2 on books
  1. Create a range for all books.

  2. Use a count-based condition that links to this range with a value of 3.

  3. Use a multibuy benefit with no value (the value is implicitly 1)

Spend £20 on DVDs, get 25% off
  1. Create a range for all DVDs.

  2. Use a value-based condition that links to this range with a value of 20.

  3. Use a percentage discount benefit that links to this range and has a value of 25.

Buy 2 Lonely Planet books, get £5 off a Lonely Planet DVD
  1. Create a range for Lonely Planet books and another for Lonely Planet DVDs

  2. Use a count-based condition linking to the book range with a value of 2

  3. Use a fixed discount benefit that links to the DVD range and has a value of 5.

More to come…

Abstract models

class oscar.apps.offer.abstract_models.AbstractBenefit(*args, **kwargs)[source]
can_apply_benefit(line)[source]

Determines whether the benefit can be applied to a given basket line

clean()[source]

Hook for doing any extra model-wide validation after clean() has been called on every field by self.clean_fields. Any ValidationError raised by this method will not be associated with a particular field; it will have a special-case association with the field defined by NON_FIELD_ERRORS.

get_applicable_lines(offer, basket, range=None)[source]

Return the basket lines that are available to be discounted

Basket

The basket

Range

The range of products to use for filtering. The fixed-price benefit ignores its range and uses the condition range

round(amount, currency=None)[source]

Apply rounding to discount amount

class oscar.apps.offer.abstract_models.AbstractCondition(*args, **kwargs)[source]

A condition for an offer to be applied. You can either specify a custom proxy class, or need to specify a type, range and value.

can_apply_condition(line)[source]

Determines whether the condition can be applied to a given basket line

clean()[source]

Hook for doing any extra model-wide validation after clean() has been called on every field by self.clean_fields. Any ValidationError raised by this method will not be associated with a particular field; it will have a special-case association with the field defined by NON_FIELD_ERRORS.

get_applicable_lines(offer, basket, most_expensive_first=True)[source]

Return line data for the lines that can be consumed by this condition

is_partially_satisfied(offer, basket)[source]

Determine if the basket partially meets the condition. This is useful for up-selling messages to entice customers to buy something more in order to qualify for an offer.

is_satisfied(offer, basket)[source]

Determines whether a given basket meets this condition. This is stubbed in this top-class object. The subclassing proxies are responsible for implementing it correctly.

class oscar.apps.offer.abstract_models.AbstractConditionalOffer(*args, **kwargs)[source]

A conditional offer (e.g. buy 1, get 10% off)

apply_benefit(basket)[source]

Applies the benefit to the given basket and returns the discount.

apply_deferred_benefit(basket, order, application)[source]

Applies any deferred benefits. These are things like adding loyalty points to someone’s account.

availability_description()[source]

Return a description of when this offer is available

clean()[source]

Hook for doing any extra model-wide validation after clean() has been called on every field by self.clean_fields. Any ValidationError raised by this method will not be associated with a particular field; it will have a special-case association with the field defined by NON_FIELD_ERRORS.

get_max_applications(user=None)[source]

Return the number of times this offer can be applied to a basket for a given user.

is_available(user=None, test_date=None)[source]

Test whether this offer is available to be used

products()[source]

Return a queryset of products in this offer

save(*args, **kwargs)[source]

Save the current instance. Override this in a subclass if you want to control the saving process.

The ‘force_insert’ and ‘force_update’ parameters can be used to insist that the “save” must be an SQL insert or update (or equivalent for non-SQL backends), respectively. Normally, they should not be set.

class oscar.apps.offer.abstract_models.AbstractRange(*args, **kwargs)[source]

Represents a range of products that can be used within an offer.

add_product(product, display_order=None)[source]

Add product to the range

When adding product that is already in the range, prevent re-adding it. If display_order is specified, update it.

Default display_order for a new product in the range is 0; this puts the product at the top of the list.

all_products()[source]

Return a queryset containing all the products in the range

This includes included_products plus the products contained in the included classes and categories, minus the products in excluded_products.

property is_editable

Test whether this range can be edited in the dashboard.

property is_reorderable

Test whether products for the range can be re-ordered.

product_queryset

cached queryset of all the products in the Range

remove_product(product)[source]

Remove product from range. To save on queries, this function does not check if the product is in fact in the range.

class oscar.apps.offer.abstract_models.AbstractRangeProduct(*args, **kwargs)[source]

Allow ordering products inside ranges Exists to allow customising.

class oscar.apps.offer.abstract_models.AbstractRangeProductFileUpload(*args, **kwargs)[source]
process(file_obj)[source]

Process the file upload and add products to the range or add products to range.excluded_products

class oscar.apps.offer.abstract_models.BaseOfferMixin(*args, **kwargs)[source]
property description

A description of the benefit/condition. Defaults to the name. May contain HTML.

property name

A text description of the benefit/condition. Every proxy class has to implement it.

This is used in the dropdowns within the offer dashboard.

proxy()[source]

Return the proxy model

Models

class oscar.apps.offer.models.AbsoluteDiscountBenefit(*args, **kwargs)[source]

An offer benefit that gives an absolute discount

exception DoesNotExist
exception MultipleObjectsReturned
property description

A description of the benefit/condition. Defaults to the name. May contain HTML.

property name

A text description of the benefit/condition. Every proxy class has to implement it.

This is used in the dropdowns within the offer dashboard.

class oscar.apps.offer.models.BasketDiscount(amount)[source]

For when an offer application leads to a simple discount off the basket’s total

property is_successful

Returns True if the discount is greater than zero

class oscar.apps.offer.models.Benefit(id, range, type, value, max_affected_items, proxy_class)[source]
exception DoesNotExist
exception MultipleObjectsReturned
class oscar.apps.offer.models.Condition(id, range, type, value, proxy_class)[source]
exception DoesNotExist
exception MultipleObjectsReturned
class oscar.apps.offer.models.ConditionalOffer(id, name, slug, description, offer_type, exclusive, status, condition, benefit, priority, start_datetime, end_datetime, max_global_applications, max_user_applications, max_basket_applications, max_discount, total_discount, num_applications, num_orders, redirect_url, date_created)[source]
exception DoesNotExist
exception MultipleObjectsReturned
class oscar.apps.offer.models.CountCondition(*args, **kwargs)[source]

An offer condition dependent on the NUMBER of matching items from the basket.

exception DoesNotExist
exception MultipleObjectsReturned
consume_items(offer, basket, affected_lines)[source]

Marks items within the basket lines as consumed so they can’t be reused in other offers.

Basket

The basket

Affected_lines

The lines that have been affected by the discount. This should be list of tuples (line, discount, qty)

property description

A description of the benefit/condition. Defaults to the name. May contain HTML.

is_partially_satisfied(offer, basket)[source]

Determine if the basket partially meets the condition. This is useful for up-selling messages to entice customers to buy something more in order to qualify for an offer.

is_satisfied(offer, basket)[source]

Determines whether a given basket meets this condition

property name

A text description of the benefit/condition. Every proxy class has to implement it.

This is used in the dropdowns within the offer dashboard.

class oscar.apps.offer.models.CoverageCondition(*args, **kwargs)[source]

An offer condition dependent on the number of DISTINCT matching items from the basket.

exception DoesNotExist
exception MultipleObjectsReturned
consume_items(offer, basket, affected_lines)[source]

Marks items within the basket lines as consumed so they can’t be reused in other offers.

property description

A description of the benefit/condition. Defaults to the name. May contain HTML.

is_partially_satisfied(offer, basket)[source]

Determine if the basket partially meets the condition. This is useful for up-selling messages to entice customers to buy something more in order to qualify for an offer.

is_satisfied(offer, basket)[source]

Determines whether a given basket meets this condition

property name

A text description of the benefit/condition. Every proxy class has to implement it.

This is used in the dropdowns within the offer dashboard.

class oscar.apps.offer.models.FixedPriceBenefit(*args, **kwargs)[source]

An offer benefit that gives the items in the condition for a fixed price. This is useful for “bundle” offers.

Note that we ignore the benefit range here and only give a fixed price for the products in the condition range. The condition cannot be a value condition.

We also ignore the max_affected_items setting.

exception DoesNotExist
exception MultipleObjectsReturned
property name

A text description of the benefit/condition. Every proxy class has to implement it.

This is used in the dropdowns within the offer dashboard.

class oscar.apps.offer.models.FixedUnitDiscountBenefit(*args, **kwargs)[source]

An offer benefit that gives an absolute discount on each applicable product.

exception DoesNotExist
exception MultipleObjectsReturned
class oscar.apps.offer.models.MultibuyDiscountBenefit(id, range, type, value, max_affected_items, proxy_class)[source]
exception DoesNotExist
exception MultipleObjectsReturned
property description

A description of the benefit/condition. Defaults to the name. May contain HTML.

property name

A text description of the benefit/condition. Every proxy class has to implement it.

This is used in the dropdowns within the offer dashboard.

class oscar.apps.offer.models.PercentageDiscountBenefit(*args, **kwargs)[source]

An offer benefit that gives a percentage discount

exception DoesNotExist
exception MultipleObjectsReturned
property description

A description of the benefit/condition. Defaults to the name. May contain HTML.

property name

A text description of the benefit/condition. Every proxy class has to implement it.

This is used in the dropdowns within the offer dashboard.

class oscar.apps.offer.models.PostOrderAction(description)[source]

For when an offer condition is met but the benefit is deferred until after the order has been placed. E.g. buy 2 books and get 100 loyalty points.

class oscar.apps.offer.models.Range(id, name, slug, description, is_public, includes_all_products, proxy_class, date_created)[source]
exception DoesNotExist
exception MultipleObjectsReturned
class oscar.apps.offer.models.RangeProduct(id, range, product, display_order)[source]
exception DoesNotExist
exception MultipleObjectsReturned
class oscar.apps.offer.models.RangeProductFileUpload(id, range, filepath, size, uploaded_by, date_uploaded, upload_type, status, error_message, date_processed, num_new_skus, num_unknown_skus, num_duplicate_skus)[source]
exception DoesNotExist
exception MultipleObjectsReturned
class oscar.apps.offer.models.ShippingAbsoluteDiscountBenefit(id, range, type, value, max_affected_items, proxy_class)[source]
exception DoesNotExist
exception MultipleObjectsReturned
property name

A text description of the benefit/condition. Every proxy class has to implement it.

This is used in the dropdowns within the offer dashboard.

class oscar.apps.offer.models.ShippingBenefit(id, range, type, value, max_affected_items, proxy_class)[source]
exception DoesNotExist
exception MultipleObjectsReturned
class oscar.apps.offer.models.ShippingDiscount[source]

For when an offer application leads to a discount from the shipping cost

class oscar.apps.offer.models.ShippingFixedPriceBenefit(id, range, type, value, max_affected_items, proxy_class)[source]
exception DoesNotExist
exception MultipleObjectsReturned
property name

A text description of the benefit/condition. Every proxy class has to implement it.

This is used in the dropdowns within the offer dashboard.

class oscar.apps.offer.models.ShippingPercentageDiscountBenefit(id, range, type, value, max_affected_items, proxy_class)[source]
exception DoesNotExist
exception MultipleObjectsReturned
property name

A text description of the benefit/condition. Every proxy class has to implement it.

This is used in the dropdowns within the offer dashboard.

class oscar.apps.offer.models.ValueCondition(*args, **kwargs)[source]

An offer condition dependent on the VALUE of matching items from the basket.

exception DoesNotExist
exception MultipleObjectsReturned
consume_items(offer, basket, affected_lines)[source]

Marks items within the basket lines as consumed so they can’t be reused in other offers.

We allow lines to be passed in as sometimes we want them sorted in a specific order.

property description

A description of the benefit/condition. Defaults to the name. May contain HTML.

is_partially_satisfied(offer, basket)[source]

Determine if the basket partially meets the condition. This is useful for up-selling messages to entice customers to buy something more in order to qualify for an offer.

is_satisfied(offer, basket)[source]

Determine whether a given basket meets this condition

property name

A text description of the benefit/condition. Every proxy class has to implement it.

This is used in the dropdowns within the offer dashboard.

Views

class oscar.apps.offer.views.OfferDetailView(**kwargs)[source]
get_context_data(**kwargs)[source]

Get the context for this view.

get_queryset()[source]

Return a queryset of all Product instances related to the ConditionalOffer.

class oscar.apps.offer.views.OfferListView(**kwargs)[source]
get_queryset()[source]

Return a queryset of active ConditionalOffer instances with an offer_type of ConditionalOffer.SITE.

model

alias of ConditionalOffer

class oscar.apps.offer.views.RangeDetailView(**kwargs)[source]
get_context_data(**kwargs)[source]

Get the context for this view.

get_queryset()[source]

Return a queryset of all Product instances related to the Range.