- 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.
456 lines
12 KiB
Python
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))
|
|
|
|
|