xiaohongshu
xiaohongshu
drf-serializer
Generate Django REST Framework serializers with validation, nested relationships, and best practices. Use when creating or updating API serializers, especially for complex models with relationships.
Full skill instructions
You are a Django REST Framework serializer expert. You generate clean, efficient, and well-validated serializers for REST APIs.
Generate serializers from models with appropriate fields and validation.
Input: Model information Output: Complete serializer with:
Example:
from rest_framework import serializers
from .models import Product
class ProductSerializer(serializers.ModelSerializer):
"""Serializer for Product model"""
class Meta:
model = Product
fields = [
'id',
'name',
'slug',
'description',
'price',
'stock',
'category',
'created_at',
'updated_at',
]
read_only_fields = ['id', 'slug', 'created_at', 'updated_at']
def validate_price(self, value):
"""Ensure price is positive"""
if value <= 0:
raise serializers.ValidationError("Price must be greater than zero")
return value
def validate_stock(self, value):
"""Ensure stock is non-negative"""
if value < 0:
raise serializers.ValidationError("Stock cannot be negative")
return value
Handle relationships with nested serialization for both read and write operations.
Pattern 1: Read-only nested
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = ['id', 'name', 'slug']
class ProductSerializer(serializers.ModelSerializer):
category = CategorySerializer(read_only=True) # Nested for reading
category_id = serializers.PrimaryKeyRelatedField(
queryset=Category.objects.all(),
source='category',
write_only=True # PK for writing
)
class Meta:
model = Product
fields = [
'id',
'name',
'price',
'category', # Nested object in response
'category_id', # ID for creating/updating
]
Pattern 2: Writable nested
class AddressSerializer(serializers.ModelSerializer):
class Meta:
model = Address
fields = ['street', 'city', 'state', 'zip_code']
class CustomerSerializer(serializers.ModelSerializer):
addresses = AddressSerializer(many=True)
class Meta:
model = Customer
fields = ['id', 'name', 'email', 'addresses']
def create(self, validated_data):
"""Handle nested address creation"""
addresses_data = validated_data.pop('addresses')
customer = Customer.objects.create(**validated_data)
for address_data in addresses_data:
Address.objects.create(customer=customer, **address_data)
return customer
def update(self, instance, validated_data):
"""Handle nested address updates"""
addresses_data = validated_data.pop('addresses', None)
# Update customer fields
for attr, value in validated_data.items():
setattr(instance, attr, value)
instance.save()
# Update addresses if provided
if addresses_data is not None:
instance.addresses.all().delete() # Clear existing
for address_data in addresses_data:
Address.objects.create(customer=instance, **address_data)
return instance
List Serializer (lightweight):
class ProductListSerializer(serializers.ModelSerializer):
"""Lightweight serializer for list view"""
category_name = serializers.CharField(source='category.name', read_only=True)
thumbnail = serializers.SerializerMethodField()
class Meta:
model = Product
fields = ['id', 'name', 'price', 'category_name', 'thumbnail']
def get_thumbnail(self, obj):
"""Get thumbnail URL"""
if obj.image:
return obj.image.url
return None
Detail Serializer (comprehensive):
class ProductDetailSerializer(serializers.ModelSerializer):
"""Detailed serializer for detail view"""
category = CategorySerializer(read_only=True)
reviews = ReviewSerializer(many=True, read_only=True)
average_rating = serializers.SerializerMethodField()
related_products = serializers.SerializerMethodField()
class Meta:
model = Product
fields = [
'id',
'name',
'slug',
'description',
'price',
'stock',
'category',
'images',
'reviews',
'average_rating',
'related_products',
'created_at',
'updated_at',
]
def get_average_rating(self, obj):
"""Calculate average rating"""
from django.db.models import Avg
result = obj.reviews.aggregate(avg_rating=Avg('rating'))
return result['avg_rating']
def get_related_products(self, obj):
"""Get related products"""
related = Product.objects.filter(
category=obj.category
).exclude(id=obj.id)[:4]
return ProductListSerializer(related, many=True, context=self.context).data
Create/Update Serializer:
class ProductWriteSerializer(serializers.ModelSerializer):
"""Serializer for creating/updating products"""
class Meta:
model = Product
fields = [
'name',
'description',
'price',
'stock',
'category',
'featured',
]
def validate(self, attrs):
"""Cross-field validation"""
if attrs.get('featured') and attrs.get('stock', 0) == 0:
raise serializers.ValidationError(
"Featured products must have stock available"
)
return attrs
Add computed or derived fields:
class OrderSerializer(serializers.ModelSerializer):
total_items = serializers.SerializerMethodField()
discount_amount = serializers.SerializerMethodField()
final_total = serializers.SerializerMethodField()
status_display = serializers.CharField(source='get_status_display', read_only=True)
class Meta:
model = Order
fields = [
'id',
'subtotal',
'tax',
'total',
'total_items',
'discount_amount',
'final_total',
'status',
'status_display',
]
def get_total_items(self, obj):
"""Count total items in order"""
return obj.items.count()
def get_discount_amount(self, obj):
"""Calculate discount amount"""
if obj.discount_code:
return obj.subtotal * (obj.discount_code.percentage / 100)
return 0
def get_final_total(self, obj):
"""Calculate final total with discounts"""
discount = self.get_discount_amount(obj)
return obj.total - discount
Allow clients to specify which fields to include:
class DynamicFieldsModelSerializer(serializers.ModelSerializer):
"""
A ModelSerializer that takes an additional `fields` argument to
control which fields should be displayed.
"""
def __init__(self, *args, **kwargs):
# Get fields from context or kwargs
fields = kwargs.pop('fields', None)
context = kwargs.get('context', {})
request = context.get('request')
if request and not fields:
# Get fields from query param: ?fields=id,name,price
fields = request.query_params.get('fields')
super().__init__(*args, **kwargs)
if fields:
fields = fields.split(',') if isinstance(fields, str) else fields
# Drop fields not specified
allowed = set(fields)
existing = set(self.fields.keys())
for field_name in existing - allowed:
self.fields.pop(field_name)
class ProductSerializer(DynamicFieldsModelSerializer):
class Meta:
model = Product
fields = ['id', 'name', 'description', 'price', 'stock', 'category']
# Usage: GET /api/products/?fields=id,name,price
Field-level validation:
def validate_email(self, value):
"""Validate email is unique"""
if User.objects.filter(email=value).exists():
raise serializers.ValidationError("Email already in use")
return value.lower()
def validate_phone(self, value):
"""Validate phone number format"""
import re
phone_regex = r'^\+?1?\d{9,15}$'
if not re.match(phone_regex, value):
raise serializers.ValidationError("Invalid phone number format")
return value
Object-level validation:
def validate(self, attrs):
"""Validate entire object"""
start_date = attrs.get('start_date')
end_date = attrs.get('end_date')
if start_date and end_date and start_date > end_date:
raise serializers.ValidationError({
'end_date': "End date must be after start date"
})
# Check business rules
if attrs.get('discount') > 0 and not attrs.get('discount_code'):
raise serializers.ValidationError(
"Discount code required when discount is applied"
)
return attrs
Custom validators:
from rest_framework.validators import UniqueTogetherValidator
class BookingSerializer(serializers.ModelSerializer):
class Meta:
model = Booking
fields = ['id', 'room', 'date', 'time_slot', 'user']
validators = [
UniqueTogetherValidator(
queryset=Booking.objects.all(),
fields=['room', 'date', 'time_slot'],
message="This time slot is already booked"
)
]
Use hyperlinks instead of primary keys:
class ProductHyperlinkedSerializer(serializers.HyperlinkedModelSerializer):
category = serializers.HyperlinkedRelatedField(
view_name='category-detail',
read_only=True
)
reviews = serializers.HyperlinkedRelatedField(
many=True,
read_only=True,
view_name='review-detail'
)
class Meta:
model = Product
fields = [
'url', # Self link
'id',
'name',
'category',
'reviews',
]
extra_kwargs = {
'url': {'view_name': 'product-detail', 'lookup_field': 'slug'}
}
Create base serializers and extend them:
class BaseProductSerializer(serializers.ModelSerializer):
"""Base serializer with common fields"""
class Meta:
model = Product
fields = ['id', 'name', 'slug', 'price']
read_only_fields = ['id', 'slug']
class ProductPublicSerializer(BaseProductSerializer):
"""Public API serializer"""
category_name = serializers.CharField(source='category.name')
class Meta(BaseProductSerializer.Meta):
fields = BaseProductSerializer.Meta.fields + [
'description',
'category_name',
]
class ProductAdminSerializer(BaseProductSerializer):
"""Admin API serializer with extra fields"""
category = CategorySerializer(read_only=True)
class Meta(BaseProductSerializer.Meta):
fields = BaseProductSerializer.Meta.fields + [
'description',
'stock',
'cost',
'category',
'active',
'featured',
'created_at',
'updated_at',
]
read_only_fields = BaseProductSerializer.Meta.read_only_fields + [
'created_at',
'updated_at',
]
Handle file uploads properly:
class ProductImageSerializer(serializers.ModelSerializer):
image = serializers.ImageField(required=True)
image_url = serializers.SerializerMethodField()
class Meta:
model = ProductImage
fields = ['id', 'image', 'image_url', 'alt_text', 'order']
def get_image_url(self, obj):
"""Get absolute URL for image"""
request = self.context.get('request')
if obj.image and request:
return request.build_absolute_uri(obj.image.url)
return None
def validate_image(self, value):
"""Validate image file"""
# Check file size (5MB max)
if value.size > 5 * 1024 * 1024:
raise serializers.ValidationError("Image size cannot exceed 5MB")
# Check file type
allowed_types = ['image/jpeg', 'image/png', 'image/webp']
if value.content_type not in allowed_types:
raise serializers.ValidationError(
f"Unsupported file type. Allowed: {', '.join(allowed_types)}"
)
return value
Handle bulk create/update:
class ProductBulkSerializer(serializers.ListSerializer):
"""Handle bulk product operations"""
def create(self, validated_data):
"""Bulk create products"""
products = [Product(**item) for item in validated_data]
return Product.objects.bulk_create(products)
def update(self, instance, validated_data):
"""Bulk update products"""
product_mapping = {product.id: product for product in instance}
data_mapping = {item['id']: item for item in validated_data}
# Update existing products
products_to_update = []
for product_id, data in data_mapping.items():
product = product_mapping.get(product_id)
if product:
for attr, value in data.items():
setattr(product, attr, value)
products_to_update.append(product)
Product.objects.bulk_update(
products_to_update,
['name', 'price', 'stock'] # Fields to update
)
return products_to_update
class ProductSerializer(serializers.ModelSerializer):
class Meta:
model = Product
fields = ['id', 'name', 'price', 'stock']
list_serializer_class = ProductBulkSerializer
read_only=True for computed or auto-generated fieldsselect_related()/prefetch_related() in viewsWhen creating a serializer:
This skill helps you create robust, well-validated DRF serializers efficiently.
xiaohongshu
technical spec
product ux expert
database patterns
Conduct multi-agent task orchestration and workflow coordination.
Initialize project with Conductor artifacts (product definition,
Expert in web animations, transitions, and motion design using Framer Motion and CSS
Creates Mermaid and ASCII diagrams for flowcharts, architecture, ERDs, state machines, mindmaps, and more. Use when user mentions diagram, flowchart, mermaid, ASCII diagram, text diagram, terminal diagram, visualize, C4, mindmap, architecture diagram, sequence diagram, ERD, or needs visual docume...
PostgreSQL bindings for H3 hexagonal grid system. Use when working with H3 cells in Postgres, including spatial indexing, geometry/geography integration, and raster analysis.
Context-Driven Development skill for projects using Conductor. Use this skill when you detect a `conductor/` directory in the project, when working on tasks defined in a `plan.md` file, or when the user asks about tracks, specs, or plans. Automatically applies TDD workflow, tracks task completion...
Display project status, active tracks, and next actions
Official Stakpak application containerization standard operating procedure, a step-by-step guidline to properly dockerize applications. This is a rule book curated by the Stakpak Team.
Generate, edit, and beat-sync AI video with leading models in one workspace.
The world's fastest calendar for remote work
Transform Your Design with AI Designer by ImgCreator.ai
Revolutionizing Video Production with AI-Powered Creativity
Extend an image past the frame and let AI fill the new aspect ratio.
Discover your celebrity doppelgänger with StarByFace!
ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.
Opus.ai: Revolutionize Your Web Experience