Khi gọi serializer.is_valid(), DRF chạy lần lượt:
1. to_internal_value() — ép kiểu từng field (IntegerField sang int...) và chạy validator cấp field (max_length, UniqueValidator...). Field nào lỗi thì bị loại khỏi bước sau.
2. validate_<field_name>(self, value) — kiểm tra riêng từng field, nhận giá trị đã ép kiểu, phải return value.
3. validate(self, attrs) — kiểm tra liên field, nhận dict toàn bộ field đã qua bước 2.
4. Class-level validators trong Meta (vd UniqueTogetherValidator).
class BookingSerializer(serializers.ModelSerializer):
def validate_start_date(self, value):
if value < timezone.now().date():
raise serializers.ValidationError('Start date must not be in the past.')
return value
def validate(self, attrs):
if attrs['end_date'] <= attrs['start_date']:
raise serializers.ValidationError({'end_date': 'Must be after start date.'})
return attrsKhác biệt cốt lõi: validate_<field> chỉ thấy một giá trị, lỗi được gắn đúng vào field đó. validate() thấy cả bộ dữ liệu — chỗ duy nhất kiểm tra được ràng buộc chéo. Lỗi ném từ validate() mặc định rơi vào non_field_errors; muốn gắn vào field cụ thể thì ném dict như ví dụ trên.
Lưu ý với partial=True (PATCH): attrs chỉ chứa field được gửi lên, nên trong validate() phải dùng attrs.get(...) kèm giá trị từ self.instance, đừng truy cập bằng key trực tiếp.