Files
vontor-cz/backend/commerce/serializers.py
David Bruno Vontor 1751badb90 Refactor frontend components and backend migrations
- Removed TradingGraph component from frontend/src/components/trading.
- Updated home page to import Services component and TradingGraph from new path.
- Modified PortfolioPage to return null instead of PortfolioGrid.
- Added initial migrations for account, advertisement, commerce, configuration, downloader, gopay, stripe, trading212, and zasilkovna apps in the backend.
- Created Services component with subcomponents for Kinematografie, Drone Service, and Website Service.
- Implemented TradingGraph component with dynamic data generation and canvas rendering.
- Updated DonationShop component to display donation tiers with icons and descriptions.
2025-12-14 03:49:16 +01:00

456 lines
12 KiB
Python

from rest_framework import serializers
from thirdparty.stripe.client import StripeClient
from .models import Refund, Order, Invoice
class RefundCreatePublicSerializer(serializers.Serializer):
email = serializers.EmailField()
invoice_number = serializers.CharField(required=False, allow_blank=True)
order_id = serializers.IntegerField(required=False)
# Optional reason fields
reason_choice = serializers.ChoiceField(
choices=Refund.Reason.choices, required=False
)
reason_text = serializers.CharField(required=False, allow_blank=True)
def validate(self, attrs):
email = attrs.get("email")
invoice_number = (attrs.get("invoice_number") or "").strip()
order_id = attrs.get("order_id")
if not invoice_number and not order_id:
raise serializers.ValidationError(
"Provide either invoice_number or order_id."
)
order = None
if invoice_number:
try:
invoice = Invoice.objects.get(invoice_number=invoice_number)
order = invoice.order
except Invoice.DoesNotExist:
raise serializers.ValidationError({"invoice_number": "Invoice not found."})
except Order.DoesNotExist:
raise serializers.ValidationError({"invoice_number": "Order for invoice not found."})
if order_id and order is None:
try:
order = Order.objects.get(id=order_id)
except Order.DoesNotExist:
raise serializers.ValidationError({"order_id": "Order not found."})
# Verify email matches order's email or user's email
if not order:
raise serializers.ValidationError("Order could not be resolved.")
order_email = (order.email or "").strip().lower()
user_email = (getattr(order.user, "email", "") or "").strip().lower()
provided = email.strip().lower()
if provided not in {order_email, user_email}:
raise serializers.ValidationError({"email": "Email does not match the order."})
attrs["order"] = order
return attrs
def create(self, validated_data):
order = validated_data["order"]
reason_choice = validated_data.get("reason_choice") or Refund.Reason.OTHER
reason_text = validated_data.get("reason_text", "")
refund = Refund.objects.create(
order=order,
reason_choice=reason_choice,
reason_text=reason_text,
)
return refund
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 django.db import transaction
from django.core.exceptions import ValidationError
from .models import (
Category,
Product,
ProductImage,
DiscountCode,
Refund,
Order,
OrderItem,
Carrier,
Payment,
)
from thirdparty.stripe.models import StripeModel
from thirdparty.zasilkovna.serializers import ZasilkovnaPacketSerializer
from thirdparty.zasilkovna.models import ZasilkovnaPacket
User = get_user_model()
# ----------------- CREATING ORDER SERIALIZER -----------------
#correct
# -- CARRIER --
class OrderCarrierSerializer(serializers.ModelSerializer):
# vstup: jen ID adresy z widgetu (write-only)
packeta_address_id = serializers.IntegerField(required=False, write_only=True)
# výstup: serializovaný packet
zasilkovna = ZasilkovnaPacketSerializer(many=True, read_only=True)
class Meta:
model = Carrier
fields = ["shipping_method", "state", "zasilkovna", "shipping_price", "packeta_address_id"]
read_only_fields = ["state", "shipping_price", "zasilkovna"]
def create(self, validated_data):
packeta_address_id = validated_data.pop("packeta_address_id", None)
carrier = Carrier.objects.create(**validated_data)
if packeta_address_id is not None:
# vytvoříme nový packet s danou addressId
packet = ZasilkovnaPacket.objects.create(addressId=packeta_address_id)
carrier.zasilkovna.add(packet)
return carrier
#correct
# -- ORDER ITEMs --
class OrderItemCreateSerializer(serializers.Serializer):
product_id = serializers.IntegerField()
quantity = serializers.IntegerField(min_value=1, default=1)
def validate(self, attrs):
product_id = attrs.get("product_id")
try:
product = Product.objects.get(pk=product_id)
except Product.DoesNotExist:
raise serializers.ValidationError({"product_id": "Product not found."})
attrs["product"] = product
return attrs
# -- PAYMENT --
class PaymentSerializer(serializers.ModelSerializer):
class Meta:
model = Payment
fields = [
"id",
"payment_method",
"stripe",
"stripe_session_id",
"stripe_payment_intent",
"stripe_session_url",
]
read_only_fields = [
"id",
"stripe",
"stripe_session_id",
"stripe_payment_intent",
"stripe_session_url",
]
def create(self, validated_data):
order = self.context.get("order") # musíš ho předat při inicializaci serializeru
carrier = self.context.get("carrier")
with transaction.atomic():
payment = Payment.objects.create(
order=order,
carrier=carrier,
**validated_data
)
# pokud je Stripe, vytvoříme checkout session
if payment.payment_method == Payment.PAYMENT.SHOP and carrier.shipping_method != Carrier.SHIPPING.STORE:
raise serializers.ValidationError("Platba v obchodě je možná pouze pro osobní odběr.")
elif payment.payment_method == Payment.PAYMENT.CASH_ON_DELIVERY and carrier.shipping_method == Carrier.SHIPPING.STORE:
raise ValidationError("Dobírka není možná pro osobní odběr.")
if payment.payment_method == Payment.PAYMENT.STRIPE:
session = StripeClient.create_checkout_session(order)
payment.stripe_session_id = session.id
payment.stripe_payment_intent = session.payment_intent
payment.stripe_session_url = session.url
payment.save(update_fields=[
"stripe_session_id",
"stripe_payment_intent",
"stripe_session_url",
])
return payment
# -- ORDER CREATE SERIALIZER --
class OrderCreateSerializer(serializers.Serializer):
# Customer/billing data (optional when authenticated)
first_name = serializers.CharField(required=False)
last_name = serializers.CharField(required=False)
email = serializers.EmailField(required=False)
phone = serializers.CharField(required=False, allow_blank=True)
address = serializers.CharField(required=False)
city = serializers.CharField(required=False)
postal_code = serializers.CharField(required=False)
country = serializers.CharField(required=False, default="Czech Republic")
note = serializers.CharField(required=False, allow_blank=True)
# Nested structures
#produkty
items = OrderItemCreateSerializer(many=True)
#doprava/vyzvednutí + zasilkovna input (serializer)
carrier = OrderCarrierSerializer()
payment = PaymentSerializer()
#slevové kódy
discount_codes = serializers.ListField(
child=serializers.CharField(), required=False, allow_empty=True
)
def validate(self, attrs):
request = self.context.get("request")
#kontrola jestli je uzivatel valid/prihlasen
is_auth = bool(getattr(getattr(request, "user", None), "is_authenticated", False))
# pokud není, tak se musí vyplnit povinné údaje
required_fields = [
"first_name",
"last_name",
"email",
"address",
"city",
"postal_code",
]
if not is_auth:
missing_fields = []
# přidame fieldy, které nejsou vyplněné
for field in required_fields:
if attrs.get(field) not in required_fields:
missing_fields.append(field)
if missing_fields:
raise serializers.ValidationError({"billing": f"Missing fields: {', '.join(missing_fields)}"})
# pokud chybí itemy:
if not attrs.get("items"):
raise serializers.ValidationError({"items": "At least one item is required."})
return attrs
def create(self, validated_data):
items_data = validated_data.pop("items", [])
carrier_data = validated_data.pop("carrier")
payment_data = validated_data.pop("payment")
codes = validated_data.pop("discount_codes", [])
request = self.context.get("request")
user = getattr(request, "user", None)
is_auth = bool(getattr(user, "is_authenticated", False))
with transaction.atomic():
# Create Order (user data imported on save if user is set)
order = Order(
user=user if is_auth else None,
first_name=validated_data.get("first_name", ""),
last_name=validated_data.get("last_name", ""),
email=validated_data.get("email", ""),
phone=validated_data.get("phone", ""),
address=validated_data.get("address", ""),
city=validated_data.get("city", ""),
postal_code=validated_data.get("postal_code", ""),
country=validated_data.get("country", "Czech Republic"),
note=validated_data.get("note", ""),
)
# Order.save se postara o to jestli má doplnit data z usera
order.save()
# Vytvoření Carrier skrz serializer
carrier = OrderCarrierSerializer(data=carrier_data)
carrier.is_valid(raise_exception=True)
carrier = carrier.save()
order.carrier = carrier
order.save(update_fields=["carrier", "updated_at"]) # will recalc total later
# Vytvořit Order Items individualně, aby se spustila kontrola položek na skladu
for item in items_data:
product = item["product"] # OrderItemCreateSerializer.validate
quantity = int(item.get("quantity", 1))
OrderItem.objects.create(order=order, product=product, quantity=quantity)
# -- Slevové kódy --
if codes:
discounts = list(DiscountCode.objects.filter(code__in=codes))
if discounts:
order.discount.add(*discounts)
# -- Payment --
payment_serializer = PaymentSerializer(
data=payment_data,
context={"order": order, "carrier": carrier}
)
payment_serializer.is_valid(raise_exception=True)
payment = payment_serializer.save()
# přiřadíme k orderu
order.payment = payment
order.save(update_fields=["payment"])
return order
# ----------------- 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 = "__all__"
read_only_fields = ["created_at", "updated_at"]
class DiscountCodeSerializer(serializers.ModelSerializer):
class Meta:
model = DiscountCode
fields = "__all__"
read_only_fields = ["used_count"]
class RefundSerializer(serializers.ModelSerializer):
class Meta:
model = Refund
fields = "__all__"
read_only_fields = ["id", "verified", "created_at"]
# ----------------- READ SERIALIZERS USED BY VIEWS -----------------
class ZasilkovnaPacketReadSerializer(ZasilkovnaPacketSerializer):
class Meta(ZasilkovnaPacketSerializer.Meta):
fields = getattr(ZasilkovnaPacketSerializer.Meta, "fields", None)
class CarrierReadSerializer(serializers.ModelSerializer):
zasilkovna = ZasilkovnaPacketReadSerializer(many=True, read_only=True)
class Meta:
model = Carrier
fields = ["shipping_method", "state", "zasilkovna", "shipping_price"]
read_only_fields = fields
class ProductMiniSerializer(serializers.ModelSerializer):
class Meta:
model = Product
fields = ["id", "name", "price"]
class OrderItemReadSerializer(serializers.ModelSerializer):
product = ProductMiniSerializer(read_only=True)
class Meta:
model = OrderItem
fields = ["id", "product", "quantity"]
read_only_fields = fields
class PaymentReadSerializer(serializers.ModelSerializer):
class Meta:
model = Payment
fields = ["payment_method"]
read_only_fields = fields
class OrderMiniSerializer(serializers.ModelSerializer):
class Meta:
model = Order
fields = ["id", "status", "total_price", "created_at"]
read_only_fields = fields
class OrderReadSerializer(serializers.ModelSerializer):
items = OrderItemReadSerializer(many=True, read_only=True)
carrier = CarrierReadSerializer(read_only=True)
payment = PaymentReadSerializer(read_only=True)
discount_codes = serializers.SerializerMethodField()
class Meta:
model = Order
fields = [
"id",
"status",
"total_price",
"currency",
"user",
"first_name",
"last_name",
"email",
"phone",
"address",
"city",
"postal_code",
"country",
"note",
"created_at",
"updated_at",
"items",
"carrier",
"payment",
"discount_codes",
]
read_only_fields = fields
def get_discount_codes(self, obj: Order):
return list(obj.discount.values_list("code", flat=True))