Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7f96932a40 | |||
| d59c867010 | |||
| 9ea851ad95 | |||
| 5146f88c7d | |||
| 20ecb9cc4c | |||
| ea2d352cae | |||
| d459eedf41 | |||
| 9d31429e14 | |||
| c5963d1d30 | |||
| 12001bc749 | |||
| 99a2ace43f | |||
| 81efaf1ba5 | |||
| 42b378fcbc | |||
| ba4175dfdb | |||
| 4194b351d2 | |||
| be68a82927 | |||
| 53b127a966 | |||
| 746c50a988 | |||
| 2520362ad5 | |||
| a857101c3f | |||
| 7eeb44a1f5 | |||
| 86bfd9b07b |
@@ -63,3 +63,8 @@ jobs:
|
|||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
# ДОБАВЛЕНО:
|
||||||
|
cache-from: type=gha
|
||||||
|
cache-to: type=gha,mode=max
|
||||||
|
# И это для медленного интернета:
|
||||||
|
timeout: 900 # 15 минут на всю сборку
|
||||||
|
|||||||
126
Dockerfile
126
Dockerfile
@@ -1,63 +1,105 @@
|
|||||||
# ==========================================
|
# =================================================
|
||||||
# Dockerfile для Django + Gunicorn + WhiteNoise
|
# STAGE 1: Builder - Установка зависимостей
|
||||||
# ==========================================
|
# =================================================
|
||||||
|
FROM python:3.12-slim AS builder
|
||||||
|
|
||||||
# 1. Базовый образ: Python 3.12 (Slim версия для меньшего размера)
|
# Устанавливаем переменные окружения
|
||||||
FROM python:3.12-slim
|
|
||||||
|
|
||||||
# 2. Переменные окружения для Python
|
|
||||||
# PYTHONDONTWRITEBYTECODE: Запрещает Python писать .pyc файлы
|
|
||||||
# PYTHONUNBUFFERED: Гарантирует, что вывод консоли (logs) виден сразу (не буферизуется)
|
|
||||||
ENV PYTHONDONTWRITEBYTECODE=1
|
ENV PYTHONDONTWRITEBYTECODE=1
|
||||||
ENV PYTHONUNBUFFERED=1
|
ENV PYTHONUNBUFFERED=1
|
||||||
# Poetry настройки: не создавать виртуальное окружение внутри контейнера (ставим системно).
|
# Говорим Poetry, чтобы он не создавал venv, а ставил пакеты в системный site-packages
|
||||||
# Дублирует `poetry config virtualenvs.create false` в пп.7 (на всякий случай).
|
|
||||||
ENV POETRY_VIRTUALENVS_CREATE=false
|
ENV POETRY_VIRTUALENVS_CREATE=false
|
||||||
# Путь настройки Django (по умолчанию для production) на случай если контейнер будет запущен не через docker-compose.
|
|
||||||
ENV DJANGO_SETTINGS_MODULE=dicquo.settings
|
|
||||||
|
|
||||||
# 3. Рабочая директория внутри контейнера
|
# Устанавливаем системные зависимости, необходимые для СБОРКИ пакетов (например, Pillow)
|
||||||
WORKDIR /app
|
# build-essential нужен для компиляции, -dev пакеты для сборки Pillow
|
||||||
|
|
||||||
# 4. Установка системных зависимостей
|
|
||||||
# - libjpeg-dev zlib1g-dev: библиотеки для работы с изображениями (Pillow)
|
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
build-essential \
|
||||||
libjpeg-dev \
|
libjpeg-dev \
|
||||||
zlib1g-dev \
|
zlib1g-dev \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
# 5. Установка Poetry через pip (быстро и надежно)
|
# Устанавливаем Poetry
|
||||||
RUN pip install --no-cache-dir poetry
|
RUN pip install --no-cache-dir poetry
|
||||||
|
|
||||||
# 6. Копируем файлы зависимостей (pyproject.toml и poetry.lock)
|
# Создаем рабочую директорию
|
||||||
# Делаем это ДО копирования всего кода, чтобы использовать кэш Docker layers.
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Копируем только файлы зависимостей для кэширования этого слоя
|
||||||
COPY pyproject.toml poetry.lock /app/
|
COPY pyproject.toml poetry.lock /app/
|
||||||
|
|
||||||
# 7. Установка зависимостей проекта
|
# Устанавливаем зависимости проекта. Poetry установит их в /usr/local/lib/python3.12/site-packages
|
||||||
# --no-interaction: не будет спрашивать подтверждения
|
RUN poetry install --no-interaction --no-ansi --no-root --only main
|
||||||
# --no-ansi: уберваем цветные символы из логов сборки (они иногда мусорят)
|
|
||||||
# --no-root: не устанавливать сам проект как пакет (мы просто копируем код)
|
|
||||||
# --only main: не ставить dev-зависимости (тесты, линтеры и т.п.) для продакшена
|
|
||||||
# RUN poetry install --no-root --only main
|
|
||||||
# Настройка Poetry: не создавать venv и установка зависимостей (без dev-зависимостей для продакшена)
|
|
||||||
RUN poetry config virtualenvs.create false \
|
|
||||||
&& poetry install --no-interaction --no-ansi --no-root --only main
|
|
||||||
|
|
||||||
# 8. Копируем весь исходный код проекта в контейнер
|
|
||||||
COPY . /app/
|
|
||||||
|
|
||||||
# 9. Сборка статики (CSS, JS)
|
# =================================================
|
||||||
# Важно: Запускаем collectstatic с фейковым SECRET_KEY, так как на этапе сборки env файла может не быть.
|
# STAGE 2: Final - Создание чистого и безопасного образа
|
||||||
RUN SECRET_KEY=dummy_build_key python dicquo/manage.py collectstatic --noinput --clear
|
# =================================================
|
||||||
|
FROM python:3.12-slim AS stage-final
|
||||||
|
|
||||||
# 10. Открываем порт 8000
|
# Устанавливаем переменные окружения
|
||||||
|
ENV PYTHONDONTWRITEBYTECODE=1
|
||||||
|
ENV PYTHONUNBUFFERED=1
|
||||||
|
ENV DJANGO_SETTINGS_MODULE=dicquo.settings
|
||||||
|
|
||||||
|
# Устанавливаем только RUNTIME системные зависимости.
|
||||||
|
# Пакеты -dev и build-essential здесь не нужны.
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
libjpeg62-turbo \
|
||||||
|
sqlite3 \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Создаем пользователя без прав root для безопасности
|
||||||
|
# RUN addgroup --system app && adduser --system --ingroup app app
|
||||||
|
|
||||||
|
# Создаем рабочую директорию
|
||||||
|
WORKDIR /home/app/web
|
||||||
|
|
||||||
|
# Копируем установленные Python-пакеты из builder-стадии
|
||||||
|
COPY --from=builder /usr/local/lib/python3.12/site-packages /usr/local/lib/python3.12/site-packages
|
||||||
|
|
||||||
|
# Копируем исполняемые файлы (gunicorn, pip и т.д.)
|
||||||
|
COPY --from=builder /usr/local/bin /usr/local/bin
|
||||||
|
|
||||||
|
# Копируем исходный код проекта и устанавливаем правильного владельца
|
||||||
|
# ИЗМЕНЕНИЕ: app:app -> 1000:1000
|
||||||
|
COPY --chown=1000:1000 . .
|
||||||
|
|
||||||
|
# Создаём директорию для конфигов nginx и даём права пользователю app
|
||||||
|
# Это выполняется ещё от root, поэтому проблем с permissions не будет.
|
||||||
|
RUN mkdir -p /nginx_configs_host/nginx && chown -R 1000:1000 /nginx_configs_host
|
||||||
|
|
||||||
|
# Создаём директорию для собранной статики и даём права пользователю app
|
||||||
|
RUN mkdir -p /home/app/web/staticfiles && chown -R 1000:1000 /home/app/web/staticfiles
|
||||||
|
|
||||||
|
# Создаём директорию для ошибок (404, 500) и даём права пользователю app
|
||||||
|
RUN mkdir -p /app/public/media/errors && chown -R 1000:1000 /app/public/media
|
||||||
|
|
||||||
|
# Создаём директорию для БД и даём права пользователю app
|
||||||
|
# Это важно когда БД монтируется как том с хоста
|
||||||
|
RUN mkdir -p /app/database && chown -R 1000:1000 /app/database
|
||||||
|
|
||||||
|
# Переключаемся на пользователя без прав root
|
||||||
|
USER 1000
|
||||||
|
|
||||||
|
|
||||||
|
# Собираем статику
|
||||||
|
# Используем dummy ключ, так как .env файла нет на этапе сборки
|
||||||
|
RUN SECRET_KEY=dummy python dicquo/manage.py collectstatic --noinput --clear
|
||||||
|
|
||||||
|
# Открываем порт
|
||||||
EXPOSE 8000
|
EXPOSE 8000
|
||||||
|
|
||||||
# 11. Команда запуска
|
# Проверка здоровья контейнера
|
||||||
# Переходим в подпапку dicquo, где лежит код Django проекта
|
# Docker будет периодически проверять, жив ли контейнер, отправляя GET запрос к главной странице.
|
||||||
WORKDIR /app/dicquo
|
# Параметры:
|
||||||
|
# --interval=30s - проверка каждые 30 секунд
|
||||||
|
# --timeout=3s - ожидаем ответ максимум 3 секунды
|
||||||
|
# --start-period=10s - даем контейнеру 10 секунд на запуск перед первой проверкой
|
||||||
|
# --retries=3 - объявляем контейнер unhealthy после 3 неудачных попыток
|
||||||
|
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
|
||||||
|
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/').read()" || exit 1
|
||||||
|
|
||||||
# Запускаем Gunicorn (по умолчанию, если не переопределено в docker-compose) на три воркера, привязывая его к
|
# Переходим в директорию с manage.py для корректного запуска gunicorn
|
||||||
# порту 8000 и указывая на точку входа приложения (wsgi.py).
|
WORKDIR /home/app/web/dicquo
|
||||||
|
|
||||||
|
# Команда запуска
|
||||||
CMD ["gunicorn", "--workers", "3", "--bind", "0.0.0.0:8000", "dicquo.wsgi:application"]
|
CMD ["gunicorn", "--workers", "3", "--bind", "0.0.0.0:8000", "dicquo.wsgi:application"]
|
||||||
|
|
||||||
|
|||||||
@@ -108,7 +108,9 @@ server {
|
|||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
# ВАЖНО: Явно указываем https, потому что клиент всегда приходит по HTTPS к Nginx
|
||||||
|
# Даже если внутри контейнера это HTTP на 127.0.0.1:8010, для Django это должно быть HTTPS
|
||||||
|
proxy_set_header X-Forwarded-Proto https;
|
||||||
|
|
||||||
# Тайм-ауты (важно для долгих операций, если они есть)
|
# Тайм-ауты (важно для долгих операций, если они есть)
|
||||||
proxy_read_timeout 180s;
|
proxy_read_timeout 180s;
|
||||||
|
|||||||
@@ -81,8 +81,10 @@ DATABASES = {
|
|||||||
'NAME': BASE_DIR.parent / 'database/db.sqlite3',
|
'NAME': BASE_DIR.parent / 'database/db.sqlite3',
|
||||||
'OPTIONS': {
|
'OPTIONS': {
|
||||||
# Таймаут ожидания блокировки SQLite (в секундах)
|
# Таймаут ожидания блокировки SQLite (в секундах)
|
||||||
# При сложных операциях (например, каскадное удаление тегов) нужно больше времени
|
# ВАЖНО: Увеличен до 60 сек для работы с несколькими воркерами Gunicorn
|
||||||
'timeout': 20,
|
'timeout': 60,
|
||||||
|
# Дополнительные опции для лучшей работы SQLite при concurrent доступе
|
||||||
|
'init_command': "PRAGMA journal_mode=WAL;", # Write-Ahead Logging для лучшей concurrency
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -154,3 +156,12 @@ if not DEBUG:
|
|||||||
WHITENOISE_ROOT = BASE_DIR.parent / 'public'
|
WHITENOISE_ROOT = BASE_DIR.parent / 'public'
|
||||||
|
|
||||||
SITE_ID = 1
|
SITE_ID = 1
|
||||||
|
|
||||||
|
# Настройки безопасности для работы за прокси
|
||||||
|
if not DEBUG:
|
||||||
|
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
|
||||||
|
SECURE_SSL_REDIRECT = True
|
||||||
|
SESSION_COOKIE_SECURE = True
|
||||||
|
CSRF_COOKIE_SECURE = True
|
||||||
|
USE_X_FORWARDED_HOST = True
|
||||||
|
USE_X_FORWARDED_PORT = True
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
{# Favicons #}<link rel="shortcut icon" type="image/x-icon" href="{% static 'img/favicon.ico' %}"/>
|
{# Favicons #}<link rel="shortcut icon" type="image/x-icon" href="{% static 'img/favicon.ico' %}"/>
|
||||||
<link rel="icon" type="image/png" href="{% static 'img/favicon.png' %}"/>
|
<link rel="icon" type="image/png" href="{% static 'img/favicon.png' %}"/>
|
||||||
{# Technical Meta #}<meta http-equiv="Last-Modified" content="{% block Last4Meta %}{% endblock %}"/>
|
{# Technical Meta #}<meta http-equiv="Last-Modified" content="{% block Last4Meta %}{% endblock %}"/>
|
||||||
|
{# Для ИИ #}<link rel="help" type="text/markdown" href="/llms.txt"/>
|
||||||
{# CSS #}<link rel="stylesheet" href="{% static 'css/dicquo.css' %}"/>
|
{# CSS #}<link rel="stylesheet" href="{% static 'css/dicquo.css' %}"/>
|
||||||
<noscript><style>body { opacity: 1; }</style></noscript>{# Показать все если JS не поддерживатся #}
|
<noscript><style>body { opacity: 1; }</style></noscript>{# Показать все если JS не поддерживатся #}
|
||||||
{% block ExtraHead %}{# Если нужно что=то добавить в `<head>` #}{% endblock %}
|
{% block ExtraHead %}{# Если нужно что=то добавить в `<head>` #}{% endblock %}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ from django_select2.forms import Select2TagWidget
|
|||||||
from taggit.models import Tag
|
from taggit.models import Tag
|
||||||
from taggit.utils import parse_tags
|
from taggit.utils import parse_tags
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.db.utils import OperationalError, ProgrammingError
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from etpgrf.typograph import Typographer
|
from etpgrf.typograph import Typographer
|
||||||
@@ -34,8 +35,13 @@ class TagSelect2Widget(Select2TagWidget):
|
|||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
# choices: список всех существующих тегов по имени
|
# choices: список всех существующих тегов по имени.
|
||||||
self.choices = [(t.name, t.name) for t in Tag.objects.all()]
|
# Важно: на этапах вроде collectstatic таблицы taggit ещё может не быть,
|
||||||
|
# поэтому оборачиваем в try/except и молча игнорируем отсутствие БД.
|
||||||
|
try:
|
||||||
|
self.choices = [(t.name, t.name) for t in Tag.objects.all()]
|
||||||
|
except (OperationalError, ProgrammingError):
|
||||||
|
self.choices = []
|
||||||
|
|
||||||
class Media:
|
class Media:
|
||||||
css = {
|
css = {
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
# Generated by Django 6.0.2 on 2026-03-19 20:34
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('web', '0003_alter_tbdictumandquotes_btypograph_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='tbdictumandquotes',
|
||||||
|
options={'ordering': ['-id'], 'verbose_name': 'ВЫСКАЗЫВАНИЕ', 'verbose_name_plural': 'ВЫСКАЗЫВАНИЯ'},
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -38,14 +38,14 @@ services:
|
|||||||
sh -c "python manage.py migrate --noinput &&
|
sh -c "python manage.py migrate --noinput &&
|
||||||
python manage.py collectstatic --noinput &&
|
python manage.py collectstatic --noinput &&
|
||||||
mkdir -p /nginx_configs_host/nginx &&
|
mkdir -p /nginx_configs_host/nginx &&
|
||||||
sed \"s|/home/user/app/dq-site|${HOST_PROJECT_PATH:-/home/default_user/projects/dq-site}|g\" /app/configs/nginx/dq-app--external-nginx.conf > /nginx_configs_host/nginx/nginx_dq.conf.example &&
|
sed \"s|/home/user/app/dq-site|${HOST_PROJECT_PATH:-/home/default_user/projects/dq-site}|g\" /nginx_configs_host/nginx/dq-app--external-nginx.conf > /nginx_configs_host/nginx/nginx_dq.conf.example &&
|
||||||
if [ ! -f /nginx_configs_host/nginx/dq-app--external-nginx.conf ]; then
|
if [ ! -f /nginx_configs_host/nginx/dq-app--external-nginx.conf ]; then
|
||||||
cp /nginx_configs_host/nginx/nginx_dq.conf.example /nginx_configs_host/nginx/dq-app--external-nginx.conf;
|
cp /nginx_configs_host/nginx/nginx_dq.conf.example /nginx_configs_host/nginx/dq-app--external-nginx.conf;
|
||||||
echo 'INIT: Created new nginx config with correct paths';
|
echo 'INIT: Created new nginx config with correct paths';
|
||||||
fi &&
|
fi &&
|
||||||
mkdir -p /app/public/media/errors &&
|
mkdir -p /app/public/media/errors &&
|
||||||
cp /app/dicquo/templates/static_404.html /app/public/media/errors/404.html &&
|
cp /home/app/web/dicquo/templates/static_404.html /app/public/media/errors/404.html &&
|
||||||
cp /app/dicquo/templates/static_500.html /app/public/media/errors/500.html &&
|
cp /home/app/web/dicquo/templates/static_500.html /app/public/media/errors/500.html &&
|
||||||
gunicorn --workers 3 --bind 0.0.0.0:8000 dicquo.wsgi:application"
|
gunicorn --workers 3 --bind 0.0.0.0:8000 dicquo.wsgi:application"
|
||||||
|
|
||||||
# 4. Проброс портов (Внешний Nginx -> localhost:8010)
|
# 4. Проброс портов (Внешний Nginx -> localhost:8010)
|
||||||
@@ -67,7 +67,10 @@ services:
|
|||||||
# Это нужно, чтобы скрипт запуска мог положить туда .example конфиг и прочитать боевой конфиг.
|
# Это нужно, чтобы скрипт запуска мог положить туда .example конфиг и прочитать боевой конфиг.
|
||||||
- ./config:/nginx_configs_host
|
- ./config:/nginx_configs_host
|
||||||
|
|
||||||
# 6. Переменные окружения
|
# 6. Запускать от имени пользователя с UID 1000 и GID 1000
|
||||||
|
user: "1000:1000"
|
||||||
|
|
||||||
|
# 7. Переменные окружения
|
||||||
env_file:
|
env_file:
|
||||||
- .env
|
- .env
|
||||||
environment:
|
environment:
|
||||||
@@ -76,14 +79,24 @@ services:
|
|||||||
# Передаем переменную с путем на хосте внутрь контейнера, чтобы sed мог её использовать
|
# Передаем переменную с путем на хосте внутрь контейнера, чтобы sed мог её использовать
|
||||||
- HOST_PROJECT_PATH=${HOST_PROJECT_PATH:-/home/default_user/projects/dq-site}
|
- HOST_PROJECT_PATH=${HOST_PROJECT_PATH:-/home/default_user/projects/dq-site}
|
||||||
|
|
||||||
# 7. Логирование (Ротация)
|
# 8. Проверка здоровья контейнера (Healthcheck)
|
||||||
|
# Docker будет периодически проверять статус контейнера. Это критично для Watchtower!
|
||||||
|
# Если контейнер объявлен "unhealthy", Watchtower сначала остановит старый образ, потом запустит новый.
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/').read()"]
|
||||||
|
interval: 30s # Проверка каждые 30 секунд
|
||||||
|
timeout: 3s # Таймаут ответа - 3 секунды
|
||||||
|
start_period: 10s # Даем 10 секунд на стартап перед первой проверкой
|
||||||
|
retries: 3 # Unhealthy после 3 неудачных попыток
|
||||||
|
|
||||||
|
# 9. Логирование (Ротация)
|
||||||
logging:
|
logging:
|
||||||
driver: "json-file"
|
driver: "json-file"
|
||||||
options:
|
options:
|
||||||
max-size: "10m"
|
max-size: "10m"
|
||||||
max-file: "3"
|
max-file: "3"
|
||||||
|
|
||||||
# 8. Ресурсы
|
# 10. Ресурсы
|
||||||
deploy:
|
deploy:
|
||||||
resources:
|
resources:
|
||||||
limits:
|
limits:
|
||||||
@@ -107,6 +120,9 @@ services:
|
|||||||
- WATCHTOWER_SCOPE=dq-scope
|
- WATCHTOWER_SCOPE=dq-scope
|
||||||
- WATCHTOWER_CLEANUP=true # Удалять старые образы после обновления
|
- WATCHTOWER_CLEANUP=true # Удалять старые образы после обновления
|
||||||
- DOCKER_API_VERSION=1.44
|
- DOCKER_API_VERSION=1.44
|
||||||
|
# Дополнительные опции для правильной работы с healthcheck
|
||||||
|
- WATCHTOWER_WAIT_ON_TIMEOUT=60 # Ждем 60 сек пока контейнер станет healthy перед финализацией
|
||||||
|
- WATCHTOWER_LIFECYCLE_HOOKS=true # Включаем lifecycle hooks для graceful shutdown
|
||||||
command: --interval 1800 --cleanup # Проверять каждые 30 минут
|
command: --interval 1800 --cleanup # Проверять каждые 30 минут
|
||||||
logging:
|
logging:
|
||||||
driver: "json-file"
|
driver: "json-file"
|
||||||
|
|||||||
8
poetry.lock
generated
8
poetry.lock
generated
@@ -128,13 +128,13 @@ Django = ">=4.1"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "etpgrf"
|
name = "etpgrf"
|
||||||
version = "0.1.4"
|
version = "0.1.6"
|
||||||
description = "Electro-Typographer: Python library for advanced web typography (non-breaking spaces, hyphenation, hanging punctuation and ."
|
description = "Electro-Typographer: Python library for advanced web typography (non-breaking spaces, hyphenation, hanging punctuation and ."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.10"
|
python-versions = ">=3.10"
|
||||||
files = [
|
files = [
|
||||||
{file = "etpgrf-0.1.4-py3-none-any.whl", hash = "sha256:62d4371e1b5fab06b99f79bd351767aed8baf7d041cae7e5d4eb63f7c9545114"},
|
{file = "etpgrf-0.1.6-py3-none-any.whl", hash = "sha256:a2d2a67048f094e1d30fe42f05f420afd19babe66ec7daa35b517ca23306d5cc"},
|
||||||
{file = "etpgrf-0.1.4.tar.gz", hash = "sha256:c699382c292e3110915331dd5539e7dde0c961e4f4ca65cf8db0e01e84dab72f"},
|
{file = "etpgrf-0.1.6.tar.gz", hash = "sha256:a050c400a30be1c2379c892fc5fa398a79d15f0169094f00023a75dec01864af"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
@@ -675,4 +675,4 @@ brotli = ["brotli"]
|
|||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.0"
|
lock-version = "2.0"
|
||||||
python-versions = "^3.12"
|
python-versions = "^3.12"
|
||||||
content-hash = "b5fca935982220439294d6b37caaf1d893492df96d65abd6389dfd3c9464b992"
|
content-hash = "64c804553b6314e8f8f5830637781a3179fd70f14cceb6730bfcb2cf24c91a31"
|
||||||
|
|||||||
@@ -1,26 +1,14 @@
|
|||||||
# DicQuo
|
# DicQuo
|
||||||
User-Agent: *
|
User-Agent: *
|
||||||
Allow: /
|
Allow: /
|
||||||
Disallow: /admin/
|
|
||||||
Disallow: /*?tag=
|
Disallow: /*?tag=
|
||||||
Disallow: /*?
|
Disallow: /*?
|
||||||
|
|
||||||
# Optimize for Yandex
|
# Optimize for Yandex
|
||||||
Clean-param: tag /
|
Clean-param: tag /
|
||||||
|
|
||||||
# AI and LLM bots settings
|
|
||||||
# OpenAI GPT
|
|
||||||
# User-agent: GPTBot
|
|
||||||
# Disallow:
|
|
||||||
|
|
||||||
# Common Crawl (used by many AI models)
|
|
||||||
# User-agent: CCBot
|
|
||||||
# Disallow:
|
|
||||||
|
|
||||||
# Google Bard/Gemini
|
|
||||||
# User-agent: Google-Extended
|
|
||||||
# Disallow:
|
|
||||||
|
|
||||||
Host: dq.cube2.ru
|
Host: dq.cube2.ru
|
||||||
Sitemap: https://dq.cube2.ru/sitemap.xml
|
Sitemap: https://dq.cube2.ru/sitemap.xml
|
||||||
|
|
||||||
|
# Ссылка на файл для ИИ-моделей
|
||||||
|
Link: /llms.txt
|
||||||
@@ -306,12 +306,17 @@ footer button:hover {
|
|||||||
|
|
||||||
/* --- ВЫРАВНИВАНИЕ СИМВОЛОВ ВИСЯЧЕЙ ПУНКТУАЦИИ (Hanging Punctuation) ТИПОГРАФА ETPGRF --- */
|
/* --- ВЫРАВНИВАНИЕ СИМВОЛОВ ВИСЯЧЕЙ ПУНКТУАЦИИ (Hanging Punctuation) ТИПОГРАФА ETPGRF --- */
|
||||||
/* --- В ПРОЕКТЕ ТОЛЬКО ЛЕВЫЕ ВИСЯЧИЕ СИМВОЛЫ (выравнивание по левому краю) --- */
|
/* --- В ПРОЕКТЕ ТОЛЬКО ЛЕВЫЕ ВИСЯЧИЕ СИМВОЛЫ (выравнивание по левому краю) --- */
|
||||||
.etp-laquo {margin-left: -0.44em;} /* « */
|
.etp-laquo { margin-left: -0.49em; } /* « */
|
||||||
.etp-ldquo, .etp-bdquo { margin-left: -0.4em;} /* “ „ */
|
.etp-ldquo, .etp-bdquo { margin-left: -0.4em; } /* “ “ */
|
||||||
.etp-lsquo {margin-left: -0.22em;} /* ‘ */
|
.etp-lsquo { margin-left: -0.22em; } /* ’ */
|
||||||
.etp-lpar, .etp-lsqb, .etp-lcub {margin-left: -0.25em;}/* ( [ { */
|
.etp-lpar, .etp-lsqb, .etp-lcub { margin-left: -0.23em; } /* ( [ { */
|
||||||
|
/* компенсирующие пробелы для левых висячих символов */
|
||||||
|
.etp-sp-laquo { padding-right: 0.49em; }
|
||||||
|
.etp-sp-ldquo, .etp-sp-bdquo { padding-right: 0.4em; }
|
||||||
|
.etp-sp-lsquo { padding-right: 0.22em; }
|
||||||
|
.etp-sp-lpar, .etp-sp-lsqb, .etp-sp-lcub { padding-right: 0.23em; }
|
||||||
|
|
||||||
/* --- СЧЕТЧИКИ (СКРЫТЫЙ ПИКСЕЛЬ) --- */
|
/* --- СЧЕТЧИКИ (СКРЫТЫЙ ПИКСЕЛЬ) top.mail.ru и Яндекс.Метрика --- */
|
||||||
.counter-pixel {
|
.counter-pixel {
|
||||||
border: 0;
|
border: 0;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ django = "^6.0.2"
|
|||||||
django-taggit = "^6.1.0"
|
django-taggit = "^6.1.0"
|
||||||
pillow = "^12.1.1"
|
pillow = "^12.1.1"
|
||||||
pytils = "^0.4.4"
|
pytils = "^0.4.4"
|
||||||
etpgrf = "^0.1.4"
|
etpgrf = "0.1.6"
|
||||||
django-environ = "^0.12.1"
|
django-environ = "^0.12.1"
|
||||||
whitenoise = "^6.11.0"
|
whitenoise = "^6.11.0"
|
||||||
gunicorn = "^25.1.0"
|
gunicorn = "^25.1.0"
|
||||||
|
|||||||
Reference in New Issue
Block a user