# Management-команды Запускаются через `django-admin` внутри контейнера: ```bash docker compose run --rm web django-admin <команда> [аргументы] ``` или из shell контейнера (`make shell`) — `./manage.py <команда>`. Ниже — только команды портала. Штатные команды Django (`migrate`, `createsuperuser`, `collectstatic`, `makemigrations`, `shell`) работают как обычно. ## initialize ``` django-admin initialize ``` Загружает начальные фикстуры: типы антенн, режимы модуляции и прочие справочники. Выполняется один раз при разворачивании новой базы. То же делает `djangoctl.sh initialize`. ## fetch_data ``` django-admin fetch_data ``` Загружает спутники и передатчики из SatNOGS DB (`DB_API_ENDPOINT`). Ручной аналог периодической задачи `fetch_data`, которая идёт раз в час. ## update_station_last_seen ``` django-admin update_station_last_seen [ ...] ``` Обновляет отметку последнего появления для перечисленных станций. Принимает один или несколько числовых идентификаторов. Нужна, когда станцию надо вернуть на связь руками, не дожидаясь очередного обращения её клиента. ## recalculate_observation_ratings ``` django-admin recalculate_observation_ratings # предпросмотр django-admin recalculate_observation_ratings --apply # запись ``` Сбрасывает завышенные оценки «good», записанные багом в ветке `data_upload` функции `rate_observation`. По умолчанию — сухой прогон; запись только по `--apply`. Команда намеренно консервативна: она трогает наблюдение, только если выполнены все условия — статус `100`, есть демодулированные данные, режим передачи вниз CW или FM, и `waterfall_status` пуст (то есть водопад никто не вычитывал). Такие наблюдения возвращаются в `0` («unknown»), а не в «bad». Полное описание проблемы и рассуждение о том, почему нельзя откатить точнее, — в [](rating.md). ## confirm_satellite_reentries ``` django-admin confirm_satellite_reentries # предпросмотр django-admin confirm_satellite_reentries --apply # запись ``` То же решение, что принимает суточная задача с тем же именем: перевод подтверждённо сошедших аппаратов в статус `re-entered` с заполнением даты схода. По умолчанию — сухой прогон; запись только по `--apply`, `--limit N` ограничивает число аппаратов за прогон. Предпросмотр печатает по строке на аппарат (номер NORAD, имя, сработавшее правило, дата, которая будет записана) и общее число будущих наблюдений этих спутников — команда их не трогает, решение снимать расписание остаётся за оператором. Оба правила и порядок отката описаны в [](satellites.md). ## release_precheck ``` django-admin release_precheck ``` Проверки данных перед обновлением портала — то, на чём упадут миграции релиза: дубли NORAD в синтетическом диапазоне 70000–100000 (`0039` ставит на него уникальность), строки в таблицах входа через соцсети (`0043` их удаляет и отказывается при непустых), и число неприменённых миграций. Ничего не пишет; при находках печатает их и завершается с ошибкой. Запускать до кнопки выкатки образом новой версии на базе прода: ``` docker run --rm --env-file .env --network <сеть compose> <образ новой версии> django-admin release_precheck ``` Что делать с находками — в CHANGELOG, раздел «Требует действий». ## dump_satellite_descriptions ``` django-admin dump_satellite_descriptions > descriptions.json django-admin dump_satellite_descriptions --limit 50 > batch.json ``` Выгружает описания спутников в JSON — исходящая половина обмена для перевода. Портал не подключает сервисов перевода: текст переводится снаружи и возвращается тем же файлом. Отдаются те аппараты, у которых **хотя бы одна** из шести языковых колонок заполнена **и хотя бы одна** пуста, то есть ровно те, где есть с чего переводить и куда. Полностью пустой аппарат — не задача на перевод, полностью заполненный уже готов. `--limit N` режет выдачу для перевода порциями. В stdout идёт только JSON и ничего больше, поэтому вывод можно перенаправлять в файл. ## load_satellite_descriptions ``` django-admin load_satellite_descriptions descriptions.json # предпросмотр django-admin load_satellite_descriptions descriptions.json --apply # запись django-admin load_satellite_descriptions - # из stdin ``` Загружает переведённый JSON обратно. По умолчанию — сухой прогон; запись только по `--apply`. Два правила решают, что будет записано: * поле, названное в `locked_fields` этого спутника, не трогается никогда — в этом и состоит смысл блокировки (см. [](satellites.md)); * значение, совпадающее с уже записанным, записью не считается. Отсюда идемпотентность: повторный прогон того же файла ничего не меняет, а выгрузка, пропущенная обратно через эту команду, — пустая операция. Спутник с неизвестным `sat_id` считается и пропускается, а не роняет загрузку: каталог живёт, и партия перевода недельной давности не должна падать целиком из-за одного слитого аппарата. Отчёт печатает, сколько записано, сколько полей пропущено по блокировке и сколько аппаратов не нашлось в каталоге. ## backfill_analytics ``` django-admin backfill_analytics [--force] [--since YYYY-MM-DD] ``` Считает суточные метрики аналитики за все прошедшие дни начиная с даты создания самой старой станции. Ставит задачу `backfill_historical_metrics` в очередь Celery и сразу возвращает управление — результата в выводе команды не будет, смотреть логи воркера. | Аргумент | Назначение | |---|---| | `--force` | Пересчитать дни, для которых метрики уже есть. Нужен после добавления новых полей метрик — иначе существующие строки останутся со значениями по умолчанию | | `--since` | Дата начала в формате `YYYY-MM-DD` вместо даты создания старейшей станции | Подробнее — [](analytics.md). ## copy_demoddata_content ``` django-admin copy_demoddata_content [--workers N] ``` Копирует файлы кадров и их `payload_json` из хранилища в таблицу `DemodDataContent`, откуда их отдаёт `/api/demoddata/`. Пока у кадра нет копии, API читает его файл, то есть делает GET в S3 на каждую строку страницы. Новые кадры получают копию сами (в задаче `decode_current_frame`), поэтому команда нужна один раз, для кадров, принятых до её появления. Идёт от новых кадров к старым пачками по 1000: файлы пачки читают `--workers` потоков (по умолчанию 32), а в базу пачка пишется одним `INSERT`. Кадр, у которого уже есть копия, не трогается. Поэтому прерванный прогон можно запустить снова, и он продолжит с того места, где остановился. Кадр, файл которого не читается, записывается в лог и пропускается: API продолжает читать его из файла, как раньше. Каждая пачка печатает прогресс: сколько скопировано и пропущено, последний `id` и скорость в кадрах в секунду. Для оценки места под базу остановите команду на первых ~100 тыс. кадрах и разделите `pg_total_relation_size('base_demoddatacontent')` на число строк в таблице.