197 Commits

Author SHA1 Message Date
erjemin e15017f3a6 add: Страницы обработки ошибок
Build and Push Docker Image / build-and-push (push) Successful in 42s
2026-05-21 00:42:54 +03:00
erjemin 958f11f398 mod: Clean-param 2026-05-20 19:54:42 +03:00
erjemin 3ab38b4885 mod: алиас sitemap sitemap.xml 2026-05-20 19:25:10 +03:00
erjemin 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
erjemin 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
erjemin 2d7e0813d6 minor: Финализация production конфигурации
Build and Push Docker Image / build-and-push (push) Successful in 3m44s
2026-05-20 14:29:39 +03:00
erjemin 5678624608 add: Production docker-compose.yml с nginx и watchtower + инструкция деплоя
Build and Push Docker Image / build-and-push (push) Has been cancelled
2026-05-20 13:36:33 +03:00
erjemin cc739c5e07 mod: таймауты SQLite 2026-05-20 12:59:58 +03:00
erjemin 54ed78185c fix: корректная отдача статики из корня каталога public 2026-05-20 00:13:14 +03:00
erjemin b376fdcaf9 mod: пересчет рейтингов при запуске контейнера 2026-05-19 23:03:05 +03:00
erjemin a3bc67613f fix: удалены чувствительные данные (кеш-файлы) из истории git + восстановлен .gitignore 2026-05-19 22:52:45 +03:00
erjemin cb9dab9e56 fix: пререндер-шаблоны работали некорректно в prod 2026-05-19 22:45:56 +03:00
erjemin 9575e0e0d9 add: пред-прод контейнер (рабочий) 2026-05-19 21:07:43 +03:00
erjemin 2396387883 add: пред-прод контейнер (рабочий) 2026-05-19 13:31:15 +03:00
erjemin 8fe641c8f4 add: пред-прод контейнер 2026-05-18 19:18:03 +03:00
erjemin 58e17d7d3b add: ключи развертывания 2026-05-18 16:35:30 +03:00
erjemin 2dee8b77cb mod: новый набор favicon 2026-05-18 16:34:43 +03:00
erjemin 03ed9d24f9 minor: комментарии 2026-05-18 15:23:03 +03:00
erjemin 71059bdae6 mod: Все для контейнера в dev-режиме. 2026-05-18 15:17:35 +03:00
erjemin 50b5ee4bdf add: .dockerignore (исключить из контейнера лишние файлы). 2026-05-17 01:23:42 +03:00
erjemin 98912808a1 del: конфиг uWSGI в контейнере не нужен. 2026-05-17 01:16:02 +03:00
erjemin 968c4219aa Merge: реворк проекта 2026-05 2026-05-16 23:35:55 +03:00
erjemin 7f81a79862 minor: + 2026-05-15 13:59:46 +03:00
erjemin ec82d4d36f mod: про проект и планы... 2026-05-15 13:59:07 +03:00
erjemin 468d8e8fc4 mod: дроп-даун логин/логаут вынесен во внешний auth.js 2026-05-15 13:20:48 +03:00
erjemin 71d0115046 mod: счетчики вынесены во внешний analytics.js 2026-05-15 13:08:20 +03:00
erjemin 1e7c18a507 mod: файлы для поисковиков и ИИ 2026-05-15 10:29:07 +03:00
erjemin 394f92ebce mod: добавлены некоторые страницы. 2026-05-15 09:48:21 +03:00
erjemin ff250b10a5 mod: pdf 2026-05-15 08:57:06 +03:00
erjemin cca42ac39c mod: нужные картинки в media-файлах 2026-05-15 08:56:14 +03:00
erjemin 369bf9aca8 add: SEO и JSON-LD + хлебные крошки. 2026-05-15 08:35:53 +03:00
erjemin 74411a7add add: SEO и JSON-LD + хлебные крошки. 2026-05-15 08:08:37 +03:00
erjemin b1a9b2649f add: FAQ + SEO и JSON-LD + хлебные крошки. 2026-05-15 07:42:10 +03:00
erjemin 09f9e7ec6a mod: SEO и JSON-LD + добавлены хлебные крошки. 2026-05-14 21:52:14 +03:00
erjemin a00c526c87 mod: картинки 2026-05-14 21:15:50 +03:00
erjemin 98b0b79c87 mod: картинки 2026-05-14 21:15:34 +03:00
erjemin 12ba0fe92d del: служебный интерфейс удалён из проекта 2026-05-14 21:12:14 +03:00
erjemin ce63d7d42b mod: картинки рекомендованных схем открывания 2026-05-14 20:42:37 +03:00
erjemin 6c59c4e0c1 add: тесты 2026-05-14 19:45:33 +03:00
erjemin 370a5ed359 minor: comment 2026-05-14 19:40:01 +03:00
erjemin 7c830530ef add: Django Management Command для генерации JavaScript-файлы отрисовки карт с геоданными ВСЕХ зданий типовых серий 2026-05-14 19:36:52 +03:00
erjemin c96d85da44 add: библиотека rjsmin для обсфуркации 2026-05-14 19:22:09 +03:00
erjemin b27d6f0546 add: Django Management Command для "склейки" корневых серий типового строительства 2026-05-14 00:27:17 +03:00
erjemin 998e6caf8f mod: raw sql --> ORM 2026-05-13 18:33:53 +03:00
erjemin 81680b75ab mod: В перерасчет рейтинга добавлен учет оконных наборов 2026-05-13 17:01:39 +03:00
erjemin d5c1024124 del: Перерасчет рейтинга удален из сервисного раздела 2026-05-13 00:50:53 +03:00
erjemin bcebc20943 add: Перерасчет рейтинга перенесен в Django Management Command 2026-05-13 00:50:12 +03:00
erjemin bfd785d78e minor: - 2026-05-12 20:09:10 +03:00
erjemin 7747155595 mod: URL для админки в .env 2026-05-12 19:45:49 +03:00
erjemin ae39f9462a mod: описание и JSON-LD 2026-05-12 10:50:06 +03:00
erjemin 5cd2acc84c add: недостающие картинки схемы открывания 2026-05-11 20:15:18 +03:00
erjemin d028f85356 mod: улучшена производительность +регистронезависимость 2026-05-11 20:12:48 +03:00
erjemin a5736e19c7 minor: пейджинация обернута в nav 2026-05-11 19:44:32 +03:00
erjemin bb08213153 mod: seo-теги и JSON-LD 2026-05-11 19:43:03 +03:00
erjemin c4fb0a29b7 minor: - 2026-05-11 19:26:06 +03:00
erjemin 0f30dca006 fix: очищаем заголовок перед добавлением в ключевые слова 2026-05-11 19:25:21 +03:00
erjemin 0efc12fe7a mod: SEO-теги и JSON-LD (chema.org) 2026-05-11 18:29:10 +03:00
erjemin c62fbceaaf fix: тизер для описания не должен транслитирироваться 2026-05-11 17:36:37 +03:00
erjemin 37f0535ddf minor: ненужный import 2026-05-11 15:13:04 +03:00
erjemin db6cd46915 minor: ненужный import 2026-05-11 15:10:35 +03:00
erjemin b719b58c7b minor: ненужный import 2026-05-11 15:09:30 +03:00
erjemin 9f4b86a418 add: Custom Management Command для автозаполнения SEO-атрибутов блога 2026-05-11 00:45:22 +03:00
erjemin bcc8c56970 add: автозаполнение SEO-полей для блога 2026-05-11 00:18:36 +03:00
erjemin dc379fa8da mod: унифицированная slug-офикация 2026-05-10 23:34:00 +03:00
erjemin 1b2666f3d7 add: SEO-поля для блогов и миграция 2026-05-10 18:53:34 +03:00
erjemin b615d2caa4 fix: Показывать логин когда есть логин 2026-05-10 14:53:45 +03:00
erjemin 71c8bbc9ea del: лишний скрипт 2026-05-10 14:53:15 +03:00
erjemin ece99a398e del: лишний скрипт 2026-05-09 21:53:49 +03:00
erjemin b74e70e4f7 mod: JS вынесен во внешний скрипт 2026-05-09 21:43:13 +03:00
erjemin 8a485b756b mod: данные о последних визитах пользователя полностью перенесены с сервера на клиент (в JS). Отключен из контекста всех шаблонов LAST_VISIT и связанные с ним функции в вьюшках 2026-05-09 21:21:20 +03:00
erjemin 4abd8a43f1 mod: блок "логин" подгружается только по клику на меню "Вход" 2026-05-09 15:05:03 +03:00
erjemin 5a507f3e17 mod: связность страница компании --> набор для сравнения 2026-05-09 14:55:26 +03:00
erjemin 8da90ad0fd mod: раздел сравнения оконных наборов. 2026-05-09 14:40:54 +03:00
erjemin 065c51681d add: раздел сравнения оконных наборов. 2026-05-08 18:04:51 +03:00
erjemin 6eb8d15ba8 mod: Изменение всех SEO-атрибутов (и добавлены хлебные крошки и LD-JSON) 2026-05-08 17:49:45 +03:00
erjemin 4d49e6775a fix: неправильное формирование default значений 2026-05-07 23:44:07 +03:00
erjemin 9b8d71ee71 mod: raw SQL на ORM, убран макаронный код и исправлена пара ошибок в сравнениях характеристик. 2026-05-07 23:20:59 +03:00
erjemin 096300406a fix: Не хватало серии дома в контекст шаблона + raw-SQL заменен на ORM 2026-05-07 22:52:04 +03:00
erjemin 9b4cf9de51 fix: убрал ссылку на текущую серию. 2026-05-05 19:08:41 +03:00
erjemin 90af1f2a9e mod: переделан raw на ORM и бурана дублирующая функция (список серий домов, она уже есть в web.catalog_series 2026-05-05 18:46:58 +03:00
erjemin 90ecfef39d minor: - 2026-05-05 18:18:56 +03:00
erjemin ef7e9b465f mod: изменение роутинга для страницы ценовых предложений (4) 2026-05-05 17:40:18 +03:00
erjemin fc5eae03c2 mod: изменение роутинга для страницы ценовых предложений (3) 2026-05-01 11:55:54 +03:00
erjemin fda5afa6e4 mod: изменение роутинга для страницы ценовых предложений (2) 2026-04-30 23:42:43 +03:00
erjemin f45165822b mod: изменение роутинга для страницы ценовых предложений 2026-04-30 23:20:34 +03:00
erjemin 004d7392ad add: "хлебные кроши" и schema.org LD-JSON для них 2026-04-28 11:05:09 +03:00
erjemin 0104db39bd mod: raw-SQL --> ORM для цен на наборы кокон в квартиру. SEO-атртибуты + добавлена schema.org 2026-04-27 20:42:00 +03:00
erjemin b1bbde2d59 minor: планы 2026-04-26 15:05:17 +03:00
erjemin 359664796d minor: Роадмап вынесен в отдельный файл. 2026-04-26 15:01:15 +03:00
erjemin 45ad5481aa mod: Рефакторинг страницы цен одного окна (вьюшки, шаблоны, тесты, новый canonical-роутинг) 2026-04-26 14:53:49 +03:00
erjemin e260ded477 mod: замена во всех прямых ссылках https://oknardia.ru на {{ request.scheme }}://{{ request.get_host }} 2026-04-25 15:24:54 +03:00
erjemin 4420bcd3d1 mod: Улучшены SEO-атрибуты, и добавлена разметка shema.org 2026-04-25 15:18:59 +03:00
erjemin 5e30d3f8a4 minor: - 2026-04-25 11:04:19 +03:00
erjemin 9fb4843f8f minor: даты "Y-m-d" 2026-04-25 08:12:41 +03:00
erjemin df74175504 mod: рефакторинг "каталога компаний" (вьюшки и шаблоны) 2026-04-25 08:06:15 +03:00
erjemin 1f03219372 del: это описание перенесено в MANAGEMENT_RUNBOOK.md 2026-04-24 17:33:45 +03:00
erjemin 002ed0f82a mod: рефакторинг "каталог типовых проёмов" 2026-04-24 17:31:56 +03:00
erjemin d654b73d65 add: __init__ 2026-04-24 17:27:53 +03:00
erjemin 80b9abfaef minor: - 2026-04-24 16:35:15 +03:00
erjemin 21656e63de minor: отключено "передергивание" uWSGI 2026-04-23 23:32:19 +03:00
erjemin ac68214297 add: management-команда regenerate_seria_prerender для оффлайн пересборки pre-render шаблонов серий; обновлены SETUP.md, README.md, MANAGEMENT_RUNBOOK.md 2026-04-22 01:24:03 +03:00
erjemin cf24fab53a fix: пре-рендер шаблоны не должны попадать в репозиторий 2026-04-22 00:03:06 +03:00
erjemin 91b4d635da mod: refactor catalog_seria_info and update README/settings 2026-04-21 21:31:17 +03:00
erjemin 4181f34bcb minor: fix: явное указание типа to_template 2026-04-21 01:05:11 +03:00
erjemin 43f63f4461 mod: refactor catalog_seria to ORM 2026-04-20 23:49:42 +03:00
erjemin 1afe77fde0 minor: изменения в замерах времени исполнения вьюшки 2026-04-20 23:23:05 +03:00
erjemin 6016247718 mod: split catalog modules and refactor profile manufacture view 2026-04-20 22:23:42 +03:00
erjemin 026a2a74ab fix: явное указание типов 2026-04-20 01:02:16 +03:00
erjemin 59d8ad1e4c minor: в отдельный catalog_profiles.py выделены все вьюхи для каталога профилей 2026-04-20 00:54:11 +03:00
erjemin 2778ccf45a mod: рефакторинг catalog_profile_model и обновление дорожной карты README 2026-04-20 00:43:27 +03:00
erjemin 5933fa46de minor: ticks стал видимым 2026-04-20 00:33:13 +03:00
erjemin de2d14332e mod: simplify catalog profile views 2026-04-19 23:43:13 +03:00
erjemin 25e148a21e mod: catalog_profile и документация 2026-04-18 15:39:39 +03:00
erjemin 7f07f82b11 add: SITEMAP_RUNBOOK.md; mod: README.md — обновлена документация проекта 2026-04-18 13:48:28 +03:00
erjemin 45f243b38b mod: rework sitemap generation and seo coverage 2026-04-18 13:28:15 +03:00
erjemin 15abbe7367 mod: migrate to poetry and add django debug toolbar for dev 2026-04-17 16:07:19 +03:00
erjemin 096aa2d78b mod: switch secrets config to .env 2026-04-17 15:18:05 +03:00
erjemin 2d7b6f86a4 minor: комментарии fix имя файла 2026-04-16 21:28:18 +03:00
erjemin 37fe09588b del: нет логам в репозитории 2026-04-16 21:20:24 +03:00
erjemin 2867ec485c mod: добавлен сервис пересчета рейтинга 2026-04-16 21:15:19 +03:00
erjemin 1f24b12754 mod: add favicon 2026-04-16 21:13:53 +03:00
erjemin e779b78707 minor: del print 2026-04-16 21:12:31 +03:00
erjemin 63cf85515b mod: switch dev to sqlite and add static fallback paths 2026-04-16 21:10:58 +03:00
erjemin f3a16788b0 minor: del print 2026-04-16 20:37:45 +03:00
erjemin b43f896c6a add: initial commit — Django 4.1 project (2022 version), docs, security templates 2026-04-16 15:55:21 +03:00
erjemin 817f63e3fb Static: недостающие файлы из error.log 2023-03-04 00:20:53 +03:00
erjemin 61f3ecee2a SEO: Title & Description 2023-03-04 00:19:41 +03:00
erjemin bf575e7bec SEO: Title & Description (каталог профилей) 2023-02-17 22:01:15 +03:00
erjemin f9085e08a1 SEO: Title & Description 2023-02-17 19:18:18 +03:00
erjemin afdf49e9eb minor: недостающая статика (png) 2023-02-17 19:12:02 +03:00
erjemin d14b83ae6f minor: недостающая статика (css, js) 2023-02-15 23:51:25 +03:00
erjemin 634d26fde7 minor: favicon.svg 2023-02-10 20:39:25 +03:00
erjemin ec8b9d5e86 minor: убрал отладочный print() 2023-01-29 23:01:26 +03:00
erjemin 96ed9a3178 minor: excel-шаблоны для загрузки и заполнения 2023-01-29 22:57:14 +03:00
erjemin cc192d1c8d minor: первый экран, изменен текст (SEO) 2023-01-29 18:20:48 +03:00
erjemin 4659b221c8 minor: кеш картинок и шаблонов (++) 2023-01-19 00:52:52 +03:00
erjemin 51bd05024c minor: кеш картинок и шаблонов (+) 2023-01-19 00:46:06 +03:00
erjemin cbb525141a minor: кеш картинок и шаблонов 2023-01-18 00:38:03 +03:00
erjemin 0b33c7454b minor: вот так работает в продашн 2023-01-18 00:15:58 +03:00
erjemin eb06fb64fe ini для uwsgi (tested) 2023-01-14 04:22:25 +03:00
erjemin ad505acacd конфиг для nginx (tested) 2023-01-14 04:21:41 +03:00
erjemin 590082704d обновленные пакеты окружения для dev на mac-m1 (python 3.10.9) 2023-01-14 04:04:08 +03:00
erjemin 72e2f3259f имя файла к touch-reload для девопа 2023-01-14 04:02:05 +03:00
erjemin 951c384a94 передергивает touch-reload при изменении кеш-шаблона 2023-01-14 03:49:55 +03:00
erjemin 83c0ea43e8 пакеты окружения (дев/прод) 2023-01-14 02:19:58 +03:00
erjemin df8109198b пакеты окружения (дев/прод) 2023-01-14 02:19:39 +03:00
erjemin 448ebaf2c9 доки по профилям монблан 2023-01-14 02:18:46 +03:00
erjemin c3f9a9caad minor (error -- был пустой slag для текущей серии) 2023-01-14 01:43:14 +03:00
erjemin c3b8309840 minor (без переменной template) 2023-01-14 01:17:59 +03:00
erjemin 4aff8efcfc minor (исправлены типы переменных для совместимости с продакшн) 2023-01-13 22:23:24 +03:00
erjemin c99f39a156 картинки для блога 2023-01-13 22:13:17 +03:00
erjemin 70651c9ada картинки к сериям домов 2023-01-13 22:08:21 +03:00
erjemin ea06b3e050 логотипы компаний из базы 2023-01-13 21:57:56 +03:00
erjemin e1b07467b4 touch-reload.txt 2023-01-13 19:46:51 +03:00
erjemin 8be7149af8 nginx conf-file for production 2023-01-13 19:18:04 +03:00
erjemin a269fa85a6 favicon & other static 2023-01-13 17:27:16 +03:00
erjemin dd5b6baf30 Служебное -- формирование sitemap-файлов 2023-01-10 01:21:28 +03:00
erjemin 6d8159b60d minor -- точные размеры в мм. в заголовке и крошках 2023-01-09 23:30:49 +03:00
erjemin 29d29401ba minor -- размеры и мм. тоже 2023-01-09 23:29:57 +03:00
erjemin 13f79f985c Служебное -- главные служебные (если есть и нет аутентификации) 2023-01-08 19:41:12 +03:00
erjemin a092551e60 Служебное -- главна и аутентификация 2023-01-08 19:36:48 +03:00
erjemin aaf7da88bc Служебное -- страница для тестирования верстки текста в блоге 2023-01-08 18:14:27 +03:00
erjemin 9cf9b9639b Переадресация для старых, внешних ссылок 2023-01-08 01:38:14 +03:00
erjemin f9c65c3ad6 Подгружаемый блок с детальным описанием состава рейтинга -- готово 2023-01-07 04:51:11 +03:00
erjemin e019d3f636 удалён лишний шаблон 2023-01-07 03:42:47 +03:00
erjemin 0d1017384c Вьюшка и шаблон: сравнение оконных наборов -- готово 2023-01-07 03:42:14 +03:00
erjemin 0d77db66d5 кеш картинки со схемами открывания 2022-12-31 13:54:15 +03:00
erjemin 62c983fedc перенос и переименование шаблонов 2022-12-31 05:13:12 +03:00
erjemin dc081fa9fd Вьюшка: подгружаемые фреймы с ценами одного типового окна -- готово 2022-12-31 05:01:39 +03:00
erjemin f892100cf8 minor 2022-12-31 04:44:38 +03:00
erjemin c958fec1b4 minor (переименование шаблонов) 2022-12-31 04:44:11 +03:00
erjemin 6d9de5f7f0 Вьюшка: Подгружаемых фреймов ценовых предложений -- minor 2022-12-31 04:36:40 +03:00
erjemin 6ff5e0848d Вьюшка: Подгружаемых фреймов ценовых предложений -- готово 2022-12-31 04:33:59 +03:00
erjemin e1360261d7 Вьюшка : Выдача предложений -- minor (убраны отладочные print) 2022-12-31 02:57:49 +03:00
erjemin df0717a765 Вьюшка и шаблоны: Выдача предложений -- готово 2022-12-31 02:55:09 +03:00
erjemin c317d499f6 Отдельный каталог для шаблонов выпадающей окон -- minor 2022-12-30 19:58:56 +03:00
erjemin f96bf35874 Выпадающее окно после ввода адреса -- готово 2022-12-30 19:52:42 +03:00
erjemin 01a93e8fea Кеш-картинки схем открывания (большие и маленькие) 2022-12-30 15:31:17 +03:00
erjemin 9a6eed3812 Ценовая выдача для одного окна 2022-12-30 15:30:05 +03:00
erjemin 8eddf9361b Оптимизация + на мини-картинках схем открывания рисуется спейсер и москитная сетка 2022-12-29 17:25:41 +03:00
erjemin e8b879ab0e Шаблон: "Каталог / Производители и поставщики окон / Компания" -- minor 2022-12-29 16:10:26 +03:00
erjemin b60ea568e5 Шаблон: "Каталог / Производители и поставщики окон / Компания" -- minor: seo, Yandex.map 2022-12-29 15:56:14 +03:00
erjemin 5cc3dae0a6 Вьюшка и шаблон: "Каталог / Производители и поставщики окон / Компания" -- готово 2022-12-29 15:46:16 +03:00
erjemin a0a3ff88ac Вьюшка: "Каталог / Производители и поставщики окон" -- minor 2022-12-29 00:18:53 +03:00
erjemin 64dfdf8245 Вьюшка и шаблон: "Каталог / Производители и поставщики окон" -- готово 2022-12-29 00:17:27 +03:00
erjemin e05ec47b48 Шаблон: "Каталог / Типовые серии зданий / Информация по серии" и "Статистика" -- minor seo 2022-12-27 15:19:08 +03:00
erjemin f95137a25c Шаблон и css: "Каталог / Оконные проёмы и балконные блоки" -- минор 2022-12-27 14:57:34 +03:00
erjemin 3df0f6271e avatar image (для блога и "о компании") 2022-12-27 14:17:04 +03:00
erjemin d82b2ffbd3 prerender small-image со схемами открывания (2022-12-27) 2022-12-27 14:15:16 +03:00
erjemin 8c31e7cc34 Вьюшка и шаблон: "Каталог / Оконные проёмы и балконные блоки" -- готово 2022-12-27 14:13:29 +03:00
erjemin ac7e164753 миграции 2022-12-26 18:30:26 +03:00
erjemin ce9fcc7511 Про кеширующие шаблоны 2022-12-26 18:30:09 +03:00
erjemin 6972f1f8da Вьюшка: "Информация по всем сериям домой" -- minor
перенесли одну формирования навигации по сериям
2022-12-26 18:28:52 +03:00
erjemin 35459ca45b prerender image со схемами открывания (2022-12-26 минор) 2022-12-26 18:13:57 +03:00
erjemin a3bdb57279 Шаблон: "Каталог / Типовые серии зданий / Информация по серии" -- minor (data, last modified, еxpires) 2022-12-26 18:04:35 +03:00
erjemin 84185bd2f0 prerender image со схамами открывания (2022-12-26) 2022-12-26 18:02:20 +03:00
erjemin 6347031a00 prerender image со схамами открывания (2022-12-26) 2022-12-26 17:42:59 +03:00
21 changed files with 917 additions and 55 deletions
+8
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
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 дней
```
+27 -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;
} }
+5 -1
View File
@@ -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. МОНТИРОВАНИЕ КОДА И ДАННЫХ
+130
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/user/path-to-oknardia-app/oknardia/templates/error/$${code}.html "$$ERROR_DIR/$${code}.html";
done &&
cp /home/user/path-to-oknardia-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"
+4 -1
View File
@@ -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.
+93 -18
View File
@@ -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, re_path, path
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
import os
# Инициализируем 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,9 +161,26 @@ 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]
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
# Проверяем что директория медиа существует # Проверяем что директория медиа существует
@@ -128,9 +195,17 @@ 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),
]
# ___ ____ _ _____ _ _ _____ _ # ___ ____ _ _____ _ _ _____ _
# | | | | \ ___| |_ _ _ ___ |_ _|___ ___| | |_ ___ ___ | _ |___ ___ ___| | # | | | | \ ___| |_ _ _ ___ |_ _|___ ___| | |_ ___ ___ | _ |___ ___ ___| |
# |_ | | | | -_| . | | | . | | | | . | . | | . | .'| _| | __| .'| | -_| | # |_ | | | | -_| . | | | . | | | | . | . | | . | .'| _| | __| .'| | -_| |
+31
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>
+29
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>
+32
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>
+31
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>
+29
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>
+29
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>
+31
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>
+29
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>
+29
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>
+29
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>
@@ -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>
@@ -1,4 +1,13 @@
### Каталог для пре-рендер шаблонов # Игнорируем все файлы в этой папке (генерируются динамически)
# Игнорируем все файлы в этом каталоге, так как они генерируются автоматически # Содержит кеш-файлы со статик-шаблонами:
# и не должны быть включены в систему контроля версий. # - *_id_static_flaps.html
*.* # - *_id_static_graph.html
# - *_id_static_map_stats.html
#
# Эти файлы генерируются при запуске management-команды:
# python manage.py regenerate_seria_prerender
#
# В продакшене они сохраняются на диск и включаются в шаблоны
# за счет системы двухуровневого кеширования.
*
!.gitignore
+63
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
+4 -1
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