25 Commits
main ... v0.0.1

Author SHA1 Message Date
6093dc2021 fin
All checks were successful
Build and Push PetClones-site / build-and-push (push) Successful in 42s
2026-04-01 17:42:21 +03:00
2a37e24242 mod: все переменные .env читаются из оружения.
All checks were successful
Build and Push PetClones-site / build-and-push (push) Successful in 1m4s
2026-04-01 16:58:26 +03:00
c529a74715 mod: БД теперь создаётся в /app/database/
All checks were successful
Build and Push PetClones-site / build-and-push (push) Successful in 46s
2026-04-01 16:04:26 +03:00
5f4769d10f mad: рабочий конфиг nginx (прокси) 2026-04-01 14:33:33 +03:00
261b72b04a del: иконка старая
All checks were successful
Build and Push PetClones-site / build-and-push (push) Successful in 36s
2026-04-01 02:27:46 +03:00
b128bbd79e feat: обновлены иконки и шаблон
- Добавлены новые иконки в public (apple-touch-icon, favicon.svg, favicon.ico, favicon-96x96)
- Обновлен base.jinja для подключения иконок из корня public
- Удалены старые иконки из staticfiles/img (теперь используются из public)
- Упрощены метаданные в шаблоне (удалены устаревшие мета-теги)
2026-04-01 02:26:27 +03:00
b97e82bcbb nginx: добавлены расширенное логирование для CrowdSec и rate-limiting для DDoS защиты
- Расширенное логирование с форматом 'main' (IP, время, метод, URL, статус)
- Буферизированные логи с 5-сек очисткой для оптимизации дискового I/O
- Rate-limiting: 100 req/s на IP, burst=200 для легитимного трафика
- Логирование статики и медиа включено для анализа попыток сканирования
- Отключен log_not_found для уменьшения размера логов (404 всё равно в error_log)
- Добавлено уточнение что WhiteNoise уже сжимает статику (gzip в nginx не нужен)
2026-04-01 02:12:33 +03:00
0b3eb517fe docs: добавлены SEO и LLM файлы для сайта (robots.txt, sitemap.xml, llms.md)
Добавлены три важных файла в папку public:
- robots.txt: инструкции для поисковых ботов
- sitemap.xml: карта сайта с оптимизированной частотой обновления
- llms.md: информация для LLM систем
2026-04-01 01:18:33 +03:00
711b34af3d refactor: использовать image из реестра вместо локальной сборки в production
- Заменен build: ... на image: git.cube2.ru/erjemin/2024-test-rosmorport:latest
- В production образ должен быть собран в CI/CD и загружен из реестра
- Watchtower будет автоматически обновлять контейнер при выходе нового image
- Это более правильный и безопасный подход для production
2026-04-01 01:00:23 +03:00
20d78194d2 fix: исправления для CI/CD сборки
All checks were successful
Build and Push PetClones-site / build-and-push (push) Successful in 1m14s
- Исправлен warning: FROM ... AS builder (заглавные буквы согласно Dockerfile best practices)
- Убран multiplatform (linux/arm64) и остановлены на linux/amd64 для надежности
  (arm64 добавим позже когда система будет стабильной)
- Увеличен timeout с 1800 до 3600 сек (30 до 60 минут) для надежности при медленном интернете
- Это должно решить проблему '499 Client Closed Request' при push в реестр Gitea
2026-04-01 00:54:07 +03:00
37719ed31e fix: разрешить копирование config/nginx в Docker (нужен для production)
Some checks failed
Build and Push PetClones-site / build-and-push (push) Failing after 1m25s
- Удалено исключение config/ из .dockerignore
- config/nginx/pet-clones--external-nginx.conf требуется для Docker образа
- Используется в docker-compose.prod.yml для настройки nginx reverse-proxy
2026-04-01 00:35:44 +03:00
a9fb77c195 feat: финальная конфигурация production Docker для RosmorPort
Some checks failed
Build and Push PetClones-site / build-and-push (push) Failing after 21s
Обновлен Dockerfile:
- Исправлены права доступа пользователя appuser (создание перед COPY)
- Правильный порядок операций (USER -> COPY -> mkdir)
- Оптимизация для production (workers=1, timeout=120, max-requests=200)
- collectstatic и удаление лишних файлов статики при build
Обновлен docker-compose.prod.yml:
- Переименован контейнер в petclones-site--backend
- Production переменные окружения (DEBUG=False)
- Volumes для media, database и nginx конфигов
- Gunicorn с параметрами для production
- Watchtower для автоматического обновления образов
- Ограничение ресурсов (0.25 CPU, 512M RAM)
- JSON logging с ротаци??бновлен Dockerfile:
- Исправлены права доступа пользователя appuser (создание перед COPY?? Исправлены пр?? Правильный порядок операций (USER -> COPY -> mkdir)
- Оптимизация для prod
2026-04-01 00:08:53 +03:00
1b0fa5e500 build: игнорировать copilotDiffState.xml (локальное состояние GitHub Copilot IDE)
- Удален copilotDiffState.xml из git истории
- Добавлено правило в .gitignore для игнорирования этого файла
- Это локальный служебный файл PyCharm, специфичный для машины разработчика
- Не должен попадать в репозиторий и загрязнять git history
2026-04-01 00:04:12 +03:00
bbf35c0c24 mod: убрал jinja2 (мусор в комментариях) 2026-03-31 23:55:21 +03:00
08668fee6d del: ini-файл uWSGI не нужен. В контейнере используется gunicorn 2026-03-31 22:33:31 +03:00
d846542e34 docs: обновлен about.jinja с информацией о проекте
Добавлена информация:
- История проекта РосМорПорт с 2004 года
- Ссылка на репозиторий (https://git.cube2.ru/erjemin/2024-test-rosmorport)
- Описание переделки в 2026 году для развертывания в Docker при переносе на новый хостинг
2026-03-31 19:19:20 +03:00
792d152be2 feat: оптимизация Docker контейнера для разработки
Обновлен Dockerfile с двухэтапной сборкой, экспортом зависимостей через poetry, сборкой статики и удалением лишних файлов (экономия ~20МБ).
Обновлен docker-compose.yml с привязкой к localhost для безопасности.
Обновлен .dockerignore для исключения ненужных файлов (source maps, RTL CSS, TTF).
Добавлены таймауты SQLite в settings.py.
Минимальные параметры Gunicorn: 1 worker, timeout 30s, max-requests 100.
Тестировано и работает на http://localhost:8040
2026-03-31 18:57:33 +03:00
6e7a4c52e0 feat: Добавлена поддержка WhiteNoise для обслуживания статических файлов
- Конфигурация collectstatic в settings.py:
  * STATIC_ROOT = staticfiles/ для собранных файлов
  * STATICFILES_DIRS указывает на public/static
  * CompressedManifestStaticFilesStorage для production
  * WhiteNoiseMiddleware в MIDDLEWARE
  * WHITENOISE_ROOT для подачи файлов из /public
- Исправлены пути в settings.py для правильной работы БД
- Обновлена конфигурация urls.py для отдачи статики
- Добавлена зависимость whitenoise ^6.6.0 в pyproject.toml
- Обновлен .gitignore (раскомментирована staticfiles/)
Статика работает в dev режиме и готова для production.
2026-03-31 16:51:48 +03:00
8385e04103 refactor: Удалена ненужная папка logs
Причины:
- В production логи nginx хранятся в системной папке хоста
- В dev образ постоянно перестраивается, логи не накапливаются
- Логи не нужны для хранения в контейнере
Удалено:
- Папка logs из корня проекта
- mkdir -p /app/logs из Dockerfile
- Монтирование logs из docker-compose.yml и docker-compose.prod.yml
- Переменная TOUCH_RELOAD из settings.py и .env файлов
Результат: более clean и минималистичная структура проекта
2026-03-31 13:45:32 +03:00
55980a0659 fix: Исправлен путь к БД SQLite - используется абсолютный путь
- Проблема: при запуске manage.py из rosmorport_tsts/ относительный путь database/db.sqlite3 не работал
- Решение: теперь путь всегда строится как абсолютный от PROJECT_ROOT
- БД корректно находится в database/db.sqlite3 в корне проекта
- Протестировано: проект запускается без ошибок, БД содержит 17 записей из production
Структура БД скопирована с production сервера.
2026-03-31 01:42:18 +03:00
ef80a66b69 refactor: Реорганизация структуры для контейнеризации
- database/ папка в корне проекта для БД
- public/ для статики и медиа (монтируются отдельно)
- Обновлены docker-compose файлы с правильными томами
2026-03-31 01:25:15 +03:00
493de32998 security: Переведена админ панель на переменную окружения ADMIN_URL
- Добавлена переменная окружения ADMIN_URL для динамического управления URL админки
- Дефолтное значение в коде: 'admin/' (стандартное, безопасное)
- Пользователь может переопределить через .env для скрытия в production
- Примечание: никогда не коммитьте реальные значения секретов в .env.example
Это позволяет:
- Легко скрывать админку от автоматических сканеров
- Использовать разные URL для dev/production
- Не хранить секреты в коде
2026-03-31 00:48:34 +03:00
31cd78079a fix: Исправлена раздача статических файлов в DEV режиме
- Исправлены пути STATICFILES_DIRS и MEDIA_ROOT (они должны быть относительно корня проекта, а не settings.py)
- Добавлена раздача STATIC_URL в urls.py для DEBUG режима
- Проверено что все статические файлы (CSS, JS) отдаются с кодом 200
Использование:
- Media:  Не используется (нет FileField/ImageField в моделях)
- Static:  Используется (CSS, JS, images в public/static)
Статика теперь корректно работает в dev окружении.
2026-03-31 00:31:32 +03:00
1a2865bae7 refactor: Облегчение pyproject.toml для production
- Удалены dev зависимости (pytest, black, ruff, mypy, ipython и т.д.)
- Оставлены только необходимые для production пакеты
- poetry.lock сокращен с 63KB до 4KB
- Проект работает нормально (django check passed)
Для локальной разработки можно установить нужные инструменты отдельно при необходимости.
2026-03-31 00:18:51 +03:00
d9e8c2d8bd feat: Настройка проекта для локальной разработки
- Добавлена конфигурация через poetry (pyproject.toml + poetry.lock)
- Переделана система конфигурации Django с .env переменными
- Добавлен файл .env для локальной разработки (SQLite БД)
- Обновлен settings.py для работы с переменными окружения
- Создана документация по локальной разработке (DEVELOPMENT.md)
- Подготовлена инфраструктура для Docker (Dockerfile, docker-compose.yml)
- Обновлен .gitignore для Python/Django проекта
Проект работает локально с миграциями и админ пользователем (admin/1234).
2026-03-31 00:09:46 +03:00
40 changed files with 1155 additions and 227 deletions

118
.dockerignore Normal file
View File

@@ -0,0 +1,118 @@
# Git
.git
.gitignore
.github
# Python
__pycache__
*.py[cod]
*$py.class
*.so
.Python
.venv
venv/
env/
ENV/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
# IDE
.idea/
.vscode/
*.swp
*.swo
*~
# OS
.DS_Store
Thumbs.db
.env
.env.local
.env.*.local
# Тестирование и coverage
.pytest_cache/
.coverage
htmlcov/
.mypy_cache/
.ruff_cache/
# Docker
Dockerfile
docker-compose*.yml
.dockerignore
# Документация и логи
logs/
*.log
README.md
target.md
test_frontend.txt
feedback.txt
my_anwer.txt
requare_dev_prod.txt
requare_dev_w_home.txt
# Оптимизация статики - исключаем лишние файлы для компактного контейнера
# Source maps (нужны только для разработки, не для production)
public/static/**/*.map
staticfiles/**/*.map
# RTL версии Bootstrap (если не используется для арабских/персидских языков)
public/static/css/bootstrap*.rtl.*
public/static/css/v*.rtl.*
staticfiles/css/bootstrap*.rtl.*
staticfiles/css/v*.rtl.*
# TTF шрифты - используем только woff2 (меньше размер, лучше поддержка в браузерах)
public/static/webfonts/*.ttf
public/static/webfonts/*.eot
public/static/webfonts/*.svg
staticfiles/webfonts/*.ttf
staticfiles/webfonts/*.eot
staticfiles/webfonts/*.svg
# Обычные (неминифицированные) версии CSS для bootstrap - используем только min версии
# (но оставляем rosmorport.css - наш проектный CSS)
# bootstrap.css, bootstrap-grid.css, bootstrap-utilities.css, etc
public/static/css/bootstrap.css
public/static/css/bootstrap-grid.css
public/static/css/bootstrap-utilities.css
public/static/css/bootstrap-reboot.css
public/static/css/v*.css
public/static/css/svg-with-js.css
public/static/css/fontawesome.css
public/static/css/regular.css
public/static/css/solid.css
public/static/css/brands.css
public/static/css/all.css
staticfiles/css/bootstrap.css
staticfiles/css/bootstrap-grid.css
staticfiles/css/bootstrap-utilities.css
staticfiles/css/bootstrap-reboot.css
staticfiles/css/v*.css
staticfiles/css/svg-with-js.css
staticfiles/css/fontawesome.css
staticfiles/css/regular.css
staticfiles/css/solid.css
staticfiles/css/brands.css
staticfiles/css/all.css
# Прочее
.editorconfig
*.md

20
.env.example Normal file
View File

@@ -0,0 +1,20 @@
# ========================================
# Django настройки
# ========================================
DEBUG=True
ALLOWED_HOSTS=localhost,127.0.0.1,0.0.0.0
SECRET_KEY=django-insecure-dev-secret-key-change-in-production-12345678
# Скрытый URL для админ панели (обфускированный для безопасности)
# Рекомендуется использовать что-то сложное, например случайную строку
# НИКОГДА не используй 'admin/' в production!
ADMIN_URL=hidden-admin-panel/
# ========================================
# База данных
# ========================================
DB_NAME=db.sqlite3
# Настройки достпа к пакетам в репозитории, чтобы wathtower мог проверять их свежесть и скачивать
REPO_USER=xxxxx
REPO_PASS=xxxxx

View File

@@ -0,0 +1,58 @@
name: Build and Push PetClones-site
on:
push:
# Запускать только при создании тега, начинающегося с 'v' (например, v1.0.0)
tags:
- 'v*'
jobs:
build-and-push:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
# Генерируем метаданные (теги) для Docker
- name: Docker meta
id: meta
uses: docker/metadata-action@v4
with:
# Важно: используем gitea.repository
images: git.cube2.ru/${{ gitea.repository }}
tags: |
type=ref,event=tag
type=raw,value=latest,enable=${{ gitea.ref_type == 'tag' }}
# Логин в реестр Gitea
- name: Login to Gitea Registry
uses: docker/login-action@v2
with:
registry: git.cube2.ru
# Важно: используем gitea.actor
username: ${{ gitea.actor }}
password: ${{ secrets.REGISTRY_PASSWORD }}
# Сборка и отправка образа
- name: Build and push image PetClones-site
uses: docker/build-push-action@v4
with:
context: .
file: Dockerfile
# Используем только amd64 для надежности (arm64 добавим позже если нужно)
platforms: linux/amd64
push: true
# Используем теги, сгенерированные шагом meta (v0.1.0 и latest)
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
# Кэширование для ускорения повторных сборок
cache-from: type=gha
cache-to: type=gha,mode=max
# Увеличиваем тайм-аут для надежности (от 1800 до 3600 сек)
timeout: 3600 # 60 минут (для надежности при медленном интернете)

9
.github/copilot-instructions.md vendored Normal file
View File

@@ -0,0 +1,9 @@
Вы - опытный помощник по программированию GitHub Copilot.
Всегда отвечай на русском языке, если не указано иное.
Комментируй код, который ты генерируешь, чтобы объяснить его функциональность на русском языке.
Не удаляй старые комментарии. Код которых хочешь удалить скрывай в комментариях.
При написании кода следуй стандартам PEP 8 для Python.
Используй современные возможности Python (3.12+) и Django (6.0+).
Предпочитай `poetry` для управления зависимостями.
Подробнее комментировать каждое действие.
Всегда спрашивай да/нет пред деплоем, git commit и git push.

28
.gitignore vendored
View File

@@ -24,6 +24,9 @@
.idea/**/uiDesigner.xml .idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml .idea/**/dbnavigator.xml
# IDE - GitHub Copilot state (local IDE state, not needed in repo)
.idea/copilotDiffState.xml
# Gradle # Gradle
.idea/**/gradle.xml .idea/**/gradle.xml
.idea/**/libraries .idea/**/libraries
@@ -89,7 +92,30 @@ media
# If your build process includes running collectstatic, then you probably don't need or want to include staticfiles/ # If your build process includes running collectstatic, then you probably don't need or want to include staticfiles/
# in your Git repository. Update and uncomment the following line accordingly. # in your Git repository. Update and uncomment the following line accordingly.
# <django-project-name>/staticfiles/ staticfiles/
# SPECIFIC FOR THE PROJECT # SPECIFIC FOR THE PROJECT
my_secret*.* my_secret*.*
# Environment variables
.env
.env.local
.env.*.local
# Poetry
poetry.lock
# Logs
logs/
*.log
# Static files in development
public/static/
public/media/
# Python virtual environments
venv/
.venv/
env/
ENV/

10
.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,10 @@
# Default ignored files
/shelf/
/workspace.xml
# Ignored default folder with query files
/queries/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
# Editor-based HTTP Client requests
/httpRequests/

29
.idea/2024-test-rosmorport.iml generated Normal file
View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="FacetManager">
<facet type="django" name="Django">
<configuration>
<option name="rootFolder" value="$MODULE_DIR$/rosmorport_tsts" />
<option name="settingsModule" value="rosmorport_tsts/settings.py" />
<option name="manageScript" value="$MODULE_DIR$/rosmorport_tsts/manage.py" />
<option name="environment" value="&lt;map/&gt;" />
<option name="doNotUseTestRunner" value="false" />
<option name="trackFilePattern" value="migrations" />
</configuration>
</facet>
</component>
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/rosmorport_tsts" isTestSource="false" />
</content>
<orderEntry type="jdk" jdkName="Python 3.14 (rosmorport-tsts-OiaYl72h-py3.14)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="PyDocumentationSettings">
<option name="format" value="PLAIN" />
<option name="myDocStringFormat" value="Plain" />
</component>
<component name="TemplatesService">
<option name="TEMPLATE_CONFIGURATION" value="Django" />
</component>
</module>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Ask2AgentMigrationStateService">
<option name="migrationStatus" value="COMPLETED" />
</component>
</project>

View File

@@ -0,0 +1,10 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="DuplicatedCode" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<Languages>
<language minSize="57" name="Python" />
</Languages>
</inspection_tool>
</profile>
</component>

View File

@@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

7
.idea/misc.xml generated Normal file
View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Black">
<option name="sdkName" value="Python 3.8" />
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.14 (rosmorport-tsts-OiaYl72h-py3.14)" project-jdk-type="Python SDK" />
</project>

8
.idea/modules.xml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/2024-test-rosmorport.iml" filepath="$PROJECT_DIR$/.idea/2024-test-rosmorport.iml" />
</modules>
</component>
</project>

7
.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

99
Dockerfile Normal file
View File

@@ -0,0 +1,99 @@
# Dockerfile для Django приложения rosmorport_tsts
# Многоэтапная сборка для оптимизации размера образа
# Первый этап: builder - установка зависимостей
FROM python:3.12-slim AS builder
# Переводим в режим без буферизации для вывода логов
ENV PYTHONUNBUFFERED=1
ENV PYTHONDONTWRITEBYTECODE=1
# Устанавливаем poetry
RUN pip install --no-cache-dir poetry==1.8.3
# Устанавливаем рабочую директорию
WORKDIR /app
# Копируем файлы с информацией о зависимостях
COPY pyproject.toml poetry.lock* ./
# Экспортируем зависимости из poetry в requirements.txt
# Это более надежный способ в Docker, чем использование виртуального окружения poetry
RUN poetry export -f requirements.txt --output requirements.txt --no-interaction
# Создаем виртуальное окружение в стандартном месте
RUN python -m venv /opt/venv
# Устанавливаем зависимости в виртуальное окружение
RUN /opt/venv/bin/pip install --no-cache-dir -r requirements.txt
# Второй этап: runtime - финальный образ
FROM python:3.12-slim
# Переводим в режим без буферизации для вывода логов
ENV PYTHONUNBUFFERED=1
ENV PYTHONDONTWRITEBYTECODE=1
# Установка PATH для виртуального окружения перед копированием
ENV PATH="/opt/venv/bin:$PATH"
# Устанавливаем рабочую директорию
WORKDIR /app
# Устанавливаем пользователя для запуска приложения (из соображений безопасности)
RUN useradd -m -u 1000 appuser && chown -R appuser:appuser /app
USER appuser
# Копируем виртуальное окружение из builder
COPY --from=builder /opt/venv /opt/venv
# Копируем содержимое проекта
COPY --chown=appuser:appuser . .
# Создаём директорию для собранной статики и даём права пользователю app
RUN mkdir -p /app/staticfiles && chown -R appuser:appuser /app/staticfiles
# Создаём директорию для media
RUN mkdir -p /app/public/media && chown -R appuser:appuser /app/public/media
# Создаём директорию для БД и даём права пользователю app
# Это важно когда БД монтируется как том с хоста
RUN mkdir -p /app/database && chown -R appuser:appuser /app/database
# Копируем внешний nginx конфиг для экспорта на хост (через volume)
# Это нужно для настройки reverse-proxy на хосте
COPY config/nginx/pet-clones--external-nginx.conf /tmp/pet-clones--external-nginx.conf.source
# Собираем статику при сборке образа (для production)
# Это гарантирует что статика готова и не нужно запускать collectstatic при старте
# PATH уже установлен в переменных окружения выше, поэтому Django должен быть найден
RUN cd /app/rosmorport_tsts && \
python manage.py collectstatic --noinput --clear --no-input
# Оптимизация размера контейнера: удаляем лишние файлы статики
# Source maps нужны только для разработки
RUN find /app/staticfiles -name "*.map" -delete
# Удаляем RTL версии Bootstrap (если проект не поддерживает RTL)
RUN find /app/staticfiles -name "*rtl*" -type f -delete
# Удаляем TTF и EOT шрифты (используем только woff2 - меньше размер)
RUN find /app/staticfiles/webfonts -type f \( -name "*.ttf" -o -name "*.eot" -o -name "*.svg" \) -delete
# Удаляем неминифицированные версии CSS (кроме нашего собственного rosmorport.css)
# bootstrap.css, all.css, fontawesome.css используем только .min версии
RUN find /app/staticfiles/css -maxdepth 1 -name "*.css" -not -name "*.min.css" -not -name "rosmorport.css" -delete
# Открываем порт
EXPOSE 8000
# CMD - выполняет миграции и запускает Gunicorn
# Миграции выполняются при каждом запуске для гибкости
# Статика уже собрана при сборке образа
# Параметры оптимизированы для минимальной нагрузкой (ветер качает, но не падает):
# - bind 0.0.0.0:8000 (все интерфейсы контейнера, необходимо для Docker проброса портов)
# - workers=1 (один worker - достаточно если не будет нагрузок)
# - timeout=120 (сокращенный таймаут - запросы должны быть быстрыми)
# - max-requests=200 (перезагрузка воркера после 200 запросов - экономия памяти)
# - access-logfile/error-logfile '-' (логи в stdout для docker)
CMD ["sh", "-c", "cd /app/rosmorport_tsts && echo '>>> Applying database migrations...' && python manage.py migrate --noinput && echo '>>> Starting Gunicorn...' && gunicorn --bind 0.0.0.0:8000 --workers 1 --worker-class sync --worker-tmp-dir /dev/shm --max-requests 200 --timeout 120 --access-logfile - --error-logfile - rosmorport_tsts.wsgi:application"]

View File

@@ -1,15 +1,20 @@
## Тестовое задание rosmorport ## Тестовое задание rosmorport
**2024 год**: _14 апреля (воскресенье)_
Тестовое задание на соискание вакансии `Frontend-разработчик` в ФГУП РосМорФлот. Тестовое задание на соискание вакансии `Frontend-разработчик` в ФГУП РосМорФлот.
* [Тестовое задание](target.md) (таким, как оно было выдано, авторская орфография и пунктуация). * [Тестовое задание](target.md) (таким, как оно было выдано, авторская орфография и пунктуация).
* [Мой ответ](my_anwer.txt) (спустя два дня) * [Мой ответ](my_anwer.txt) (спустя два дня)
* Развёрнуто на [pet-clones.cocorico.ru](https://pet-clones.cocorico.ru/) (login: admin / pwd: 1234) * Развёрнуто на [pet-clones.cube2.ru](https://pet-clones.cube2.ru/) (login: admin / pwd: 1234)
* [Ответ РосМорФлот](feedback.txt) (спустя четыре дня, в ответ на просьбу обратной связи... судя по логам * [Ответ РосМорФлот](feedback.txt) спустя четыре дня, после нескольких обращений об обратной связи... судя по логам
заходил один раз сразу по получения решения). заходил один раз, никих действий (новых записей) произведено не было.
Мое мнение о тестовом задании: в задании нет никаких рекомендаций, ограничений и запретов на Мое мнение о тестовом задании: в задании нет никаких рекомендаций, ограничений и запретов на
какие-либо _выбранные технологии_... Зачем задании столько требований по Backend, если вакансия какие-либо _выбранные технологии_... Зачем задании столько требований по Backend, если вакансия
`Frontend-разработчик` -- загадка. И, похоже, дополнительно нужен навык чтения мыслей на расстоянии. `Frontend-разработчик` -- загадка. И, похоже, дополнительно нужен навык чтения мыслей на расстоянии.
---
**2026 год**: _1 апреля (среда)_
Проект, при переносе на новый хостинг, переделан для разве­ртывания в Docker. Просто жалко выбрасывать.

View File

@@ -0,0 +1,122 @@
# == Конфикурационный файл config/nginx/pet-clones--external-nginx.conf ==
# Внешний nginx конфиг для проксирования к контейнеру petclones-site--backend
# Это конфиг для ХОСТА (не внутри контейнера)
# Upstream (группировка backends) для проксирования запросов в Django контейнер
upstream petclones-backend {
# Контейнер слушает на 127.0.0.1:8040 согласно docker-compose.prod.yml
server 127.0.0.1:8040;
# Сохраняем persistent соединения (требует proxy_http_version 1.1 и proxy_set_header Connection "")
keepalive 32;
}
# Основной server блок для обработки HTTP запросов
server {
# Слушаем на стандартном HTTP порту (80) для IPv4 и IPv6
listen 80;
listen [::]:80;
server_name pet-clones.cube2.ru;
# Редирект HTTP → HTTPS (будет активирован после добавления SSL сертификата через certbot)
# Раскомментируй эту строку после получения SSL сертификата:
# return 301 https://$server_name$request_uri;
# Логирование запросов и ошибок для этого виртуального хоста
access_log /var/log/nginx/petclones.access.log;
error_log /var/log/nginx/petclones.error.log warn;
# ОСНОВНОЙ БЛОК ПРОКСИРОВАНИЯ
# Все запросы, кроме перехвачённых специфичными location'ами, проксируются сюда
location / {
# Проксируем все запросы в upstream контейнер
proxy_pass http://petclones-backend;
# --- ЗАГОЛОВКИ ДЛЯ КОРРЕКТНОЙ РАБОТЫ ПРИЛОЖЕНИЯ ---
# Передаём оригинальный хост (важно для правильной работы Django и генерации URLs)
proxy_set_header Host $host;
# Передаём оригинальный IP клиента (для логирования и rate-limiting)
proxy_set_header X-Real-IP $remote_addr;
# Добавляем этот прокси в цепь X-Forwarded-For (если было несколько проксей)
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# Передаём оригинальный протокол (HTTP или HTTPS) - важно для правильной работы redirect'ов
proxy_set_header X-Forwarded-Proto $scheme;
# Передаём оригинальный хост (может отличаться от Host если используется load balancer)
proxy_set_header X-Forwarded-Host $server_name;
# Передаём порт если не стандартный
proxy_set_header X-Forwarded-Port $server_port;
# --- HTTP KEEP-ALIVE ДЛЯ ПРОИЗВОДИТЕЛЬНОСТИ ---
# Используем HTTP 1.1 для поддержки keep-alive
proxy_http_version 1.1;
# Очищаем Connection header (иначе будет "Connection: close")
proxy_set_header Connection "";
# --- ТАЙМАУТЫ ДЛЯ ДЛИТЕЛЬНЫХ ЗАПРОСОВ ---
# Время ожидания подключения к backend
proxy_connect_timeout 60s;
# Время ожидания отправки запроса на backend
proxy_send_timeout 60s;
# Время ожидания ответа от backend (может быть увеличено если есть долгие запросы)
proxy_read_timeout 60s;
# --- БУФЕРИЗАЦИЯ ОТВЕТОВ ---
# Включаем буферизацию (nginx буферизирует ответ перед отправкой клиенту)
# Это экономит память backend если клиент медленный
proxy_buffering on;
# Размер буфера для первой части ответа (заголовки)
proxy_buffer_size 4k;
# Количество и размер буферов для основного тела ответа
proxy_buffers 8 4k;
}
# КЭШИРОВАНИЕ СТАТИЧЕСКИХ ФАЙЛОВ
# Кэшируем: HTML, изображения, стили, скрипты, шрифты и иконки
# ВАЖНО: WhiteNoise уже сжимает статику в контейнере (Gzip в nginx не нужен)
location ~* \.(htm|html|gif|png|jpe?g|svg|ico|css|js|woff|woff2|ttf|eot)$ {
# Проксируем запрос к backend (статика часть контейнера)
proxy_pass http://petclones-backend;
# Передаём хост в заголовке
proxy_set_header Host $host;
# --- КЭШИРОВАНИЕ В БРАУЗЕРЕ КЛИЕНТА ---
# Если файлы имеют хеш в названии (например: app.abc123.js), то cache-control может быть очень долгой
# "immutable" говорит браузеру что файл никогда не изменится (если изменится, то будет другой хеш)
add_header Cache-Control "public, max-age=2592000"; # 30 дней в секундах (30*24*60*60)
}
# КЭШИРОВАНИЕ МЕДИА ФАЙЛОВ
# Медиа загруженные пользователями кэшируются меньше чем статика (на случай удаления или обновления)
location ~* ^/media/.*\.(jpg|jpeg|png|gif|webp|svg|pdf|mp4|webm)$ {
proxy_pass http://petclones-backend;
proxy_set_header Host $host;
# Кэшируем на 30 дней (можно менять в зависимости от политики)
add_header Cache-Control "public, max-age=604800, immutable"; # 7 дней в секундах (604800 = 7*24*60*60)
}
}
# HTTPS SERVER БЛОК (ДОБАВИТЬ ПОСЛЕ ПОЛУЧЕНИЯ SSL СЕРТИФИКАТА)
# После получения SSL сертификата через certbot, раскомментируй и настрой эту секцию:
#
# server {
# listen 443 ssl http2;
# listen [::]:443 ssl http2;
# server_name pet-clones.cube2.ru;
#
# # SSL сертификаты от certbot
# ssl_certificate /etc/letsencrypt/live/pet-clones.cube2.ru/fullchain.pem;
# ssl_certificate_key /etc/letsencrypt/live/pet-clones.cube2.ru/privkey.pem;
#
# # Параметры безопасности SSL
# ssl_protocols TLSv1.2 TLSv1.3;
# ssl_ciphers HIGH:!aNULL:!MD5;
# ssl_prefer_server_ciphers on;
#
# # остальная конфигурация идентична HTTP блоку выше...
# # (копируешь content из server блока выше и вставляешь сюда)
# }
#
# ТАКЖЕ НЕ ЗАБУДЬ:
# 1. Установи certbot: sudo apt-get install certbot python3-certbot-nginx
# 2. Получи сертификат: sudo certbot certonly --standalone -d pet-clones.cube2.ru
# 3. Перезагрузи nginx: sudo systemctl restart nginx

View File

@@ -1,70 +0,0 @@
# ТЕСТОВОЕ ЗАДАНИЕ РОСМОРПОРТ
# == Конфикурационный файл pet-clones--nginx.conf ==
# Описываем апстрим-потоки которые должен подключить Nginx
# Для каждого сайта надо настроить свой поток, со своим уникальным именем.
# Если будете настраивать несколько python (django) сайтов - измените название upstream
upstream pet-clone {
# расположение файла Unix-сокет для взаимодействие с uwsgi
server unix:///home/web/pet-clones.cocorico.ru/socket/clone_pets.sock;
}
# конфигурируем сервер
server {
server_name pet-clones.cocorico.ru; # доменное имя сайта
listen 443 ssl; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/pet-clones.cocorico.ru/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/pet-clones.cocorico.ru/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
# ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA';
ssl_session_cache builtin:1000 shared:SSL:25m;
keepalive_timeout 75s;
charset utf-8; # кодировка по умолчанию
access_log /home/web/pet-clones.cocorico.ru/logs/clone-pets-access.log; # логи с доступом
error_log /home/web/pet-clones.cocorico.ru/logs/clone-pets-error.log; # логи с ошибками
client_max_body_size 100M; # максимальный объем файла для загрузки на сайт (max upload size)
error_page 404 /404.html;
error_page 500 /500.html;
location /media { alias /home/web/pet-clones.cocorico.ru/public/media; } # Расположение media-файлов Django
location /static { alias /home/web/pet-clones.cocorico.ru/public/static; } # Расположение static-файлов Django
location /robots.txt { root /home/web/pet-clones.cocorico.ru/public; } # Расположение robots.txt
location /favicon.ico { root /home/web/pet-clones.cocorico.ru/public/static/img; } # Расположение favicon.ico
location /favicon.png { root /home/web/pet-clones.cocorico.ru/public/static/img; } # Расположение favicon
location = /404.html {
root /home/web/pet-clones.cocorico.ru/rosmorport_tsts/templates-django/404.html;
internal;
}
location = /500.html {
root /home/web/pet-clones.cocorico.ru/rosmorport_tsts/templates-django/500.html;
internal;
}
location ~ \.(xml|html|htm|txt|svg)$ {
root /home/web/pet-clones.cocorico.ru/public; # Расположение статичных *.xml, *.html и *.txt
}
location / {
uwsgi_pass pet-clone; # upstream обрабатывающий обращений
include uwsgi_params; # конфигурационный файл uwsgi;
uwsgi_read_timeout 1800; # вдруг некоторые запросы очень долго обрабатываются?
uwsgi_send_timeout 200; # на всякий случай время записи в сокет тоже побольше...
}
}
server {
if ($host = pet-clones.cocorico.ru) {
return 301 https://$host$request_uri;
} # managed by Certbot
server_name pet-clones.cocorico.ru;
listen 80;
return 404; # managed by Certbot
}

View File

@@ -1,66 +0,0 @@
# === Конфигурационный файл uwsgi pets-clone--uwsgi.ini ===
# ░▒▓████▓▒░▒▓█████▓▒░▒▓█▓▒░ ▒▓██████▓▒░ ВНИМАНИЕ ДЛЯ ТЕХ КТО ДЕПЛОИТ ИЗ ПОД MICROSOFT WINDOWS!!
# ░▒▓█▓▒▒▓█▓▒▒▓█▓▒▒▓█▓▒▒▓█▓▒░ ▒▓█▓▒░ ОКОНЧАНИЯ СТРОК ДОЛЖНЫ БЫТЬ В ФОРМАТЕ UNIX (LF) а не CR+LR (как в Win)
#-░▒▓█▓▒░----▒▓█████▓▒-▒▓█▓▒░---▒▓████▓▒░----------------------------------------------------------------
# ░▒▓█▓▒▒▓█▓▒▒▓█▓▒▒▓█▓▒▒▓█▓▒░ ▒▓█▓▒░ Заметка: CR - carriage return (\r, возврат каретки, ВК)
# ░▒▓████▓▒░▒▓█▓▒▒▓█▓▒▒▓█████▓▒▒▓█▓▒░ LF - line feed (\n, перевод строки, ПС)
[uwsgi]
# НАСТРОЙКИ ДЛЯ DJANGO
# Корневая папка проекта (полный путь)
chdir = /home/web/pet-clones.cocorico.ru/rosmorport_tsts
module = rosmorport_tsts.wsgi
# полный путь к виртуальному окружению
home = /home/web/pet-clones.cocorico.ru/env
# полный путь к файлу сокета
socket = /home/web/pet-clones.cocorico.ru/socket/clone_pets.sock
# ЗАГАДОЧНЫЕ НАСТРОЙКИ, ПО ИДЕЕ ОНИ НУЖНЫ, НО И БЕЗ НИХ ВСЁ РАБОТАЕТ
# расположение wsgi.py
wsgi-file = /home/web/pet-clones.cocorico.ru/rosmorport_tsts/rosmorport_tsts/wsgi.py
# расположение виртуального окружения (как оно работает если этот параметр не указан, не ясно)
virtualenv = /home/web/pet-clones.cocorico.ru/env
# имя файла при изменении которого происходит авторестарт приложения
# (когда этого параметра нет, то гичего не авторестартится, но с ним все рестартится.
# Cтоит изменить любой Python-исходник проекта, как изменения сразу вступают в силу.
touch-reload = /home/web/pet-clones.cocorico.ru/logs/touchreload.txt
py-autoreload = 5
#
# НАСТРОЙКИ ОБЩИЕ
# быть master-процессом
master = true
# максимальное количество процессов
processes = 2
# если uWSGI устнаовлен как сервис через apt-get то нужно установить еще плугин:
# sudo apt-get install uwsgi-plugin-python
# и добавить в этот конфиг: plugin = python
plugin = python3
# права доступа к файлу сокета. По умолчанию должно хватать 664. Но каких-то прав не хватает, поэтому 666.
chmod-socket = 666
# очищать окружение от служебных файлов uwsgi по завершению
vacuum = true
# количество секунд после которых подвисший процес будет перезапущен
# Так как некоторе скрипты требуют изрядно времени (особенно полная переиндексация) то ставим значение побольще
harakiri = 2600
# В общем случае, при некотых значениях harakiri логах uWSGI может вываливаться предупреждение:
# WARNING: you have enabled harakiri without post buffering. Slow upload could be rejected on post-unbuffered webservers
# можно оставить harakiri закоментированным, но нам нужно 900 и на него не ругается. Ругается на 30.
# разрешаем многопоточность
enable-threads = true
vacuum = true
thunder-lock = true
max-requests = 500
# пользователь и группа пользователей от имени которых запускать uWSGI
# указываем www-data: к этой группе относится nginz, и ранее мы включили в эту группу нашего [user]
# uid = nginx
# gid = nginx
# uid = root
# gid = root
uid = web
gid = web
print = ---------------- Запущен uWSGI для pet-clones.cocorico.ru ---------------

0
database/.gitkeep Normal file
View File

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

@@ -0,0 +1,107 @@
% cat docker-compose.yml
# docker-compose.prod.yml для production развертывания проекта rosmorport_tsts
# Для использование на хосте провайдера, перейдите в папку с приложением, размемтите
# его там под именем `docker-compose.yml`
#
# Запуск на продакшене:
# cd <папка-с-приложением>
# mkdir -p config/nginx media database
# sudo chmod -R 777 media database config/nginx
# sodo docker compose up -d
# version: '3.9'
services:
# Django приложение в production
web:
# Используем образ из Gitea реестра (собран в CI/CD)
image: git.cube2.ru/erjemin/2024-test-rosmorport:latest
# Имя контейнера
container_name: petclones-site--backend
# Переменные окружения
env_file:
- .env
# Переменные окружения для production
environment:
- DOCKER_ENV=1
- DEBUG=False
- PYTHONUNBUFFERED=1
# Тома для медиа, статики и БД
# ВАЖНО: Используем локальные монтирования, а не именованные тома!
# Данные должны быть доступны на хосте для nginx и резервного копирования
volumes:
# Локальная папка для media (загруженные пользователями файлы)
# Контейнер: /app/public/media → Хост: ./media
- ./media:/app/public/media:rw
# Локальная папка для базы данных (SQLite файл)
# Контейнер: /app/database → Хост: ./database
- ./database:/app/database:rw
# Volume для экспорта конфигов из контейнера на хост
# Контейнер: /tmp/nginx_configs → Хост: ./config/nginx
- ./config/nginx:/tmp/nginx_configs:rw
# Command: запуск Django приложения с миграциями
command: >
sh -c "
cd /app/rosmorport_tsts &&
echo 'Применение миграций БД...' &&
python manage.py migrate --noinput &&
echo 'Сбор статических файлов...' &&
python manage.py collectstatic --noinput &&
echo '🚀 Запуск gunicorn...' &&
gunicorn --bind 0.0.0.0:8000 --workers 1 --worker-class sync --worker-tmp-dir /dev/shm --max-requests 200 --timeout 120 --access-logfile - --error-logfile - rosmorport_tsts.wsgi:application
"
# Перенаправляем порты
ports:
- "127.0.0.1:8040:8000"
# Политика перезапуска
restart: unless-stopped
# Метки для Watchtower (авто-обновление)
labels:
- "com.centurylinklabs.watchtower.scope=petclones-site--scope"
# Ограничения ресурсов
deploy:
resources:
limits:
cpus: '0.25'
memory: 256M
# Логирование в JSON-файлы (для сбора логов через docker logs)
logging:
driver: "json-file"
options:
max-size: "5m" # больше лимит в продакшене для логирования
max-file: "1" # храним 1 файл лога
# WATCHTOWER ДЛЯ АВТОМАТИЧЕСКОГО ОБНОВЛЕНИЯ ОБРАЗОВ ИЗ РЕЕСТРА
watchtower:
image: containrrr/watchtower
container_name: petclones-site--watchtower
restart: always
volumes:
- /var/run/docker.sock:/var/run/docker.sock
# Переменные окружения
env_file:
- .env
environment:
- DOCKER_API_VERSION=1.44
# Берем учетные данные из .env файла
- REPO_USER=${REPO_USER}
- REPO_PASS=${REPO_PASS}
# Ограничиваем область видимости только этим проектом
- WATCHTOWER_SCOPE=petclones-site--scope
# Если нужно указать реестр явно (обычно watchtower сам понимает из имени образа)
# - WATCHTOWER_REGISTRY_URL=git.cube2.ru
command: --interval 1800 --cleanup # Проверять каждые 30 минут
logging:
driver: "json-file"
options:
max-size: "1m"
max-file: "1"

41
docker-compose.yml Normal file
View File

@@ -0,0 +1,41 @@
# docker-compose.yml для локальной разработки проекта rosmorport_tsts
# Использование: docker-compose up
# version: '3.9'
services:
# Django приложение
web:
# Строим образ из Dockerfile в текущей директории
build:
context: .
dockerfile: Dockerfile
# Имя контейнера
container_name: rosmorpor-tst-site--backend
# Переменные окружения для разработки
env_file:
- .env
environment:
- DOCKER_ENV=1
- DJANGO_LOG_LEVEL=DEBUG
- DEBUG=True
- PYTHONUNBUFFERED=1
# Монтируем текущую директорию для разработки
volumes:
- .:/app
- ./public/media:/app/public/media
- ./database:/app/database
# Перенаправляем порты
ports:
- "127.0.0.1:8040:8000"
# Сетевая конфигурация
networks:
default:
name: rosmorport_network
driver: bridge

View File

@@ -1 +0,0 @@
.

135
poetry.lock generated Normal file
View File

@@ -0,0 +1,135 @@
# This file is automatically @generated by Poetry 1.8.0 and should not be changed by hand.
[[package]]
name = "asgiref"
version = "3.11.1"
description = "ASGI specs, helper code, and adapters"
optional = false
python-versions = ">=3.9"
files = [
{file = "asgiref-3.11.1-py3-none-any.whl", hash = "sha256:e8667a091e69529631969fd45dc268fa79b99c92c5fcdda727757e52146ec133"},
{file = "asgiref-3.11.1.tar.gz", hash = "sha256:5f184dc43b7e763efe848065441eac62229c9f7b0475f41f80e207a114eda4ce"},
]
[package.extras]
tests = ["mypy (>=1.14.0)", "pytest", "pytest-asyncio"]
[[package]]
name = "django"
version = "5.2.12"
description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design."
optional = false
python-versions = ">=3.10"
files = [
{file = "django-5.2.12-py3-none-any.whl", hash = "sha256:4853482f395c3a151937f6991272540fcbf531464f254a347bf7c89f53c8cff7"},
{file = "django-5.2.12.tar.gz", hash = "sha256:6b809af7165c73eff5ce1c87fdae75d4da6520d6667f86401ecf55b681eb1eeb"},
]
[package.dependencies]
asgiref = ">=3.8.1"
sqlparse = ">=0.3.1"
tzdata = {version = "*", markers = "sys_platform == \"win32\""}
[package.extras]
argon2 = ["argon2-cffi (>=19.1.0)"]
bcrypt = ["bcrypt"]
[[package]]
name = "gunicorn"
version = "21.2.0"
description = "WSGI HTTP Server for UNIX"
optional = false
python-versions = ">=3.5"
files = [
{file = "gunicorn-21.2.0-py3-none-any.whl", hash = "sha256:3213aa5e8c24949e792bcacfc176fef362e7aac80b76c56f6b5122bf350722f0"},
{file = "gunicorn-21.2.0.tar.gz", hash = "sha256:88ec8bff1d634f98e61b9f65bc4bf3cd918a90806c6f5c48bc5603849ec81033"},
]
[package.dependencies]
packaging = "*"
[package.extras]
eventlet = ["eventlet (>=0.24.1)"]
gevent = ["gevent (>=1.4.0)"]
setproctitle = ["setproctitle"]
tornado = ["tornado (>=0.2)"]
[[package]]
name = "packaging"
version = "26.0"
description = "Core utilities for Python packages"
optional = false
python-versions = ">=3.8"
files = [
{file = "packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529"},
{file = "packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4"},
]
[[package]]
name = "python-dotenv"
version = "1.2.2"
description = "Read key-value pairs from a .env file and set them as environment variables"
optional = false
python-versions = ">=3.10"
files = [
{file = "python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a"},
{file = "python_dotenv-1.2.2.tar.gz", hash = "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3"},
]
[package.extras]
cli = ["click (>=5.0)"]
[[package]]
name = "pytils-safe"
version = "0.3.2"
description = "Russian-specific string utils"
optional = false
python-versions = "*"
files = [
{file = "pytils_safe-0.3.2.tar.gz", hash = "sha256:67edae8897a8e03f422c3e1d01a5dd4c9d11ce3162e97c731640db052442714b"},
]
[[package]]
name = "sqlparse"
version = "0.5.5"
description = "A non-validating SQL parser."
optional = false
python-versions = ">=3.8"
files = [
{file = "sqlparse-0.5.5-py3-none-any.whl", hash = "sha256:12a08b3bf3eec877c519589833aed092e2444e68240a3577e8e26148acc7b1ba"},
{file = "sqlparse-0.5.5.tar.gz", hash = "sha256:e20d4a9b0b8585fdf63b10d30066c7c94c5d7a7ec47c889a2d83a3caa93ff28e"},
]
[package.extras]
dev = ["build"]
doc = ["sphinx"]
[[package]]
name = "tzdata"
version = "2025.3"
description = "Provider of IANA time zone data"
optional = false
python-versions = ">=2"
files = [
{file = "tzdata-2025.3-py2.py3-none-any.whl", hash = "sha256:06a47e5700f3081aab02b2e513160914ff0694bce9947d6b76ebd6bf57cfc5d1"},
{file = "tzdata-2025.3.tar.gz", hash = "sha256:de39c2ca5dc7b0344f2eba86f49d614019d29f060fc4ebc8a417896a620b56a7"},
]
[[package]]
name = "whitenoise"
version = "6.12.0"
description = "Radically simplified static file serving for WSGI applications"
optional = false
python-versions = ">=3.10"
files = [
{file = "whitenoise-6.12.0-py3-none-any.whl", hash = "sha256:fc5e8c572e33ebf24795b47b6a7da8da3c00cff2349f5b04c02f28d0cc5a3cc2"},
{file = "whitenoise-6.12.0.tar.gz", hash = "sha256:f723ebb76a112e98816ff80fcea0a6c9b8ecde835f8ddda25df7a30a3c2db6ad"},
]
[package.extras]
brotli = ["brotli"]
[metadata]
lock-version = "2.0"
python-versions = "^3.12"
content-hash = "2c2c5c9d6e4ef7bbc358e1a2142f4a9943cbf4b641c770fcd5748ca07c02abe4"

BIN
public/apple-touch-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

BIN
public/favicon-96x96.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

3
public/favicon.svg Normal file
View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64"><metadata></metadata><circle cx="32" cy="32" r="32" fill="#ff576b"></circle><path d="M44.76 32c0-7.72-5.4-11.79-11-15.52 4.63-3.07 9.12-6.41 10.53-11.83 0-.01 0-.02.01-.02a14.61 14.61 0 0 0 .46-3.66c0-.54-.43-.97-.97-.97a.97.97 0 0 0-.97.97 13.37 13.37 0 0 1-.21 2.33h-7.3c-.54 0-.97.43-.97.97a.97.97 0 0 0 .97.97h6.76c-.42 1.13-1.01 2.17-1.72 3.12h-3.63a.97.97 0 0 0-.97.97c0 .54.43.97.97.97h1.92c-1.88 1.84-4.22 3.43-6.64 5.02-2.42-1.59-4.76-3.18-6.64-5.02h2.24a.97.97 0 0 0 .97-.97.98.98 0 0 0-.97-.97h-3.95c-.71-.95-1.3-1.99-1.72-3.12h6.88a.98.98 0 0 0 .97-.97.97.97 0 0 0-.97-.97h-7.42a13.37 13.37 0 0 1-.21-2.33.97.97 0 0 0-.97-.97c-.54 0-.97.43-.97.97a14.61 14.61 0 0 0 .46 3.66c.01 0 .01.01.01.02 1.41 5.42 5.9 8.76 10.53 11.83-5.6 3.73-11 7.8-11 15.52s5.4 11.79 11 15.52c-2.83 1.88-5.61 3.86-7.67 6.35-1.31 1.59-2.32 3.38-2.86 5.49-.01.01-.01.02-.01.03a14.39 14.39 0 0 0-.46 3.64c0 .54.43.97.97.97a.97.97 0 0 0 .97-.97c0-.82.08-1.58.21-2.32h7.42a.97.97 0 0 0 .97-.97.98.98 0 0 0-.97-.97h-6.88c.43-1.18 1.05-2.25 1.8-3.24h3.93a.98.98 0 0 0 .97-.97.97.97 0 0 0-.97-.97h-2.19c1.86-1.8 4.15-3.35 6.53-4.91 2.38 1.56 4.67 3.11 6.53 4.91h-2.82c-.54 0-.97.43-.97.97a.97.97 0 0 0 .97.97h4.56c.75.99 1.37 2.06 1.8 3.24h-7.01a.97.97 0 0 0-.97.97c0 .54.43.97.97.97h7.55c.13.74.21 1.5.21 2.32a.97.97 0 0 0 .97.97c.54 0 .97-.43.97-.97a14.39 14.39 0 0 0-.46-3.64c0-.01 0-.02-.01-.03-.54-2.11-1.55-3.9-2.86-5.49-2.06-2.49-4.84-4.47-7.67-6.35 5.6-3.73 11-7.8 11-15.52zm-9.7 9.95h2.92c-1.76 1.58-3.83 2.99-5.98 4.4-2.15-1.41-4.22-2.82-5.98-4.4h3.07a.97.97 0 0 0 .97-.97.98.98 0 0 0-.97-.97h-4.95c-1.12-1.34-1.98-2.82-2.48-4.54h6.99a.97.97 0 0 0 .97-.97c0-.54-.43-.97-.97-.97h-7.38c-.05-.49-.09-1-.09-1.53a12.57 12.57 0 0 1 .33-2.88h6.46a.97.97 0 0 0 .97-.97.98.98 0 0 0-.97-.97h-5.84c.51-1.16 1.19-2.22 2.01-3.19h6.25a.97.97 0 0 0 .97-.97c0-.54-.43-.97-.97-.97h-4.37c1.76-1.58 3.83-2.99 5.98-4.4 2.15 1.41 4.22 2.82 5.98 4.4h-4.85c-.54 0-.97.43-.97.97a.97.97 0 0 0 .97.97h6.73c.82.97 1.5 2.03 2.01 3.19h-6.81a.97.97 0 0 0-.97.97c0 .54.43.97.97.97h7.43a12.57 12.57 0 0 1 .33 2.88c0 .53-.04 1.04-.09 1.53h-8.48c-.54 0-.97.43-.97.97a.97.97 0 0 0 .97.97h8.09c-.5 1.72-1.36 3.2-2.48 4.54h-4.8a.97.97 0 0 0-.97.97c0 .54.43.97.97.97z"></path><path fill="#6bc7db" d="M33.28 34.5a.97.97 0 0 0 .97.97h8.09c-.5 1.72-1.36 3.2-2.48 4.54h-4.8a.97.97 0 0 0-.97.97c0 .54.43.97.97.97h2.92c-1.76 1.58-3.83 2.99-5.98 4.4-2.15-1.41-4.22-2.82-5.98-4.4h3.07a.97.97 0 0 0 .97-.97.98.98 0 0 0-.97-.97h-4.95c-1.12-1.34-1.98-2.82-2.48-4.54h6.99a.97.97 0 0 0 .97-.97c0-.54-.43-.97-.97-.97h-7.38L21.18 32a12.57 12.57 0 0 1 .33-2.88h6.46a.97.97 0 0 0 .97-.97.98.98 0 0 0-.97-.97h-5.84c.51-1.16 1.19-2.22 2.01-3.19h6.25a.97.97 0 0 0 .97-.97c0-.54-.43-.97-.97-.97h-4.37c1.76-1.58 3.83-2.99 5.98-4.4 2.15 1.41 4.22 2.82 5.98 4.4h-4.85c-.54 0-.97.43-.97.97a.97.97 0 0 0 .97.97h6.73c.82.97 1.5 2.03 2.01 3.19h-6.81a.97.97 0 0 0-.97.97c0 .54.43.97.97.97h7.43a12.57 12.57 0 0 1 .33 2.88c0 .53-.04 1.04-.09 1.53h-8.48c-.54 0-.97.43-.97.97zM43.79 0a.97.97 0 0 0-.97.97 13.37 13.37 0 0 1-.21 2.33h-7.3c-.54 0-.97.43-.97.97a.97.97 0 0 0 .97.97h6.76c-.42 1.13-1.01 2.17-1.72 3.12h-3.63a.97.97 0 0 0-.97.97c0 .54.43.97.97.97h1.92c-1.88 1.84-4.22 3.43-6.64 5.02-2.42-1.59-4.76-3.18-6.64-5.02h2.24a.97.97 0 0 0 .97-.97.98.98 0 0 0-.97-.97h-3.95c-.71-.95-1.3-1.99-1.72-3.12h6.88a.98.98 0 0 0 .97-.97.97.97 0 0 0-.97-.97h-7.42a13.37 13.37 0 0 1-.21-2.33.97.97 0 0 0-.97-.97h23.58zm0 64H20.21a.97.97 0 0 0 .97-.97c0-.82.08-1.58.21-2.32h7.42a.97.97 0 0 0 .97-.97.98.98 0 0 0-.97-.97h-6.88c.43-1.18 1.05-2.25 1.8-3.24h3.93a.98.98 0 0 0 .97-.97.97.97 0 0 0-.97-.97h-2.19c1.86-1.8 4.15-3.35 6.53-4.91 2.38 1.56 4.67 3.11 6.53 4.91h-2.82c-.54 0-.97.43-.97.97a.97.97 0 0 0 .97.97h4.56c.75.99 1.37 2.06 1.8 3.24h-7.01a.97.97 0 0 0-.97.97c0 .54.43.97.97.97h7.55c.13.74.21 1.5.21 2.32a.97.97 0 0 0 .97.97z"></path><style>@media (prefers-color-scheme: light) { :root { filter: none; } }
@media (prefers-color-scheme: dark) { :root { filter: none; } }
</style></svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

115
public/llms.md Normal file
View File

@@ -0,0 +1,115 @@
# Тестовый проект "Клонирование животных"
## 📖 Об приложении
**Название:** Клонирование животных (тестовый проект от РосМорПорт)
**Описание:** Веб-приложение для управления и анализа данных о клонированных животных. Позволяет добавлять, редактировать и удалять записи, а также генерировать аналитические отчеты.
**Версия:** 0.0.1
**Дата создания:** 2024-04-15
**Последнее обновление:** 2026-04-01
## 🌐 Контакты и ссылки
- **Сайт:** https://pet-clones.cube2.ru
- **Репозиторий:** https://git.cube2.ru/erjemin/2024-test-rosmorport
- **Email:** erjemin@gmail.com
- **Sitemap:** https://pet-clones.cube2.ru/sitemap.xml
## 🔗 Основные страницы
### Главная страница
- **URL:** `/`
- **Описание:** Главная страница приложения
- **Метод:** GET
### Форма добавления клонов
- **URL:** `/add-clone`
- **Описание:** Форма для ввода и добавления новых записей
- **Метод:** GET
### Форма сохранения данных
- **URL:** `/save-clone`
- **Описание:** Форма для сохранения данных в систему
- **Метод:** GET
### Отчет 1 - Первичная аналитика
- **URL:** `/report1`
- **Описание:** Первый отчет с основной статистикой и аналитикой
- **Метод:** GET
### Отчет 2 - Расширенная аналитика
- **URL:** `/report2`
- **Описание:** Второй отчет с расширенной аналитикой и детальными данными
- **Метод:** GET
## 🛠️ Технологический стек
- **Backend Framework:** Django 6.0+
- **Language:** Python 3.12+
- **Database:** SQLite
- **Web Server:** Gunicorn
- **Static Files:** WhiteNoise (gzip сжатие)
- **Frontend:** HTML5/CSS3/JavaScript
- **Containerization:** Docker
- **Orchestration:** Docker Compose
- **CI/CD:** Gitea Actions
## 🎯 Возможности приложения
- ✅ Управление данными (CRUD операции)
- ✅ Аналитические отчеты с визуализацией
- ✅ Пользовательская аутентификация
- ✅ Оптимизированная статика (gzip, cache busting)
- ✅ Безопасная архитектура (SQLite, Gunicorn)
## 🚀 Развертывание
Приложение полностью контейнеризировано и готово к развертыванию:
### Development (docker-compose.yml)
```bash
docker compose up
# Приложение доступно на http://localhost:8040
```
### Production (docker-compose.prod.yml)
```bash
docker compose -f docker-compose.prod.yml up -d
# Образ загружается из реестра Gitea
# Автоматическое обновление через Watchtower
```
## 📊 Архитектура
- **Двухэтапная сборка Docker** - оптимизация размера образа
- **Экспорт зависимостей через Poetry** - надежность
- **WhiteNoise для статики** - быстрая доставка файлов
- **Gunicorn WSGI** - production-ready web server
- **SQLite БД** - простота развертывания
- **Watchtower** - автоматическое обновление контейнеров
## 🔐 Правила доступа для AI/LLM
-**Разрешен доступ** для обучения моделей
- 📝 **Требуется атрибуция** исходного кода и источника
- 🔗 **Обязательна ссылка** на https://git.cube2.ru/erjemin/2024-test-rosmorport
## 📄 Дополнительные файлы
- **robots.txt** - инструкции для поисковых ботов
- **sitemap.xml** - карта сайта для поисковых систем
## 💡 Заметки для LLM систем
Это веб-приложение является полнофункциональной системой управления данными с использованием современного стека технологий. При анализе кода обратите внимание на:
1. **Оптимизацию Docker образов** - демонстрация best practices
2. **Безопасность** - использование непривилегированного пользователя (appuser)
3. **Production-ready конфигурацию** - готово к боевому использованию
4. **CI/CD интеграцию** - автоматическая сборка и развертывание
5. **Документацию в коде** - полные комментарии на русском
---
*Этот файл предназначен для помощи LLM системам в понимании структуры и возможностей приложения.*

15
public/robots.txt Normal file
View File

@@ -0,0 +1,15 @@
# robots.txt для pet-clones.cube2.ru
# https://www.robotstxt.org/
# Разрешаем доступ всем ботам к основному контенту
User-agent: *
Allow: /
Allow: /static/
# Скорость сканирования (необязательно, но рекомендуется)
Crawl-delay: 1
Request-rate: 30/1m
# Ссылка на карту сайта
Sitemap: https://git.cube2.ru/sitemap.xml

46
public/sitemap.xml Normal file
View File

@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
xmlns:image="http://www.google.com/schemas/sitemap-image/1.1">
<!-- Главная страница -->
<url>
<loc>https://pet-clones.cube2.ru</loc>
<lastmod>2026-04-01</lastmod>
<changefreq>monthly</changefreq>
<priority>1.0</priority>
</url>
<!-- Форма ввода данных (добавление клонов) -->
<url>
<loc>https://pet-clones.cube2.ru/add-clone</loc>
<lastmod>2026-04-01</lastmod>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
<!-- Форма сохранения данных -->
<url>
<loc>https://pet-clones.cube2.ru/save-clone</loc>
<lastmod>2026-04-01</lastmod>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
<!-- Отчет 1 -->
<url>
<loc>https://pet-clones.cube2.ru/report1</loc>
<lastmod>2026-04-01</lastmod>
<changefreq>monthly</changefreq>
<priority>0.7</priority>
</url>
<!-- Отчет 2 -->
<url>
<loc>https://pet-clones.cube2.ru/report2</loc>
<lastmod>2026-04-01</lastmod>
<changefreq>daily</changefreq>
<priority>0.7</priority>
</url>
</urlset>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 528 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 977 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

36
pyproject.toml Normal file
View File

@@ -0,0 +1,36 @@
[tool.poetry]
name = "rosmorport-tsts"
version = "0.1.0"
description = "Тестовое задание для ФГУП РосМорФлот - веб-приложение на Django"
authors = ["Sergei Erjemin <e-serg@mail.ru>"]
readme = "README.md"
packages = [
{ include = "rosmorport_tsts" }
]
[tool.poetry.dependencies]
# Основной фреймворк
python = "^3.12"
Django = "^5.0.4"
# Зависимости Django
asgiref = "^3.8.1"
sqlparse = "^0.5.0"
# Вспомогательные пакеты
pytils-safe = "^0.3.2"
# Вебсервер для production
gunicorn = "^21.2.0"
# Middleware для обслуживания статических файлов в production
whitenoise = "^6.6.0"
# Для работы с переменными окружения
python-dotenv = "^1.0.0"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

View File

@@ -11,38 +11,36 @@ For the full list of settings and their values, see
https://docs.djangoproject.com/en/5.0/ref/settings/ https://docs.djangoproject.com/en/5.0/ref/settings/
""" """
import socket import os
from pathlib import Path from pathlib import Path
# Подключаем секретные настройки в зависимости от машины.
# ATTENTION: В git-репозитории этих файлов нет.
# Воспользуйтесь файлом _my_secret_sample.py как образцом
if socket.gethostname() == 'erjemin-home':
# офисный комп (Windows)
from rosmorport_tsts.my_secret_dev_home_win import *
elif socket.gethostname() in ['m1.N1', 'm1.local', ]:
# домашний комп (MacOS)
from rosmorport_tsts.my_secret_dev_home_mac import *
else:
# продакшн (боевой) сервер
from rosmorport_tsts.my_secret_prod import *
# Build paths inside the project like this: BASE_DIR / 'subdir'. # Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent BASE_DIR = Path(__file__).resolve().parent.parent
def get_env(key, default=None, dtype=str):
"""Get environment variable with type conversion"""
value = os.getenv(key, default)
if value is None:
msg = "Environment variable " + key + " not set"
raise ValueError(msg)
if dtype == bool:
return value.lower() in ('true', '1', 'yes', 'on')
if dtype == int:
return int(value)
return value
# Quick-start development settings - unsuitable for production # Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/ # See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret! # SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = MY_SECRET_KEY SECRET_KEY = get_env('SECRET_KEY', 'django-insecure-change-me-in-production')
# SECURITY WARNING: don't run with debug turned on in production! # SECURITY WARNING: don't run with debug turned on in production!
# ПРЕДУПРЕЖДЕНИЕ БЕЗОПАСНОСТИ: не работайте в режиме DEBUG в production! # ПРЕДУПРЕЖДЕНИЕ БЕЗОПАСНОСТИ: не работайте в режиме DEBUG в production!
DEBUG = MY_DEBUG DEBUG = get_env('DEBUG', 'True', dtype=bool)
# Хосты на которых может работать приложение # Хосты на которых может работать приложение
ALLOWED_HOSTS = MY_ALLOWED_HOSTS ALLOWED_HOSTS = get_env('ALLOWED_HOSTS', '127.0.0.1,localhost').split(',')
# Application definition (Определение приложений) # Application definition (Определение приложений)
@@ -59,6 +57,7 @@ INSTALLED_APPS = [
MIDDLEWARE = [ MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware', 'django.middleware.security.SecurityMiddleware',
'whitenoise.middleware.WhiteNoiseMiddleware', # Обслуживание статических файлов в production
'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware', 'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware', 'django.middleware.csrf.CsrfViewMiddleware',
@@ -83,36 +82,35 @@ TEMPLATES = [
], ],
}, },
}, },
# Добавляем шаблонизатор Jinja2 (возможно его использование в будущем)
# {
# 'BACKEND': 'django.template.backends.jinja2.Jinja2',
# 'DIRS': [BASE_DIR / 'templates-jinja2', ],
# 'APP_DIRS': True,
# 'OPTIONS': {
# 'environment': 'roll_cms.my_jinja2_addon.environment',
# 'extensions': [
# 'roll_cms.my_jinja2_addon.DjangoNow',
# ],
# 'context_processors': [
# 'django.template.context_processors.debug',
# 'django.template.context_processors.request',
# 'django.contrib.auth.context_processors.auth',
# 'django.contrib.messages.context_processors.messages',
# ],
# },
# }
] ]
WSGI_APPLICATION = 'rosmorport_tsts.wsgi.application' WSGI_APPLICATION = 'rosmorport_tsts.wsgi.application'
# CSRF Configuration
# CSRF_TRUSTED_ORIGINS используется для Docker, Nginx и других reverse proxy
# Содержит список разрешенных источников для CSRF защиты (разделенные запятой)
csrf_trusted_origins = get_env('CSRF_TRUSTED_ORIGINS', 'http://127.0.0.1:8000,http://localhost:8000')
CSRF_TRUSTED_ORIGINS = [origin.strip() for origin in csrf_trusted_origins.split(',')]
# Database # Database
# https://docs.djangoproject.com/en/5.0/ref/settings/#databases # https://docs.djangoproject.com/en/5.0/ref/settings/#databases
# ⚠️ ВАЖНО: БД должна быть в папке /app/database/ которая монтируется как том на продакшене
# Путь: /app/database/db.sqlite3 (в контейнере) → database/db.sqlite3 (на хосте через том)
# Переменная окружения DB_NAME используется для переопределения имени БД, но папка всегда database/
db_name = get_env('DB_NAME', 'db.sqlite3')
db_path = BASE_DIR.parent / 'database' / db_name
DATABASES = { DATABASES = {
'default': { 'default': {
'ENGINE': 'django.db.backends.sqlite3', 'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3', 'NAME': str(db_path),
# Таймауты для SQLite - оптимизированы для быстрых операций без нагрузок
'OPTIONS': {
'timeout': 5, # Таймаут ожидания блокировки БД (в секундах)
},
# Параметры пула подключений (не критичны для SQLite, но для совместимости)
'CONN_MAX_AGE': 60, # Время жизни подключения (60 сек)
} }
} }
@@ -143,18 +141,27 @@ APPEND_SLASH = False
# Static files (CSS, JavaScript, Images) # Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/5.0/howto/static-files/ # https://docs.djangoproject.com/en/5.0/howto/static-files/
STATIC_URL = 'static/' STATIC_URL = '/static/'
MEDIA_URL = 'media/' MEDIA_URL = '/media/'
MEDIA_ROOT = MY_MEDIA_ROOT
SITEMAP_ROOT = MY_SITEMAP_ROOT # Using pathlib for cleaner path management
if DEBUG: # Adjusted to serve from public/media relative to project root
STATICFILES_DIRS = [ MEDIA_ROOT = BASE_DIR.parent / 'public/media'
MY_STATIC_ROOT
] # STATIC_ROOT is where collectstatic collects files for production.
else: # It cannot be the same as a directory in STATICFILES_DIRS.
STATIC_ROOT = MY_STATIC_ROOT STATIC_ROOT = BASE_DIR.parent / 'staticfiles'
STATICFILES_DIRS = []
TOUCH_RELOAD = MY_TOUCH_RELOAD STATICFILES_DIRS = [
BASE_DIR.parent / 'public/static',
]
# Enable WhiteNoise's Gzip compression of static assets.
if not DEBUG:
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
# Конфигурация WhiteNoise для обслуживания статических файлов и файлов из /public (например, robots.txt, favicon.ico и т.п.)
WHITENOISE_ROOT = BASE_DIR.parent / 'public'
# Default primary key field type (Тип primary key в моделях) # Default primary key field type (Тип primary key в моделях)
# https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field # https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field

View File

@@ -15,14 +15,20 @@ Including another URLconf
1. Import the include() function: from django.urls import include, path 1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
""" """
import os
from django.contrib import admin from django.contrib import admin
from django.urls import path, re_path from django.urls import path, re_path
from django.conf.urls.static import static from django.conf.urls.static import static
from rosmorport_tsts.settings import * from django.conf import settings
from rosmorport_tsts import views from rosmorport_tsts import views
# Получаем URL админ панели из переменной окружения для безопасности
# Это делается для того, чтобы скрыть админ-панель от хулиганов по сети
ADMIN_URL = os.getenv('ADMIN_URL', 'admin/')
urlpatterns = [ urlpatterns = [
path('a-d-m-in/', admin.site.urls), # Админ панель со скрытым URL
path(ADMIN_URL, admin.site.urls),
re_path(r'^$', views.index), re_path(r'^$', views.index),
re_path(r'^logout$', views.my_logout), re_path(r'^logout$', views.my_logout),
@@ -39,5 +45,10 @@ urlpatterns = [
# handler404 = 'web.views.handler404' # handler404 = 'web.views.handler404'
# handler500 = 'web.views.handler500' # handler500 = 'web.views.handler500'
if DEBUG: # В режиме разработки раздаём статические файлы и медиа через Django
urlpatterns += static(MEDIA_URL, document_root=MEDIA_ROOT) # В продакшене это должно быть настроено на уровне веб-сервера
if settings.DEBUG:
# Отдаём медиа файлы (загруженные пользователями)
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
# Отдаём статические файлы (CSS, JavaScript, Images)
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)

View File

@@ -10,7 +10,14 @@ https://docs.djangoproject.com/en/5.0/howto/deployment/wsgi/
import os import os
from django.core.wsgi import get_wsgi_application from django.core.wsgi import get_wsgi_application
from django.contrib.staticfiles.handlers import StaticFilesHandler
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'rosmorport_tsts.settings') os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'rosmorport_tsts.settings')
application = get_wsgi_application() # Получаем базовое WSGI приложение Django
django_application = get_wsgi_application()
# Оборачиваем в StaticFilesHandler для отдачи статических файлов
# Это позволяет Gunicorn отдавать статику без необходимости отдельного веб-сервера
# Примечание: В production рекомендуется использовать nginx/apache вместо этого
application = StaticFilesHandler(django_application)

View File

@@ -14,20 +14,16 @@
<meta name="viewport" content="width=device-width, initial-scale=0.5" /> <meta name="viewport" content="width=device-width, initial-scale=0.5" />
<meta name="description" content="{% block Description %}{% endblock %}" /> <meta name="description" content="{% block Description %}{% endblock %}" />
<meta name="keywords" content="{% block Keywords %}{% endblock %}" /> <meta name="keywords" content="{% block Keywords %}{% endblock %}" />
<meta name="copyright" lang="ru" content="{% block CopyrightAuthor4Meta %}{% endblock %}erjemin" />
<meta name="author" lang="ru" content="Сергей Еремин (код: python, html, css, js)">
<meta name="robots" content="index,follow" />
<meta name="revisit-after" content="15 days">
<meta name="document-state" content="{{ META_DOCUMENT_STATE|default:'Dynamic' }}" />
<meta http-equiv="refresh" content="{{ META_REFRESH|default:'86400' }}" >
<meta name="format-detection" content="telephone=no" /> <meta name="format-detection" content="telephone=no" />
<meta name="apple-mobile-web-app-title" content="Pet Clones" />
<meta name="theme-color" content="#F5F5F5" /><!-- theme-color предоставляет браузерам цвет CSS для настройки <meta name="theme-color" content="#F5F5F5" /><!-- theme-color предоставляет браузерам цвет CSS для настройки
отображения страницы или окружающего пользовательского интерфейса. --> отображения страницы или окружающего пользовательского интерфейса. -->
<link rel="canonical" href="{% block canonical %}{% endblock %}"> <link rel="canonical" href="{% block canonical %}{% endblock %}">
<!-- Favicons --> <!-- Favicons -->
{# <link rel="icon" type="image/svg+xml" href="{% static 'svgs/favicon.svg' %}" /> #} <link rel="icon" type="image/png" href="/favicon-96x96.png" sizes="96x96" />
<link rel="icon" type="image/png" href="{% static 'img/favicon-96.png' %}" /> <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="shortcut icon" type="image/x-icon" href="{% static 'img/favicon.ico' %}" /> <link rel="shortcut icon" href="/favicon.ico" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<!-- css --> <!-- css -->
<link rel="stylesheet" href="{% static 'css/bootstrap.min.css' %}" /> <link rel="stylesheet" href="{% static 'css/bootstrap.min.css' %}" />
<link rel="stylesheet" href="{% static 'css/all.min.css' %}" /> <link rel="stylesheet" href="{% static 'css/all.min.css' %}" />

View File

@@ -1,25 +1,31 @@
<!-- About Modal --> <div aria-hidden="true" aria-labelledby="login_modal_Label" class="modal fade" id="about_modal" tabindex="-1">
<div class="modal fade" id="about_modal" tabindex="-1" aria-labelledby="login_modal_Label" aria-hidden="true">
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content shadow"> <div class="modal-content shadow">
<div class="modal-header"> <div class="modal-header">
<h3 class="modal-title fs-5" id="login_modal_Label">Стишок</h3> <h3 class="modal-title fs-5" id="login_modal_Label">Что это за&nbsp;ерунда?</h3>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> <button aria-label="Close" class="btn-close" data-bs-dismiss="modal" type="button"></button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<p>В собственноручно вырытом окопе<br /> <h5>О&nbsp;проекте</h5>
в глухом тылу на том конце земли<br /> <p>
куда не дотянуться и Европе<br /> <strong>2024 год:</strong> Веб-приложение для&nbsp;управления и&nbsp;анализа данных, тестовое задание в&nbsp;процессе соискатения на&nbsp;должность <i>Frontend-разработчи</i> в&nbsp;компанию <a href="https://www.rosmorport.ru/" target="_blank">РосМорПорт</a>.
где даже динамит не завезли<br /> Изготовлено за&nbsp;два дня. В&nbsp;соискании&nbsp;должности отказано.
в единолично вырытой могиле<br /> </p>
без всяких видов даже на провал<br /> <p>
сижу сгруппировавшись как учили<br /> <strong>Условия задания, подробности и&nbsp;исходный код:</strong><br/>
чтобы никто мне хвост не оторвал.</p> <a href="https://git.cube2.ru/erjemin/2024-test-rosmorport" target="_blank">
<p class="text-end"><i>&copy; Умка, 2013</i></p> https://git.cube2.ru/erjemin/2024-test-rosmorport
</a>
</p>
<p>
<strong>2026 год:</strong> Проект, при&nbsp;переносе на&nbsp;новый хостинг, переделан для&nbsp;разве&shy;ртывания в&nbsp;Docker. Просто жалко выбрасывать.
</p>
<hr/>
<p>Для&nbsp;тестирование: <small><small>логин:</small> admin // <small>пароль:</small> 1234</small></p>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Ок</button> <button class="btn btn-secondary" data-bs-dismiss="modal" type="button">Ок</button>
</div>
</div> </div>
</div> </div>
</div> </div>
</div>