✅ Решения заданий

⚡ Ответы кратко

  1. request.get_json() → None при отсутствии JSON.
  2. create_question при успехе → 201 Created.
  3. Question.query.get(id) → db.session.get(Question, id).
  4. Забытый commit → объект не сохраняется в БД, изменения теряются.

Задание 1. Объект request — ответы

  1. Query-параметр category:
    category = request.args.get('category')  # 'books'
    page = request.args.get('page', 1, type=int)  # 2, с дефолтом
  2. JSON тело:
    data = request.get_json()
    name = data.get('name')  # 'Flask'
  3. Поле HTML-формы:
    username = request.form.get('username')
  4. Проверить метод DELETE:
    if request.method == 'DELETE':
        pass
    # Или декоратор: methods=['DELETE']
  5. Заголовок Authorization:
    auth = request.headers.get('Authorization')

Задание 2. HTTP статус-коды — ответы

  1. Создан вопрос → 201 Created
  2. Вопрос с ID=999 не найден → 404 Not Found
  3. Отсутствует поле text400 Bad Request
  4. Список вопросов → 200 OK
  5. Удалён, без тела ответа → 204 No Content
  6. Нет токена → 401 Unauthorized

Задание 3. SQLAlchemy 2.x — решение

from sqlalchemy import select

# Фрагмент A
questions = db.session.execute(select(Question)).scalars().all()

# Фрагмент B
question = db.session.get(Question, id)

# Фрагмент C
stat = db.session.execute(
    select(Statistic).filter_by(question_id=q_id)
).scalar_one_or_none()

# Фрагмент D
active = db.session.execute(
    select(Item).where(Item.active == True).order_by(Item.name)
).scalars().all()

Задание 4. Найти ошибку — решение

В коде три ошибки:

  1. Строка A: нет проверки, что question не None. Если ID не найден — будет AttributeError.
  2. Строка A: нет проверки, что data не None и содержит ключ 'text'. Если JSON не передан — TypeError.
  3. Строка B: нет db.session.commit() — изменения не сохранятся.
@questions_bp.route('/<int:id>', methods=['PUT'])
def update_question(id):
    question = db.session.get(Question, id)        # исправлено
    if question is None:                            # добавлено
        return jsonify({'message': 'Не найден'}), 404

    data = request.get_json()
    if not data or 'text' not in data:             # добавлено
        return jsonify({'message': 'Нет text'}), 400

    question.text = data['text']
    db.session.commit()                            # добавлено
    return jsonify({'message': 'ok'}), 200

Задание 5. PATCH эндпоинт — решение

@questions_bp.route('/<int:id>', methods=['PATCH'])
def patch_question(id):
    """Частичное обновление вопроса."""
    question = db.session.get(Question, id)
    if question is None:
        return jsonify({'message': 'Вопрос не найден'}), 404

    data = request.get_json()
    if not data:
        return jsonify({'message': 'Тело запроса пустое'}), 400

    # Обновляем только переданные поля
    if 'text' in data:
        question.text = data['text']

    db.session.commit()
    return jsonify({
        'id': question.id,
        'text': question.text
    }), 200

PATCH vs PUT: PUT заменяет весь объект; PATCH обновляет только переданные поля. В нашем случае у Question только одно поле text, поэтому они идентичны — но паттерн PATCH важен при наличии нескольких полей.

Задание 6. Логика add_response — ответы

  1. Единственный commit в конце: commit() отправляет транзакцию в БД. Если вызвать его дважды, первый сохранит только Response, но Statistic окажется несинхронизированной при сбое между двумя commit(). Единственный commit() гарантирует атомарность: либо оба объекта сохраняются, либо оба откатываются.
  2. Двойная отправка is_agree=true: agree_count станет 2. Каждый вызов инкрементирует счётчик. Защиты от дубликатов нет (нужна отдельная логика — IP/сессии).
  3. Первый ответ is_agree=false для вопроса без Statistic: создаётся запись Statistic(agree_count=0, disagree_count=0), затем disagree_count += 1. Итоговое состояние: agree_count=0, disagree_count=1.
← К оглавлению урока