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

Как информация проходит валидацию?

2.0 Middle🔥 111 комментариев
#Python Core

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

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

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

Процесс валидации информации в веб-приложении

Валидация — это проверка корректности данных перед их обработкой. Она происходит на нескольких уровнях, от фронтенда до базы данных.

Уровни валидации (layered approach)

1. Клиентская валидация (HTML5 + JavaScript)

Цель: быстрая обратная связь пользователю, экономия трафика.

<input 
  type="email" 
  name="email" 
  required 
  pattern="[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}"
  placeholder="example@domain.com"
>

На JavaScript:

const form = document.querySelector("form");

form.addEventListener("submit", (e) => {
  e.preventDefault();
  
  const email = document.querySelector("input[name=email]").value;
  
  // Простая проверка
  if (!email.includes("@")) {
    alert("Введите корректный email");
    return;
  }
  
  // Если прошла клиентскую валидацию — отправляем на сервер
  form.submit();
});

⚠️ ВАЖНО: клиентская валидация не безопасна! Хакер может её обойти через DevTools.

2. Серверная валидация (основная)

Цель: защита от подделок данных, безопасность.

FastAPI + Pydantic (рекомендуется)

from pydantic import BaseModel, EmailStr, Field, validator
from typing import Optional

class UserCreate(BaseModel):
    email: EmailStr  # Автоматически валидирует email
    username: str = Field(..., min_length=3, max_length=50)
    password: str = Field(..., min_length=8)
    age: Optional[int] = Field(None, ge=0, le=150)
    
    @validator("username")
    def username_alphanumeric(cls, v):
        if not v.replace("_", "").replace("-", "").isalnum():
            raise ValueError("Username может содержать только буквы, цифры, - и _")
        return v
    
    @validator("password")
    def password_strength(cls, v):
        if not any(char.isupper() for char in v):
            raise ValueError("Password должен содержать хотя бы одну заглавную букву")
        return v

@app.post("/users")
async def create_user(user: UserCreate):  # Pydantic автоматически валидирует
    # Если здесь — данные уже прошли валидацию
    db.add_user(user.email, user.username, user.password)
    return {"status": "created"}

Django + DRF (Django REST Framework)

from rest_framework import serializers

class UserSerializer(serializers.ModelSerializer):
    email = serializers.EmailField()
    username = serializers.CharField(min_length=3, max_length=50)
    password = serializers.CharField(min_length=8, write_only=True)
    
    class Meta:
        model = User
        fields = ["email", "username", "password"]
    
    def validate_username(self, value):
        if User.objects.filter(username=value).exists():
            raise serializers.ValidationError("Пользователь с таким username уже существует")
        return value
    
    def validate_password(self, value):
        if not any(char.isupper() for char in value):
            raise serializers.ValidationError("Password должен содержать заглавные буквы")
        return value
    
    def validate(self, data):
        # Валидация на уровне объекта (несколько полей)
        if data["email"] in BLOCKED_EMAILS:
            raise serializers.ValidationError("Этот email заблокирован")
        return data

class UserCreateView(generics.CreateAPIView):
    serializer_class = UserSerializer
    
    def create(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)  # Валидация + выброс ошибок
        self.perform_create(serializer)
        return Response(serializer.data, status=status.HTTP_201_CREATED)

3. Валидация на уровне базы данных

Цель: последняя линия защиты, целостность данных.

CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    email VARCHAR(255) UNIQUE NOT NULL,
    username VARCHAR(50) NOT NULL UNIQUE,
    age INT CHECK (age >= 0 AND age <= 150),
    created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);

SQLAlchemy:

from sqlalchemy import Column, String, Integer, CheckConstraint, UniqueConstraint
from sqlalchemy.orm import declarative_base

Base = declarative_base()

class User(Base):
    __tablename__ = "users"
    
    id = Column(Integer, primary_key=True)
    email = Column(String(255), unique=True, nullable=False)
    username = Column(String(50), unique=True, nullable=False)
    age = Column(Integer, nullable=True)
    
    __table_args__ = (
        CheckConstraint("age >= 0 AND age <= 150"),
    )

Полная схема валидации (на примере регистрации)

Пользователь заполнил форму
    ↓
[Клиент] Браузер проверяет HTML5 constraints
    ↓
[Клиент] JavaScript валидирует перед отправкой
    ↓
Отправляем POST /api/v1/users на сервер
    ↓
[Сервер] Парсим JSON
    ↓
[Сервер] Pydantic/Serializer валидирует тип данных
    ↓
[Сервер] Кастомные валидаторы проверяют бизнес-логику
    ↓
[Сервер] Проверяем уникальность в БД (email, username)
    ↓
[БД] Constraints проверяют целостность
    ↓
Данные успешно сохранены или получена ошибка 400 Bad Request

Типичные ошибки валидации

❌ Неправильный email

# Плохо
if "@" in email:
    return True

# Хорошо
from pydantic import EmailStr
email: EmailStr  # Использует RFC 5322 стандарт

❌ Отсутствие уникальности

# Плохо — нет проверки дубликатов
user = User(email="john@example.com")
db.add(user)

# Хорошо — проверяем уникальность
def validate_email(cls, v):
    if User.objects.filter(email=v).exists():
        raise ValueError("Email уже зарегистрирован")
    return v

❌ Доверие клиентской валидации

# ОЧЕНЬ ПЛОХО — полагаться только на клиент
# Хакер пришлёт любые данные без клиентской валидации

# ПРАВИЛЬНО — валидация на сервере ВСЕГДА

❌ Слабая валидация пароля

# Плохо
if len(password) > 6:
    return True

# Хорошо
password_requirements = [
    (r"[A-Z]", "заглавные буквы"),
    (r"[a-z]", "строчные буквы"),
    (r"[0-9]", "цифры"),
    (r"[!@#$%^&*]", "спецсимволы"),
]
for pattern, name in password_requirements:
    if not re.search(pattern, password):
        raise ValueError(f"Пароль должен содержать {name}")

Обработка ошибок валидации

from fastapi import FastAPI, HTTPException
from pydantic import ValidationError

app = FastAPI()

@app.exception_handler(ValidationError)
async def validation_exception_handler(request, exc):
    return JSONResponse(
        status_code=400,
        content={
            "error": "Validation failed",
            "details": exc.errors()  # Подробные ошибки
        }
    )

@app.post("/users")
async def create_user(user: UserCreate):
    try:
        user_data = user.dict()
        # Дополнительная валидация бизнес-логики
        if is_email_blocked(user.email):
            raise HTTPException(status_code=400, detail="Email blocked")
        
        db.create_user(user_data)
        return {"status": "success"}
    except Exception as e:
        raise HTTPException(status_code=500, detail="Internal error")

Итог

Валидация — это многоуровневый процесс: клиент → сервер → БД. Каждый уровень выполняет свою роль. Никогда не доверяй только одному уровню!