Refactored commerce models to support refunds, invoices, and improved carrier/payment logic. Added new serializers and viewsets for products, categories, images, discount codes, and refunds. Introduced Stripe client integration and removed legacy Stripe admin/model code. Updated Dockerfile for PDF generation dependencies. Removed obsolete migration files and updated configuration app initialization. Added invoice template and tasks for order cleanup.
250 lines
5.9 KiB
Python
250 lines
5.9 KiB
Python
from rest_framework import serializers
|
|
from drf_spectacular.utils import extend_schema_field
|
|
from decimal import Decimal
|
|
from django.contrib.auth import get_user_model
|
|
|
|
from .models import (
|
|
Category,
|
|
Product,
|
|
ProductImage,
|
|
DiscountCode,
|
|
Refund,
|
|
Order,
|
|
OrderItem,
|
|
Carrier,
|
|
Payment,
|
|
)
|
|
|
|
User = get_user_model()
|
|
|
|
|
|
class UserBriefSerializer(serializers.ModelSerializer):
|
|
class Meta:
|
|
model = User
|
|
fields = ["id", "first_name", "last_name", "email"]
|
|
|
|
|
|
class ProductBriefSerializer(serializers.ModelSerializer):
|
|
class Meta:
|
|
model = Product
|
|
fields = ["id", "name", "price"]
|
|
|
|
|
|
class OrderItemReadSerializer(serializers.ModelSerializer):
|
|
product = ProductBriefSerializer(read_only=True)
|
|
total_price = serializers.SerializerMethodField()
|
|
|
|
def get_total_price(self, obj):
|
|
return obj.get_total_price(list(obj.order.discount.all()) if getattr(obj, "order", None) else None)
|
|
|
|
class Meta:
|
|
model = OrderItem
|
|
fields = ["id", "product", "quantity", "total_price"]
|
|
read_only_fields = ["id", "total_price"]
|
|
|
|
|
|
class CarrierReadSerializer(serializers.ModelSerializer):
|
|
class Meta:
|
|
model = Carrier
|
|
fields = ["id", "shipping_method", "weight", "returning"]
|
|
read_only_fields = ["id"]
|
|
|
|
|
|
class PaymentReadSerializer(serializers.Serializer):
|
|
payment_method = serializers.CharField(read_only=True)
|
|
stripe_id = serializers.IntegerField(source="stripe.id", read_only=True, allow_null=True)
|
|
|
|
|
|
class OrderReadSerializer(serializers.ModelSerializer):
|
|
items = OrderItemReadSerializer(many=True, read_only=True)
|
|
carrier = CarrierReadSerializer(read_only=True)
|
|
payment = PaymentReadSerializer(source="payment", read_only=True)
|
|
user = serializers.SerializerMethodField()
|
|
|
|
def get_user(self, obj):
|
|
request = self.context.get("request") if hasattr(self, "context") else None
|
|
if request and getattr(request, "user", None) and request.user.is_authenticated and obj.user:
|
|
return UserBriefSerializer(obj.user).data
|
|
return None
|
|
|
|
class Meta:
|
|
model = Order
|
|
fields = [
|
|
"id",
|
|
"status",
|
|
"total_price",
|
|
"currency",
|
|
"first_name",
|
|
"last_name",
|
|
"email",
|
|
"phone",
|
|
"address",
|
|
"city",
|
|
"postal_code",
|
|
"country",
|
|
"note",
|
|
"created_at",
|
|
"updated_at",
|
|
"carrier",
|
|
"payment",
|
|
"user",
|
|
"items",
|
|
]
|
|
read_only_fields = [
|
|
"id",
|
|
"status",
|
|
"total_price",
|
|
"currency",
|
|
"created_at",
|
|
"updated_at",
|
|
]
|
|
|
|
|
|
class OrderMiniSerializer(serializers.ModelSerializer):
|
|
amount = serializers.DecimalField(max_digits=10, decimal_places=2, source="total_price", read_only=True)
|
|
shipping_method = serializers.CharField(source="carrier.shipping_method", read_only=True)
|
|
|
|
class Meta:
|
|
model = Order
|
|
fields = ["id", "amount", "status", "email", "shipping_method"]
|
|
read_only_fields = fields
|
|
|
|
|
|
# ----------------- CREATE PAYLOAD SERIALIZERS (PUBLIC) -----------------
|
|
|
|
class OrderItemCreateSerializer(serializers.Serializer):
|
|
product_id = serializers.IntegerField(label="Product ID")
|
|
quantity = serializers.IntegerField(min_value=1, label="Quantity")
|
|
|
|
|
|
class CarrierCreateSerializer(serializers.Serializer):
|
|
shipping_method = serializers.ChoiceField(
|
|
choices=Carrier.SHIPPING.choices,
|
|
label="Shipping Method",
|
|
help_text="Choose 'store' (pickup) or 'packeta'",
|
|
)
|
|
weight = serializers.DecimalField(
|
|
required=False,
|
|
max_digits=10,
|
|
decimal_places=2,
|
|
label="Weight (kg)",
|
|
)
|
|
|
|
|
|
class PaymentCreateSerializer(serializers.Serializer):
|
|
payment_method = serializers.ChoiceField(
|
|
choices=Payment.PAYMENT.choices,
|
|
label="Payment Method",
|
|
help_text="Choose 'shop', 'stripe' or 'cash_on_delivery'",
|
|
)
|
|
|
|
|
|
class OrderCreateSerializer(serializers.Serializer):
|
|
# Customer/billing
|
|
first_name = serializers.CharField(max_length=100, label="First Name")
|
|
last_name = serializers.CharField(max_length=100, label="Last Name")
|
|
email = serializers.EmailField(label="Email")
|
|
phone = serializers.CharField(max_length=20, required=False, allow_blank=True, label="Phone")
|
|
address = serializers.CharField(max_length=255, label="Address")
|
|
city = serializers.CharField(max_length=100, label="City")
|
|
postal_code = serializers.CharField(max_length=20, label="Postal Code")
|
|
country = serializers.CharField(max_length=100, required=False, default="Czech Republic", label="Country")
|
|
note = serializers.CharField(required=False, allow_blank=True, label="Note")
|
|
|
|
# Nested
|
|
items = OrderItemCreateSerializer(many=True)
|
|
carrier = CarrierCreateSerializer()
|
|
payment = PaymentCreateSerializer()
|
|
discount_codes = serializers.ListField(
|
|
child=serializers.CharField(), required=False, allow_empty=True, label="Discount Codes"
|
|
)
|
|
|
|
def validate(self, attrs):
|
|
if not attrs.get("items"):
|
|
raise serializers.ValidationError({"items": "At least one item is required."})
|
|
return attrs
|
|
|
|
|
|
# ----------------- ADMIN/READ MODELS -----------------
|
|
|
|
class CategorySerializer(serializers.ModelSerializer):
|
|
class Meta:
|
|
model = Category
|
|
fields = [
|
|
"id",
|
|
"name",
|
|
"url",
|
|
"parent",
|
|
"description",
|
|
"image",
|
|
]
|
|
|
|
|
|
class ProductImageSerializer(serializers.ModelSerializer):
|
|
class Meta:
|
|
model = ProductImage
|
|
fields = [
|
|
"id",
|
|
"product",
|
|
"image",
|
|
"alt_text",
|
|
"is_main",
|
|
]
|
|
|
|
|
|
class ProductSerializer(serializers.ModelSerializer):
|
|
class Meta:
|
|
model = Product
|
|
fields = [
|
|
"id",
|
|
"name",
|
|
"description",
|
|
"code",
|
|
"category",
|
|
"price",
|
|
"url",
|
|
"stock",
|
|
"is_active",
|
|
"limited_to",
|
|
"default_carrier",
|
|
"created_at",
|
|
"updated_at",
|
|
]
|
|
read_only_fields = ["created_at", "updated_at"]
|
|
|
|
|
|
class DiscountCodeSerializer(serializers.ModelSerializer):
|
|
class Meta:
|
|
model = DiscountCode
|
|
fields = [
|
|
"id",
|
|
"code",
|
|
"description",
|
|
"percent",
|
|
"amount",
|
|
"valid_from",
|
|
"valid_to",
|
|
"active",
|
|
"usage_limit",
|
|
"used_count",
|
|
"specific_products",
|
|
"specific_categories",
|
|
]
|
|
read_only_fields = ["used_count"]
|
|
|
|
|
|
class RefundSerializer(serializers.ModelSerializer):
|
|
class Meta:
|
|
model = Refund
|
|
fields = [
|
|
"id",
|
|
"order",
|
|
"reason_choice",
|
|
"reason_text",
|
|
"verified",
|
|
"created_at",
|
|
]
|
|
read_only_fields = ["id", "verified", "created_at"]
|
|
|
|
|