Compare commits
37 Commits
8cbbd15829
...
v0.0.1
| Author | SHA1 | Date | |
|---|---|---|---|
| 6093dc2021 | |||
| 2a37e24242 | |||
| c529a74715 | |||
| 5f4769d10f | |||
| 261b72b04a | |||
| b128bbd79e | |||
| b97e82bcbb | |||
| 0b3eb517fe | |||
| 711b34af3d | |||
| 20d78194d2 | |||
| 37719ed31e | |||
| a9fb77c195 | |||
| 1b0fa5e500 | |||
| bbf35c0c24 | |||
| 08668fee6d | |||
| d846542e34 | |||
| 792d152be2 | |||
| 6e7a4c52e0 | |||
| 8385e04103 | |||
| 55980a0659 | |||
| ef80a66b69 | |||
| 493de32998 | |||
| 31cd78079a | |||
| 1a2865bae7 | |||
| d9e8c2d8bd | |||
|
|
a007337f75 | ||
|
|
c91d5f00b0 | ||
|
|
1a52626440 | ||
|
|
d8b1af4cda | ||
|
|
aacbe7e013 | ||
|
|
9d8a1bdf6b | ||
|
|
a8c156e25d | ||
|
|
5975923cbb | ||
|
|
3de98e01b3 | ||
|
|
e8c8cb393f | ||
|
|
65042dcebc | ||
|
|
04c204fc57 |
118
.dockerignore
Normal 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
@@ -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
|
||||
58
.gitea/workflows/docker-publish.yaml
Normal 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
@@ -0,0 +1,9 @@
|
||||
Вы - опытный помощник по программированию GitHub Copilot.
|
||||
Всегда отвечай на русском языке, если не указано иное.
|
||||
Комментируй код, который ты генерируешь, чтобы объяснить его функциональность на русском языке.
|
||||
Не удаляй старые комментарии. Код которых хочешь удалить скрывай в комментариях.
|
||||
При написании кода следуй стандартам PEP 8 для Python.
|
||||
Используй современные возможности Python (3.12+) и Django (6.0+).
|
||||
Предпочитай `poetry` для управления зависимостями.
|
||||
Подробнее комментировать каждое действие.
|
||||
Всегда спрашивай да/нет пред деплоем, git commit и git push.
|
||||
30
.gitignore
vendored
@@ -24,6 +24,9 @@
|
||||
.idea/**/uiDesigner.xml
|
||||
.idea/**/dbnavigator.xml
|
||||
|
||||
# IDE - GitHub Copilot state (local IDE state, not needed in repo)
|
||||
.idea/copilotDiffState.xml
|
||||
|
||||
# Gradle
|
||||
.idea/**/gradle.xml
|
||||
.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/
|
||||
# in your Git repository. Update and uncomment the following line accordingly.
|
||||
# <django-project-name>/staticfiles/
|
||||
staticfiles/
|
||||
|
||||
# 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
@@ -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
@@ -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="<map/>" />
|
||||
<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>
|
||||
6
.idea/copilot.data.migration.ask2agent.xml
generated
Normal 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>
|
||||
10
.idea/inspectionProfiles/Project_Default.xml
generated
Normal 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>
|
||||
6
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal 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
@@ -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
@@ -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
@@ -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
@@ -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"]
|
||||
|
||||
19
README.md
@@ -1,5 +1,20 @@
|
||||
## Тестовое задание rosmorport
|
||||
|
||||
* [Задание](target.md) (таким, как оно было выдано, авторская орфография и пунктуация).
|
||||
* Пакеты virtualenv: [dev (home win)](requare_dev_w_home.txt) / [prod (ubuntu)](requare_dev_prod.txt).
|
||||
**2024 год**: _14 апреля (воскресенье)_
|
||||
|
||||
Тестовое задание на соискание вакансии `Frontend-разработчик` в ФГУП РосМорФлот.
|
||||
|
||||
* [Тестовое задание](target.md) (таким, как оно было выдано, авторская орфография и пунктуация).
|
||||
* [Мой ответ](my_anwer.txt) (спустя два дня)
|
||||
* Развёрнуто на [pet-clones.cube2.ru](https://pet-clones.cube2.ru/) (login: admin / pwd: 1234)
|
||||
* [Ответ РосМорФлот](feedback.txt) спустя четыре дня, после нескольких обращений об обратной связи... судя по логам
|
||||
заходил один раз, никих действий (новых записей) произведено не было.
|
||||
|
||||
Мое мнение о тестовом задании: в задании нет никаких рекомендаций, ограничений и запретов на
|
||||
какие-либо _выбранные технологии_... Зачем задании столько требований по Backend, если вакансия
|
||||
`Frontend-разработчик` -- загадка. И, похоже, дополнительно нужен навык чтения мыслей на расстоянии.
|
||||
|
||||
---
|
||||
|
||||
**2026 год**: _1 апреля (среда)_
|
||||
Проект, при переносе на новый хостинг, переделан для развертывания в Docker. Просто жалко выбрасывать.
|
||||
122
config/nginx/pet-clones--external-nginx.conf
Normal 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
|
||||
@@ -1,85 +0,0 @@
|
||||
# ТЕСТОВОЕ ЗАДАНИЕ РОСМОРПОРТ
|
||||
# == Конфикурационный файл pets-clone--nginx.conf ==
|
||||
|
||||
# Описываем апстрим-потоки которые должен подключить Nginx
|
||||
# Для каждого сайта надо настроить свой поток, со своим уникальным именем.
|
||||
# Если будете настраивать несколько python (django) сайтов - измените название upstream
|
||||
|
||||
upstream pet-clone {
|
||||
# расположение файла Unix-сокет для взаимодействие с uwsgi
|
||||
server unix:///home/web/clone.cocorico.ru/socket/clone_pets.sock;
|
||||
# также можно использовать веб-сокет (порт) для взаимодействие с uwsgi. Но это медленнее
|
||||
# server 127.0.0.1:8021; # для взаимодействия с uwsgi через веб-порт 8021
|
||||
}
|
||||
|
||||
# конфигурируем сервер
|
||||
server {
|
||||
server_name clone.cocorico.ru; # доменное имя сайта
|
||||
listen 443 ssl; # managed by Certbot
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/clone.cocorico.ru/fullchain.pem; # managed by Certbot
|
||||
ssl_certificate_key /etc/letsencrypt/live/clone.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_protocols TLSv1 TLSv1.1;
|
||||
# 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';
|
||||
resolver 217.16.16.15 217.16.20.15 217.16.22.15 217.16.16.31 77.88.8.8 valid=10s;
|
||||
ssl_session_cache builtin:1000 shared:SSL:25m;
|
||||
keepalive_requests 200;
|
||||
keepalive_timeout 75s;
|
||||
|
||||
charset utf-8; # кодировка по умолчанию
|
||||
|
||||
access_log /home/web/clone.cocorico.ru/logs/clone-pets-access.log; # логи с доступом
|
||||
error_log /home/web/clone.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/clone.cocorico.ru/public/media; } # Расположение media-файлов Django
|
||||
location /static { alias /home/web/clone.cocorico.ru/public/static; } # Расположение static-файлов Django
|
||||
|
||||
location /robots.txt { root /home/web/clone.cocorico.ru/public; } # Расположение robots.txt
|
||||
location /favicon.ico { root /home/web/clone.cocorico.ru/public/static/img; } # Расположение favicon.ico
|
||||
location /favicon.png { root /home/web/clone.cocorico.ru/public/static/img; } # Расположение favicon
|
||||
location = /404.html {
|
||||
root /home/web/clone.cocorico.ru/rosmorport_tsts/templates-django/404.html;
|
||||
internal;
|
||||
}
|
||||
location = /500.html {
|
||||
root /home/web/clone.cocorico.ru/rosmorport_tsts/templates-django/500.html;
|
||||
internal;
|
||||
}
|
||||
location ~ \.(xml|html|htm|txt|svg)$ {
|
||||
root /home/web/clone.cocorico.ru/public; # Расположение статичных *.xml, *.html и *.txt
|
||||
}
|
||||
|
||||
location / {
|
||||
# uwsgi_pass pet-clone; # upstream обрабатывающий обращений
|
||||
# # uwsgi_pass 127.0.0.1:8001; # upstream обрабатывающий обращений
|
||||
# include uwsgi_params; # конфигурационный файл uwsgi;
|
||||
# uwsgi_read_timeout 1800; # вдруг некоторые запросы очень долго обрабатываются?
|
||||
# uwsgi_send_timeout 200; # на всякий случай время записи в сокет тоже побольше...
|
||||
proxy_pass http://127.0.0.1:8080;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
if ($host = clone.cocorico.ru) {
|
||||
return 301 https://$host$request_uri;
|
||||
} # managed by Certbot
|
||||
|
||||
|
||||
listen 80;
|
||||
server_name clone.cocorico.ru;
|
||||
return 404; # managed by Certbot
|
||||
|
||||
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
# === Конфикурационный файл uwsgi pets-clone--uwsgi.ini ===
|
||||
[uwsgi]
|
||||
|
||||
# НАСТРОЙКИ ДЛЯ DJANGO
|
||||
# Корневая папка проекта (полный путь)
|
||||
chdir: /home/web/clone.cocorico.ru/rosmorport_tsts
|
||||
# Django wsgi файл rosmorport_tsts/wsgi.py записываем так:
|
||||
module: rosmorport_tsts.wsgi
|
||||
# полный путь к виртуальному окружению
|
||||
home: /home/web/clone.cocorico.ru/env
|
||||
# полный путь к файлу сокета
|
||||
# route: ^/websocket 127.0.0.1:8021
|
||||
socket: unix:///home/web/clone.cocorico.ru/socket/clone_pets.sock
|
||||
# Исходящие сообщения в лог
|
||||
daemonize: /home/web/clone.cocorico.ru/logs/clone-pets-uwsgi.log
|
||||
|
||||
# ЗАГАДОЧНЫЕ НАСТРОЙКИ, ПО ИДЕЕ ОНИ НУЖНЫ, НО И БЕЗ НИХ ВСЁ РАБОТАЕТ
|
||||
# расположение wsgi.py
|
||||
# wsgi-file: /home/web/clone.cocorico.ru/rosmorport_tsts/rosmorport_tsts/wsgi.py
|
||||
# расположение виртуального окружения (как оно работает если этот параметр не указан, не ясно)
|
||||
virtualenv: /home/web/clone.cocorico.ru/env
|
||||
# имя файла при изменении которого происходит авторестарт приложения
|
||||
# (когда этого параметра нет, то гичего не авторестартится, но с ним все рестартится.
|
||||
# Cтоит изменить любой Python-исходник проекта, как изменения сразу вступают в силу.
|
||||
touch-reload: /home/web/clone.cocorico.ru/logs/touchreload.txt
|
||||
py-autoreload: 5
|
||||
|
||||
# НАСТРОЙКИ ОБЩИЕ
|
||||
# быть master-процессом
|
||||
master: true
|
||||
# максимальное количество процессов
|
||||
processes: 1
|
||||
# если uWSGI устнаовлен как сервис через apt-get то нужно установить еще плугин:
|
||||
# sudo apt-get install uwsgi-plugin-python3
|
||||
# и добавить в этот конфиг: plugin: python3
|
||||
plugin: python3
|
||||
# права доступа к файлу сокета. По умолчанию должно хватать 664. Но каких-то прав не хватает, поэтому 666.
|
||||
chmod-socket: 777
|
||||
# очищать окружение от служебных файлов 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: к этой группе относится nginх, и ранее мы включили в эту группу нашего [user]
|
||||
# uid : nginx
|
||||
# gid : nginx
|
||||
# uid : www-data
|
||||
# gid : www-data
|
||||
uid: web
|
||||
gid: web
|
||||
|
||||
|
||||
print: ---------------- Запущен uWSGI ----------------
|
||||
0
database/.gitkeep
Normal file
107
docker-compose.prod.yml
Normal 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
@@ -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
|
||||
|
||||
3
feedback.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
Мы ознакомились с результатом выполнения тестового задания и отметили творческий подход,
|
||||
однако выбранные технологии и качество программной реализации не соответствуют нашим
|
||||
стандартам.
|
||||
@@ -1 +0,0 @@
|
||||
.
|
||||
13
my_anwer.txt
Normal file
@@ -0,0 +1,13 @@
|
||||
Добрый день ....
|
||||
|
||||
Не успел сделать прокрутку в tbody ... закопался в деплоем (попался на RFLF гWSGI-конфига) :(
|
||||
|
||||
В целом, в задании, мне кажется, иногда путают высоту и ширину (в блоке шириной 75px даже слово
|
||||
из 8 букв не уместить, тем более меню которые надо выравнивать странно). Так что делал как посчитал
|
||||
разумным. Прошу извинить.
|
||||
|
||||
Проект: https://pet-clones.cocorico.ru/
|
||||
Гит: https://git.cube2.ru/erjemin/2024-test-rosmorport
|
||||
|
||||
login: admin
|
||||
pwd: 1234
|
||||
135
poetry.lock
generated
Normal 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
|
After Width: | Height: | Size: 7.0 KiB |
BIN
public/favicon-96x96.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
public/favicon.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
3
public/favicon.svg
Normal 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
@@ -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
@@ -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
@@ -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>
|
||||
|
||||
|
Before Width: | Height: | Size: 528 B |
|
Before Width: | Height: | Size: 977 B |
|
Before Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 4.2 KiB |
36
pyproject.toml
Normal 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"
|
||||
|
||||
|
||||
49
rosmorport_tsts/rosmorport_tsts/_my_secret__sample.py
Normal file
@@ -0,0 +1,49 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# ВСЕ СЕКРЕТНЫЕ НАСТРОЙКИ ПРОЕКТА ДЛЯ РАЗВЕРТЫВАНИЯ НА ПРОДАКШЕНЕ (турция)
|
||||
"""
|
||||
В этот файл вынесены все секретные настройки, чтобы не светить их в settings.py
|
||||
Например, при размещении в публичный репозиториях.
|
||||
"""
|
||||
|
||||
MY_DEBUG = False
|
||||
|
||||
# Хосты на которых может работать приложение
|
||||
MY_ALLOWED_HOSTS = [
|
||||
'127.0.0.1',
|
||||
'localhost',
|
||||
'pet-clones.cocorico.ru', # prod
|
||||
]
|
||||
|
||||
|
||||
# Ключ Django
|
||||
MY_SECRET_KEY = 'django-insecure-sample---secrets-must-be-kept-secret-SSxk75QmR6w37zu5okbthaNDCMQtPQ7kn7wYQcGPKENq'
|
||||
|
||||
# Настройки для сообщений об ошибках когда все упало и т.п.
|
||||
MY_ADMINS = (
|
||||
('S.Erjemin', 'erjemin@gmail.com'),
|
||||
)
|
||||
|
||||
#########################################
|
||||
# настройки для почтового сервера
|
||||
MY_EMAIL = 'admin@you.site'
|
||||
MY_EMAIL_FROM = 'admin@you.site'
|
||||
MY_EMAIL_HOST = 'smtp.mail.ru' # host
|
||||
MY_EMAIL_HOST_USER = 'admin@you.site' # login
|
||||
MY_EMAIL_HOST_PASSWORD = 'secrets-must-be-kept-secret' # password
|
||||
MY_EMAIL_PORT = 2525 # port
|
||||
MY_EMAIL_USE_TLS = True
|
||||
|
||||
# Настройки подключения к БД MySQL
|
||||
MY_DATABASE_HOST = 'localhost' # db-host
|
||||
MY_DATABASE_NAME = 'db-name' # db-name
|
||||
MY_DATABASE_PORT = '3306'
|
||||
MY_DATABASE_USER = 'db-user'
|
||||
MY_DATABASE_PASSWORD = 'secrets-must-be-kept-secret'
|
||||
|
||||
MY_TOUCH_RELOAD = '/home/web/you.site/logs/touchreload.txt'
|
||||
|
||||
MY_MEDIA_ROOT = '/home/web/you.site/public/media'
|
||||
|
||||
MY_STATIC_ROOT = '/home/web/you.site/public/static/'
|
||||
|
||||
MY_SITEMAP_ROOT = '/home/web/you.site/public'
|
||||
@@ -11,19 +11,19 @@ class TbPetsClones(models.Model):
|
||||
"""Модель для хранения клонов питомцев"""
|
||||
class PetType(models.IntegerChoices):
|
||||
"""Тип питомца"""
|
||||
DOG = 1, "Собачка"
|
||||
CAT = 2, "Кошечка"
|
||||
BIR = 3, "Птичка"
|
||||
FIS = 6, "Рыбка"
|
||||
ROD = 4, "Грызун"
|
||||
REP = 5, "Рептилия"
|
||||
OTH = 0, "Неизвестная зверушка"
|
||||
DOG = 1, "🐕 Собачка"
|
||||
CAT = 2, "🐈 Кошечка"
|
||||
BIR = 3, "🦜 Птичка"
|
||||
ROD = 4, "🐟 Рыбка"
|
||||
REP = 5, "🦎 Рептилия"
|
||||
FIS = 6, "🐜 Насекомое"
|
||||
OTH = 0, "👾 Неизвестная зверушка"
|
||||
|
||||
class PetSex(models.IntegerChoices):
|
||||
"""Пол питомца"""
|
||||
MA = 1, "Мужской"
|
||||
FE = 2, "Женский"
|
||||
UN = 0, "Неизвестно"
|
||||
MA = 1, "♂ Мужской"
|
||||
FE = 2, "♀ Женский"
|
||||
UN = 0, "⸰ Неизвестно"
|
||||
|
||||
iPetType = models.SmallIntegerField(
|
||||
default=PetType.OTH,
|
||||
|
||||
@@ -11,38 +11,36 @@ For the full list of settings and their values, see
|
||||
https://docs.djangoproject.com/en/5.0/ref/settings/
|
||||
"""
|
||||
|
||||
import socket
|
||||
import os
|
||||
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 *
|
||||
elif socket.gethostname() in ['orangepi5', 'srv05962228.ultasrv.net']:
|
||||
# продакшн (боевой) сервер
|
||||
from rosmorport_tsts.my_secret_prod import *
|
||||
|
||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||
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
|
||||
# See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/
|
||||
|
||||
# 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!
|
||||
# ПРЕДУПРЕЖДЕНИЕ БЕЗОПАСНОСТИ: не работайте в режиме 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 (Определение приложений)
|
||||
@@ -59,6 +57,7 @@ INSTALLED_APPS = [
|
||||
|
||||
MIDDLEWARE = [
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
'whitenoise.middleware.WhiteNoiseMiddleware', # Обслуживание статических файлов в production
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'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'
|
||||
|
||||
# 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
|
||||
# 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 = {
|
||||
'default': {
|
||||
'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)
|
||||
# https://docs.djangoproject.com/en/5.0/howto/static-files/
|
||||
|
||||
STATIC_URL = 'static/'
|
||||
MEDIA_URL = 'media/'
|
||||
MEDIA_ROOT = MY_MEDIA_ROOT
|
||||
SITEMAP_ROOT = MY_SITEMAP_ROOT
|
||||
if DEBUG:
|
||||
STATICFILES_DIRS = [
|
||||
MY_STATIC_ROOT
|
||||
]
|
||||
else:
|
||||
STATIC_ROOT = MY_STATIC_ROOT
|
||||
STATICFILES_DIRS = []
|
||||
TOUCH_RELOAD = MY_TOUCH_RELOAD
|
||||
STATIC_URL = '/static/'
|
||||
MEDIA_URL = '/media/'
|
||||
|
||||
# Using pathlib for cleaner path management
|
||||
# Adjusted to serve from public/media relative to project root
|
||||
MEDIA_ROOT = BASE_DIR.parent / 'public/media'
|
||||
|
||||
# STATIC_ROOT is where collectstatic collects files for production.
|
||||
# It cannot be the same as a directory in STATICFILES_DIRS.
|
||||
STATIC_ROOT = BASE_DIR.parent / 'staticfiles'
|
||||
|
||||
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 в моделях)
|
||||
# https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field
|
||||
|
||||
@@ -15,14 +15,20 @@ Including another URLconf
|
||||
1. Import the include() function: from django.urls import include, path
|
||||
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||
"""
|
||||
import os
|
||||
from django.contrib import admin
|
||||
from django.urls import path, re_path
|
||||
from django.conf.urls.static import static
|
||||
from rosmorport_tsts.settings import *
|
||||
from django.conf import settings
|
||||
from rosmorport_tsts import views
|
||||
|
||||
# Получаем URL админ панели из переменной окружения для безопасности
|
||||
# Это делается для того, чтобы скрыть админ-панель от хулиганов по сети
|
||||
ADMIN_URL = os.getenv('ADMIN_URL', 'admin/')
|
||||
|
||||
urlpatterns = [
|
||||
path('a-d-m-in/', admin.site.urls),
|
||||
# Админ панель со скрытым URL
|
||||
path(ADMIN_URL, admin.site.urls),
|
||||
|
||||
re_path(r'^$', views.index),
|
||||
re_path(r'^logout$', views.my_logout),
|
||||
@@ -39,5 +45,10 @@ urlpatterns = [
|
||||
# handler404 = 'web.views.handler404'
|
||||
# handler500 = 'web.views.handler500'
|
||||
|
||||
if DEBUG:
|
||||
urlpatterns += static(MEDIA_URL, document_root=MEDIA_ROOT)
|
||||
# В режиме разработки раздаём статические файлы и медиа через Django
|
||||
# В продакшене это должно быть настроено на уровне веб-сервера
|
||||
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)
|
||||
|
||||
@@ -10,7 +10,14 @@ https://docs.djangoproject.com/en/5.0/howto/deployment/wsgi/
|
||||
import os
|
||||
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
from django.contrib.staticfiles.handlers import StaticFilesHandler
|
||||
|
||||
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)
|
||||
|
||||
@@ -14,20 +14,16 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=0.5" />
|
||||
<meta name="description" content="{% block Description %}{% 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="apple-mobile-web-app-title" content="Pet Clones" />
|
||||
<meta name="theme-color" content="#F5F5F5" /><!-- theme-color предоставляет браузерам цвет CSS для настройки
|
||||
отображения страницы или окружающего пользовательского интерфейса. -->
|
||||
<link rel="canonical" href="{% block canonical %}{% endblock %}">
|
||||
<!-- Favicons -->
|
||||
{# <link rel="icon" type="image/svg+xml" href="{% static 'svgs/favicon.svg' %}" /> #}
|
||||
<link rel="icon" type="image/png" href="{% static 'img/favicon-96.png' %}" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="{% static 'img/favicon.ico' %}" />
|
||||
<link rel="icon" type="image/png" href="/favicon-96x96.png" sizes="96x96" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<link rel="shortcut icon" href="/favicon.ico" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
|
||||
<!-- css -->
|
||||
<link rel="stylesheet" href="{% static 'css/bootstrap.min.css' %}" />
|
||||
<link rel="stylesheet" href="{% static 'css/all.min.css' %}" />
|
||||
|
||||
@@ -24,8 +24,16 @@
|
||||
{% for PET in PETS %}<tr>
|
||||
<td>{{ PET.szPetSerNum }}</td>
|
||||
<td>{% if PET.szPetName %}{{ PET.szPetName }}{% else %}—{% endif %}</td>
|
||||
<td>{{ PET.iPetType }}</td>
|
||||
<td>{{ PET.iPetSex }}</td>
|
||||
<td>{% if PET.iPetType == 0 %}<i class="fa-solid fa-ghost"></i>{% elif PET.iPetType == 1 %}
|
||||
<i class="fa-solid fa-dog"></i>{% elif PET.iPetType == 2 %}
|
||||
<i class="fa-solid fa-cat"></i>{% elif PET.iPetType == 3 %}
|
||||
<i class="fa-solid fa-dove"></i>{% elif PET.iPetType == 4 %}
|
||||
<i class="fa-solid fa-fish"></i>{% elif PET.iPetType == 5 %}
|
||||
<i class="fa-solid fa-frog"></i>{% elif PET.iPetType == 6 %}
|
||||
<i class="fa-solid fa-locust"></i>{% endif %}</td>
|
||||
<td>{% if PET.iPetSex == 0 %}<i class="fa-solid fa-genderless"></i>{% elif PET.iPetSex == 1 %}
|
||||
<i class="fa-solid fa-mars"></i>{% elif PET.iPetSex == 2 %}
|
||||
<i class="fa-solid fa-venus"></i>{% endif %}</td>
|
||||
<td>{% if PET.bPetIsReg %}<i class="fa-solid fa-circle-check"></i>{% endif %}</td>
|
||||
<td>{% if not PET.bPetIsAlive %}<i class="fa-solid fa-skull-crossbones"></i>{% endif %}</td>
|
||||
<td>{% if PET.szPetOwner %}{{ PET.szPetOwner }}{% else %}—{% endif %}</td>
|
||||
|
||||
@@ -39,8 +39,16 @@
|
||||
{% for PET in PETS %}<tr class="t{{ PET.iPetType }} s{{ PET.iPetSex }}">
|
||||
<td>{{ PET.szPetSerNum }}</td>
|
||||
<td>{% if PET.szPetName %}{{ PET.szPetName }}{% else %}—{% endif %}</td>
|
||||
<td>{{ PET.iPetType }}</td>
|
||||
<td>{{ PET.iPetSex }}</td>
|
||||
<td>{% if PET.iPetType == 0 %}<i class="fa-solid fa-ghost"></i>{% elif PET.iPetType == 1 %}
|
||||
<i class="fa-solid fa-dog"></i>{% elif PET.iPetType == 2 %}
|
||||
<i class="fa-solid fa-cat"></i>{% elif PET.iPetType == 3 %}
|
||||
<i class="fa-solid fa-dove"></i>{% elif PET.iPetType == 4 %}
|
||||
<i class="fa-solid fa-fish"></i>{% elif PET.iPetType == 5 %}
|
||||
<i class="fa-solid fa-frog"></i>{% elif PET.iPetType == 6 %}
|
||||
<i class="fa-solid fa-locust"></i>{% endif %}</td>
|
||||
<td>{% if PET.iPetSex == 0 %}<i class="fa-solid fa-genderless"></i>{% elif PET.iPetSex == 1 %}
|
||||
<i class="fa-solid fa-mars"></i>{% elif PET.iPetSex == 2 %}
|
||||
<i class="fa-solid fa-venus"></i>{% endif %}</td>
|
||||
<td>{% if PET.bPetIsReg %}<i class="fa-solid fa-circle-check"></i>{% endif %}</td>
|
||||
<td>{% if not PET.bPetIsAlive %}<i class="fa-solid fa-skull-crossbones"></i>{% endif %}</td>
|
||||
<td>{% if PET.szPetOwner %}{{ PET.szPetOwner }}{% else %}—{% endif %}</td>
|
||||
@@ -82,27 +90,12 @@
|
||||
|
||||
{% block JS_1 %}<script type="text/javascript">
|
||||
$(document).ready(function() {
|
||||
$('#type').change(function(){
|
||||
let t = $(this).find("option:selected").attr('value');
|
||||
let s = $('#sex').find("option:selected").attr('value');
|
||||
if (t === 'tA')
|
||||
$('tbody > tr').show();
|
||||
else {
|
||||
$('tbody > tr').hide();
|
||||
$('tbody > tr.' + t).show();
|
||||
}
|
||||
if(s !== 'sA') $('tbody > tr:not(.' + s +')').hide();
|
||||
});
|
||||
$('#sex').change(function(){
|
||||
let s = $(this).find("option:selected").attr('value');
|
||||
$('#type,#sex').change(function(){
|
||||
let t = $('#type').find("option:selected").attr('value');
|
||||
if (s === 'sA')
|
||||
$('tbody > tr').show();
|
||||
else {
|
||||
$('tbody > tr').hide();
|
||||
$('tbody > tr.' + s).show();
|
||||
}
|
||||
if(s !== 'tA') $('tbody > tr:not(.' + t + ')').hide();
|
||||
let s = $('#sex').find("option:selected").attr('value');
|
||||
$('tbody > tr').show();
|
||||
if(t !== 'tA') $('tbody > tr:not(.'+t+')').hide();
|
||||
if(s !== 'sA') $('tbody > tr:not(.'+s+')').hide();
|
||||
});
|
||||
});
|
||||
</script>{% endblock %}
|
||||
@@ -1,25 +1,31 @@
|
||||
<!-- About Modal -->
|
||||
<div class="modal fade" id="about_modal" tabindex="-1" aria-labelledby="login_modal_Label" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content shadow">
|
||||
<div class="modal-header">
|
||||
<h3 class="modal-title fs-5" id="login_modal_Label">Стишок</h3>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>В собственноручно вырытом окопе<br />
|
||||
в глухом тылу на том конце земли<br />
|
||||
куда не дотянуться и Европе<br />
|
||||
где даже динамит не завезли<br />
|
||||
в единолично вырытой могиле<br />
|
||||
без всяких видов даже на провал<br />
|
||||
сижу сгруппировавшись как учили<br />
|
||||
чтобы никто мне хвост не оторвал.</p>
|
||||
<p class="text-end"><i>© Умка, 2013</i></p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Ок</button>
|
||||
</div>
|
||||
<div aria-hidden="true" aria-labelledby="login_modal_Label" class="modal fade" id="about_modal" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content shadow">
|
||||
<div class="modal-header">
|
||||
<h3 class="modal-title fs-5" id="login_modal_Label">Что это за ерунда?</h3>
|
||||
<button aria-label="Close" class="btn-close" data-bs-dismiss="modal" type="button"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<h5>О проекте</h5>
|
||||
<p>
|
||||
<strong>2024 год:</strong> Веб-приложение для управления и анализа данных, тестовое задание в процессе соискатения на должность <i>Frontend-разработчи</i> в компанию <a href="https://www.rosmorport.ru/" target="_blank">РосМорПорт</a>.
|
||||
Изготовлено за два дня. В соискании должности отказано.
|
||||
</p>
|
||||
<p>
|
||||
<strong>Условия задания, подробности и исходный код:</strong><br/>
|
||||
<a href="https://git.cube2.ru/erjemin/2024-test-rosmorport" target="_blank">
|
||||
https://git.cube2.ru/erjemin/2024-test-rosmorport
|
||||
</a>
|
||||
</p>
|
||||
<p>
|
||||
<strong>2026 год:</strong> Проект, при переносе на новый хостинг, переделан для разве­ртывания в Docker. Просто жалко выбрасывать.
|
||||
</p>
|
||||
<hr/>
|
||||
<p>Для тестирование: <small><small>логин:</small> admin // <small>пароль:</small> 1234</small></p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-secondary" data-bs-dismiss="modal" type="button">Ок</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,14 +1,14 @@
|
||||
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD>-<2D><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> (<28><><EFBFBD><EFBFBD><EFBFBD>), <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> 3 <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>:
|
||||
- <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>.
|
||||
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>, <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> 75 <20><><EFBFBD><EFBFBD>. <20> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD>-<2D><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> (<28><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> Frontend<EFBFBD>), <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> 11pt, <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>, <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>. <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>, <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>. <20><><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> (<28><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>) <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>. <20><><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>, <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> Django (<28><><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>, <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>). <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>, <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> (<28><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>, <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>, <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>, <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>).
|
||||
<EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>, <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> (<28><><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>), <20><><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>, <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>.
|
||||
- <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>.
|
||||
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> 75 <20><><EFBFBD><EFBFBD>., <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>, <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> 11pt. <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>, <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>, <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>: <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>, <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>, <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>. <20><><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>: <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> - <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>; <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> - <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> 1<EFBFBD>, <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> 2<EFBFBD>; <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> - <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>. <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>, <20><> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>.
|
||||
- <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>.
|
||||
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>. <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD> <20><><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>.
|
||||
Необходимо реализовать интерфейс веб-приложения (сайта), который состоит из 3 областей:
|
||||
- Информационная область.
|
||||
Представлена в виде полосы в верхней части экрана, ширина должна быть фиксированная 75 пикс. В этой области слева располагается название веб-приложения («Тестовое задание Frontend»), размер шрифта 11pt, вертикальное выравнивание по центру, горизонтальное по левому краю. Слева кнопка «Вход», выравнивание аналогичное названию но по правому краю. При нажатии на неё появляется всплывающее окно с формой ввода логина и пароля (обязательные поля) и кнопкой «Войти». При попытке войти должен производиться запрос к серверу, на сервере происходит авторизация средствами Django (или иного фреймворка, пользователь должен существовать в БД фреймворка). В случае успешной авторизации страница приложения перезагружается, иначе появляется сообщение что авторизоваться не удалось с пояснением причины (пользователя не существует, пароль не верный, ошибка подключения, иная ошибка).
|
||||
В случае если пользователь авторизован кнопка «Войти» заменяется на «Выйти», рядом отображается имя пользователя (слева от кнопки), при её нажатии будет отправлен запрос на сервер завершающий сессию пользователя, страница должна быть перезагружена.
|
||||
- Область меню.
|
||||
Представлена в виде полосы шириной 75 пикс., цвет отличается от информационной области, размер шрифта 11pt. Выравнивание объектов внутри горизонтально по левому краю, вертикально по центру, внутри расположены пункты меню: «Ввод данных», «Отчёты», «Информация». При наведении курсора на пункт меню вертикально вниз выпадают соответствующие подпункты: «Ввод данных» - «Форма ввода»; «Отчёты» - «Отчёт 1», «Отчёт 2»; «Информация» - «О приложении». Если пользователь не авторизован, то в первых двух пунктах меню вместо подпунктов будет отображено «Необходима авторизация».
|
||||
- Рабочая область.
|
||||
Остальное свободное место на странице. Информационная область и область меню должны оставаться видимыми на странице даже если содержимое рабочей области больше отведённого под неё места.
|
||||
|
||||
<EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>, <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> (<28><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>) <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>, <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>.
|
||||
<EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>, <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> 7 <20><><EFBFBD><EFBFBD><EFBFBD>, <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD> <20><><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> (checkbox) <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>. <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>. <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>, <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>.
|
||||
<EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> 1<> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>, <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>. <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>, <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> (<28><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD> <20><><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>). <20><><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>, <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> (<28><> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>).
|
||||
<EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> 2<> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> 1<> <20><><EFBFBD><EFBFBD><EFBFBD>, <20><> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD> (1+) <20><> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>. <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> (<28><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>, <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>, <20><><EFBFBD> <20><><EFBFBD><EFBFBD>).
|
||||
<EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>.
|
||||
При нажатии на подпункты меню должен отправляться запрос на сервер, а сервер возвращать содержимое (вёрстку) в зависимости от выбранного пункта, этим содержимым будет заменено содержимое рабочей области.
|
||||
При нажатии на подпункт «Форма ввода» в рабочей области должна появиться форма ввода документа на усмотрение тестируемого, но которая должна содержать как минимум 7 полей, среди них поля с «галочками» (checkbox) и выпадающие списки. Должна существовать возможность сохранить все введённые данные в базу данных. Должны присутствовать обязательные и необязательные поля, в интерфейсе их необходимо пометить соответствующим образом.
|
||||
При нажатии на подпункт «Отчёт 1» необходимо вывести таблицу, в которой будет представлены все внесённые в базу записи через интерфейс «Форма ввода». У таблицы должна присутствовать «шапка», которая должна отображаться всегда вне зависимости от объёма данных в отчёте (оставаться на виду при прокрутке отчёта вниз). Под таблицей необходимо предусмотреть строку справочной информации, в которой будет отображено количество записей в отчёте и время выполнения запроса к базе данных и обработки данных (не от клиента к серверу и обратно).
|
||||
При нажатии на подпункт «Отчёт 2» будет отображён аналогичный «Отчёту 1» отчёт, но с возможностью фильтрации по полям (1+) на выбор тестируемого. Техническая реализация фильтра – на усмотрение тестируемого (обработка на клиенте, запрос к серверу с параметрами, или иное).
|
||||
При нажатии на подпункт «О приложении» будет отображено всплывающее окно с содержимым на выбор тестируемого.
|
||||