from decimal import Decimal as D
from django.utils.translation import gettext_lazy as _
from oscar.core import prices
[docs]class Base(object):
"""
Shipping method interface class
This is the superclass to the classes in this module. This allows
using all shipping methods interchangeably (aka polymorphism).
The interface is all properties.
"""
#: Used to store this method in the session. Each shipping method should
#: have a unique code.
code = "__default__"
#: The name of the shipping method, shown to the customer during checkout
name = "Default shipping"
#: A more detailed description of the shipping method shown to the customer
#: during checkout. Can contain HTML.
description = ""
#: Whether the charge includes a discount
is_discounted = False
[docs] def calculate(self, basket):
"""
Return the shipping charge for the given basket
"""
raise NotImplementedError
# pylint: disable=unused-argument
[docs] def discount(self, basket):
"""
Return the discount on the standard shipping charge
"""
# The regular shipping methods don't add a default discount.
# For offers and vouchers, the discount will be provided
# by a wrapper that Repository.apply_shipping_offer() adds.
return D("0.00")
[docs]class Free(Base):
"""
This shipping method specifies that shipping is free.
"""
code = "free-shipping"
name = _("Free shipping")
[docs] def calculate(self, basket):
# If the charge is free then tax must be free (musn't it?) and so we
# immediately set the tax to zero
return prices.Price(currency=basket.currency, excl_tax=D("0.00"), tax=D("0.00"))
[docs]class NoShippingRequired(Free):
"""
This is a special shipping method that indicates that no shipping is
actually required (e.g. for digital goods).
"""
code = "no-shipping-required"
name = _("No shipping required")
[docs]class FixedPrice(Base):
"""
This shipping method indicates that shipping costs a fixed price and
requires no special calculation.
"""
code = "fixed-price-shipping"
name = _("Fixed price shipping")
# Charges can be either declared by subclassing and overriding the
# class attributes or by passing them to the constructor
charge_excl_tax = None
charge_incl_tax = None
def __init__(self, charge_excl_tax=None, charge_incl_tax=None):
if charge_excl_tax is not None:
self.charge_excl_tax = charge_excl_tax
if charge_incl_tax is not None:
self.charge_incl_tax = charge_incl_tax
[docs] def calculate(self, basket):
return prices.Price(
currency=basket.currency,
excl_tax=self.charge_excl_tax,
incl_tax=self.charge_incl_tax,
)
# pylint: disable=abstract-method
[docs]class OfferDiscount(Base):
"""
Wrapper class that applies a discount to an existing shipping
method's charges.
"""
is_discounted = True
def __init__(self, method, offer):
self.method = method
self.offer = offer
# Forwarded properties
@property
def code(self):
"""
Returns the :py:attr:`code <oscar.apps.shipping.methods.Base.code>` of the wrapped shipping method.
"""
return self.method.code
@property
def name(self):
"""
Returns the :py:attr:`name <oscar.apps.shipping.methods.Base.name>` of the wrapped shipping method.
"""
return self.method.name
@property
def discount_name(self):
"""
Returns the :py:attr:`name <oscar.apps.offer.abstract_models.BaseOfferMixin.name>` of the applied Offer.
"""
return self.offer.name
@property
def description(self):
"""
Returns the :py:attr:`description <.Base.description>` of the wrapped shipping method.
"""
return self.method.description
[docs] def calculate_excl_discount(self, basket):
"""
Returns the shipping charge for the given basket without
discount applied.
"""
return self.method.calculate(basket)
[docs]class TaxExclusiveOfferDiscount(OfferDiscount):
"""
Wrapper class which extends OfferDiscount to be exclusive of tax.
"""
[docs] def calculate(self, basket):
base_charge = self.method.calculate(basket)
discount = self.offer.shipping_discount(
base_charge.excl_tax, base_charge.currency
)
excl_tax = base_charge.excl_tax - discount
return prices.Price(
currency=base_charge.currency,
excl_tax=excl_tax,
tax_code=base_charge.tax_code,
)
[docs] def discount(self, basket):
base_charge = self.method.calculate(basket)
return self.offer.shipping_discount(base_charge.excl_tax, base_charge.currency)
[docs]class TaxInclusiveOfferDiscount(OfferDiscount):
"""
Wrapper class which extends OfferDiscount to be inclusive of tax.
"""
[docs] def calculate(self, basket):
base_charge = self.method.calculate(basket)
discount = self.offer.shipping_discount(
base_charge.incl_tax, base_charge.currency
)
incl_tax = base_charge.incl_tax - discount
excl_tax = self.calculate_excl_tax(base_charge, incl_tax)
return prices.Price(
currency=base_charge.currency,
excl_tax=excl_tax,
incl_tax=incl_tax,
tax_code=base_charge.tax_code,
)
[docs] def calculate_excl_tax(self, base_charge, incl_tax):
"""
Return the charge excluding tax (but including discount).
"""
if incl_tax == D("0.00"):
return D("0.00")
# We assume we can linearly scale down the excl tax price before
# discount.
excl_tax = base_charge.excl_tax * (incl_tax / base_charge.incl_tax)
return excl_tax.quantize(D("0.01"))
[docs] def discount(self, basket):
base_charge = self.method.calculate(basket)
return self.offer.shipping_discount(base_charge.incl_tax, base_charge.currency)