add: management-команда regenerate_seria_prerender для оффлайн пересборки pre-render шаблонов серий; обновлены SETUP.md, README.md, MANAGEMENT_RUNBOOK.md
This commit is contained in:
175
MANAGEMENT_RUNBOOK.md
Normal file
175
MANAGEMENT_RUNBOOK.md
Normal file
@@ -0,0 +1,175 @@
|
||||
# MANAGEMENT_RUNBOOK.md
|
||||
|
||||
Единый runbook по management-командам проекта.
|
||||
|
||||
Документ отвечает на 3 вопроса:
|
||||
- что запускать;
|
||||
- когда запускать;
|
||||
- как безопасно откатываться/повторять запуск.
|
||||
|
||||
## Каталог команд
|
||||
|
||||
1. `generate_sitemaps` — оффлайн генерация sitemap-файлов.
|
||||
2ю `regenerate_seria_prerender` — оффлайн пересборка pre-render шаблонов для `catalog_seria_info`.
|
||||
|
||||
## Общие правила запуска
|
||||
|
||||
- Запускать команды из корня репозитория.
|
||||
- Для локального/CI запуска использовать `poetry`.
|
||||
- Не запускать тяжелые операции через HTTP-эндпоинты `/service/*`.
|
||||
- Перезапуск веб-сервера (`gunicorn`/`uWSGI`) делать отдельным шагом оркестрации, а не из кода Django.
|
||||
|
||||
Базовый шаблон запуска:
|
||||
|
||||
```bash
|
||||
cd /Users/e-serg/PRJ/2022-oknardia
|
||||
poetry run python oknardia/manage.py <command> [args]
|
||||
```
|
||||
|
||||
## 1) Команда `generate_sitemaps`
|
||||
|
||||
Назначение:
|
||||
- пересобрать `sitemap.xml` и chunk-файлы в `MEDIA_ROOT/_serv_sitemap`.
|
||||
|
||||
Базовый запуск:
|
||||
|
||||
```bash
|
||||
cd /Users/e-serg/PRJ/2022-oknardia
|
||||
poetry run python oknardia/manage.py generate_sitemaps
|
||||
```
|
||||
|
||||
Запуск с параметрами:
|
||||
|
||||
```bash
|
||||
cd /Users/e-serg/PRJ/2022-oknardia
|
||||
poetry run python oknardia/manage.py generate_sitemaps \
|
||||
--compare-min-depth 2 \
|
||||
--compare-max-depth 4 \
|
||||
--max-items 40000 \
|
||||
--max-file-size 5242880 \
|
||||
--max-files-qty 998
|
||||
```
|
||||
|
||||
Когда запускать:
|
||||
- после деплоя;
|
||||
- по расписанию (cron/systemd timer);
|
||||
- после крупных изменений данных каталога/блога.
|
||||
|
||||
### Важные замечания
|
||||
|
||||
Чтобы `sitemap.xml` отдавал прокси-nginx напрямую из файловой системы, нужно, чтобы он физически лежал
|
||||
в `MEDIA_ROOT/_serv_sitemap/sitemap.xml`.
|
||||
|
||||
Допустимо, что файл доступен по двум URL (корневой и media), но в `robots.txt` должен быть указан один
|
||||
канонический вариант `sitemap.xml`
|
||||
|
||||
#### NGINX snippet (alias для корневого sitemap)
|
||||
|
||||
```nginx
|
||||
# Корневой sitemap.xml (для привычного для поисковиков URL)
|
||||
location = /sitemap.xml {
|
||||
alias /<путь-к-каталогку-с-докер-приложением>/media/_serv_sitemap/sitemap.xml;
|
||||
default_type application/xml;
|
||||
add_header Cache-Control "public, max-age=300";
|
||||
}
|
||||
```
|
||||
|
||||
## 2) Команда `regenerate_seria_prerender`
|
||||
|
||||
Назначение:
|
||||
- пересобрать pre-render шаблоны для страниц серий (`catalog_seria_info`) в каталоге `seria_info/prepared/`.
|
||||
|
||||
Проверка без записи файлов:
|
||||
|
||||
```bash
|
||||
cd /Users/e-serg/PRJ/2022-oknardia
|
||||
poetry run python oknardia/manage.py regenerate_seria_prerender --dry-run
|
||||
```
|
||||
|
||||
Пересборка только отсутствующих файлов:
|
||||
|
||||
```bash
|
||||
cd /Users/e-serg/PRJ/2022-oknardia
|
||||
poetry run python oknardia/manage.py regenerate_seria_prerender
|
||||
```
|
||||
|
||||
Принудительная пересборка всех root-серий:
|
||||
|
||||
```bash
|
||||
cd /Users/e-serg/PRJ/2022-oknardia
|
||||
poetry run python oknardia/manage.py regenerate_seria_prerender --force
|
||||
```
|
||||
|
||||
Выборочная пересборка:
|
||||
|
||||
```bash
|
||||
cd /Users/e-serg/PRJ/2022-oknardia
|
||||
poetry run python oknardia/manage.py regenerate_seria_prerender --seria-id 843 --seria-id 2100 --force
|
||||
```
|
||||
|
||||
Когда запускать:
|
||||
- после обновления логики `catalog_seria_info`;
|
||||
- после массового обновления данных серий/окон/квартир;
|
||||
- после очистки `seria_info/prepared/`.
|
||||
|
||||
## Оркестрация и reload веб-сервера
|
||||
|
||||
Важно:
|
||||
- reload веб-сервера не встроен в management-команды;
|
||||
- это отдельная операция окружения.
|
||||
|
||||
Пример для systemd + gunicorn:
|
||||
|
||||
```bash
|
||||
sudo systemctl reload gunicorn
|
||||
```
|
||||
|
||||
Рекомендуемый batch-сценарий:
|
||||
|
||||
```bash
|
||||
cd /Users/e-serg/PRJ/2022-oknardia
|
||||
poetry run python oknardia/manage.py regenerate_seria_prerender --force
|
||||
poetry run python oknardia/manage.py generate_sitemaps
|
||||
sudo systemctl reload gunicorn
|
||||
```
|
||||
|
||||
## Cron/systemd timer (пример)
|
||||
|
||||
Пример cron (раз в сутки в 03:20):
|
||||
|
||||
```bash
|
||||
20 3 * * * cd /Users/e-serg/PRJ/2022-oknardia && poetry run python oknardia/manage.py regenerate_seria_prerender --force && poetry run python oknardia/manage.py generate_sitemaps >> /var/log/oknardia-maintenance.log 2>&1
|
||||
```
|
||||
|
||||
Если нужен reload после batch, добавляй отдельной строкой/шагом оркестратора.
|
||||
|
||||
## Диагностика
|
||||
|
||||
Быстрая проверка конфигурации:
|
||||
|
||||
```bash
|
||||
cd /Users/e-serg/PRJ/2022-oknardia
|
||||
poetry run python oknardia/manage.py check
|
||||
```
|
||||
|
||||
Типовые причины проблем:
|
||||
- нет прав записи в директории `templates/seria_info/prepared` или `MEDIA_ROOT/_serv_sitemap`;
|
||||
- устаревшее виртуальное окружение / неустановленные зависимости;
|
||||
- запуск не из того каталога.
|
||||
|
||||
## План миграции `/service/*` -> management commands
|
||||
|
||||
Текущее направление:
|
||||
- все тяжелые и административные операции переносить из HTTP в management-команды;
|
||||
- `/service/*` оставлять только как thin UI/мониторинг или убрать полностью.
|
||||
|
||||
Кандидаты на перенос:
|
||||
- действия из `service.py` (`/service/make_rating`, sitemap/служебные задачи и т.п.);
|
||||
- любые операции, которые могут идти дольше обычного web-request.
|
||||
|
||||
---
|
||||
|
||||
См. также:
|
||||
- `SETUP.md`
|
||||
- `README.md`
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
* Рефакторинг `catalog_profile_manufacture` (`/catalog/profile/<id>-<manufacturer>`): упрощена валидация URL, убран дублирующий код маппинга для `PROFILES` и `MERCHANTS` через общие хелперы, стандартизирован хвост контекста (`LAST_VISIT`, `LOG_VISIT`, `ticks`) через `_append_visit_context`.
|
||||
* Рефакторинг `catalog_seria` (`/catalog/seria/`): raw SQL ⟶ ORM для списка корневых серий, подготовка данных упрощена, хвост контекста с визитами и `ticks` вынесен в общий helper внутри `catalog_series.py`.
|
||||
* Рефакторинг `catalog_seria_info` и связанных функций в `catalog_series.py`: raw SQL ⟶ ORM (`catalog_seria_info`, `seria_nav`, `seria_info_year`, `seria_info_geo_code`), снижена нагрузка на БД за счёт предвыборки и переиспользования агрегатов (`quantities_by_pair`, `offers_by_window`), добавлены безопасные fallback-значения для пустых выборок, включена потоковая обработка `iterator(chunk_size=500)` для гео-данных, обновлены комментарии и docstring под фактическую логику (таблица окон, pre-render light/heavy шаблонов, гео+статистика серии).
|
||||
*
|
||||
* Добавлена management-команда `regenerate_seria_prerender` для оффлайн-пересборки pre-render шаблонов `catalog_seria_info` (все или выбранные root-серии), с режимами `--dry-run` и `--force`; серверный reload (Gunicon? uWSGI или что там еще будет) должен быть вынесен из кода приложения в оркестрацию (cron/systemd/deploy step).
|
||||
*
|
||||
*
|
||||
*
|
||||
@@ -35,8 +35,7 @@
|
||||
|
||||
* [`AGENTS.md`](AGENTS.md) – контекст проекта для AI-ассистентов (архитектура, конвенции, рабочие сценарии).
|
||||
* [`SETUP.md`](SETUP.md) – пошаговая настройка окружения, запуск проекта и базовые команды разработки.
|
||||
* Сервисные утилиты:
|
||||
- [`SITEMAP_RUNBOOK.md`](SITEMAP_RUNBOOK.md) – sitemap (генерация, веса, cron, nginx)
|
||||
* [`MANAGEMENT_RUNBOOK.md`](MANAGEMENT_RUNBOOK.md) – единый runbook по management-командам и batch-операциям.
|
||||
|
||||
|
||||
|
||||
|
||||
21
SETUP.md
21
SETUP.md
@@ -233,6 +233,26 @@ python manage.py remove_stale_contenttypes # Удалить устаревши
|
||||
# Служебные
|
||||
python manage.py check # Проверить конфигурацию
|
||||
python manage.py check --deploy # Проверка для продакшена
|
||||
python manage.py generate_sitemaps # Оффлайн генерация sitemap XML
|
||||
python manage.py regenerate_seria_prerender --dry-run # Проверка пересборки pre-render шаблонов серий
|
||||
python manage.py regenerate_seria_prerender --force # Принудительная пересборка pre-render шаблонов серий
|
||||
```
|
||||
|
||||
### Пересборка pre-render шаблонов серий (рекомендуемый сценарий)
|
||||
|
||||
Шаблоны для `catalog_seria_info` пересобираются оффлайн management-командой, без reload из кода Django.
|
||||
|
||||
```bash
|
||||
cd /path/to/project
|
||||
poetry run python oknardia/manage.py regenerate_seria_prerender --force
|
||||
# затем (опционально) один внешний reload процесса приложения, если это требуется вашей конфигурацией
|
||||
# sudo systemctl reload gunicorn
|
||||
```
|
||||
|
||||
Для выборочной пересборки используйте `--seria-id` несколько раз:
|
||||
|
||||
```bash
|
||||
poetry run python oknardia/manage.py regenerate_seria_prerender --seria-id 843 --seria-id 2100 --force
|
||||
```
|
||||
|
||||
## 📚 Дополнительные ресурсы
|
||||
@@ -240,7 +260,6 @@ python manage.py check --deploy # Проверка для продак
|
||||
- [Django документация](https://docs.djangoproject.com/en/stable/)
|
||||
- [AGENTS.md](./AGENTS.md) — архитектура и конвенции проекта
|
||||
- [README.md](./README.md) — основная информация о проекте
|
||||
- [SECURITY_AUDIT_REPORT.md](./SECURITY_AUDIT_REPORT.md) — отчёт безопасности
|
||||
|
||||
## ❓ Решение проблем
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ from oknardia.models import (
|
||||
Building_Info,
|
||||
)
|
||||
from web.report1 import get_last_all_user_visit_list, get_last_user_visit_cookies, get_last_user_visit_list
|
||||
from web.add_func import get_flaps_for_big_pictures, touch_reload_wsgi
|
||||
from web.add_func import get_flaps_for_big_pictures
|
||||
import time
|
||||
import os
|
||||
import math
|
||||
@@ -217,7 +217,6 @@ def catalog_seria_info(
|
||||
string_prerender = render_to_string("seria_info/all_seria_info_pre_light.html", to_template)
|
||||
with open(light_template_w_path, "w", encoding="utf-8") as file:
|
||||
file.write(string_prerender)
|
||||
touch_reload_wsgi(light_template_w_path)
|
||||
else:
|
||||
to_template.update({"THIS_SERIA_NAME": q_seria.sName})
|
||||
|
||||
@@ -346,7 +345,7 @@ def seria_info_geo_code(seria_id: int | str = DEFAULT_SERIA_ID_FOR_CATALOG) -> d
|
||||
жилые/муниципальные/государственные площади, число жителей, квартир,
|
||||
лицевых счетов и диапазон показателя состояния домов.
|
||||
|
||||
:param seria_id: int | str -- id серии, для которой нужно получить данные
|
||||
:param seria_id: int | str -- id серии, для которой нужно получить данные.
|
||||
:return: dict -- {
|
||||
"DATA4GEO": [...],
|
||||
"MUNICIPAL_M2": ...,
|
||||
|
||||
113
oknardia/web/management/commands/regenerate_seria_prerender.py
Normal file
113
oknardia/web/management/commands/regenerate_seria_prerender.py
Normal file
@@ -0,0 +1,113 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pytils
|
||||
from django.conf import settings
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.db.models import F
|
||||
from django.test import RequestFactory
|
||||
|
||||
from oknardia.models import Seria_Info
|
||||
from web import catalog_series
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
"""Пересоздает pre-render шаблоны для страниц серий (/catalog/seria/.../all<ID>)."""
|
||||
|
||||
help = "Пересоздает pre-render шаблоны catalog_seria_info для выбранных или всех корневых серий."
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument(
|
||||
"--seria-id",
|
||||
type=int,
|
||||
action="append",
|
||||
default=[],
|
||||
help="ID серии (можно передавать несколько раз). По умолчанию пересоздаются все корневые серии.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--force",
|
||||
action="store_true",
|
||||
help="Пересоздать даже если pre-render файл уже существует.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--dry-run",
|
||||
action="store_true",
|
||||
help="Только показать, что будет сделано, без генерации файлов.",
|
||||
)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
seria_ids: list[int] = options["seria_id"]
|
||||
force: bool = options["force"]
|
||||
dry_run: bool = options["dry_run"]
|
||||
|
||||
# Берем только корневые серии, потому что для них строятся канонические URL /all<ID>.
|
||||
query = Seria_Info.objects.filter(id=F("kRoot_id")).only("id", "sName").order_by("id")
|
||||
if seria_ids:
|
||||
query = query.filter(id__in=seria_ids)
|
||||
|
||||
targets = list(query)
|
||||
if not targets:
|
||||
raise CommandError("Не найдено подходящих корневых серий для пересоздания pre-render.")
|
||||
|
||||
templates_root = Path(settings.TEMPLATES[0]["DIRS"][0])
|
||||
prepared_dir = templates_root / settings.PATH_FOR_SERIA_INFO_HTML_INCLUDE
|
||||
prepared_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
request_factory = RequestFactory()
|
||||
created = 0
|
||||
planned = 0
|
||||
skipped = 0
|
||||
|
||||
for seria in targets:
|
||||
target_file = prepared_dir / f"{seria.id}_id.html"
|
||||
if target_file.exists() and not force:
|
||||
skipped += 1
|
||||
self.stdout.write(f"SKIP {seria.id}: {target_file}")
|
||||
continue
|
||||
|
||||
if dry_run:
|
||||
action = "REGEN" if target_file.exists() else "CREATE"
|
||||
self.stdout.write(f"{action} {seria.id}: {target_file}")
|
||||
planned += 1
|
||||
continue
|
||||
|
||||
if target_file.exists():
|
||||
target_file.unlink()
|
||||
|
||||
slug = pytils.translit.slugify(seria.sName)
|
||||
request = request_factory.get(f"/catalog/seria/{slug}/all{seria.id}")
|
||||
|
||||
# В команде принудительно включаем «production-mode» для вьюхи,
|
||||
# чтобы она прошла тяжелую ветку и пересоздала pre-render файл.
|
||||
old_debug = catalog_series.DEBUG
|
||||
try:
|
||||
catalog_series.DEBUG = False
|
||||
response = catalog_series.catalog_seria_info(request, slug, seria.id)
|
||||
finally:
|
||||
catalog_series.DEBUG = old_debug
|
||||
|
||||
if response.status_code != 200:
|
||||
raise CommandError(
|
||||
f"Серия {seria.id}: ожидался status=200, получен {response.status_code}."
|
||||
)
|
||||
if not target_file.exists():
|
||||
raise CommandError(f"Серия {seria.id}: pre-render файл не создан: {target_file}")
|
||||
|
||||
created += 1
|
||||
self.stdout.write(self.style.SUCCESS(f"OK {seria.id}: {target_file}"))
|
||||
|
||||
if dry_run:
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS(
|
||||
f"DRY-RUN. Обработано: {len(targets)}. Будет создано/пересоздано: {planned}. Пропущено: {skipped}."
|
||||
)
|
||||
)
|
||||
else:
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS(
|
||||
f"Готово. Обработано: {len(targets)}. Создано/пересоздано: {created}. Пропущено: {skipped}."
|
||||
)
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user