Django from Basics to Production · APIs with Django REST Framework, and Production
Serializers: Validation and Representation
Turn models into JSON and validate incoming data with DRF serializers, including field-level and object-level rules.
What you'll build
An OrderSerializer that validates incoming orders (quantity range, a product that exists and is in stock, a cross-field discount rule), exposes read-only computed fields, and never lets clients set internal fields like status.
The idea
- A serializer converts model instances to primitive data (
serializer.data) and validates input (is_valid(),validated_data,errors). ModelSerializergenerates fields from a model; list them explicitly and mark server-controlled onesread_only_fields.validate_<field>()checks one field;validate()checks combinations.SerializerMethodFieldadds computed output.
The code
import sys, types
import django
from django.conf import settings
sys.modules["shop"] = types.ModuleType("shop") # lets this single file act as the "shop" app
sys.modules["shop"].__file__ = __file__
settings.configure(
SECRET_KEY="dev-only", DEBUG=False, ALLOWED_HOSTS=["testserver"], ROOT_URLCONF=__name__,
DATABASES={"default": {"ENGINE": "django.db.backends.sqlite3", "NAME": ":memory:"}},
INSTALLED_APPS=["django.contrib.contenttypes", "django.contrib.auth", "rest_framework", "shop"],
)
django.setup()
from django.db import connection, models
from rest_framework import serializers
class Product(models.Model):
name = models.CharField(max_length=100)
stock = models.PositiveIntegerField()
class Meta: app_label = "shop"
class Order(models.Model):
product = models.ForeignKey(Product, on_delete=models.PROTECT)
quantity = models.PositiveIntegerField()
discount_code = models.CharField(max_length=20, blank=True)
status = models.CharField(max_length=20, default="new")
class Meta: app_label = "shop"
with connection.schema_editor() as editor:
editor.create_model(Product); editor.create_model(Order)
mug = Product.objects.create(name="Mug", stock=5)
class OrderSerializer(serializers.ModelSerializer):
product_name = serializers.SerializerMethodField()
class Meta:
model = Order
fields = ["id", "product", "product_name", "quantity", "discount_code", "status"]
read_only_fields = ["status"]
def get_product_name(self, order):
return order.product.name
def validate_quantity(self, value):
if not 1 <= value <= 10:
raise serializers.ValidationError("Order between 1 and 10 items.")
return value
def validate(self, data):
if data["quantity"] > data["product"].stock:
raise serializers.ValidationError({"quantity": f"Only {data['product'].stock} in stock."})
if data.get("discount_code") and data["quantity"] < 3:
raise serializers.ValidationError("Discount codes need at least 3 items.")
return data
for payload in [{"product": mug.pk, "quantity": 2, "status": "shipped"},
{"product": mug.pk, "quantity": 8},
{"product": 999, "quantity": 1, "discount_code": "SAVE"},
{"product": mug.pk, "quantity": 1, "discount_code": "SAVE"}]:
s = OrderSerializer(data=payload)
if s.is_valid():
print("saved:", dict(OrderSerializer(s.save()).data))
else:
print("errors:", {k: [str(e) for e in v] for k, v in s.errors.items()})
Output
saved: {'id': 1, 'product': 1, 'product_name': 'Mug', 'quantity': 2, 'discount_code': '', 'status': 'new'}
errors: {'quantity': ['Only 5 in stock.']}
errors: {'product': ['Invalid pk "999" - object does not exist.']}
errors: {'non_field_errors': ['Discount codes need at least 3 items.']}Run this on your computer — see “Get Started” for setup.
Common mistakes
fields = "__all__", exposing and accepting every column.- Validating in views instead of serializers, so rules differ between endpoints.
SerializerMethodFields that query the database per object (N+1); prefetch in the view's queryset.
Key points
- Serializers validate input and shape output.
- List fields explicitly; make server-controlled fields read-only.
- Field validators for single values,
validate()for cross-field rules.
Further reading: Serializers · Validators
Practice questions
How do you stop clients from setting a field like status through the API?
Answer
List it in read_only_fields (or declare it read_only=True): it's included in output but ignored in input.
What's the difference between validate_<field>() and validate()?
Answer
validate_<field> checks a single field's value; validate receives all validated fields and checks rules that involve several of them.
How can SerializerMethodField cause performance problems?
Answer
If it accesses related objects, it runs queries per serialized object (N+1); prefetch or annotate in the view's queryset.
Open in the app to mark lessons complete, take notes and track your progress.