In Django REST Framework, when do you use validate_<field_name>() versus validate(), and in what order does is_valid() run them?
answer
- one value versus the whole payload
- field errors stop the pipeline
- validators before validate()
- return the value, return attrs
basics
~20 svalidate_<field_name>(value) checks one field after its own conversion and validators; validate(attrs) checks rules across fields after every field passed and serializer-level validators ran. Both return the value, and a ValidationError in validate() lands under non_field_errors unless given a dict.
solid answer
~40 s`is_valid()` runs fields first: each writable field converts its input, applies its validators, and then, if the serializer defines `validate_<field_name>(self, value)`, calls it with the converted value; it must return the value to keep. Errors from all fields are collected, and if any exist validation stops, so `validate()` never runs. Next come serializer-level validators, such as a `UniqueTogetherValidator`, then `validate(self, attrs)` with the dict of converted values, which must return it; returning `None` fails an assertion. I put single-value rules, like `guests >= 1`, in `validate_guests()`, and rules involving several fields, like `check_out > check_in` or `guests <= room.capacity`, in `validate()`. A `ValidationError("...")` raised in `validate()` appears under `non_field_errors`; raising it with a dict such as `{"check_out": "..."}` attaches the message to that field.
code
python · 27 linesfrom django.utils import timezone
from rest_framework import serializers
from bookings.models import Booking
class BookingSerializer(serializers.ModelSerializer):
class Meta:
model = Booking
fields = ["id", "room", "check_in", "check_out", "guests"]
def validate_guests(self, value):
if value < 1:
raise serializers.ValidationError("At least one guest is required.")
return value
def validate_check_in(self, value):
if value < timezone.localdate():
raise serializers.ValidationError("Check-in cannot be in the past.")
return value
def validate(self, attrs):
if attrs["check_out"] <= attrs["check_in"]:
raise serializers.ValidationError({"check_out": "Check-out must be after check-in."})
if attrs["guests"] > attrs["room"].capacity:
raise serializers.ValidationError("Too many guests for this room.") # non_field_errors
return attrsgo deeper
Know that validate_<field_name>() checks one field and validate() checks the whole payload, and that both must return what they validated.
Explain the exact order, why validate() is skipped when any field fails, where serializer-level validators fit, and how error keys are chosen.
Place rules deliberately between field hooks, reusable validators, validate() and database constraints, and keep non-ValidationError exceptions out of hooks.
Keep business rules in one place that the API, forms and background jobs all call, instead of copies that drift between serializers and model clean().
## Two hooks, two scopes Django REST Framework gives a serializer two methods for custom rules: | Hook | Receives | Runs | Error lands under | |---|---|---|---| | `validate_<field_name>(self, value)` | one converted value | after that field's own conversion and validators | the field's name | | `validate(self, attrs)` | dict of all converted values | after every field passed and serializer-level validators ran | `non_field_errors`, or the keys of a dict you raise | Both must **return** what they received, optionally modified. A field method that forgets to return passes `None` onward as the field's value; `validate()` returning `None` fails an assertion (`.validate() should return the validated data`). ## The order is_valid() follows 1. **Per field**, for every writable field: - empty-value handling: a missing required field fails with `This field is required.`, a missing optional one is skipped or given its default, and `null` is accepted only with `allow_null=True`; - conversion to a Python value, for example parsing `"2026-10-03"` into a `date`; - the field's validators, including a `UniqueValidator` on a unique model field; - `validate_<field_name>(value)`, **only if the field produced a value**. A field that failed, or was skipped because it was absent and optional, never reaches it. 2. **Collect**: if any field failed, one `ValidationError` with every field's errors is raised and the pipeline stops. 3. **Serializer-level validators**: `Meta.validators`, or the uniqueness validators a `ModelSerializer` generates from the model's unique-together and unique constraints. 4. **`validate(attrs)`**, the last step, only reached when everything before it passed. The consequence clients notice: field errors and `validate()` errors **never appear together**. A booking with a malformed `check_in` gets only the field error; the date-range rule runs once the dates parse. ## Choosing the hook - **One value, no context**: `validate_guests()` rejects zero or negative guest counts; `validate_check_in()` rejects dates in the past. - **Several values**: `validate()` compares `check_in` with `check_out`, and checks `guests` against `room.capacity`, because it needs the room and the count together. - **Normalisation**: a field method may return a cleaned value, such as a trimmed and upper-cased promo code, and that returned value is what `validated_data` holds. - **A reusable rule**: a plain function or class passed in a field's `validators=[...]` list, so several serializers share it. - **Uniqueness across fields**: a `UniqueTogetherValidator`, generated or declared in `Meta.validators`, rather than a hand-written query in `validate()`. ## Field validators versus validate_<field_name>() Both check one value in the field phase, but they differ in reach: | Mechanism | Declared | Reusable across serializers | Can see the serializer | |---|---|---|---| | `validators=[...]` on a field | on the field, or through `extra_kwargs` | yes | only if the validator sets `requires_context = True` | | `validate_<field_name>()` | as a serializer method | no | yes, including `self.context` and `self.instance` | A method is the natural place when the rule needs the request, for example "only staff may book more than 30 days ahead", because `self.context["request"]` is available. A validator is better when the same rule applies in several serializers. A validator that sets `requires_context = True` is called with the field as a second argument, which is how DRF's own `UniqueValidator` reaches the instance being updated. ## Raising errors well - Raise `serializers.ValidationError`. A Django `django.core.exceptions.ValidationError` raised inside either hook is converted as well. - In `validate()`, raise with a **dict** to point at a field: `raise serializers.ValidationError({"check_out": "Check-out must be after check-in."})`. A plain string goes under `non_field_errors`, whose name comes from the `NON_FIELD_ERRORS_KEY` setting. - Do not let other exceptions escape: a `KeyError` or `AttributeError` inside a hook is not a validation error and becomes a server error. - Keep side effects out: hooks run before anything is saved, and a later hook can still reject the payload. ## What DRF does not run `is_valid()` does not call the model's `clean()` or `full_clean()`. A rule written in `Booking.clean()` is ignored by the API unless `validate()` calls it or repeats it, which is why teams that serve both a Django form and an API often move shared rules into functions both call.
- Is validate_<field_name>() called in DRF when the field is absent from the payload?Not when it is skipped. A required field that is missing fails with `This field is required.` before the method runs; an optional field without a default, or any missing field under `partial=True`, is skipped and its method never runs. If the field has a default, the default goes through the method. Rules that must hold whether or not the field was sent belong in `validate()`.
- How do you attach an error from DRF's validate() to a specific field?Raise `serializers.ValidationError` with a dict, such as `{"check_out": ["Check-out must be after check-in."]}`. DRF keeps the keys, so the error appears under `check_out` in `serializer.errors`. A string or list is treated as a non-field error and goes under `non_field_errors`, or whatever `NON_FIELD_ERRORS_KEY` is set to.
A booking desk checks each form box first: a date that is not a date is sent back before anyone looks at the whole form. Only a form whose boxes are all readable goes to the supervisor, who checks that the stay makes sense as a whole and that the room is big enough.
saying these in an interview costs you the question
- validate() runs even when a field failed, so all errors come back at once.
- validate_<field_name>() receives the raw string from the request.
- validate() can return True to signal success.
- Errors raised in validate() are keyed under __all__ as in Django forms.
- DRF runs Model.clean() automatically after validate().