← Назад к вопросам

Как вернуть в Django ответ с сервера, если пришли некорректные данные с клиента?

1.2 Junior🔥 161 комментариев
#Django#REST API и HTTP

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Возврат ошибок валидации в Django

Общие принципы

В Django есть несколько уровней обработки ошибок валидации:

  1. Form валидация — для HTML форм
  2. Model валидация — на уровне БД и модели
  3. API JSON ответы — для REST API
  4. Исключения — для обработки ошибок

Подход 1: Django Forms

Для традиционных HTML форм:

from django import forms
from django.http import JsonResponse
from django.views import View

class UserForm(forms.Form):
    username = forms.CharField(
        min_length=3,
        max_length=50,
        error_messages={
            'required': 'Username is required',
            'min_length': 'Username too short',
            'max_length': 'Username too long',
        }
    )
    email = forms.EmailField(
        error_messages={
            'invalid': 'Invalid email format',
            'required': 'Email is required',
        }
    )
    age = forms.IntegerField(
        min_value=18,
        max_value=120,
        error_messages={
            'min_value': 'Age must be >= 18',
            'max_value': 'Age must be <= 120',
        }
    )

class CreateUserView(View):
    def post(self, request):
        form = UserForm(request.POST)
        
        if form.is_valid():
            # Данные корректны
            username = form.cleaned_data['username']
            email = form.cleaned_data['email']
            age = form.cleaned_data['age']
            
            # Создание пользователя
            user = User.objects.create(
                username=username,
                email=email,
                age=age
            )
            return JsonResponse({'user_id': user.id})
        
        else:
            # Ошибки валидации
            return JsonResponse(
                {'errors': form.errors},
                status=400
            )

Подход 2: Model валидация

Проверка на уровне модели:

from django.db import models
from django.core.exceptions import ValidationError
from django.core.validators import MinValueValidator, MaxValueValidator

class User(models.Model):
    username = models.CharField(
        max_length=50,
        unique=True,
        validators=[]
    )
    email = models.EmailField(unique=True)
    age = models.IntegerField(
        validators=[
            MinValueValidator(18, 'Age must be >= 18'),
            MaxValueValidator(120, 'Age must be <= 120'),
        ]
    )
    
    def clean(self):
        """Контекстная валидация"""
        errors = {}
        
        # Проверка уникальности
        if User.objects.filter(username=self.username).exclude(pk=self.pk).exists():
            errors['username'] = 'Username already exists'
        
        # Бизнес-правила
        if self.age < 18:
            errors['age'] = 'Must be adult'
        
        if errors:
            raise ValidationError(errors)
    
    def save(self, *args, **kwargs):
        # Всегда вызывайте clean() перед save()
        self.full_clean()  # Вызывает clean() и валидирует поля
        super().save(*args, **kwargs)

# В представлении
from django.core.exceptions import ValidationError

def create_user_view(request):
    try:
        user = User(
            username=request.POST['username'],
            email=request.POST['email'],
            age=int(request.POST['age'])
        )
        user.full_clean()  # Валидация
        user.save()
        return JsonResponse({'user_id': user.id})
    
    except ValidationError as e:
        # Словарь ошибок по полям
        return JsonResponse(
            {'errors': e.message_dict},
            status=400
        )
    except (KeyError, ValueError) as e:
        return JsonResponse(
            {'errors': {'general': str(e)}},
            status=400
        )

Подход 3: REST Framework (DRF) — рекомендуемый

Django REST Framework — идеален для JSON API:

from rest_framework import serializers, viewsets, status
from rest_framework.response import Response
from rest_framework.decorators import api_view

class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ['id', 'username', 'email', 'age']
        extra_kwargs = {
            'username': {
                'min_length': 3,
                'max_length': 50,
                'error_messages': {
                    'required': 'Username required',
                    'blank': 'Username cannot be blank',
                    'max_length': 'Username too long',
                    'min_length': 'Username too short',
                }
            },
            'email': {
                'required': True,
            },
            'age': {
                'min_value': 18,
                'max_value': 120,
                'error_messages': {
                    'min_value': 'Age >= 18',
                    'max_value': 'Age <= 120',
                }
            }
        }
    
    def validate_username(self, value):
        """Field-level валидация"""
        if User.objects.filter(username=value).exists():
            raise serializers.ValidationError(
                'Username already exists'
            )
        return value.lower()
    
    def validate(self, data):
        """Object-level валидация"""
        # Проверки на уровне объекта
        if data.get('age', 0) < 18:
            raise serializers.ValidationError({
                'age': 'User must be adult'
            })
        return data

class UserViewSet(viewsets.ViewSet):
    def create(self, request):
        serializer = UserSerializer(data=request.data)
        
        if serializer.is_valid():
            serializer.save()
            return Response(
                serializer.data,
                status=status.HTTP_201_CREATED
            )
        
        # Автоматически возвращает ошибки в JSON
        return Response(
            serializer.errors,
            status=status.HTTP_400_BAD_REQUEST
        )

# Использование в URLs
from django.urls import path
from rest_framework.routers import DefaultRouter

router = DefaultRouter()
router.register(r'users', UserViewSet, basename='user')

urlpatterns = router.urls

Структура JSON ошибок

Традиционная структура:

{
  "errors": {
    "username": ["Username is required"],
    "email": ["Invalid email format"],
    "age": ["Age must be >= 18"]
  }
}

DRF структура (по умолчанию):

{
  "username": ["Username is required"],
  "email": ["Invalid email format"],
  "age": ["Age must be >= 18"]
}

Кастомный обработчик ошибок

from rest_framework.exceptions import APIException
from rest_framework.views import exception_handler
import logging

logger = logging.getLogger(__name__)

class ValidationAPIException(APIException):
    status_code = 400
    default_detail = 'Validation error'

def custom_exception_handler(exc, context):
    """Кастомный обработчик исключений"""
    response = exception_handler(exc, context)
    
    if response is not None:
        # Логируем ошибку
        logger.warning(
            f'Validation error: {response.data}',
            extra={'user': context['request'].user}
        )
        
        # Преобразуем в нашу структуру
        response.data = {
            'success': False,
            'errors': response.data,
            'message': 'Validation failed'
        }
    
    return response

# Настройка в settings.py
REST_FRAMEWORK = {
    'EXCEPTION_HANDLER': 'your_app.utils.custom_exception_handler',
}

Полный пример с обработкой

from django.views.decorators.http import require_http_methods
from django.http import JsonResponse
import json
import logging

logger = logging.getLogger(__name__)

@require_http_methods(["POST"])
def create_user_api(request):
    try:
        # Парсинг JSON
        data = json.loads(request.body)
    except json.JSONDecodeError:
        return JsonResponse(
            {'error': 'Invalid JSON'},
            status=400
        )
    
    # Валидация
    errors = {}
    
    username = data.get('username', '').strip()
    if not username:
        errors['username'] = 'Required'
    elif len(username) < 3:
        errors['username'] = 'Too short (min 3)'
    elif len(username) > 50:
        errors['username'] = 'Too long (max 50)'
    
    email = data.get('email', '').strip().lower()
    if not email:
        errors['email'] = 'Required'
    elif '@' not in email:
        errors['email'] = 'Invalid format'
    
    age = data.get('age')
    try:
        age = int(age)
        if age < 18 or age > 120:
            errors['age'] = 'Must be 18-120'
    except (TypeError, ValueError):
        errors['age'] = 'Must be integer'
    
    if errors:
        logger.warning(f'Validation failed: {errors}')
        return JsonResponse(
            {'success': False, 'errors': errors},
            status=400
        )
    
    # Создание
    try:
        user = User.objects.create(
            username=username,
            email=email,
            age=age
        )
        return JsonResponse(
            {'success': True, 'user_id': user.id},
            status=201
        )
    except Exception as e:
        logger.error(f'User creation failed: {e}')
        return JsonResponse(
            {'success': False, 'error': 'Server error'},
            status=500
        )

Лучшие практики

  • Используйте DRF для JSON API
  • Всегда вызывайте full_clean() на моделях перед save
  • Логируйте ошибки валидации без деталей клиенту
  • Структурируйте ошибки по полям для удобства клиента
  • Возвращайте правильные HTTP статусы (400 для валидации)
  • Не передавайте внутренние ошибки — скрывайте детали реализации
  • Валидируйте на нескольких уровнях — форма, модель, представление

Django обеспечивает мощную систему валидации на всех уровнях приложения.

Как вернуть в Django ответ с сервера, если пришли некорректные данные с клиента? | PrepBro