schon/engine/core/serializers/detail.py
Egor fureunoir Gorbunov eef774c3a3 feat(markdown): integrate markdown rendering and editor support
Replace WYSIWYG editor with Markdown editor across all relevant models and admin fields. Add utilities for rendering and stripping markdown. Adjust serializers, views, and templates to support markdown content. Introduce `PastedImage` model and upload endpoint for handling inline image uploads in markdown.

This change simplifies content formatting while enhancing flexibility with markdown support.
2026-02-27 23:36:51 +03:00

435 lines
10 KiB
Python

import logging
from contextlib import suppress
from typing import Any
from drf_spectacular.utils import extend_schema_field
from rest_framework.fields import JSONField, SerializerMethodField
from rest_framework.serializers import ListSerializer, ModelSerializer
from rest_framework_recursive.fields import RecursiveField
from engine.core.models import (
Attribute,
AttributeGroup,
AttributeValue,
Brand,
Category,
Feedback,
Order,
OrderProduct,
Product,
ProductImage,
ProductTag,
PromoCode,
Promotion,
Stock,
Vendor,
Wishlist,
)
from engine.core.serializers.simple import (
BrandSimpleSerializer,
CategorySimpleSerializer,
ProductSimpleSerializer,
)
from engine.core.serializers.utility import AddressSerializer
from engine.core.typing import FilterableAttribute
from engine.core.utils.markdown import render_markdown
logger = logging.getLogger(__name__)
class AttributeGroupDetailSerializer(ModelSerializer):
children = RecursiveField(many=True)
class Meta:
model = AttributeGroup
fields = [
"uuid",
"name",
"children",
"created",
"modified",
]
class CategoryDetailListSerializer(ListSerializer):
def to_representation(self, data):
items = list(data)
with suppress(Exception):
Category.bulk_prefetch_filterable_attributes(items)
return super().to_representation(items)
class CategoryDetailSerializer(ModelSerializer):
children = SerializerMethodField()
description = SerializerMethodField()
filterable_attributes = SerializerMethodField()
brands = BrandSimpleSerializer(many=True, read_only=True)
min_price = SerializerMethodField()
max_price = SerializerMethodField()
class Meta:
model = Category
list_serializer_class = CategoryDetailListSerializer
fields = [
"uuid",
"name",
"description",
"image",
"markup_percent",
"filterable_attributes",
"brands",
"children",
"slug",
"created",
"modified",
]
def get_description(self, obj: Category) -> str:
return render_markdown(obj.description or "")
def get_filterable_attributes(self, obj: Category) -> list[FilterableAttribute]:
return obj.filterable_attributes
@extend_schema_field(CategorySimpleSerializer(many=True))
def get_children(self, obj: Category) -> list[dict[str, Any]]:
request = self.context.get("request")
if request is not None and request.user.has_perm("view_category"):
children = obj.children.all()
else:
children = obj.children.filter(is_active=True)
if obj.children.exists():
serializer = CategorySimpleSerializer(
children, many=True, context=self.context
)
return list(serializer.data)
return []
def get_min_price(self, obj: Category):
return obj.min_price
def get_max_price(self, obj: Category):
return obj.max_price
class BrandDetailSerializer(ModelSerializer):
categories = CategorySimpleSerializer(many=True)
description = SerializerMethodField()
class Meta:
model = Brand
fields = [
"uuid",
"name",
"description",
"categories",
"created",
"modified",
"big_logo",
"small_logo",
]
def get_description(self, obj: Brand) -> str:
return render_markdown(obj.description or "")
class BrandProductDetailSerializer(ModelSerializer):
class Meta:
model = Brand
fields = [
"uuid",
"name",
"created",
"modified",
"big_logo",
"small_logo",
]
class ProductTagDetailSerializer(ModelSerializer):
class Meta:
model = ProductTag
fields = [
"uuid",
"tag_name",
"name",
"created",
"modified",
]
class ProductImageDetailSerializer(ModelSerializer):
class Meta:
model = ProductImage
fields = [
"uuid",
"alt",
"image",
"priority",
"created",
"modified",
]
class AttributeDetailSerializer(ModelSerializer):
categories = CategoryDetailSerializer(many=True)
group = AttributeGroupDetailSerializer()
class Meta:
model = Attribute
fields = [
"uuid",
"name",
"value_type",
"categories",
"group",
"created",
"modified",
]
class AttributeInnerSerializer(ModelSerializer):
group = AttributeGroupDetailSerializer()
class Meta:
model = Attribute
fields = [
"uuid",
"name",
"value_type",
"group",
"created",
"modified",
]
class AttributeValueDetailSerializer(ModelSerializer):
attribute = AttributeInnerSerializer()
class Meta:
model = AttributeValue
fields = [
"uuid",
"attribute",
"value",
"created",
"modified",
]
class VendorDetailSerializer(ModelSerializer):
class Meta:
model = Vendor
fields = [
"uuid",
"name",
"authentication",
"markup_percent",
"created",
"modified",
]
class StockDetailSerializer(ModelSerializer):
vendor = VendorDetailSerializer()
class Meta:
model = Stock
fields = [
"uuid",
"vendor",
"price",
"purchase_price",
"quantity",
"sku",
"digital_asset",
"created",
"modified",
]
class PromoCodeDetailSerializer(ModelSerializer):
class Meta:
model = PromoCode
fields = [
"uuid",
"code",
"discount_amount",
"discount_percent",
"start_time",
"end_time",
"used_on",
"created",
"modified",
]
class ProductDetailSerializer(ModelSerializer):
brand = BrandProductDetailSerializer()
category = CategorySimpleSerializer()
tags = ProductTagDetailSerializer(
many=True,
)
images = ProductImageDetailSerializer(
many=True,
)
attributes = AttributeValueDetailSerializer(
many=True,
)
description = SerializerMethodField()
rating = SerializerMethodField()
price = SerializerMethodField()
quantity = SerializerMethodField()
feedbacks_count = SerializerMethodField()
discount_price = SerializerMethodField()
personal_orders_only = SerializerMethodField()
class Meta:
model = Product
fields = [
"uuid",
"sku",
"name",
"description",
"partnumber",
"is_digital",
"brand",
"category",
"feedbacks_count",
"quantity",
"tags",
"slug",
"images",
"attributes",
"rating",
"price",
"created",
"modified",
"discount_price",
"personal_orders_only",
]
def get_description(self, obj: Product) -> str:
return render_markdown(obj.description or "")
def get_rating(self, obj: Product) -> float:
return obj.rating
def get_price(self, obj: Product) -> float:
return obj.price
def get_feedbacks_count(self, obj: Product) -> int:
return obj.feedbacks_count
def get_personal_orders_only(self, obj: Product) -> bool:
return obj.personal_orders_only
def get_quantity(self, obj: Product) -> int:
return obj.quantity
def get_discount_price(self, obj: Product) -> float | None:
return obj.discount_price
class PromotionDetailSerializer(ModelSerializer):
products = ProductDetailSerializer(
many=True,
)
description = SerializerMethodField()
class Meta:
model = Promotion
fields = [
"uuid",
"name",
"discount_percent",
"description",
"products",
"created",
"modified",
]
def get_description(self, obj: Promotion) -> str:
return render_markdown(obj.description or "")
class WishlistDetailSerializer(ModelSerializer):
products = ProductSimpleSerializer(
many=True,
)
class Meta:
model = Wishlist
fields = [
"uuid",
"products",
"created",
"modified",
]
class OrderProductDetailSerializer(ModelSerializer):
product = ProductDetailSerializer()
billing_address = AddressSerializer(read_only=True, required=False)
shipping_address = AddressSerializer(read_only=True, required=False)
attributes = JSONField(required=False)
class Meta:
model = OrderProduct
fields = [
"uuid",
"product",
"quantity",
"buy_price",
"comments",
"notifications",
"billing_address",
"shipping_address",
"attributes",
"status",
"created",
"modified",
]
class FeedbackDetailSerializer(ModelSerializer):
order_product = OrderProductDetailSerializer()
class Meta:
model = Feedback
fields = [
"uuid",
"rating",
"comment",
"order_product",
"created",
"modified",
]
class OrderDetailSerializer(ModelSerializer):
promo_code = PromoCodeDetailSerializer()
order_products = OrderProductDetailSerializer(
many=True,
)
total_price = SerializerMethodField(read_only=True)
class Meta:
model = Order
fields = [
"uuid",
"status",
"total_price",
"promo_code",
"billing_address",
"shipping_address",
"buy_time",
"order_products",
"human_readable_id",
"created",
"modified",
]
def get_total_price(self, obj: Order) -> float:
return obj.total_price