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.

Create a free account to save this page, take notes and track your progress. Sign in

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).
  • ModelSerializer generates fields from a model; list them explicitly and mark server-controlled ones read_only_fields.
  • validate_<field>() checks one field; validate() checks combinations.
  • SerializerMethodField adds computed output.

The code

Validating and representing orders
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.

Was this page helpful?