Exercise 1 — Serializer
class SeatRefSerializer(serializers.Serializer):
sector = serializers.CharField(max_length=50)
row = serializers.CharField(max_length=10)
number = serializers.IntegerField(min_value=1)
class ReserveInputSerializer(serializers.Serializer):
event = serializers.UUIDField()
seats = SeatRefSerializer(many=True, min_length=1, max_length=8) # limits
promo_code = serializers.CharField(max_length=32, required=False, write_only=True)
def validate_event(self, value):
if not Event.objects.filter(uuid=value).exists(): # trivial reference
raise serializers.ValidationError("The event does not exist")
return value- Messages per case: empty/oversized lists → "This field must have between 1 and 8 items"; format → an error per index naming the concrete field; uuid → "The event does not exist" (a business message, not
Invalid pk "..."). - "Past event" queries mutable state that could change between validate and action (a race): the service decides with the event locked (10). The serializer is shape; the service, the world.
Exercise 2 — Layers
- Broken JSON → 400 (DRF parser, syntax layer); wrong types → 400 with per-field
errors(shape layer); seat taken → 409 withtype: seat-already-reserved(business layer, from 10/13's DomainError). - "Seat free" in the serializer reintroduces check-then-act without a lock: two requests validate fine and both create — exactly the 03/10 race that the constraint converts into a late 409 with bad UX. Mutable business validation goes under a lock, in the service.
Exercise 3 — Contract
- The usual bad guesses: viewsets with multiple serializers (list vs detail), FileField/binary, undocumented 4xx errors. The YAML points them out.
@extend_schema(request=ReserveInputSerializer, responses={201: ReservationOut, 409: ProblemDetail, 422: ProblemDetail}, summary="Create reservation")— and the webhook withrequest=GatewayWebhookIn, responses={200: None}.--fail-on-warnforces documenting what the AutoSchema can't deduce: every fixed warning is an endpoint the frontend consumes without asking you.
Exercise 4 — Contract test
def test_reservation_matches_contract(self, client, ...):
resp = client.post("/api/reservations/", payload)
self.assertEqual(resp.status_code, 201)
self.assertEqual(set(resp.data), set(ReservationOut.Meta.fields)) # schema keys- With the extracted examples and
jsonschema.validate(resp.data, example_schema): docs lie when code evolves without updating them — the test makes lying break CI.
Professor's summary
- Layers: syntax → shape → business; business under a lock, not in the serializer.
- Serializer with explicit limits, explicit fields and domain messages.
- OpenAPI generated from code + extend_schema where it guesses; --fail-on-warn in CI.
- One contract test per critical endpoint: docs stop lying.
Next: Lesson 16 — gRPC, GraphQL, WebSockets/SSE.