Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,5 @@ TELEGRAM_CHANNEL=

CLICKUP_API_TOKEN=
CLICKUP_SPACE_ID=
ALLOW_REACT_DEV_DEMO_SEED=False
REACT_DEV_DEMO_PASSWORD=
98 changes: 98 additions & 0 deletions docs/react-dev-demo-data.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
<!-- Roadmap: DEV-072 -->

# Тестовые данные React-dev

## Назначение

Команда `seed_react_dev_demo` создаёт связанный и повторяемый набор данных для
ручного smoke-тестирования кабинета эксперта DEV-053 на изолированном
React-dev backend.

## Состав данных

- программа `[DEMO] Экспертная оценка React-dev`;
- менеджер, эксперт и два участника;
- активный Expert-профиль и членство эксперта в программе;
- командная Application, Team и два принятых TeamMember;
- три числовых Criteria: два `int` и один `float`;
- три финальных Submission с описаниями и `links`;
- три назначения одному эксперту;
- решение без Evaluation;
- решение с частично заполненным draft;
- решение с submitted Evaluation и completed assignment.

## Меры безопасности

Команда завершается до любой записи, если не выполнено хотя бы одно условие:

- setting `ALLOW_REACT_DEV_DEMO_SEED=True`;
- передан флаг `--confirm-react-dev`;
- в окружении задана переменная `REACT_DEV_DEMO_PASSWORD`.

Setting по умолчанию выключен. Значение пароля не хранится в репозитории и не
выводится командой. Все изменения выполняются в одной транзакции.

Reset находит программу только по одновременному точному совпадению имени и
служебного tag. Аккаунты с фиксированными demo-email сохраняются и обновляются:
это не позволяет каскадному удалению затронуть их возможные внешние связи.
Обычные пользователи и программы с похожими названиями не удаляются.

## Обычный запуск

В окружении изолированного React-dev backend включить setting, задать пароль и
выполнить:

```console
python manage.py seed_react_dev_demo --confirm-react-dev
```

Повторный запуск обновляет управляемые записи и не создаёт дубликаты.

## Dry-run

```console
python manage.py seed_react_dev_demo --confirm-react-dev --dry-run
```

Команда показывает план и итоговые количества, затем откатывает транзакцию.

## Reset

```console
python manage.py seed_react_dev_demo --confirm-react-dev --reset
```

Удаляется только программа с точными идентификаторами DEV-072 и все связанные
с ней Application, Team, Criteria, Submission, Assignment и Evaluation.
Фиксированные demo-аккаунты переиспользуются, после чего контур создаётся снова.

## Тестовые email

- `demo.manager@procollab.test`
- `demo.expert@procollab.test`
- `demo.participant1@procollab.test`
- `demo.participant2@procollab.test`

## Проверка кабинета эксперта

1. Войти на React-dev под `demo.expert@procollab.test`.
2. Открыть `/office/expert/submissions`.
3. Проверить три карточки: «Не начато», «Черновик» и «Отправлено».
4. Открыть решение без оценки и создать draft вручную.
5. Открыть черновик, изменить баллы и проверить autosave.
6. Открыть отправленную оценку и проверить полностью read-only состояние.
7. Убедиться, что detail показывает title, description и links, но не данные
Application, Team и участников.

## Удаление DEMO-данных

Предпочтительный способ очистки и повторного создания — запуск с `--reset`.
Ручное удаление по одному префиксу `[DEMO]` запрещено: похожие названия не
являются признаком владения набором.

## Запрет production

На production setting `ALLOW_REACT_DEV_DEMO_SEED` должен оставаться `False`.
Команду нельзя запускать на production или legacy dev базе. Флаг
`--confirm-react-dev` означает явное подтверждение, что подключена отдельная
React-dev база.
Empty file.
Empty file.
87 changes: 87 additions & 0 deletions partner_programs/management/commands/seed_react_dev_demo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Roadmap: DEV-072
# Безопасная точка запуска демонстрационного набора только для React-dev.

import os

from django.conf import settings
from django.core.management.base import BaseCommand, CommandError

from partner_programs.services.react_dev_demo import (
DEMO_PROGRAM_NAME,
DEMO_USER_SPECS,
ReactDevDemoDataError,
build_react_dev_demo_data,
)


class Command(BaseCommand):
help = "Создать или обновить DEMO-набор экспертной оценки для React-dev."
requires_system_checks = []

def add_arguments(self, parser):
parser.add_argument(
"--confirm-react-dev",
action="store_true",
help="Подтвердить, что используется изолированная база данных React-dev.",
)
parser.add_argument(
"--reset",
action="store_true",
help="Удалить точную DEMO-программу и заново создать ее набор данных.",
)
parser.add_argument(
"--dry-run",
action="store_true",
help="Построить набор в транзакции и откатить все изменения.",
)

def handle(self, *args, **options):
if not settings.ALLOW_REACT_DEV_DEMO_SEED:
raise CommandError("Создание DEMO-данных React-dev отключено в настройках.")
if not options["confirm_react_dev"]:
raise CommandError("Для запуска передайте флаг --confirm-react-dev.")

password = os.environ.get("REACT_DEV_DEMO_PASSWORD")
if not password:
raise CommandError("Требуется переменная окружения REACT_DEV_DEMO_PASSWORD.")

mode = "пересоздание" if options["reset"] else "создание/обновление"
if options["dry_run"]:
mode = f"пробный запуск: {mode}"
self.stdout.write(f"План: {mode}")
self.stdout.write(f"Программа: {DEMO_PROGRAM_NAME}")
self.stdout.write(
"Пользователи: " + ", ".join(spec["email"] for spec in DEMO_USER_SPECS)
)

try:
summary = build_react_dev_demo_data(
password=password,
reset=options["reset"],
dry_run=options["dry_run"],
)
except ReactDevDemoDataError as exc:
raise CommandError(str(exc)) from exc

labels = {
"users": "пользователи",
"programs": "программы",
"program_memberships": "участники программ",
"applications": "заявки",
"teams": "команды",
"team_members": "участники команд",
"criteria": "критерии",
"submissions": "сдачи",
"assignments": "назначения",
"evaluations": "оценки",
"scores": "баллы",
}
self.stdout.write("Результат:")
for label, count in summary.as_dict().items():
self.stdout.write(f" {labels[label]}: {count}")
if options["dry_run"]:
self.stdout.write(
self.style.WARNING("Пробный запуск завершен; все изменения отменены.")
)
else:
self.stdout.write(self.style.SUCCESS("DEMO-данные React-dev готовы."))
Loading
Loading