← Назад к курсу

Пособие по созданию веб-приложений и веб-сервисов на Python

Оглавление

  1. Проектирование веб-приложений
  2. Выбор фреймворков и библиотек
  3. Архитектурные подходы
  4. Примеры приложений
  5. Деплой и DevOps

1. Проектирование веб-приложений

1.1. Этапы проектирования

  1. Анализ требований

    • Определение целевой аудитории
    • Функциональные и нефункциональные требования
    • Ограничения и ресурсы
  2. Проектирование архитектуры

    # Пример структуры проекта
    my_project/
    ├── app/
    │   ├── __init__.py
    │   ├── models.py      # Модели данных
    │   ├── views.py       # Логика представлений
    │   ├── routes.py      # Маршруты
    │   └── services.py    # Бизнес-логика
    ├── config.py
    ├── requirements.txt
    └── run.py
    
  3. Выбор базы данных

    • PostgreSQL - для сложных данных
    • MySQL - проверенное решение
    • SQLite - для прототипов
    • MongoDB - для документоориентированных данных
    • Redis - для кэширования
  4. Проектирование API

    # Пример RESTful дизайна
    # GET    /api/users        - список пользователей
    # POST   /api/users        - создание пользователя
    # GET    /api/users/{id}   - получение пользователя
    # PUT    /api/users/{id}   - обновление пользователя
    # DELETE /api/users/{id}   - удаление пользователя
    

2. Выбор фреймворков и библиотек

2.1. Основные фреймворки

Фреймворк Назначение Преимущества
Django Полноценные веб-приложения Батарейки в комплекте, ORM, админка
Flask Микросервисы, API, прототипы Легковесный, гибкий
FastAPI Современные API Высокая скорость, автодокументация
Tornado Асинхронные приложения Высокая производительность

2.2. Библиотеки для Django

# requirements.txt для Django-проекта
Django>=4.0
djangorestframework  # REST API
django-crispy-forms  # Формы
django-filter        # Фильтрация
django-debug-toolbar # Отладка
psycopg2-binary      # PostgreSQL драйвер
celery               # Асинхронные задачи
redis                # Кэширование

2.3. Библиотеки для Flask/FastAPI

# requirements.txt для API-проекта
Flask  # или FastAPI
Flask-SQLAlchemy     # ORM
Flask-Migrate        # Миграции
Flask-JWT-Extended   # Аутентификация
Flask-CORS           # CORS поддержка
Pydantic             # Валидация данных (для FastAPI)
uvicorn              # ASGI сервер

2.4. Универсальные библиотеки

# Общие зависимости
SQLAlchemy           # ORM
Alembic              # Миграции базы данных
Pillow               # Работа с изображениями
python-dotenv        # Переменные окружения
requests             # HTTP-запросы
python-jose          # JWT токены
passlib              # Хэширование паролей

3. Архитектурные подходы

3.1. MVC (Model-View-Controller)

# Django пример MVC
# models.py
from django.db import models

class Post(models.Model):
    title = models.CharField(max_length=200)
    content = models.TextField()

# views.py
from django.views.generic import ListView
from .models import Post

class PostListView(ListView):
    model = Post
    template_name = 'blog/post_list.html'

# post_list.html (Template)
{% for post in object_list %}
    <h2>{{ post.title }}</h2>
    <p>{{ post.content }}</p>
{% endfor %}

3.2. Микросервисная архитектура

# service_a/api.py (FastAPI)
from fastapi import FastAPI

app = FastAPI()

@app.get("/users/{user_id}")
async def get_user(user_id: int):
    return {"user_id": user_id}

# service_b/api.py (Flask)
from flask import Flask, jsonify
import requests

app = Flask(__name__)

@app.route('/profile/<int:user_id>')
def get_profile(user_id):
    # Запрос к service_a
    user_data = requests.get(f'http://service_a:8000/users/{user_id}')
    return jsonify(user_data.json())

3.3. Репозиторий + Сервисный слой

# repository.py
class UserRepository:
    def __init__(self, session):
        self.session = session
    
    def get_by_id(self, user_id):
        return self.session.query(User).filter_by(id=user_id).first()

# service.py
class UserService:
    def __init__(self, repository):
        self.repository = repository
    
    def get_user_profile(self, user_id):
        user = self.repository.get_by_id(user_id)
        if not user:
            raise ValueError("User not found")
        return {
            "id": user.id,
            "name": user.name,
            "email": user.email
        }

4. Примеры приложений

4.1. Блог на Django

# models.py
from django.db import models
from django.contrib.auth.models import User

class Category(models.Model):
    name = models.CharField(max_length=100)

class Post(models.Model):
    title = models.CharField(max_length=200)
    content = models.TextField()
    author = models.ForeignKey(User, on_delete=models.CASCADE)
    category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

# views.py
from django.views.generic import ListView, DetailView, CreateView
from django.contrib.auth.mixins import LoginRequiredMixin
from .models import Post

class PostListView(ListView):
    model = Post
    paginate_by = 10

class PostCreateView(LoginRequiredMixin, CreateView):
    model = Post
    fields = ['title', 'content', 'category']
    
    def form_valid(self, form):
        form.instance.author = self.request.user
        return super().form_valid(form)

4.2. REST API сервис на FastAPI

# main.py
from fastapi import FastAPI, HTTPException, Depends
from sqlalchemy.orm import Session
from typing import List
import models, schemas, crud
from database import SessionLocal, engine

models.Base.metadata.create_all(bind=engine)

app = FastAPI()

def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

@app.post("/users/", response_model=schemas.User)
def create_user(user: schemas.UserCreate, db: Session = Depends(get_db)):
    db_user = crud.get_user_by_email(db, email=user.email)
    if db_user:
        raise HTTPException(status_code=400, detail="Email already registered")
    return crud.create_user(db=db, user=user)

@app.get("/users/", response_model=List[schemas.User])
def read_users(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
    users = crud.get_users(db, skip=skip, limit=limit)
    return users

# schemas.py
from pydantic import BaseModel, EmailStr

class UserBase(BaseModel):
    email: EmailStr
    name: str

class UserCreate(UserBase):
    password: str

class User(UserBase):
    id: int
    
    class Config:
        orm_mode = True

4.3. CRM система на Flask

# app.py
from flask import Flask, render_template, request, jsonify
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager, login_required, current_user

app = Flask(__name__)
app.config['SECRET_KEY'] = 'your-secret-key'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///crm.db'
db = SQLAlchemy(app)
login_manager = LoginManager(app)

# Модели
class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    email = db.Column(db.String(120), unique=True)
    password = db.Column(db.String(200))

class Client(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(100))
    email = db.Column(db.String(120))
    phone = db.Column(db.String(20))
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'))

# Маршруты
@app.route('/')
@login_required
def dashboard():
    return render_template('dashboard.html')

@app.route('/api/clients', methods=['GET', 'POST'])
@login_required
def clients():
    if request.method == 'POST':
        data = request.json
        client = Client(
            name=data['name'],
            email=data['email'],
            phone=data['phone'],
            user_id=current_user.id
        )
        db.session.add(client)
        db.session.commit()
        return jsonify({'message': 'Client created'}), 201
    
    clients = Client.query.filter_by(user_id=current_user.id).all()
    return jsonify([{
        'id': c.id,
        'name': c.name,
        'email': c.email,
        'phone': c.phone
    } for c in clients])

# templates/dashboard.html
"""
<!DOCTYPE html>
<html>
<head>
    <title>CRM Dashboard</title>
</head>
<body>
    <h1>Welcome, {{ current_user.email }}</h1>
    <div id="clients-list"></div>
    
    <script>
        fetch('/api/clients')
            .then(response => response.json())
            .then(data => {
                const container = document.getElementById('clients-list');
                data.forEach(client => {
                    container.innerHTML += `<p>${client.name} - ${client.email}</p>`;
                });
            });
    </script>
</body>
</html>
"""

4.4. Real-time приложение с WebSockets

# websocket_app.py
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
from typing import List

app = FastAPI()

class ConnectionManager:
    def __init__(self):
        self.active_connections: List[WebSocket] = []
    
    async def connect(self, websocket: WebSocket):
        await websocket.accept()
        self.active_connections.append(websocket)
    
    def disconnect(self, websocket: WebSocket):
        self.active_connections.remove(websocket)
    
    async def send_personal_message(self, message: str, websocket: WebSocket):
        await websocket.send_text(message)
    
    async def broadcast(self, message: str):
        for connection in self.active_connections:
            await connection.send_text(message)

manager = ConnectionManager()

@app.websocket("/ws/{client_id}")
async def websocket_endpoint(websocket: WebSocket, client_id: int):
    await manager.connect(websocket)
    try:
        while True:
            data = await websocket.receive_text()
            await manager.broadcast(f"Client #{client_id}: {data}")
    except WebSocketDisconnect:
        manager.disconnect(websocket)
        await manager.broadcast(f"Client #{client_id} left")

5. Деплой и DevOps

5.1. Конфигурация для продакшена

# config/production.py
import os

class ProductionConfig:
    SECRET_KEY = os.environ.get('SECRET_KEY')
    DEBUG = False
    SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL')
    SQLALCHEMY_TRACK_MODIFICATIONS = False
    
    # Настройки безопасности
    SESSION_COOKIE_SECURE = True
    REMEMBER_COOKIE_SECURE = True
    SESSION_COOKIE_HTTPONLY = True
    
    # Логирование
    LOG_LEVEL = 'WARNING'

5.2. Docker конфигурация

# Dockerfile
FROM python:3.11-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

CMD ["gunicorn", "--workers", "4", "--bind", "0.0.0.0:8000", "app:app"]
# docker-compose.yml
version: '3.8'

services:
  web:
    build: .
    ports:
      - "8000:8000"
    environment:
      - DATABASE_URL=postgresql://user:password@db:5432/app_db
    depends_on:
      - db
      - redis
  
  db:
    image: postgres:15
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: password
      POSTGRES_DB: app_db
    volumes:
      - postgres_data:/var/lib/postgresql/data
  
  redis:
    image: redis:7-alpine
  
  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
    depends_on:
      - web

volumes:
  postgres_data:

5.3. CI/CD конфигурация (GitHub Actions)

# .github/workflows/deploy.yml
name: Deploy to Production

on:
  push:
    branches: [ main ]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Set up Python
        uses: actions/setup-python@v4
        with:
          python-version: '3.11'
      
      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install -r requirements.txt
      
      - name: Run tests
        run: |
          pytest
  
  deploy:
    needs: test
    runs-on: ubuntu-latest
    steps:
      - name: Deploy to server
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.HOST }}
          username: ${{ secrets.USERNAME }}
          key: ${{ secrets.SSH_KEY }}
          script: |
            cd /var/www/myapp
            git pull origin main
            docker-compose down
            docker-compose build
            docker-compose up -d

5.4. Мониторинг и логирование

# logging_config.py
import logging
from logging.handlers import RotatingFileHandler

def setup_logging(app):
    # Файловый обработчик
    file_handler = RotatingFileHandler(
        'app.log', 
        maxBytes=10000, 
        backupCount=3
    )
    file_handler.setLevel(logging.WARNING)
    
    # Форматтер
    formatter = logging.Formatter(
        '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
    )
    file_handler.setFormatter(formatter)
    
    app.logger.addHandler(file_handler)
    
    # Sentry для продакшена
    if app.config['ENV'] == 'production':
        import sentry_sdk
        from sentry_sdk.integrations.flask import FlaskIntegration
        
        sentry_sdk.init(
            dsn=app.config['SENTRY_DSN'],
            integrations=[FlaskIntegration()]
        )

6. Рекомендации и лучшие практики

6.1. Безопасность

  • Всегда валидируйте входные данные
  • Используйте параметризованные запросы для SQL
  • Хэшируйте пароли (bcrypt, argon2)
  • Реализуйте rate limiting
  • Используйте HTTPS в продакшене
  • Регулярно обновляйте зависимости

6.2. Производительность

  • Используйте кэширование (Redis, Memcached)
  • Оптимизируйте запросы к базе данных
  • Реализуйте пагинацию для больших наборов данных
  • Используйте асинхронные задачи для долгих операций
  • Минимифицируйте статические файлы

6.3. Масштабирование

  • Разделяйте приложение на микросервисы при необходимости
  • Используйте очереди сообщений (RabbitMQ, Kafka)
  • Реализуйте горизонтальное масштабирование
  • Используйте балансировщики нагрузки

6.4. Тестирование

# tests/test_api.py
import pytest
from app import create_app

@pytest.fixture
def client():
    app = create_app({'TESTING': True})
    with app.test_client() as client:
        yield client

def test_create_user(client):
    response = client.post('/api/users', json={
        'email': 'test@example.com',
        'password': 'secret'
    })
    assert response.status_code == 201
    assert 'id' in response.json

Заключение

Это пособие охватывает основные аспекты разработки веб-приложений на Python. Ключевые моменты:

  1. Выбирайте фреймворк под задачу: Django для монолитов, FastAPI/Flask для API
  2. Проектируйте архитектуру заранее: учитывайте масштабируемость и поддержку
  3. Следуйте лучшим практикам: безопасность, тестирование, документация
  4. Автоматизируйте процессы: CI/CD, деплой, мониторинг
  5. Не изобретайте велосипед: используйте проверенные библиотеки и решения

Для углубленного изучения каждой темы рекомендуется:

  • Официальная документация фреймворков
  • Книга "Two Scoops of Django"
  • Курсы на Coursera/Stepik по веб-разработке
  • Изучение исходного кода популярных проектов на GitHub

Удачной разработки!