7.8 KiB
Полное удаление сотрудника
Администратор может полностью удалить аккаунт сотрудника через существующий маршрут управления пользователями:
DELETE /api/v1/users/admin/users/{user_id}/
Authorization: Bearer <access-token>
Тело запроса не требуется. Успех — 204 No Content без JSON-тела. Сотрудник
исчезает из списка, поиска и карточки; повторный DELETE возвращает 404.
Деактивация и обратная активация остаются отдельными операциями.
Права и ошибки
Право удаления имеет существующий активный пользователь с is_staff=True,
как и для остальных административных endpoint'ов. Проверка повторяется внутри
транзакции после получения блокировок: ранее аутентифицированный, но уже удалённый
или лишённый прав администратор не может завершить изменение.
| HTTP | Код ошибки | Значение |
|---|---|---|
| 400 | self_delete_forbidden |
Нельзя удалить самого себя |
| 401 | Стандартная ошибка аутентификации | Нет действующего access token |
| 403 | Стандартная ошибка прав / permission_denied |
Требуется активный администратор |
| 404 | not_found |
Сотрудник отсутствует, в том числе после удаления |
| 409 | last_active_admin |
Нельзя оставить систему без активного администратора |
| 409 | user_delete_conflict |
Защищённая связь препятствует удалению |
Ошибки используют стандартный envelope проекта: success: false, data: null,
errors: [{code, message}], meta. Запрет удаления себя имеет приоритет над
проверкой последнего администратора. Существующие связи не блокируют удаление;
409 для защищённой связи сохраняет безопасное поведение при расширении модели.
DELETE, создание аккаунта администратором, смена роли/активности и соответствующие операции Django admin используют одни блокировки пользователей. Пакетное действие Django admin атомарно: если выбран сам исполнитель, вся операция отклоняется. Сохранение собственных учётных данных и смена пароля также блокируют актуальную строку User, чтобы начавшийся до DELETE запрос не создал аккаунт заново.
Данные после удаления
- Физически удаляются User, Profile, связи с группами/permissions, записи OutstandingToken и связанные BlacklistedToken. Сами справочники прав остаются.
- Отчёты ReportUpload, загрузки реестров RegisterUpload, ExchangePackageImport сохраняются; их ссылки на автора обнуляются штатным SET_NULL. Сохраняются загруженные файлы, предметные данные и цепочки импортов.
- BackgroundJob сохраняются,
user_idочищается явно, поскольку это integer, а не внешний ключ. Выполняющиеся задачи не отменяются. - Аватар удаляется из storage после commit, если его не использует другой профиль. При ошибке storage удаление аккаунта остаётся успешным, ошибка регистрируется в серверном журнале для последующей очистки файла.
- Технический Django admin LogEntry сохраняет штатный CASCADE для записей, исполнителем которых был удалённый пользователь; предметные журналы выше не удаляются. Содержимое исторических документов не переписывается.
- Старый access перестаёт проходить JWTAuthentication. Refresh endpoint дополнительно проверяет существование и активность пользователя и возвращает 401 для удалённого/неактивного аккаунта. Повторный refresh активного пользователя поддерживается как раньше. Записей восстановления пароля в текущей модели нет.
Изменений схемы БД и миграций не требуется. Откат версии приложения не восстанавливает уже удалённые аккаунты.
Передача frontend-разработчику (Глебу)
- Добавить отдельное действие «Удалить» в меню сотрудника; для текущего пользователя скрыть или отключить его.
- Перед запросом показать имя выбранного сотрудника и подтверждение полного необратимого удаления аккаунта. Сообщить, что отчёты и история импортов останутся.
- Вызвать DELETE; на время запроса блокировать повторное действие. После 204 закрыть диалог и обновить список/счётчик; если последняя строка страницы удалена, перейти на предыдущую существующую страницу.
- Обработать стандартный error envelope, включая 400/403/404/409. При 404 обновить список. Не пытаться читать JSON из успешного ответа 204.
- Обновить клиент из OpenAPI и проверить удаление обычного, неактивного и другого административного аккаунта, запрет удаления себя и потерю прав во время запроса.
Проверка backend
PYTHONPATH=src uv run --no-sync pytest tests/apps/user
Восемь тестов test_management_concurrency.py пропускаются SQLite и требуют
PostgreSQL. Для них использовать отдельную пустую тестовую БД и явно задавать все
TEST_POSTGRES_HOST, TEST_POSTGRES_PORT, TEST_POSTGRES_USER,
TEST_POSTGRES_PASSWORD, TEST_POSTGRES_DB, чтобы исключить fallback к рабочей БД:
scripts/run-tests-prod.sh ../tests/apps/user
Регрессии обмена и подготовленных выгрузок находятся в
tests/apps/exchange/test_api.py, tests/apps/external_data/test_source_record_export.py
и tests/apps/external_data/test_export_tasks.py. Изменений upload/export API эта
доработка не вносит.