Внутренний инструмент: кто с кем и когда встречается. Сотрудник добавляет встречу, любой другой видит расписание на день или неделю, а занять слот, в котором кто-то из участников уже занят, система не даст.
- Регистрация и вход — имя, логин, пароль (хэш, сессионная кука).
- Создание встречи — тема, дата, время начала/окончания, участники (создатель включается автоматически).
- Просмотр всеми — списки «День» и «Неделя» показывают все встречи компании с участниками; свои встречи подсвечены жёлтой меткой.
- Защита слота — если интервал пересекается с другой встречей любого участника,
бронь отклоняется с внятным объяснением: кто занят, чем и когда
(
docs/conflict.png). Встречи «встык» (11:00–12:00 и 12:00–13:00) конфликтом не считаются. - Статистика — сколько времени и на скольких встречах провёл каждый сотрудник
за неделю, плюс итоги по компании (
docs/stats.png). - Удаление — создатель встречи может её отменить.
Нужен Python 3.10 или новее — либо uv, который сам поставит и Python, и зависимости. Единственная зависимость приложения — Flask. Проверено на Python 3.10 и 3.14 (тесты проходят на обоих).
# если uv ещё нет:
# Linux/macOS: curl -LsSf https://astral.sh/uv/install.sh | sh
# Windows: powershell -c "irm https://astral.sh/uv/install.ps1 | iex"
uv sync # создаст окружение и поставит зависимости из uv.lock
uv run python app.py # запускLinux/macOS:
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
python app.pyWindows (PowerShell):
py -m venv .venv
.venv\Scripts\Activate.ps1
pip install -r requirements.txt
python app.pyОткройте http://127.0.0.1:5000 — попадёте на страницу входа. Зарегистрируйте
пару сотрудников (в разных браузерах/вкладках инкогнито, чтобы иметь две сессии)
или загрузите демо-данные — 4 сотрудника и неделя встреч, пароль у всех demo1234:
uv run python seed_demo.py # или: python seed_demo.py (в активированном venv)Логины демо-сотрудников: m.orlova, i.petrov, a.sokolova, p.volkov.
uv run pytest # или: pip install pytest && pytest| Симптом | Что сделать |
|---|---|
ModuleNotFoundError: No module named 'flask' |
Не активировано окружение: source .venv/bin/activate (или запускайте через uv run). |
SyntaxError при старте |
Python старше 3.10 — проверьте python --version. |
| Порт 5000 занят | flask --app app run --port 5001 (тем же способом — через uv run или в venv). |
| Хочется начать с чистой базы | Остановите сервер и удалите planerka.db. |
«не создалась …» при seed_demo.py |
Скрипт рассчитан на чистую базу — удалите planerka.db и повторите. |
База данных (planerka.db) и ключ сессий (.secret_key) создаются автоматически
при первом запуске рядом с app.py — ничего настраивать не нужно.
| Что | Почему |
|---|---|
| Flask + Jinja (server-rendered) | Задача — формы и списки; SPA и сборка фронтенда здесь лишние. |
SQLite (stdlib sqlite3) |
«Простая БД»: нулевая установка, один файл planerka.db, транзакции есть. |
| Один CSS-файл, без JS | Нативные <input type="date/time">, чекбоксы-чипсы, CSS-анимации. |
app.py # вся логика: схема БД, авторизация, встречи, конфликты, статистика
templates/ # base, login, register, day, week, stats, new
static/style.css # дизайн-система «вокзальное табло»
test_app.py # смоук-тесты критичных путей
seed_demo.py # демо-данные
users meetings meeting_participants
id id meeting_id → meetings (CASCADE)
name title user_id → users
username (unique) starts_at "YYYY-MM-DDTHH:MM" PK (meeting_id, user_id)
password_hash ends_at (CHECK ends > starts)
created_by → users
Время хранится строкой ISO YYYY-MM-DDTHH:MM — сравнивается лексикографически,
индекса по starts_at хватает для выборок дня/недели.
Классическое пересечение интервалов: занято, если существующая.start < новая.end AND существующая.end > новая.start хотя бы у одного участника. Проверка и вставка
выполняются одной транзакцией BEGIN IMMEDIATE, поэтому две одновременные брони
одного слота не проскочат — вторая дождётся первой и получит отказ.
Сумма длительностей встреч недели по каждому участнику
(julianday(ends) − julianday(starts)), лидерборд с барами и итоговые
«человеко-часы» по компании.
Это тестовое задание, а не боевой сервис — «раздельные аккаунты и доступы» условны и упрощены намеренно:
- Авторизация условная: логин/пароль без e-mail-подтверждения, ролей и
восстановления; любой может зарегистрироваться и видит все встречи (так и
задумано — инструмент внутренний). Пароли при этом захэшированы, сессии подписаны
секретным ключом (генерируется в
.secret_keyпри первом запуске). - Без CSRF-токенов — для внутреннего инструмента на сессионных куках это первый кандидат на добавление в проде.
- Часовые пояса: всё в наивном локальном времени сервера — один офис.
- Встреча живёт внутри одного дня (форма даёт одну дату) — «через полночь» не бронируется.
- Запуск дев-сервером Flask (
debug=True) — для проверки задания этого достаточно. - Список участников — чекбоксы: удобно до пары десятков сотрудников, дальше нужен поиск.
CSRF-токены и rate-limit на вход; WSGI-сервер (gunicorn) и debug=False;
Postgres + миграции (alembic) при росте; редактирование встреч и повторяющиеся
встречи; часовые пояса (UTC в базе); поиск по участникам; e-mail/календарные
уведомления.
Скриншоты: docs/login.png, docs/day.png, docs/week.png, docs/stats.png,
docs/conflict.png.
