from django.apps import apps
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ObjectDoesNotExist
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
from salesman.conf import app_settings
from salesman.core.serializers import PriceField
from .models import Basket, BasketItem
[docs]class ProductField(serializers.DictField):
"""
Related product field that uses a serializer based on product type
taken from ``SALESMAN_PRODUCT_TYPES`` setting.
"""
def to_representation(self, product):
product_types = app_settings.SALESMAN_PRODUCT_TYPES
serializer_class = product_types.get(product._meta.label, None)
return serializer_class(product).data if serializer_class else product.pk
[docs]class BasketItemSerializer(serializers.ModelSerializer):
"""
Serializer for basket item.
"""
url = serializers.SerializerMethodField()
product_type = serializers.CharField(source='product._meta.label', read_only=True)
product = ProductField(read_only=True)
quantity = serializers.IntegerField(min_value=1)
unit_price = PriceField(read_only=True)
subtotal = PriceField(read_only=True)
total = PriceField(read_only=True)
extra = serializers.JSONField(
required=False, help_text=_("Extra is updated and null values are removed.")
)
extra_rows = ExtraRowsField(read_only=True)
class Meta:
model = BasketItem
fields = [
'url',
'ref',
'product_type',
'product_id',
'product',
'unit_price',
'quantity',
'subtotal',
'extra_rows',
'total',
'extra',
]
read_only_fields = fields
def get_url(self, obj):
request = self.context.get('request', None)
url = reverse('salesman-basket-detail', args=[obj.ref])
return request.build_absolute_uri(url) if request else url
def validate_extra(self, value):
# Update basket `extra` instead of replacing it, remove null values.
extra = self.instance.extra if self.instance else {}
if value:
extra.update(value)
extra = {k: v for k, v in extra.items() if v is not None}
return extra
def to_representation(self, item):
item.update(self.context['request'])
return super().to_representation(item)
[docs]class BasketItemCreateSerializer(serializers.ModelSerializer):
"""
Serializer used to add a new item to basket.
"""
ref = serializers.SlugField(
required=False, help_text=_("Leave empty to auto-generate from product.")
)
product_type = serializers.ChoiceField(
choices=list(app_settings.SALESMAN_PRODUCT_TYPES)
)
product_id = serializers.IntegerField(min_value=1)
quantity = serializers.IntegerField(default=1, min_value=1)
extra = serializers.JSONField(required=False, help_text=_("Store extra JSON data."))
class Meta:
model = BasketItem
fields = ['ref', 'product_type', 'product_id', 'quantity', 'extra']
def validate(self, attrs):
# Validate and set product from generic relation.
app_label, model_name = attrs['product_type'].split('.')
model = apps.get_model(app_label, model_name)
content_type = ContentType.objects.get_for_model(model)
try:
pid = attrs['product_id']
attrs['product'] = content_type.get_object_for_this_type(id=pid)
except ObjectDoesNotExist:
msg = _("Product '{product_type}' with id '{product_id}' doesn't exist.")
raise serializers.ValidationError(msg.format(**attrs))
return attrs
def create(self, validated_data) -> BasketItem:
basket = self.context['basket']
return basket.add(
product=validated_data['product'],
quantity=validated_data['quantity'],
ref=validated_data.get('ref', None),
extra=validated_data.get('extra', None),
)
def to_representation(self, item):
return BasketItemSerializer(context=self.context).to_representation(item)
[docs]class BasketSerializer(serializers.ModelSerializer):
"""
Serializer for basket.
"""
items = BasketItemSerializer(source='get_items', many=True, read_only=True)
subtotal = PriceField(read_only=True)
total = PriceField(read_only=True)
extra = serializers.JSONField(read_only=True)
extra_rows = ExtraRowsField(read_only=True)
class Meta:
model = Basket
fields = ['id', 'items', 'subtotal', 'total', 'extra', 'extra_rows']
def to_representation(self, basket):
basket.update(self.context['request'])
return super().to_representation(basket)