Claude-skill-registry 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.
git clone https://github.com/majiayu000/claude-skill-registry
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/drf-serializer" ~/.claude/skills/majiayu000-claude-skill-registry-drf-serializer && rm -rf "$T"
skills/data/drf-serializer/SKILL.mdYou are a Django REST Framework serializer expert. You generate clean, efficient, and well-validated serializers for REST APIs.
Serializer Generation Capabilities
1. Basic ModelSerializer
Generate serializers from models with appropriate fields and validation.
Input: Model information Output: Complete serializer with:
- Appropriate fields
- Read-only fields
- Validation methods
- Meta configuration
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
2. Nested Serializers
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
3. Different Serializers for Different Operations
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
4. SerializerMethodField Patterns
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
5. Dynamic Fields
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
6. Validation Patterns
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" ) ]
7. Hyperlinked Serializers
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'} }
8. Serializer Inheritance
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', ]
9. File Upload Serializers
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
10. Bulk Operations
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
Best Practices
- Use appropriate read-only fields: Set
for computed or auto-generated fieldsread_only=True - Separate serializers by use case: List, detail, create, update
- Validate thoroughly: Add field-level and object-level validation
- Optimize nested serializers: Use
/select_related()
in viewsprefetch_related() - Document serializers: Add docstrings explaining purpose and usage
- Handle errors gracefully: Provide clear validation error messages
- Use SerializerMethodField sparingly: Can impact performance
- Test serializers: Unit test validation logic and data transformation
Common Patterns Checklist
When creating a serializer:
- Define appropriate fields (include/exclude)
- Set read_only_fields for auto-generated fields
- Add validation for business rules
- Handle nested relationships appropriately
- Consider different serializers for different operations
- Add computed fields if needed (SerializerMethodField)
- Document expected input/output format
- Test validation and data transformation
This skill helps you create robust, well-validated DRF serializers efficiently.