🏠 Домашнее задание — Summary session 3
Это ДЗ из разбора лекции Summary session 3. На лекции разбирались задачи по расширению проекта Community Pulse поддержкой категорий вопросов. Задачи ниже взяты из этого разбора.
Часть 1 — Расширение модели: добавить категории
Расширить функциональность существующего API для поддержки категорий вопросов.
Задача 1. Создание модели Category
Создайте новую модель Category с использованием Flask-SQLAlchemy в модуле app/models/.
Модель должна содержать поля:
id: первичный ключ, целое число, авто-инкрементname: строка (макс. 100 символов), не должна быть пустой
Модель Question должна быть обновлена: добавить поле category_id как внешний ключ на categories.id.
Задача 2. Миграция базы данных
Создайте новую миграцию для добавления таблицы категорий и обновления таблицы вопросов с использованием Flask-Migrate:
flask db migrate -m "Add category model"
flask db upgrade
Часть 2 — Обновление схем и эндпоинтов
Задача 3. Обновление схем Pydantic
В файле app/schemas/question.py:
- Добавьте схему
CategoryBaseдля сериализации и валидации данных категории - Обновите
QuestionCreate: добавьте необязательное полеcategory_id - Обновите
QuestionResponse: добавьте полеcategory_id(может быть None)
Задача 4. Эндпоинты для категорий
Создайте новый Blueprint categories_bp в файле app/routers/categories.py.
Реализуйте эндпоинты:
POST /categories— создание новой категорииGET /categories— получение списка всех категорийPUT /categories/<id>— обновление категории по IDDELETE /categories/<id>— удаление категории по ID
Задача 5. Обновление эндпоинтов вопросов
Обновите существующие эндпоинты:
GET /questions— возвращать вопросы с информацией о категорияхPOST /questions— поддерживать указаниеcategory_idпри создании
Подготовка окружения
Клонирование / создание ветки
# PowerShell
# Если Community Pulse уже в репозитории:
git checkout -b lesson/12-categories
# Создать venv и установить зависимости
python -m venv venv
venv\Scripts\activate
pip install flask flask-sqlalchemy flask-migrate pydantic email-validator
# Проверить установку
python -c "import flask; print(flask.__version__)"
Структура файлов после выполнения ДЗ
/community_pulse/
|-- app/
| |-- __init__.py
| |-- routers/
| | |-- questions.py
| | |-- response.py
| | |-- categories.py # новый файл
| |-- models/
| | |-- questions.py # обновлён (добавлен Category, ForeignKey)
| | |-- response.py
| |-- schemas/
| |-- question.py # обновлён (CategoryBase, category_id)
| |-- response.py
|-- config.py
|-- run.py
|-- migrations/ # создан flask db init
Пошаговое решение
Показать решение: Задача 1 — Модель Category
# app/models/questions.py (обновлённый)
from app.models import db
class Category(db.Model):
__tablename__ = 'categories'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), nullable=False)
questions = db.relationship('Question', backref='category', lazy=True)
def __repr__(self):
return f'<Category {self.name}>'
class Question(db.Model):
__tablename__ = 'questions'
id = db.Column(db.Integer, primary_key=True)
text = db.Column(db.String(255), nullable=False)
category_id = db.Column(db.Integer, db.ForeignKey('categories.id'), nullable=True)
responses = db.relationship('Response', backref='question', lazy=True)
def __repr__(self):
return f'<Question {self.id}: {self.text[:40]}>'
Показать решение: Задача 3 — Схемы Pydantic
# app/schemas/question.py (обновлённый)
from typing import Optional
from pydantic import BaseModel, Field, ConfigDict
class CategoryBase(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: int
name: str
class QuestionCreate(BaseModel):
text: str = Field(..., min_length=12, description="Текст вопроса")
category_id: Optional[int] = Field(None, description="ID категории (опционально)")
class QuestionResponse(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: int
text: str
category_id: Optional[int] = None
class MessageResponse(BaseModel):
message: str
Показать решение: Задача 4 — Blueprint категорий
# app/routers/categories.py
from flask import Blueprint, request, jsonify
from pydantic import ValidationError
from app.models import db
from app.models.questions import Category
from app.schemas.question import CategoryBase
categories_bp = Blueprint('categories', __name__, url_prefix='/categories')
@categories_bp.route('/', methods=['GET'])
def get_categories():
categories = Category.query.all()
return jsonify([CategoryBase.model_validate(c).model_dump() for c in categories]), 200
@categories_bp.route('/', methods=['POST'])
def create_category():
data = request.get_json()
if not data or 'name' not in data:
return jsonify({'error': 'Поле name обязательно'}), 400
category = Category(name=data['name'])
db.session.add(category)
db.session.commit()
return jsonify(CategoryBase.model_validate(category).model_dump()), 201
@categories_bp.route('/<int:id>', methods=['PUT'])
def update_category(id):
category = db.session.get(Category, id)
if category is None:
return jsonify({'message': 'Категория не найдена'}), 404
data = request.get_json()
if 'name' in data:
category.name = data['name']
db.session.commit()
return jsonify(CategoryBase.model_validate(category).model_dump()), 200
@categories_bp.route('/<int:id>', methods=['DELETE'])
def delete_category(id):
category = db.session.get(Category, id)
if category is None:
return jsonify({'message': 'Категория не найдена'}), 404
db.session.delete(category)
db.session.commit()
return jsonify({'message': f'Категория {id} удалена'}), 200
Показать решение: регистрация нового Blueprint
# app/__init__.py (обновлённый)
from flask import Flask
from flask_migrate import Migrate
from app.models import db
from app.routers.questions import questions_bp
from app.routers.response import response_bp
from app.routers.categories import categories_bp # новый
def create_app(config=None):
app = Flask(__name__)
if config:
app.config.from_object(config)
db.init_app(app)
Migrate(app, db)
app.register_blueprint(questions_bp)
app.register_blueprint(response_bp)
app.register_blueprint(categories_bp) # зарегистрировать!
return app
Проверка в VS Code
Запуск и тестирование
# Запустить приложение
python run.py
# Тест через PowerShell (Invoke-RestMethod)
Invoke-RestMethod -Uri "http://localhost:5000/categories/" -Method POST `
-ContentType "application/json" `
-Body '{"name": "Наука"}'
Invoke-RestMethod -Uri "http://localhost:5000/categories/" -Method GET
Invoke-RestMethod -Uri "http://localhost:5000/questions/" -Method POST `
-ContentType "application/json" `
-Body '{"text": "Поддерживаете ли вы развитие науки?", "category_id": 1}'
Конфигурация отладки (.vscode/launch.json)
{
"version": "0.2.0",
"configurations": [
{
"name": "Community Pulse",
"type": "debugpy",
"request": "launch",
"program": "${workspaceFolder}/run.py",
"console": "integratedTerminal",
"env": {
"FLASK_ENV": "development",
"FLASK_DEBUG": "1"
}
}
]
}
Запустите через F5. Поставьте точку останова на db.session.commit() при создании категории и проверьте состояние объекта в панели переменных.
Связь с разделами теории
- Модели и связи: theory.html → Flask-SQLAlchemy
- Миграции: theory.html → Миграции
- Blueprints: theory.html → Blueprints
- Pydantic-схемы: theory.html → Pydantic-контракты
- Полный пример CRUD: examples.html
Критерии выполнения
- Модель Category создана с полями id и name
- Question обновлён: добавлен category_id с ForeignKey
- Миграция создана и применена
- Blueprint categories_bp реализован и зарегистрирован
- Все 4 CRUD-эндпоинта категорий работают
- GET /questions возвращает category_id в ответе
- POST /questions принимает опциональный category_id
- Код в ветке
lesson/12-categories