import logging
from decimal import Decimal
from django.utils.translation import ugettext as _
from django.contrib.auth import get_user_model
from rest_framework import serializers
from oscar.core.loading import get_model
from oscarapi.basket import operations
from oscarapi.utils.settings import overridable
from oscarapi.serializers.fields import (
DrillDownHyperlinkedIdentityField,
DrillDownHyperlinkedRelatedField,
)
from oscarapi.serializers.utils import (
OscarModelSerializer,
OscarHyperlinkedModelSerializer,
)
from oscarapi.serializers.fields import TaxIncludedDecimalField
logger = logging.getLogger(__name__)
User = get_user_model()
Basket = get_model("basket", "Basket")
Line = get_model("basket", "Line")
LineAttribute = get_model("basket", "LineAttribute")
StockRecord = get_model("partner", "StockRecord")
Voucher = get_model("voucher", "Voucher")
Product = get_model("catalogue", "Product")
[docs]class VoucherSerializer(OscarModelSerializer):
class Meta:
model = Voucher
fields = overridable(
"OSCARAPI_VOUCHER_FIELDS",
default=("name", "code", "start_datetime", "end_datetime"),
)
class OfferDiscountSerializer(
serializers.Serializer
): # pylint: disable=abstract-method
description = serializers.CharField()
name = serializers.CharField()
amount = serializers.DecimalField(
decimal_places=2, max_digits=12, source="discount"
)
class VoucherDiscountSerializer(OfferDiscountSerializer):
voucher = VoucherSerializer(required=False)
[docs]class BasketSerializer(serializers.HyperlinkedModelSerializer):
lines = serializers.HyperlinkedIdentityField(view_name="basket-lines-list")
offer_discounts = OfferDiscountSerializer(many=True, required=False)
total_excl_tax = serializers.DecimalField(
decimal_places=2, max_digits=12, required=False
)
total_excl_tax_excl_discounts = serializers.DecimalField(
decimal_places=2, max_digits=12, required=False
)
total_incl_tax = TaxIncludedDecimalField(
excl_tax_field="total_excl_tax", decimal_places=2, max_digits=12, required=False
)
total_incl_tax_excl_discounts = TaxIncludedDecimalField(
excl_tax_field="total_excl_tax_excl_discounts",
decimal_places=2,
max_digits=12,
required=False,
)
total_tax = TaxIncludedDecimalField(
excl_tax_value=Decimal("0.00"), decimal_places=2, max_digits=12, required=False
)
currency = serializers.CharField(required=False)
voucher_discounts = VoucherDiscountSerializer(many=True, required=False)
owner = serializers.HyperlinkedRelatedField(
view_name="user-detail",
required=False,
allow_null=True,
queryset=User.objects.all(),
)
class Meta:
model = Basket
fields = overridable(
"OSCARAPI_BASKET_FIELDS",
default=(
"id",
"owner",
"status",
"lines",
"url",
"total_excl_tax",
"total_excl_tax_excl_discounts",
"total_incl_tax",
"total_incl_tax_excl_discounts",
"total_tax",
"currency",
"voucher_discounts",
"offer_discounts",
"is_tax_known",
),
)
class LineAttributeSerializer(OscarHyperlinkedModelSerializer):
url = DrillDownHyperlinkedIdentityField(
view_name="lineattribute-detail",
extra_url_kwargs={"basket_pk": "line.basket.id", "line_pk": "line.id"},
)
line = DrillDownHyperlinkedIdentityField(
view_name="basket-line-detail", extra_url_kwargs={"basket_pk": "line.basket.id"}
)
class Meta:
model = LineAttribute
fields = "__all__"
[docs]class BasketLineSerializer(OscarHyperlinkedModelSerializer):
"""
This serializer computes the prices of this line by using the basket
strategy.
"""
url = DrillDownHyperlinkedIdentityField(
view_name="basket-line-detail", extra_url_kwargs={"basket_pk": "basket.id"}
)
attributes = LineAttributeSerializer(
many=True, fields=("url", "option", "value"), required=False, read_only=True
)
price_excl_tax = serializers.DecimalField(
decimal_places=2,
max_digits=12,
source="line_price_excl_tax_incl_discounts",
read_only=True,
)
price_incl_tax = TaxIncludedDecimalField(
decimal_places=2,
max_digits=12,
excl_tax_field="line_price_excl_tax_incl_discounts",
source="line_price_incl_tax_incl_discounts",
read_only=True,
)
price_incl_tax_excl_discounts = TaxIncludedDecimalField(
decimal_places=2,
max_digits=12,
excl_tax_field="line_price_excl_tax",
source="line_price_incl_tax",
read_only=True,
)
price_excl_tax_excl_discounts = serializers.DecimalField(
decimal_places=2, max_digits=12, source="line_price_excl_tax", read_only=True
)
warning = serializers.CharField(
read_only=True, required=False, source="get_warning"
)
stockrecord = DrillDownHyperlinkedRelatedField(
view_name="product-stockrecord-detail",
extra_url_kwargs={"product_pk": "product_id"},
queryset=StockRecord.objects.all(),
)
class Meta:
model = Line
fields = overridable(
"OSCARAPI_BASKETLINE_FIELDS",
default=(
"url",
"product",
"quantity",
"attributes",
"price_currency",
"price_excl_tax",
"price_incl_tax",
"price_incl_tax_excl_discounts",
"price_excl_tax_excl_discounts",
"is_tax_known",
"warning",
"basket",
"stockrecord",
"date_created",
),
)
def to_representation(self, obj):
# This override is needed to reflect offer discounts or strategy
# related prices immediately in the response
operations.assign_basket_strategy(obj.basket, self.context["request"])
# Oscar stores the calculated discount in line._discount_incl_tax or
# line._discount_excl_tax when offers are applied. So by just
# retrieving the line from the db you will loose this values, that's
# why we need to get the line from the in-memory resultset here
lines = (x for x in obj.basket.all_lines() if x.id == obj.id)
line = next(lines, None)
return super(BasketLineSerializer, self).to_representation(line)
class VoucherAddSerializer(serializers.Serializer):
vouchercode = serializers.CharField(max_length=128, required=True)
def validate(self, attrs):
# oscar expects this always to be uppercase.
attrs["vouchercode"] = attrs["vouchercode"].upper()
request = self.context.get("request")
try:
voucher = Voucher.objects.get(code=attrs.get("vouchercode"))
# check expiry date
if not voucher.is_active():
message = _("The '%(code)s' voucher has expired") % {
"code": voucher.code
}
raise serializers.ValidationError(message)
# check voucher rules
is_available, message = voucher.is_available_to_user(request.user)
if not is_available:
raise serializers.ValidationError(message)
except Voucher.DoesNotExist:
raise serializers.ValidationError(_("Voucher code unknown"))
# set instance to the voucher so we can use this in the view
self.instance = voucher
return attrs
def create(self, validated_data):
return Voucher.objects.create(**validated_data)