ModelSerializer: the shortcut for model-backed APIs
TaskSerializer from part 2 repeated every field Django's Task model already declares. ModelSerializer generates that same field list from the model directly — the same relationship ModelForm has to Form in plain Django.
Rewriting it as a ModelSerializer
# tasks/serializers.py
from rest_framework import serializers
from .models import Task
class TaskSerializer(serializers.ModelSerializer):
class Meta:
model = Task
fields = ["id", "title", "description", "completed", "due_date", "owner", "created_at"]
read_only_fields = ["owner", "created_at"]Meta.fields lists which model fields to expose — explicit, the same reasoning as ModelForm in the Django series: never "__all__", since that silently exposes any field added to the model later, whether or not it should be client-visible. read_only_fields is the ModelSerializer-specific shortcut for fields that should appear in output but never be settable from input — owner gets assigned by the view (part 4), not chosen by whoever's sending the request.
What ModelSerializer infers automatically
- Field types —
Task.completedbeing aBooleanFieldon the model meansModelSerializergenerates aBooleanFieldon the serializer, with no explicit declaration needed. - Validation —
title'smax_length=200on the model becomes the samemax_lengthvalidation on the serializer field, automatically. id— included by default without listing it, since it's the model's primary key.
Anything that needs different behavior than the model's own definition — a field that should be read-only, a custom validator (part 11) — still gets declared explicitly on the serializer class, exactly like the plain Serializer from part 2; ModelSerializer only removes the fields that don't need anything beyond what the model already says.
.save() — creating and updating directly
serializer = TaskSerializer(data={"title": "New task", "owner": 1})
serializer.is_valid()
task = serializer.save() # creates a new Task row
serializer = TaskSerializer(existing_task, data={"title": "Updated title"}, partial=True)
serializer.is_valid()
task = serializer.save() # updates existing_task in placeModelSerializer adds .save() — something the plain Serializer from part 2 doesn't have, since it has no model to persist to. Passing an existing instance as the first argument alongside data= switches .save() from create to update; partial=True allows updating only some fields instead of requiring every one Meta.fields lists.
Listing owner in fields without also adding it to read_only_fields. Without that, any authenticated request can set any task's owner to any user ID it wants — the field being present in the serializer's output doesn't mean it should also be writable from input.
Next: an actual view, so this serializer has a request to work with instead of test data typed into a shell.