8 Commits

Author SHA1 Message Date
60e0eead1c mod: Страницы обработки ошибок при старте переносятся на хост 2026-05-21 01:16:16 +03:00
e15017f3a6 add: Страницы обработки ошибок
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 42s
2026-05-21 00:42:54 +03:00
958f11f398 mod: Clean-param 2026-05-20 19:54:42 +03:00
3ab38b4885 mod: алиас sitemap sitemap.xml 2026-05-20 19:25:10 +03:00
4b038302c3 add: Добавлен алиас /sitemap.xml -> /media/_serv_sitemap/sitemap.xml в nginx конфиг
- Поисковики ищут sitemap.xml в корне сайта
- Nginx теперь проксирует /sitemap.xml на /media/_serv_sitemap/sitemap.xml
- Установлен кеш на 1 день (чаще обновляется, чем картинки)
2026-05-20 18:38:02 +03:00
3c10b490b3 minor: Улучшены комментарии nginx конфига и исправлен sed паттерн для корректной замены путей
- Уточнены комментарии про архитектуру (медиа, ошибки, sitemap через Django)
- Исправлен sed паттерн: было '/home/user/app/oknardia-site' -> '/home/user/path-to-oknardia-app'
- Улучшен www редирект для правильной подстановки домена
- Убраны дублирующиеся комментарии про требуемую замену
2026-05-20 18:35:07 +03:00
2d7e0813d6 minor: Финализация production конфигурации
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 3m44s
2026-05-20 14:29:39 +03:00
5678624608 add: Production docker-compose.yml с nginx и watchtower + инструкция деплоя
Some checks failed
Build and Push Docker Image / build-and-push (push) Has been cancelled
2026-05-20 13:36:33 +03:00
18 changed files with 815 additions and 34 deletions

View File

@@ -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 мог проверять их свежесть и скачивать для обновления.
# Получить эти данные можно в настройках вашего репозитория, например: # Получить эти данные можно в настройках вашего репозитория, например:

242
PRODUCTION_DEPLOY.md Normal file
View File

@@ -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 дней
```

View File

@@ -1,27 +1,20 @@
# config/nginx/oknardia-app--external-nginx.conf # config/nginx/oknardia-app--external-nginx.conf
# ============================================================================== # ==============================================================================
# ЭТАЛОННЫЙ КОНФИГУРАЦИОННЫЙ ФАЙЛ NGINX (Reverse Proxy для Docker) # КОНФИГУРАЦИОННЫЙ ФАЙЛ NGINX (Reverse Proxy для Docker + Production)
# ============================================================================== # ==============================================================================
# #
# ВНИМАНИЕ: # ИНФОРМАЦИЯ:
# Этот файл является шаблоном. При первом деплое он копируется # 1. Этот файл используется как шаблон при деплое
# в `/home/user/path-to-oknardia-app/config/nginx/oknardia-app--external-nginx.conf`, # 2. При первом деплое пути `/home/user/path-to-oknardia-app` заменяются на реальный путь через sed
# а затем (уже на хосте, руками) через силинк в `/etc/nginx/sites-available/` и активируется. # 3. Сгенерированный конфиг скопируется в `/etc/nginx/sites-available/oknardia`
# При последующих деплоях `oknardia-app--external-nginx.conf` НЕ ПЕРЕЗАПИСЫВАЕТСЯ, # 4. Последующие деплои ОБНОВЛЯЮТ этот файл автоматически (sed + копирование)
# чтобы не затереть SSL-сертификаты и ручные правки.
# #
# Если вы изменили этот файл в репозитории и хотите применить изменения на проде: # АРХИТЕКТУРА:
# вам нужно обновить файл в `/home/user/path-to-oknardia-app/config/nginx/oknardia-app--external-nginx.conf` вручную (diff + copy). # - Nginx (порты 80/443) <-> Gunicorn контейнер (localhost:8000)
# # - Медиа файлы отдаются из `/home/user/path-to-oknardia-app/media/` напрямую
# Так же (рядом) будет создан образец этого файла `nginx_oknardia.conf.example`, который будет обновляться при деплоях # - Ошибки 5xx берутся из `media/_error` (копируются контейнером при старте)
# из репозитория, чтобы вы могли видеть, что изменилось и при необходимости перенести эти изменения на прод. # - Sitemap.xml отдается через Django/WhiteNoise (в media/_serv_sitemap/)
# # - Static файлы (/static/) тоже отдаются через Django/WhiteNoise
# Предполагаемая структура на сервере:
# /home/user/path-to-oknardia-app/
# ├── docker-compose.yml
# ├── .env
# ├── media/ <-- Сюда Nginx смотрит напрямую (Docker volume)
# └── ...
# 1. Описываем, где живет наш Django в Docker # 1. Описываем, где живет наш Django в Docker
upstream oknardia-django { upstream oknardia-django {
@@ -70,22 +63,28 @@ server {
# --- МЕДИА ФАЙЛЫ (Загруженный контент) --- # --- МЕДИА ФАЙЛЫ (Загруженный контент) ---
# Nginx отдает их напрямую с диска хоста, не дергая Docker. # Nginx отдает их напрямую с диска хоста, не дергая Docker.
# Сюда входит: загруженные картинки, документы, свитмапы, ошибки 5xx
# Путь должен совпадать с тем, где лежит volume на хост-машине. # Путь должен совпадать с тем, где лежит volume на хост-машине.
# ВАЖНО: Убедитесь, что пользователь nginx (www-data) имеет права на чтение этой папки!
# ТРЕБУЕТСЯ ЗАМЕНА ПРИ ДЕПЛОЕ: /home/user/path-to-oknardia-app -> ваш реальный путь
location /media/ { location /media/ {
alias /home/user/path-to-oknardia-app/media/; alias /home/user/path-to-oknardia-app/media/;
expires 30d; # Кешируем картинки на месяц expires 30d; # Кешируем картинки на месяц
add_header Cache-Control "public, no-transform"; add_header Cache-Control "public, no-transform";
} }
# --- SITEMAP.XML ---
# Sitemap хранится в media/_serv_sitemap/ но должен быть доступен из корня
# для поисковиков (они ищут http://example.com/sitemap.xml)
location = /sitemap.xml {
alias /home/user/path-to-oknardia-app/media/_serv_sitemap/sitemap.xml;
expires 7d; # Кешируем на неделю (редко меняется)
add_header Cache-Control "public";
}
# --- СТРАНИЦЫ ОШИБОК (Custom Error Pages) --- # --- СТРАНИЦЫ ОШИБОК (Custom Error Pages) ---
# Если Django упал (502) или сработал тайм-аут (504), Nginx должен отдать статический HTML. # Если Django упал (502) или сработал тайм-аут (504), Nginx должен отдать статический HTML.
# Эти файлы должны лежать в папке, доступной Nginx (например, в `media/_error`). # Эти файлы копируются в `media/_error` при старте контейнера Docker.
# #
# ВАЖНО: # ВАЖНО: error_page директива перехватывает ошибки от апстрима (Gunicorn).
# 1. Файлы 50x.html (500, 502, 503, 504) копируются в `media/_error` при старте контейнера (см. docker-compose.prod.yml -> command).
# 2. error_page директива перехватывает ошибки от апстрима (Gunicorn).
error_page 500 /500.html; error_page 500 /500.html;
error_page 502 /502.html; error_page 502 /502.html;
error_page 503 /503.html; error_page 503 /503.html;
@@ -96,8 +95,6 @@ server {
location = /503.html { root /home/user/path-to-oknardia-app/media/_error; internal; } location = /503.html { root /home/user/path-to-oknardia-app/media/_error; internal; }
location = /504.html { root /home/user/path-to-oknardia-app/media/_error; internal; } location = /504.html { root /home/user/path-to-oknardia-app/media/_error; internal; }
# 404 (и другие) тоже нужно кастомизировать... обычно Django сам отдает 404.
# Но, например, Nginx отдаст 404 при ошике доступа к media-файлам (они храняться на хосте, а не в контейнере)
error_page 400 /400.html; error_page 400 /400.html;
error_page 401 /401.html; error_page 401 /401.html;
error_page 403 /403.html; error_page 403 /403.html;
@@ -112,7 +109,6 @@ server {
location = /413.html { root /home/user/path-to-oknardia-app/media/_error; internal; } location = /413.html { root /home/user/path-to-oknardia-app/media/_error; internal; }
location = /429.html { root /home/user/path-to-oknardia-app/media/_error; internal; } location = /429.html { root /home/user/path-to-oknardia-app/media/_error; internal; }
# Указываем единую страницу (на реконструкции) для всех прочих ошибок
error_page 405 406 407 408 409 410 411 412 414 415 416 417 418 421 422 423 424 425 426 428 431 451 /under_reconstruction.html; error_page 405 406 407 408 409 410 411 412 414 415 416 417 418 421 422 423 424 425 426 428 431 451 /under_reconstruction.html;
location = /under_reconstruction.html { root /home/user/path-to-oknardia-app/media/_error; internal; } location = /under_reconstruction.html { root /home/user/path-to-oknardia-app/media/_error; internal; }
@@ -141,10 +137,11 @@ server {
} }
} }
# 3. Редирект с www на без-www (SEO best practice) # 3. Редирект с www на основной домен (SEO best practice)
server { server {
server_name www.tmp.oknardia.ru; server_name www.tmp.oknardia.ru;
listen 80; listen 80;
return 301 $scheme://oknardia.ru$request_uri; # Всегда редиректим на основной домен listen [::]:80;
return 301 $scheme://tmp.oknardia.ru$request_uri;
} }

130
docker-compose.prod.yml Normal file
View File

@@ -0,0 +1,130 @@
# ==============================================================================
# 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/path-to-oknardia-app|$${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 &&
ERROR_DIR=/home/app/web/public/media/_error &&
mkdir -p "$$ERROR_DIR" &&
for code in 400 401 403 404 413 429 500 502 503 504; do
cp /home/app/oknardia/templates/error/$${code}.html "$$ERROR_DIR/$${code}.html";
done &&
cp /home/app/oknardia/templates/error/under_reconstruction.html "$$ERROR_DIR/under_reconstruction.html" &&
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"

View File

@@ -1,12 +1,12 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""oknardia Конфигурация URL""" """oknardia Конфигурация URL"""
from django.contrib import admin from django.contrib import admin
from django.urls import include, path, re_path from django.urls import include, re_path, path
from django.conf.urls.static import static
from django.http import FileResponse from django.http import FileResponse
from pathlib import Path from pathlib import Path
import environ import environ
import mimetypes import mimetypes
import os
# Инициализируем env # Инициализируем env
env = environ.Env() env = environ.Env()
@@ -174,10 +174,13 @@ PUBLIC_ROOT_URLPATTERNS = [
# (чтобы отдавать файлы быстро и не проверять остальные рулы) # (чтобы отдавать файлы быстро и не проверять остальные рулы)
urlpatterns = [*PUBLIC_ROOT_URLPATTERNS, *urlpatterns] urlpatterns = [*PUBLIC_ROOT_URLPATTERNS, *urlpatterns]
handler404 = 'web.views.handler404'
handler400 = 'web.views.handler400'
handler403 = 'web.views.handler403'
handler500 = 'web.views.handler500'
# Для локального тестирования production конфига: отдача медиа через Django # Для локального тестирования production конфига: отдача медиа через Django
# В реальном production медиа обслуживает Nginx! # В реальном production медиа обслуживает Nginx!
import os
if DEBUG or env.bool('ALLOW_MEDIA_SERVE', default=False): if DEBUG or env.bool('ALLOW_MEDIA_SERVE', default=False):
from django.views.static import serve as serve_static from django.views.static import serve as serve_static
# Проверяем что директория медиа существует # Проверяем что директория медиа существует
@@ -196,6 +199,13 @@ 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),]
# --- странички для тестирования страниц с кодами ошибок
urlpatterns += [
re_path(r'^400$', views.handler400),
re_path(r'^403$', views.handler403),
re_path(r'^404$', views.handler404),
re_path(r'^500$', views.handler500),
]
# ___ ____ _ _____ _ _ _____ _ # ___ ____ _ _____ _ _ _____ _
# | | | | \ ___| |_ _ _ ___ |_ _|___ ___| | |_ ___ ___ | _ |___ ___ ___| | # | | | | \ ___| |_ _ _ ___ |_ _|___ ___| | |_ ___ ___ | _ |___ ___ ___| |
# |_ | | | | -_| . | | | . | | | | . | . | | . | .'| _| | __| .'| | -_| | # |_ | | | | -_| . | | | . | | | | . | . | | . | .'| _| | __| .'| | -_| |

View File

@@ -0,0 +1,31 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>400 — Некорректный запрос</title>
<!-- Аналитика: Google Analytics 4, Yandex.Metrika, Top.Mail.Ru -->
<script src="/static/js/analytics.js" type="text/javascript"></script>
<style>
.container::before {
content: "";
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
background-image: url('/media/img_seria/1LG-504D12_2.jpg');
opacity: 0.6; /* Полупрозрачность только для картинки */
z-index: -1; /* Уводит картинку под текст */
background-position: 18% 15%;
background-repeat: no-repeat;
background-size: 200px auto;
}
</style>
</head>
<body>
<div class="container" style="margin: 10% 0 0 20%;">
<h1 style="font-size: 120px; margin: 0; color: #ccc; background-image: url('/static/img/oknardia_logo.gif'); background-position: left 65%; background-repeat: no-repeat; background-size: 400px auto;"><i style="padding-left: 420px">400</i></h1>
<h2 style="color: #333; font-size: 40px;">Некорректный запрос</h2>
<h4 style="color: #666; margin: 0 0 0 30px; font-size: 20px;">Сервер не может обработать ваш запрос. Пожалуйста, проверьте корректность данных.</h4>
<p style="margin-top: 55px;"><a href="/">Вернуться на главную oknardia.ru</a></p>
</div>
</body>
</html>

View File

@@ -0,0 +1,29 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>401 — Требуется авторизация</title>
<style>
.container::before {
content: "";
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
background-image: url('/media/img_seria/1LG-504D12_2.jpg');
opacity: 0.6;
z-index: -1;
background-position: 18% 15%;
background-repeat: no-repeat;
background-size: 200px auto;
}
</style>
</head>
<body>
<div class="container" style="margin: 10% 0 0 20%;">
<h1 style="font-size: 120px; margin: 0; color: #ccc; background-image: url('/media/img_seria/1LG-504D12_2.jpg'); background-position: left 65%; background-repeat: no-repeat; background-size: 400px auto;"><i style="padding-left: 420px">401</i></h1>
<h2 style="color: #333; font-size: 40px;">Требуется авторизация</h2>
<h4 style="color: #666; margin: 0 0 0 30px; font-size: 20px;">Для доступа к этому ресурсу требуется авторизация.</h4>
<p style="margin-top: 55px;"><a href="/login-logout">Войти в систему</a> или <a href="/">вернуться на главную</a></p>
</div>
</body>
</html>

View File

@@ -0,0 +1,32 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>403 — Доступ запрещён</title>
<!-- Аналитика: Google Analytics 4, Yandex.Metrika, Top.Mail.Ru -->
<script src="/static/js/analytics.js" type="text/javascript"></script>
<style>
.container::before {
content: "";
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
background-image: url('/media/img_seria/1LG-504D12_2.jpg');
opacity: 0.6; /* Полупрозрачность только для картинки */
z-index: -1; /* Уводит картинку под текст */
background-position: 18% 15%;
background-repeat: no-repeat;
background-size: 200px auto;
}
</style>
</head>
<body>
<div class="container" style="margin: 10% 0 0 20%;">
<h1 style="font-size: 120px; margin: 0; color: #ccc; background-image: url('/static/img/oknardia_logo.gif'); background-position: left 65%; background-repeat: no-repeat; background-size: 400px auto;"><i style="padding-left: 420px">403</i></h1>
<h2 style="color: #333; font-size: 40px;">Доступ запрещён</h2>
<h4 style="color: #666; margin: 0 0 0 30px; font-size: 20px;">У вас нет прав для доступа к этому ресурсу. Если вы считаете это ошибкой, свяжитесь с администратором.</h4>
<p style="margin-top: 55px;"><a href="/">Вернуться на главную oknardia.ru</a></p>
</div>
</body>
</html>

View File

@@ -0,0 +1,31 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>404 — Страница не найдена</title>
<!-- Аналитика: Google Analytics 4, Yandex.Metrika, Top.Mail.Ru -->
<script src="/static/js/analytics.js" type="text/javascript"></script>
<style>
.container::before {
content: "";
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
background-image: url('/media/img_seria/1LG-504D12_2.jpg');
opacity: 0.6; /* Полупрозрачность только для картинки */
z-index: -1; /* Уводит картинку под текст */
background-position: 18% 15%;
background-repeat: no-repeat;
background-size: 200px auto;
}
</style>
</head>
<body>
<div class="container" style="margin: 10% 0 0 20%;">
<h1 style="font-size: 120px; margin: 0; color: #ccc; background-image: url('/static/img/oknardia_logo.gif'); background-position: left 65%; background-repeat: no-repeat; background-size: 400px auto;"><i style="padding-left: 420px">404</i></h1>
<h2 style="color: #333; font-size: 40px;">Страница не найдена</h2>
<h4 style="color: #666; margin: 0 0 0 30px; font-size: 20px;">К сожалению, запрашиваемая страница не существует или была удалена.</h4>
<p style="margin-top: 55px;"><a href="/">Вернуться на главную oknardia.ru</a></p>
</div>
</body>
</html>

View File

@@ -0,0 +1,29 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>413 — Файл слишком большой</title>
<style>
.container::before {
content: "";
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
background-image: url('/media/img_seria/1LG-504D12_2.jpg');
opacity: 0.6;
z-index: -1;
background-position: 18% 15%;
background-repeat: no-repeat;
background-size: 200px auto;
}
</style>
</head>
<body>
<div class="container" style="margin: 10% 0 0 20%;">
<h1 style="font-size: 120px; margin: 0; color: #ccc; background-image: url('/media/img_seria/1LG-504D12_2.jpg'); background-position: left 65%; background-repeat: no-repeat; background-size: 400px auto;"><i style="padding-left: 420px">413</i></h1>
<h2 style="color: #333; font-size: 40px;">Файл слишком большой</h2>
<h4 style="color: #666; margin: 0 0 0 30px; font-size: 20px;">Размер загружаемого файла превышает допустимый лимит (максимум 10 МБ).</h4>
<p style="margin-top: 55px;"><a href="/">Вернуться на главную oknardia.ru</a></p>
</div>
</body>
</html>

View File

@@ -0,0 +1,29 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>429 — Слишком много запросов</title>
<style>
.container::before {
content: "";
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
background-image: url('/media/img_seria/1LG-504D12_2.jpg');
opacity: 0.6;
z-index: -1;
background-position: 18% 15%;
background-repeat: no-repeat;
background-size: 200px auto;
}
</style>
</head>
<body>
<div class="container" style="margin: 10% 0 0 20%;">
<h1 style="font-size: 120px; margin: 0; color: #ccc; background-image: url('/media/img_seria/1LG-504D12_2.jpg'); background-position: left 65%; background-repeat: no-repeat; background-size: 400px auto;"><i style="padding-left: 420px">429</i></h1>
<h2 style="color: #333; font-size: 40px;">Слишком много запросов</h2>
<h4 style="color: #666; margin: 0 0 0 30px; font-size: 20px;">Вы отправляете слишком много запросов. Пожалуйста, немного подождите и попробуйте снова.</h4>
<p style="margin-top: 55px;"><a href="/">Вернуться на главную oknardia.ru</a></p>
</div>
</body>
</html>

View File

@@ -0,0 +1,31 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>500 — Внутренняя ошибка сервера</title>
<!-- Аналитика: Google Analytics 4, Yandex.Metrika, Top.Mail.Ru -->
<script src="/static/js/analytics.js" type="text/javascript"></script>
<style>
.container::before {
content: "";
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
background-image: url('/media/img_seria/1LG-504D12_2.jpg');
opacity: 0.6; /* Полупрозрачность только для картинки */
z-index: -1; /* Уводит картинку под текст */
background-position: 18% 15%;
background-repeat: no-repeat;
background-size: 200px auto;
}
</style>
</head>
<body>
<div class="container" style="margin: 10% 0 0 20%;">
<h1 style="font-size: 120px; margin: 0; color: #ccc; background-image: url('/media/img_seria/1LG-504D12_2.jpg'); background-position: left 65%; background-repeat: no-repeat; background-size: 400px auto;"><i style="padding-left: 420px">500</i></h1>
<h2 style="color: #333; font-size: 40px;">Внутренняя ошибка сервера</h2>
<h4 style="color: #666; margin: 0 0 0 30px; font-size: 20px;">На нашем сервере произошла непредвиденная ошибка. Команда разработчиков уже работает над решением проблемы.</h4>
<p style="color: #999; margin: 20px 0 0 30px; font-size: 14px;">Если проблема сохраняется, пожалуйста, свяжитесь с администратором: <a href="mailto:info@oknardia.ru">info@oknardia.ru</a></p>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,29 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>502 — Плохой шлюз</title>
<style>
.container::before {
content: "";
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
background-image: url('/media/img_seria/1LG-504D12_2.jpg');
opacity: 0.6;
z-index: -1;
background-position: 18% 15%;
background-repeat: no-repeat;
background-size: 200px auto;
}
</style>
</head>
<body>
<div class="container" style="margin: 10% 0 0 20%;">
<h1 style="font-size: 120px; margin: 0; color: #ccc; background-image: url('/media/img_seria/1LG-504D12_2.jpg'); background-position: left 65%; background-repeat: no-repeat; background-size: 400px auto;"><i style="padding-left: 420px">502</i></h1>
<h2 style="color: #333; font-size: 40px;">Плохой шлюз</h2>
<h4 style="color: #666; margin: 0 0 0 30px; font-size: 20px;">Сервер приложения временно недоступен. Мы работаем над восстановлением работоспособности.</h4>
<p style="color: #999; margin: 20px 0 0 30px; font-size: 14px;">Пожалуйста, попробуйте перезагрузить страницу через несколько минут. Если проблема сохраняется, свяжитесь с нами: <a href="mailto:info@oknardia.ru">info@oknardia.ru</a></p>
</div>
</body>
</html>

View File

@@ -0,0 +1,29 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>503 — Сервис недоступен</title>
<style>
.container::before {
content: "";
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
background-image: url('/media/img_seria/1LG-504D12_2.jpg');
opacity: 0.6;
z-index: -1;
background-position: 18% 15%;
background-repeat: no-repeat;
background-size: 200px auto;
}
</style>
</head>
<body>
<div class="container" style="margin: 10% 0 0 20%;">
<h1 style="font-size: 120px; margin: 0; color: #ccc; background-image: url('/media/img_seria/1LG-504D12_2.jpg'); background-position: left 65%; background-repeat: no-repeat; background-size: 400px auto;"><i style="padding-left: 420px">503</i></h1>
<h2 style="color: #333; font-size: 40px;">Сервис недоступен</h2>
<h4 style="color: #666; margin: 0 0 0 30px; font-size: 20px;">Сайт временно недоступен для плановых работ. Мы скоро вернёмся!</h4>
<p style="color: #999; margin: 20px 0 0 30px; font-size: 14px;">Информация об обслуживании обновляется автоматически. Спасибо за терпение!</p>
</div>
</body>
</html>

View File

@@ -0,0 +1,29 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>504 — Тайм-аут шлюза</title>
<style>
.container::before {
content: "";
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
background-image: url('/media/img_seria/1LG-504D12_2.jpg');
opacity: 0.6;
z-index: -1;
background-position: 18% 15%;
background-repeat: no-repeat;
background-size: 200px auto;
}
</style>
</head>
<body>
<div class="container" style="margin: 10% 0 0 20%;">
<h1 style="font-size: 120px; margin: 0; color: #ccc; background-image: url('/media/img_seria/1LG-504D12_2.jpg'); background-position: left 65%; background-repeat: no-repeat; background-size: 400px auto;"><i style="padding-left: 420px">504</i></h1>
<h2 style="color: #333; font-size: 40px;">Тайм-аут шлюза</h2>
<h4 style="color: #666; margin: 0 0 0 30px; font-size: 20px;">Сервер приложения слишком долго отвечает на запрос. Это может быть перегрузка или технический сбой.</h4>
<p style="color: #999; margin: 20px 0 0 30px; font-size: 14px;">Попробуйте перезагрузить страницу через несколько минут. Если проблема повторится, свяжитесь с поддержкой: <a href="mailto:info@oknardia.ru">info@oknardia.ru</a></p>
</div>
</body>
</html>

View File

@@ -0,0 +1,29 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>Технические работы</title>
<style>
.container::before {
content: "";
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
background-image: url('/media/img_seria/1LG-504D12_2.jpg');
opacity: 0.6;
z-index: -1;
background-position: 18% 15%;
background-repeat: no-repeat;
background-size: 200px auto;
}
</style>
</head>
<body>
<div class="container" style="margin: 10% 0 0 20%;">
<h1 style="font-size: 80px; margin: 0; color: #ccc; background-image: url('/media/img_seria/1LG-504D12_2.jpg'); background-position: left 65%; background-repeat: no-repeat; background-size: 400px auto;"><i style="padding-left: 420px"></i></h1>
<h2 style="color: #333; font-size: 40px;">Технические работы</h2>
<h4 style="color: #666; margin: 0 0 0 30px; font-size: 20px;">Приносим извинения. В данный момент сайт проходит техническое обслуживание. Мы скоро вернёмся!</h4>
<p style="margin-top: 55px;"><a href="/">Вернуться на главную oknardia.ru</a></p>
</div>
</body>
</html>

View File

@@ -242,3 +242,66 @@ def get_address(request: HttpRequest) -> HttpResponse:
'ticks': float(time.perf_counter() - time_start), 'ticks': float(time.perf_counter() - time_start),
}) })
return render(request, "popup/popup_show_apartment_variants.html", to_template) return render(request, "popup/popup_show_apartment_variants.html", to_template)
# ============================================================================
# ОБРАБОТЧИКИ ОШИБОК ДЛЯ ТЕСТИРОВАНИЯ В DEBUG РЕЖИМЕ
# ============================================================================
# Используется в urls.py при DEBUG=True для тестирования верстки страниц ошибок.
# Позволяет визуально проверить, как выглядят страницы 400, 403, 404 и 500
# без необходимости искусственно вызывать реальные ошибки.
def handler400(request: HttpRequest, exception=None) -> HttpResponse:
"""Отображает страницу ошибки 400 (Bad Request).
Используется только в DEBUG режиме для тестирования верстки.
:param request: входящий http-запрос
:param exception: исключение (если есть)
:return response: исходящий http-ответ с кодом 400
"""
response = render(request, "error/400.html", {})
response.status_code = 400
return response
def handler403(request: HttpRequest, exception=None) -> HttpResponse:
"""Отображает страницу ошибки 403 (Forbidden).
Используется только в DEBUG режиме для тестирования верстки.
:param request: входящий http-запрос
:param exception: исключение (если есть)
:return response: исходящий http-ответ с кодом 403
"""
response = render(request, "error/403.html", {})
response.status_code = 403
return response
def handler404(request: HttpRequest, exception=None) -> HttpResponse:
"""Отображает страницу ошибки 404 (Not Found).
Используется только в DEBUG режиме для тестирования верстки.
:param request: входящий http-запрос
:param exception: исключение (если есть)
:return response: исходящий http-ответ с кодом 404
"""
response = render(request, "error/404.html", {})
response.status_code = 404
return response
def handler500(request: HttpRequest) -> HttpResponse:
"""Отображает страницу ошибки 500 (Internal Server Error).
Используется только в DEBUG режиме для тестирования верстки.
:param request: входящий http-запрос
:return response: исходящий http-ответ с кодом 500
"""
response = render(request, "error/500.html", {})
response.status_code = 500
return response

View File

@@ -1,5 +1,5 @@
# Маркетплейс-агрегатор цен на установку пластиковых и деревянных окон — ОКНАРДИЯ # Маркетплейс-агрегатор цен на установку пластиковых и деревянных окон — ОКНАРДИЯ
# robots.txt последняя версия: 2026-05-15 # robots.txt последняя версия: 2026-05-20
User-Agent: * User-Agent: *
Allow: / Allow: /
@@ -10,6 +10,9 @@ Disallow: /*?*token*
Disallow: /*.json$ Disallow: /*.json$
Disallow: /.*\.(js|css)$ # CSS и JS обслуживаются отдельно через CDN Disallow: /.*\.(js|css)$ # CSS и JS обслуживаются отдельно через CDN
# Параметры, которые не влияют на смысловое содержимое
Clean-param: page-back /
# Быстрые краулеры # Быстрые краулеры
User-agent: YandexBot User-agent: YandexBot
Crawl-delay: 15 Crawl-delay: 15