Compare commits
5 Commits
a3bc67613f
..
v0.3.0
| Author | SHA1 | Date | |
|---|---|---|---|
| 2d7e0813d6 | |||
| 5678624608 | |||
| cc739c5e07 | |||
| 54ed78185c | |||
| b376fdcaf9 |
@@ -152,6 +152,14 @@ ALLOW_MEDIA_SERVE=False
|
|||||||
# - AWS Systems Manager Parameter Store
|
# - AWS Systems Manager Parameter Store
|
||||||
# - HashiCorp Vault
|
# - HashiCorp Vault
|
||||||
|
|
||||||
|
|
||||||
|
# ****************************************************************************************************************
|
||||||
|
# Системные пути на хосте (ТОЛЬКО ДЛЯ ПРОДАКШЕНА)
|
||||||
|
# Используется скриптом для генерации корректного Nginx конфига (alias к медиа-файлам).
|
||||||
|
# На локальной машине разработчика (Dev) эта переменная игнорируется.
|
||||||
|
# В ПРОДАКШЕНЕ: Укажите полный путь к папке проекта на сервере
|
||||||
|
HOST_PROJECT_PATH=/home/username/projects
|
||||||
|
|
||||||
# ****************************************************************************************************************
|
# ****************************************************************************************************************
|
||||||
# Настройки доступа к пакетам в репозитории, чтобы wathtower мог проверять их свежесть и скачивать для обновления.
|
# Настройки доступа к пакетам в репозитории, чтобы wathtower мог проверять их свежесть и скачивать для обновления.
|
||||||
# Получить эти данные можно в настройках вашего репозитория, например:
|
# Получить эти данные можно в настройках вашего репозитория, например:
|
||||||
|
|||||||
@@ -0,0 +1,242 @@
|
|||||||
|
# Production Deployment Guide для Oknardia
|
||||||
|
|
||||||
|
## Структура на хосте
|
||||||
|
|
||||||
|
Структура директорий на production сервере должна быть:
|
||||||
|
|
||||||
|
```
|
||||||
|
~/docker-app/oknardia-site/
|
||||||
|
├── .env # Переменные окружения Django
|
||||||
|
├── config/ # Конфиги
|
||||||
|
│ └── nginx/
|
||||||
|
│ └── oknardia-app--external-nginx.conf # Конфиг Nginx (шаблон)
|
||||||
|
├── database/ # БД SQLite (создается автоматически)
|
||||||
|
│ └── oknardia.sqlite3
|
||||||
|
├── media/ # Медиа файлы, uploads, кеши
|
||||||
|
│ ├── img_avatar/
|
||||||
|
│ ├── img_seria/
|
||||||
|
│ ├── _serv_sitemap/
|
||||||
|
│ └── _error/ # Error pages (генерируются контейнером)
|
||||||
|
└── docker-compose.prod.yml # Конфиг Docker
|
||||||
|
```
|
||||||
|
|
||||||
|
## Установка на Production сервере
|
||||||
|
|
||||||
|
### 1. Создать директории и скопировать файлы
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# На хосте
|
||||||
|
mkdir -p ~/docker-app/oknardia-site/{config/nginx,database,media}
|
||||||
|
|
||||||
|
# Скопировать docker-compose.prod.yml и .env из проекта
|
||||||
|
cp docker-compose.prod.yml ~/docker-app/oknardia-site/
|
||||||
|
cp .env ~/docker-app/oknardia-site/
|
||||||
|
|
||||||
|
# Скопировать конфиг nginx
|
||||||
|
cp config/nginx/oknardia-app--external-nginx.conf ~/docker-app/oknardia-site/config/nginx/
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Настроить .env на сервере
|
||||||
|
|
||||||
|
Отредактировать `~/docker-app/oknardia-site/.env`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# ОБЯЗАТЕЛЬНО заполнить эти переменные!
|
||||||
|
|
||||||
|
# Путь к проекту на хосте (используется для sed-замены в конфиге nginx)
|
||||||
|
HOST_PROJECT_PATH=/home/default_user/projects/oknardia-site
|
||||||
|
|
||||||
|
# Credentials для скачивания образа из реестра Gitea
|
||||||
|
REPO_USER=имя_пользователя_gitea
|
||||||
|
REPO_PASS=токен_из_личного_кабинета_gitea
|
||||||
|
|
||||||
|
# Остальное скопировать из локального .env
|
||||||
|
DEBUG=False
|
||||||
|
DJANGO_SECRET_KEY=...
|
||||||
|
```
|
||||||
|
|
||||||
|
**Важно:** `HOST_PROJECT_PATH` должен совпадать с корневой папкой проекта на хосте, куда ты скопировал docker-compose.prod.yml!
|
||||||
|
|
||||||
|
### 3. Запустить контейнеры
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd ~/docker-app/oknardia-site/
|
||||||
|
|
||||||
|
# Первый запуск (холодный старт)
|
||||||
|
docker compose -f docker-compose.prod.yml up -d
|
||||||
|
|
||||||
|
# Проверить логи
|
||||||
|
docker compose -f docker-compose.prod.yml logs -f web
|
||||||
|
|
||||||
|
# Проверить статус контейнеров
|
||||||
|
docker compose -f docker-compose.prod.yml ps
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Проверить что nginx конфиг был сгенерирован
|
||||||
|
|
||||||
|
После первого запуска контейнер должен:
|
||||||
|
- Создать `/config/nginx/oknardia-app--external-nginx.conf` (боевой конфиг)
|
||||||
|
- Создать `/config/nginx/oknardia-app--external-nginx.conf.example` (exemplo)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ls -la ~/docker-app/oknardia-site/config/nginx/
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Настроить Nginx на хосте
|
||||||
|
|
||||||
|
На хосте установить Nginx и скопировать/ссылаться на конфиг из контейнера:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# На хосте (например, Ubuntu 20.04)
|
||||||
|
sudo systemctl install nginx
|
||||||
|
|
||||||
|
# Включить сгенерированный конфиг в основной конфиг Nginx
|
||||||
|
# /etc/nginx/sites-available/oknardia или /etc/nginx/conf.d/oknardia.conf
|
||||||
|
|
||||||
|
# Пример:
|
||||||
|
sudo ln -s /home/default_user/projects/oknardia-site/config/nginx/oknardia-app--external-nginx.conf \
|
||||||
|
/etc/nginx/sites-enabled/oknardia
|
||||||
|
|
||||||
|
# Проверить и перезагрузить Nginx
|
||||||
|
sudo nginx -t
|
||||||
|
sudo systemctl reload nginx
|
||||||
|
```
|
||||||
|
|
||||||
|
## Команды для управления
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd ~/docker-app/oknardia-site/
|
||||||
|
|
||||||
|
# Запустить контейнеры (если они были остановлены)
|
||||||
|
docker compose -f docker-compose.prod.yml up -d
|
||||||
|
|
||||||
|
# Остановить контейнеры
|
||||||
|
docker compose -f docker-compose.prod.yml down
|
||||||
|
|
||||||
|
# Перезагрузить контейнеры (удалить и создать заново)
|
||||||
|
docker compose -f docker-compose.prod.yml down
|
||||||
|
docker compose -f docker-compose.prod.yml up -d
|
||||||
|
|
||||||
|
# Посмотреть логи
|
||||||
|
docker compose -f docker-compose.prod.yml logs -f web
|
||||||
|
docker compose -f docker-compose.prod.yml logs -f watchtower
|
||||||
|
|
||||||
|
# Выполнить команду внутри контейнера Django
|
||||||
|
docker compose -f docker-compose.prod.yml exec web python manage.py shell
|
||||||
|
|
||||||
|
# Пересоздать пре-рендер кеши вручную
|
||||||
|
docker compose -f docker-compose.prod.yml exec web python manage.py regenerate_seria_prerender
|
||||||
|
```
|
||||||
|
|
||||||
|
## Что делает docker-compose.prod.yml
|
||||||
|
|
||||||
|
### Сервис `web` (Django + Gunicorn)
|
||||||
|
|
||||||
|
При старте контейнера:
|
||||||
|
1. ✅ Применяет миграции БД (`migrate --noinput`)
|
||||||
|
2. ✅ Собирает статику (`collectstatic --noinput`)
|
||||||
|
3. ✅ Генерирует sitemap'ы (`generate_sitemaps`)
|
||||||
|
4. ✅ Пересоздает пре-рендер кеши для серий (`regenerate_seria_prerender`)
|
||||||
|
5. ✅ Пересчитывает рейтинги (`make_rating`)
|
||||||
|
6. ✅ Копирует конфиг nginx с авто-заменой путей (через `sed`)
|
||||||
|
7. ✅ Запускает Gunicorn на `127.0.0.1:8000`
|
||||||
|
|
||||||
|
### Сервис `watchtower` (авто-обновление)
|
||||||
|
|
||||||
|
- 👁️ Следит за реестром Gitea (проверка каждые 30 минут)
|
||||||
|
- 🔄 Автоматически скачивает новый образ, если он есть
|
||||||
|
- ♻️ По-graceful перезагружает контейнер web
|
||||||
|
- 🗑️ Удаляет старые образы (WATCHTOWER_CLEANUP=true)
|
||||||
|
|
||||||
|
Watchtower использует метку на контейнере `web`:
|
||||||
|
```yaml
|
||||||
|
labels:
|
||||||
|
- "com.centurylinklabs.watchtower.scope=oknardia-scope"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Файлы, которые монтируются с хоста
|
||||||
|
|
||||||
|
| На хосте | В контейнере | Назначение |
|
||||||
|
|----------|----------|-----------|
|
||||||
|
| `./database/` | `/home/app/database/` | БД SQLite (КРИТИЧНО!) |
|
||||||
|
| `./media/` | `/home/app/public/media/` | Медиа файлы, uploads |
|
||||||
|
| `./config/` | `/nginx_configs_host/` | Конифиги (читаются и пишущутся контейнером) |
|
||||||
|
|
||||||
|
## Переменные окружения в docker-compose.prod.yml
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
environment:
|
||||||
|
- DEBUG=False # Production: без debug
|
||||||
|
- DJANGO_LOG_LEVEL=INFO # Минимум логов
|
||||||
|
- ALLOW_MEDIA_SERVE=False # Nginx обслуживает медиа
|
||||||
|
- HOST_PROJECT_PATH=... # Путь для sed-замены в конфиге nginx
|
||||||
|
- REPO_USER=${REPO_USER} # Из .env (для Watchtower)
|
||||||
|
- REPO_PASS=${REPO_PASS} # Из .env (для Watchtower)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Health Check
|
||||||
|
|
||||||
|
Контейнер имеет healthcheck, который проверяет доступность Django:
|
||||||
|
- Проверка каждые 3 минуты
|
||||||
|
- Таймаут: 12 секунд
|
||||||
|
- Unhealthy после 3 неудачных попыток
|
||||||
|
- Это критично для Watchtower (он ждет что контейнер станет healthy перед finalization)
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### ContainerError при запуске
|
||||||
|
|
||||||
|
Проверить логи:
|
||||||
|
```bash
|
||||||
|
docker compose -f docker-compose.prod.yml logs web
|
||||||
|
```
|
||||||
|
|
||||||
|
### БД не синхронизируется между перезагрузками
|
||||||
|
|
||||||
|
Убедиться что `./database/` правильно монтирует:
|
||||||
|
```bash
|
||||||
|
docker compose -f docker-compose.prod.yml exec web ls -la /home/app/database/
|
||||||
|
```
|
||||||
|
|
||||||
|
### Watchtower не обновляет контейнер
|
||||||
|
|
||||||
|
Проверить:
|
||||||
|
- Есть ли интернет на сервере
|
||||||
|
- Правильны ли REPO_USER и REPO_PASS в .env
|
||||||
|
- Существует ли образ `oknardia:latest` в реестре
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose -f docker-compose.prod.yml logs watchtower
|
||||||
|
```
|
||||||
|
|
||||||
|
### Nginx конфиг не обновился
|
||||||
|
|
||||||
|
Конфиг копируется при старте контейнера. Если нужно пересоздать:
|
||||||
|
```bash
|
||||||
|
docker compose -f docker-compose.prod.yml down
|
||||||
|
docker compose -f docker-compose.prod.yml up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
Потом проверить:
|
||||||
|
```bash
|
||||||
|
cat ~/docker-app/oknardia-site/config/nginx/oknardia-app--external-nginx.conf
|
||||||
|
```
|
||||||
|
|
||||||
|
## Резервные копии
|
||||||
|
|
||||||
|
Важно регулярно бэкапить:
|
||||||
|
- `./database/oknardia.sqlite3` - БД
|
||||||
|
- `./media/` - Загруженные файлы
|
||||||
|
|
||||||
|
Пример cronjob для ежедневного бэкапа:
|
||||||
|
```bash
|
||||||
|
# /etc/cron.daily/backup-oknardia
|
||||||
|
#!/bin/bash
|
||||||
|
BACKUP_DIR="/backup/oknardia"
|
||||||
|
APP_DIR="/home/default_user/projects/oknardia-site"
|
||||||
|
|
||||||
|
mkdir -p $BACKUP_DIR
|
||||||
|
tar -czf $BACKUP_DIR/oknardia-$(date +%Y%m%d).tar.gz $APP_DIR
|
||||||
|
find $BACKUP_DIR -name "oknardia-*.tar.gz" -mtime +30 -delete # Удалять старше 30 дней
|
||||||
|
```
|
||||||
|
|
||||||
@@ -29,13 +29,17 @@ services:
|
|||||||
# 3. Собираем статику
|
# 3. Собираем статику
|
||||||
# 4. Генерируем sitemap'ы
|
# 4. Генерируем sitemap'ы
|
||||||
# 5. Пересоздаём пре-рендер шаблоны серий
|
# 5. Пересоздаём пре-рендер шаблоны серий
|
||||||
# 6. Запускаем Gunicorn (нет live reload — это production режим!)
|
# 6. Пересчитываем рейтинги профилей, стеклопакетов и наборов
|
||||||
|
# (метод Манна-Уитни: может занять 30-60 сек на БД с 94+ профилями)
|
||||||
|
# 7. Запускаем Gunicorn (нет live reload — это production режим!)
|
||||||
|
# (timeout=120 сек для запуска, достаточно для всех процедур инициализации)
|
||||||
command: >
|
command: >
|
||||||
sh -c "mkdir -p /home/app/public/media &&
|
sh -c "mkdir -p /home/app/public/media &&
|
||||||
python manage.py migrate --noinput &&
|
python manage.py migrate --noinput &&
|
||||||
python manage.py collectstatic --noinput &&
|
python manage.py collectstatic --noinput &&
|
||||||
python manage.py generate_sitemaps &&
|
python manage.py generate_sitemaps &&
|
||||||
python manage.py regenerate_seria_prerender &&
|
python manage.py regenerate_seria_prerender &&
|
||||||
|
python manage.py make_rating &&
|
||||||
python -m gunicorn --workers 2 --bind 0.0.0.0:8000 --timeout 120 oknardia.wsgi:application"
|
python -m gunicorn --workers 2 --bind 0.0.0.0:8000 --timeout 120 oknardia.wsgi:application"
|
||||||
|
|
||||||
# 2. МОНТИРОВАНИЕ КОДА И ДАННЫХ
|
# 2. МОНТИРОВАНИЕ КОДА И ДАННЫХ
|
||||||
|
|||||||
@@ -0,0 +1,124 @@
|
|||||||
|
# ==============================================================================
|
||||||
|
# Docker Compose для PRODUCTION
|
||||||
|
# Этот файл запускается на боевом сервере.
|
||||||
|
# Вариант 1 (если переименовали в docker-compose.yml): docker compose up -d
|
||||||
|
# Вариант 2 (если оставили имя): docker compose -f docker-compose.prod.yml up -d
|
||||||
|
# ==============================================================================
|
||||||
|
|
||||||
|
services:
|
||||||
|
# --- ОСНОВНОЙ СЕРВИС: DJANGO + GUNICORN + WHITENOISE ---
|
||||||
|
web:
|
||||||
|
# Имя контейнера для удобства
|
||||||
|
container_name: oknardia-backend
|
||||||
|
|
||||||
|
# В production используем готовый, собранный образ из реестра (Gitea)
|
||||||
|
image: git.cube2.ru/erjemin/oknardia:latest
|
||||||
|
|
||||||
|
# ПЕРЕЗАПУСК при сбое
|
||||||
|
restart: always
|
||||||
|
|
||||||
|
# Метки для Watchtower (авто-обновление)
|
||||||
|
labels:
|
||||||
|
- "com.centurylinklabs.watchtower.scope=oknardia-scope"
|
||||||
|
|
||||||
|
# КОМАНДА ЗАПУСКА (Production режим)
|
||||||
|
# При старте контейнера:
|
||||||
|
# 1. Применяем миграции
|
||||||
|
# 2. Собираем статику
|
||||||
|
# 3. Генерируем sitemap'ы
|
||||||
|
# 4. Пересоздаём пре-рендер шаблоны серий
|
||||||
|
# 5. Пересчитываем рейтинги
|
||||||
|
# 6. Создаём папку конфигов nginx (если нет)
|
||||||
|
# 7. Копируем конфиг nginx с авто-заменой путей через sed
|
||||||
|
# (переменная HOST_PROJECT_PATH берется из .env и подставляется в контейнер)
|
||||||
|
# 8. Инициализируем боевой конфиг (если нет, копируем из примера)
|
||||||
|
# 9. Запускаем Gunicorn
|
||||||
|
command: >
|
||||||
|
sh -c "python manage.py migrate --noinput &&
|
||||||
|
python manage.py collectstatic --noinput &&
|
||||||
|
python manage.py generate_sitemaps &&
|
||||||
|
python manage.py regenerate_seria_prerender &&
|
||||||
|
python manage.py make_rating &&
|
||||||
|
mkdir -p /nginx_configs_host/nginx &&
|
||||||
|
sed \"s|/home/user/app/oknardia-site|$${HOST_PROJECT_PATH}|g\" /home/app/config/nginx/oknardia-app--external-nginx.conf > /nginx_configs_host/nginx/oknardia-app--external-nginx.conf.example &&
|
||||||
|
if [ ! -f /nginx_configs_host/nginx/oknardia-app--external-nginx.conf ]; then
|
||||||
|
cp /nginx_configs_host/nginx/oknardia-app--external-nginx.conf.example /nginx_configs_host/nginx/oknardia-app--external-nginx.conf;
|
||||||
|
echo 'INIT: Created new nginx config with correct paths';
|
||||||
|
fi &&
|
||||||
|
python -m gunicorn --workers 2 --bind 0.0.0.0:8000 --timeout 120 oknardia.wsgi:application"
|
||||||
|
|
||||||
|
# Пробрасывание портов
|
||||||
|
# Слушаем только на localhost хоста для безопасности
|
||||||
|
ports:
|
||||||
|
- "127.0.0.1:8060:8000"
|
||||||
|
|
||||||
|
# МОНТИРОВАНИЕ ТОМОВ (Volumes)
|
||||||
|
volumes:
|
||||||
|
# БД SQLite
|
||||||
|
- ./database:/home/app/database
|
||||||
|
|
||||||
|
# Медиа файлы
|
||||||
|
- ./media:/home/app/public/media
|
||||||
|
|
||||||
|
# Конфиги nginx
|
||||||
|
- ./config:/nginx_configs_host
|
||||||
|
|
||||||
|
# Пользователь и права
|
||||||
|
user: "0:0"
|
||||||
|
|
||||||
|
# ПЕРЕМЕННЫЕ ОКРУЖЕНИЯ (Production)
|
||||||
|
# .env файл содержит все sensitive данные (DB, Email, API keys, REPO_USER/PASS)
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
environment:
|
||||||
|
- DJANGO_SETTINGS_MODULE=oknardia.settings
|
||||||
|
- PYTHONUNBUFFERED=1
|
||||||
|
- DEBUG=False
|
||||||
|
- DJANGO_LOG_LEVEL=INFO
|
||||||
|
- ALLOW_MEDIA_SERVE=False
|
||||||
|
|
||||||
|
# ЗДОРОВЬЕ КОНТЕЙНЕРА (Healthcheck)
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/').read()"]
|
||||||
|
interval: 3m
|
||||||
|
timeout: 12s
|
||||||
|
start_period: 60s
|
||||||
|
retries: 3
|
||||||
|
|
||||||
|
# ЛОГИРОВАНИЕ (Ротация)
|
||||||
|
logging:
|
||||||
|
driver: "json-file"
|
||||||
|
options:
|
||||||
|
max-size: "10m"
|
||||||
|
max-file: "5"
|
||||||
|
|
||||||
|
# РЕСУРСЫ
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
cpus: '0.75'
|
||||||
|
memory: 768M
|
||||||
|
mem_limit: 768M
|
||||||
|
|
||||||
|
# --- WATCHTOWER: АВТО-ОБНОВЛЕНИЕ ОБРАЗОВ ---
|
||||||
|
watchtower:
|
||||||
|
image: containrrr/watchtower
|
||||||
|
container_name: oknardia_watchtower
|
||||||
|
restart: always
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
environment:
|
||||||
|
- REPO_USER=${REPO_USER}
|
||||||
|
- REPO_PASS=${REPO_PASS}
|
||||||
|
- WATCHTOWER_SCOPE=oknardia-scope
|
||||||
|
- WATCHTOWER_CLEANUP=true
|
||||||
|
- DOCKER_API_VERSION=1.44
|
||||||
|
- WATCHTOWER_WAIT_ON_TIMEOUT=60
|
||||||
|
- WATCHTOWER_LIFECYCLE_HOOKS=true
|
||||||
|
command: --interval 1800 --cleanup
|
||||||
|
logging:
|
||||||
|
driver: "json-file"
|
||||||
|
options:
|
||||||
|
max-size: "10m"
|
||||||
|
max-file: "3"
|
||||||
|
|
||||||
@@ -188,7 +188,10 @@ if database_engine == 'django.db.backends.sqlite3':
|
|||||||
'default': {
|
'default': {
|
||||||
'ENGINE': 'django.db.backends.sqlite3',
|
'ENGINE': 'django.db.backends.sqlite3',
|
||||||
'NAME': str(sqlite_db_path),
|
'NAME': str(sqlite_db_path),
|
||||||
}
|
'OPTIONS': {
|
||||||
|
'timeout': 20,
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
else:
|
else:
|
||||||
# База не SQLite (mariaDB, например): читаем все параметры подключения из env.
|
# База не SQLite (mariaDB, например): читаем все параметры подключения из env.
|
||||||
|
|||||||
+80
-15
@@ -1,24 +1,13 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
"""oknardia Конфигурация URL
|
"""oknardia Конфигурация URL"""
|
||||||
|
|
||||||
Список `urlpatterns` направляет URL-адреса в представления. Дополнительную информацию см.:
|
|
||||||
https://docs.djangoproject.com/en/4.1/topics/http/urls/
|
|
||||||
Примеры:
|
|
||||||
Представления функций
|
|
||||||
1. Добавьте import: из представлений импорта my_app
|
|
||||||
2. Добавьте URL-адрес в urlpatterns: path('', views.home, name='home')
|
|
||||||
Представления на основе классов
|
|
||||||
1. Добавьте импорт: from other_app.views import Home
|
|
||||||
2. Добавьте URL-адрес в шаблоны URL-адресов: path('', Home.as_view(), name='home')
|
|
||||||
Включение другой конфигурации URL
|
|
||||||
1. Импортируйте функцию include(): из django.urls import include, path
|
|
||||||
2. Добавьте URL-адрес в urlpatterns: path('blog/', include('blog.urls'))
|
|
||||||
"""
|
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.urls import include, path, re_path
|
from django.urls import include, path, re_path
|
||||||
from django.conf.urls.static import static
|
from django.conf.urls.static import static
|
||||||
|
from django.http import FileResponse
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import environ
|
import environ
|
||||||
|
import mimetypes
|
||||||
|
|
||||||
# Инициализируем env
|
# Инициализируем env
|
||||||
env = environ.Env()
|
env = environ.Env()
|
||||||
PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent
|
PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent
|
||||||
@@ -27,6 +16,67 @@ from oknardia.settings import *
|
|||||||
from web import views, autocomplete_addr, user_manager, blog, diagrams, report1, report2, catalog, prices, service, \
|
from web import views, autocomplete_addr, user_manager, blog, diagrams, report1, report2, catalog, prices, service, \
|
||||||
catalog_profiles, catalog_series, catalog_openings, catalog_companies
|
catalog_profiles, catalog_series, catalog_openings, catalog_companies
|
||||||
|
|
||||||
|
|
||||||
|
def _serve_public_root_file(request, path):
|
||||||
|
"""Отдает статик-файл из public/ с правильным Content-Type и UTF-8 для текстовых файлов."""
|
||||||
|
file_path = PUBLIC_ROOT / path
|
||||||
|
|
||||||
|
# Безопасность: проверяем, что файл гарантированно внутри PUBLIC_ROOT
|
||||||
|
try:
|
||||||
|
file_path = file_path.resolve()
|
||||||
|
file_path.relative_to(PUBLIC_ROOT.resolve())
|
||||||
|
except ValueError:
|
||||||
|
from django.http import Http404
|
||||||
|
raise Http404("Файл не найден")
|
||||||
|
|
||||||
|
if not file_path.is_file():
|
||||||
|
from django.http import Http404
|
||||||
|
raise Http404("Файл не найден")
|
||||||
|
|
||||||
|
# Определяем MIME type
|
||||||
|
mime_type, _ = mimetypes.guess_type(str(file_path))
|
||||||
|
|
||||||
|
# Для текстовых файлов добавляем charset=utf-8
|
||||||
|
if mime_type and mime_type.startswith('text'):
|
||||||
|
mime_type = f"{mime_type}; charset=utf-8"
|
||||||
|
|
||||||
|
response = FileResponse(open(file_path, 'rb'), content_type=mime_type)
|
||||||
|
|
||||||
|
# Определяем Content-Disposition в зависимости от типа файла:
|
||||||
|
# inline — браузер отображает/обрабатывает (иконки, картинки, CSS, JS, robots.txt, manifest)
|
||||||
|
# attachment — браузер скачивает (архивы, документы и т.д.)
|
||||||
|
inline_types = {
|
||||||
|
'image/', # все картинки (PNG, SVG, JPG и т.д.)
|
||||||
|
'text/', # все текстовые файлы
|
||||||
|
'application/javascript',
|
||||||
|
'application/json',
|
||||||
|
'application/xml',
|
||||||
|
'application/rss+xml',
|
||||||
|
'application/x-web-app-manifest+json', # manifest
|
||||||
|
}
|
||||||
|
|
||||||
|
if mime_type and any(mime_type.lower().startswith(t) for t in inline_types):
|
||||||
|
response['Content-Disposition'] = 'inline'
|
||||||
|
else:
|
||||||
|
# Для остального (неизвестные типы, архивы и т.д.) — скачивание
|
||||||
|
response['Content-Disposition'] = f'attachment; filename="{file_path.name}"'
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
def _iter_public_root_files():
|
||||||
|
"""Находит все обычные файлы в корне public/, кроме служебных артефактов (начинающихся с точки)."""
|
||||||
|
if not PUBLIC_ROOT.exists():
|
||||||
|
return
|
||||||
|
|
||||||
|
for file_path in sorted(PUBLIC_ROOT.iterdir()):
|
||||||
|
if not file_path.is_file():
|
||||||
|
continue
|
||||||
|
# Пропускаем служебные файлы (начинающиеся с точки)
|
||||||
|
if file_path.name.startswith('.'):
|
||||||
|
continue
|
||||||
|
yield file_path.name
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path(ADMIN_URL, admin.site.urls),
|
path(ADMIN_URL, admin.site.urls),
|
||||||
|
|
||||||
@@ -111,6 +161,20 @@ urlpatterns = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# Динамическая генерация URL patterns для всех файлов в корне public/
|
||||||
|
# (кроме служебных файлов, начинающихся с точки)
|
||||||
|
# В DEV режиме и при ALLOW_MEDIA_SERVE=True отдаются через Django,
|
||||||
|
# в PRODUCTION это должно обслуживать Nginx/WhiteNoise
|
||||||
|
PUBLIC_ROOT_URLPATTERNS = [
|
||||||
|
path(filename, _serve_public_root_file, {'path': filename})
|
||||||
|
for filename in _iter_public_root_files()
|
||||||
|
]
|
||||||
|
|
||||||
|
# Добавляем static файлы для корня ДО основных URL patterns
|
||||||
|
# (чтобы отдавать файлы быстро и не проверять остальные рулы)
|
||||||
|
urlpatterns = [*PUBLIC_ROOT_URLPATTERNS, *urlpatterns]
|
||||||
|
|
||||||
|
|
||||||
# Для локального тестирования production конфига: отдача медиа через Django
|
# Для локального тестирования production конфига: отдача медиа через Django
|
||||||
# В реальном production медиа обслуживает Nginx!
|
# В реальном production медиа обслуживает Nginx!
|
||||||
import os
|
import os
|
||||||
@@ -128,6 +192,7 @@ if DEBUG or env.bool('ALLOW_MEDIA_SERVE', default=False):
|
|||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
# --- страничка для тестирования верстки текста в блоге
|
# --- страничка для тестирования верстки текста в блоге
|
||||||
urlpatterns += [re_path(r'^blog/tmp[/*]$', service.tmp),]
|
urlpatterns += [re_path(r'^blog/tmp[/*]$', service.tmp),]
|
||||||
|
|||||||
Reference in New Issue
Block a user