🏠 Домашнее задание 5

Расширение функциональности Community Pulse: категории вопросов

⚡ Суть задания

  • Создать модель Category (id, name)
  • Добавить внешний ключ category_id в модель Question
  • Создать миграцию через Flask-Migrate
  • Применить: flask db upgrade

Задание из LMS (дословно)

Цели задания: Расширить функциональность существующего API для поддержки категорий вопросов.

Задачи:
  1. Создание модели Category: Создайте новую модель Category с использованием SQLAlchemy в модуле models. Модель должна содержать следующие поля:
    • id: первичный ключ, целое число, авто-инкремент.
    • name: строка, название категории, не должно быть пустым.
  2. Обновление модели Question: Модель Question должна быть обновлена, чтобы включить ссылку на Category через внешний ключ.
  3. Миграция базы данных: Создайте новую миграцию для добавления таблицы категорий и обновления таблицы вопросов с использованием Flask-Migrate.

Подготовка окружения

1. Создание виртуального окружения

# PowerShell / терминал VS Code
cd E:\projects\community_pulse

python -m venv venv
.\venv\Scripts\activate    # Windows PowerShell

# Установка зависимостей
pip install flask flask-sqlalchemy flask-migrate pydantic

2. Структура проекта (должна быть у вас)

/community_pulse/
|-- app/
|   |-- __init__.py
|   |-- routers/
|   |   |-- questions.py
|   |   |-- responses.py
|   |-- models/
|   |   |-- questions.py
|   |   |-- responses.py
|   |-- schemas/
|       |-- question.py
|-- config.py
|-- run.py

3. Инициализация git (если ещё не сделано)

git init
git add .
git commit -m "initial: community pulse project"

Пошаговое решение

Шаг 1: Создать модель Category

Создайте файл app/models/category.py:

# app/models/category.py

from app import db

class Category(db.Model):
    """Модель категории вопросов."""
    __tablename__ = 'categories'

    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    name = db.Column(db.String(100), nullable=False, unique=True)

    # обратная связь: список вопросов в этой категории
    questions = db.relationship('Question', backref='category', lazy=True)

    def __repr__(self):
        return f'<Category {self.id}: {self.name}>'

Почему nullable=False: название категории обязательно — без него запись теряет смысл.

Почему unique=True: две категории с одинаковым именем — логическая ошибка данных.

Шаг 2: Обновить модель Question — добавить ForeignKey

Отредактируйте app/models/questions.py:

# app/models/questions.py

from app import db

class Question(db.Model):
    __tablename__ = 'questions'

    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    text = db.Column(db.String(500), nullable=False)

    # НОВОЕ: внешний ключ на таблицу категорий
    category_id = db.Column(
        db.Integer,
        db.ForeignKey('categories.id'),
        nullable=True   # nullable=True — вопрос может быть без категории
    )

    responses = db.relationship('Response', backref='question', lazy=True)

    def __repr__(self):
        return f'<Question {self.id}: {self.text[:30]}>'

Логика nullable=True: существующие вопросы в БД не имеют категории, поэтому при добавлении столбца через миграцию они получат NULL — это допустимо.

Шаг 3: Добавить Flask-Migrate в create_app()

Обновите app/__init__.py:

# app/__init__.py

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate

db = SQLAlchemy()
migrate = Migrate()

def create_app(config_name='development'):
    app = Flask(__name__)

    from config import config_map
    app.config.from_object(config_map[config_name])

    db.init_app(app)
    migrate.init_app(app, db)  # НОВОЕ: инициализация миграций

    from app.routers.questions import questions_bp
    from app.routers.responses import responses_bp
    app.register_blueprint(questions_bp, url_prefix='/questions')
    app.register_blueprint(responses_bp, url_prefix='/responses')

    return app

Шаг 4: Добавить FLASK_APP в окружение

# PowerShell
$env:FLASK_APP = "run.py"
$env:APP_ENV = "development"

Шаг 5: Инициализировать Flask-Migrate

# Если папка migrations ещё не создана:
flask db init

# Создать новую миграцию
flask db migrate -m "add category table and category_id to questions"

Flask-Migrate автоматически сгенерирует скрипт миграции в папке migrations/versions/. Проверьте его содержимое — там должны быть операции:

  • op.create_table('categories', ...) — создание таблицы категорий
  • op.add_column('questions', sa.Column('category_id', ...)) — добавление столбца
  • op.create_foreign_key(...) — создание связи

Шаг 6: Применить миграцию

flask db upgrade

Команда применит изменения к базе данных. Если всё прошло без ошибок — таблица categories создана, в questions появился столбец category_id.

Шаг 7: Зафиксировать изменения в git

git add app/models/category.py app/models/questions.py app/__init__.py migrations/
git commit -m "feat: add Category model with FK to Question + migration"

Проверка в VS Code

Терминал

# Запускаем приложение
python run.py

# В другом терминале — тестируем через curl
# Пока нет эндпоинтов для категорий, проверяем существующие:
curl http://localhost:5000/questions/
# должен вернуть []

# Создаём вопрос (category_id необязателен)
curl -X POST http://localhost:5000/questions/ \
     -H "Content-Type: application/json" \
     -d '{"text": "Тестовый вопрос без категории"}'

Настройка launch.json для F5

Создайте файл .vscode/launch.json:

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Flask: Community Pulse",
      "type": "debugpy",
      "request": "launch",
      "module": "flask",
      "args": ["run", "--debug"],
      "env": {
        "FLASK_APP": "run.py",
        "APP_ENV": "development"
      },
      "jinja": true,
      "justMyCode": false
    }
  ]
}

После этого нажмите F5 — Flask запустится в режиме отладки. Точки останова ставятся кликом на поле слева от номера строки.

Точки останова для проверки

  1. Откройте app/routers/questions.py
  2. Поставьте точку останова на строке db.session.add(question)
  3. Отправьте POST запрос через Postman или curl
  4. VS Code остановится на точке: вы можете проверить значение question, schema.text, и т.д.

Связь с теорией урока

Дополнительно (не обязательно для ДЗ): реализуйте Blueprint categories_bp с эндпоинтами GET /categories/ и POST /categories/, чтобы можно было управлять категориями через API. Это хорошая практика для закрепления материала.