Basket modifiers

Sometimes we need to add extra charges or discounts to the basket and its items. This is done using basket modifiers which allow for manipulation of both the items individually and the basket itself. When a custom modifier is defined it can choose to process the basket or item or both.

Modifiers are registered in SALESMAN_BASKET_MODIFIERS setting and should be formatted as a list of dotted paths to a class extending salesman.basket.modifiers.BasketModifier class.

Note

For this example, we assume your custom app is named shop.

Create modifiers

A unique identifier property is required to be set on modifiers. To add extra rows to basket or items individually use salesman.basket.modifiers.BasketModifier.add_extra_row() method. Eg:

# modifiers.py
from salesman.basket.modifiers import BasketModifier


class DiscountModifier(BasketModifier):
    """
    Apply 10% discount on entire basket.
    """

    identifier = "discount"

    def process_basket(self, basket, request):
        if basket.count:
            amount = basket.subtotal / -10
            self.add_extra_row(basket, request, label="Discount 10%", amount=amount)


class SpecialTaxModifier(BasketModifier):
    """
    Add 10% tax on items with price grater than 99.
    """

    identifier = "special-tax"

    def process_item(self, item, request):
        if item.total > 99:
            label = "Special tax"
            amount = item.total / 10
            extra = {"message": f"Price threshold is exceeded by {item.total - 99}"}
            self.add_extra_row(item, request, label, amount, extra)


class ShippingCostModifier(BasketModifier):
    """
    Add flat shipping cost to the basket.
    """

    identifier = "shipping-cost"

    def process_basket(self, basket, request):
        if basket.count:
            self.add_extra_row(basket, request, label="Shipping", amount=30)

A more complex modifier

Basket modifiers support additional hooks that allow for more control.

# modifiers.py
import random

from salesman.basket.modifiers import BasketModifier


class ComplexModifier(BasketModifier):
    """
    Complex modifier that doesn't make much sense but is used to show advance usage.
    Refer to the BaskeModifier class to see available methods and call ordering.
    """

    identifier = "complex"

    def setup_basket(self, basket, request):
        self.basket = basket

        # Set discount limit through the request.
        try:
            self.max_discounts = int(self.request.GET["max_discounts"])
        except (KeyError, ValueError):
            self.max_discounts = 0
        self.num_discounts = 0

        # Keep track of item with highest discount (amount, item).
        self.highest_discounted_item = (0, None)

    def setup_item(self, item, request):
        # Set a random tax on each item.
        item.tax_percent = random.choice([10, 20, 30])

    def process_item(self, item, request):
        # Apply a discount to item if max not reached.
        if self.num_discounts < self.max_discounts:
            amount = item.subtotal / -10
            self.add_extra_row(
                item,
                request,
                label="Discount 10%",
                amount=item.subtotal / -10,
                identifier="complex-discount",
            )
            self.num_discounts += 1

            # Set highest discounted item.
            if amount > self.highest_discounted_item[0]:
                self.highest_discounted_item = (amount, item)

        # Apply random tax after discount.
        tax_amount = item.total / item.tax_percent
        self.add_extra_row(
            item,
            request,
            label="Random tax",
            amount=tax_amount,
            identifier="complex-tax",
        )

    def finalize_item(self, item, request):
        # Set highest discounted item.
        amount = item.subtotal - item.total
        if amount > self.highest_discounted_item[0]:
            self.highest_discounted_item = (amount, item)

    def process_basket(self, basket, request):
        # Set special discount for highest discounted item.
        item = self.highest_discounted_item[1]
        if item:
            self.add_extra_row(
                item,
                request,
                label="Special discount 5%",
                amount=item.total / -5,
                extra={"diff": self.highest_discounted_item[0]},
                identifier="complex-special-discount",
            )

        # Add discount based on item quantity.
        if basket.quantity % 2 == 0:
            label = "Even discount 10%"
            amount = basket.subtotal / -10
        else:
            label = "Odd discount 13%"
            amount = basket.subtotal / -13

        self.add_extra_row(basket, request, label, amount)

    def finalize_basket(self, basket, request):
        # Set after all modifiers have processed the order.
        basket.extra["is_expensive"] = basket.total > 1000

Register modifiers

Register modifiers in your settings.py, ordering is preserved when processing:

SALESMAN_BASKET_MODIFIERS = [
    'shop.modifiers.DiscountModifier',
    'shop.modifiers.SpecialTaxModifier',
    'shop.modifiers.ShippingCostModifier',
    'shop.modifiers.ComplexModifier',
]

Your basket will now contain extra rows when needed. They will appear as extra_rows list field on both the basket and its items.