Compare commits
22
Commits
v0.2.6
...
06a4ea6870
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
06a4ea6870 | ||
|
|
1cc070b7c0 | ||
|
|
73f68e9a2f | ||
|
|
21144eb825 | ||
|
|
d0e8ca2e8d | ||
|
|
39fbfd2193 | ||
|
|
8379d734dd | ||
|
|
10c99fc216 | ||
|
|
496d212118 | ||
|
|
8f0ee1c8c3 | ||
|
|
e5f7e65de8 | ||
|
|
150601c413 | ||
|
|
612e99700f | ||
|
|
d78b3bbfb5 | ||
|
|
23b111ec22 | ||
|
|
30b554fce4 | ||
|
|
66328ff4b6 | ||
|
|
6b6f294c9b | ||
|
|
f64b7d8799 | ||
|
|
cf2986cea1 | ||
|
|
3c97daf998 | ||
|
|
8e9c764635 |
@@ -40,8 +40,14 @@ jobs:
|
|||||||
uses: docker/build-push-action@v4
|
uses: docker/build-push-action@v4
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
|
# platforms: linux/amd64
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
push: true
|
push: true
|
||||||
# Используем теги, сгенерированные шагом meta (v1.0.0 и latest)
|
# Используем теги, сгенерированные шагом meta (v1.0.0 и latest)
|
||||||
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: 1800 # 30 минут
|
||||||
|
|||||||
+105
-467
@@ -1,279 +1,70 @@
|
|||||||
### PyCharm+iml template
|
# ==============================================================================
|
||||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
# .gitignore для проекта ETPGRF Site (Django + Node.js/Frontend + Docker)
|
||||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
# ==============================================================================
|
||||||
|
|
||||||
# User-specific stuff
|
# ------------------------------------------------------------------------------
|
||||||
.idea/**/workspace.xml
|
# 1. Среды разработки и редакторы кода (IDE & Editors)
|
||||||
.idea/**/tasks.xml
|
# ------------------------------------------------------------------------------
|
||||||
.idea/**/usage.statistics.xml
|
# JetBrains IDEs (PyCharm, WebStorm, IntelliJ IDEA и др.):
|
||||||
.idea/**/dictionaries
|
# Игнорируем полностью конфигурационную директорию .idea, чтобы локальные настройки,
|
||||||
.idea/**/shelf
|
# списки задач, кэши индексации и конфигурации раннеров не попадали в репозиторий.
|
||||||
|
.idea/
|
||||||
# AWS User-specific
|
*.iml
|
||||||
.idea/**/aws.xml
|
|
||||||
|
|
||||||
# Generated files
|
|
||||||
.idea/**/contentModel.xml
|
|
||||||
|
|
||||||
# Sensitive or high-churn files
|
|
||||||
.idea/**/dataSources/
|
|
||||||
.idea/**/dataSources.ids
|
|
||||||
.idea/**/dataSources.local.xml
|
|
||||||
.idea/**/sqlDataSources.xml
|
|
||||||
.idea/**/dynamic.xml
|
|
||||||
.idea/**/uiDesigner.xml
|
|
||||||
.idea/**/dbnavigator.xml
|
|
||||||
|
|
||||||
# Gradle
|
|
||||||
.idea/**/gradle.xml
|
|
||||||
.idea/**/libraries
|
|
||||||
|
|
||||||
# Gradle and Maven with auto-import
|
|
||||||
# When using Gradle or Maven with auto-import, you should exclude module files,
|
|
||||||
# since they will be recreated, and may cause churn. Uncomment if using
|
|
||||||
# auto-import.
|
|
||||||
# .idea/artifacts
|
|
||||||
# .idea/compiler.xml
|
|
||||||
# .idea/jarRepositories.xml
|
|
||||||
# .idea/modules.xml
|
|
||||||
# .idea/*.iml
|
|
||||||
# .idea/modules
|
|
||||||
# *.iml
|
|
||||||
# *.ipr
|
|
||||||
|
|
||||||
# CMake
|
|
||||||
cmake-build-*/
|
|
||||||
|
|
||||||
# Mongo Explorer plugin
|
|
||||||
.idea/**/mongoSettings.xml
|
|
||||||
|
|
||||||
# File-based project format
|
|
||||||
*.iws
|
*.iws
|
||||||
|
*.ipr
|
||||||
|
|
||||||
# IntelliJ
|
# Visual Studio Code:
|
||||||
out/
|
# Локальные настройки редактора, конфигурации отладки и расширений
|
||||||
|
.vscode/
|
||||||
|
*.code-workspace
|
||||||
|
|
||||||
# mpeltonen/sbt-idea plugin
|
# Временные файлы текстовых редакторов (Vim, Emacs, Nano):
|
||||||
.idea_modules/
|
*~
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*.swn
|
||||||
|
.#*
|
||||||
|
\#*#
|
||||||
|
|
||||||
# JIRA plugin
|
# ------------------------------------------------------------------------------
|
||||||
atlassian-ide-plugin.xml
|
# 2. Файлы операционных систем (OS metadata)
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
# Cursive Clojure plugin
|
# macOS: файлы кэша структуры папок и метаданных Finder
|
||||||
.idea/replstate.xml
|
|
||||||
|
|
||||||
# SonarLint plugin
|
|
||||||
.idea/sonarlint/
|
|
||||||
|
|
||||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
|
||||||
com_crashlytics_export_strings.xml
|
|
||||||
crashlytics.properties
|
|
||||||
crashlytics-build.properties
|
|
||||||
fabric.properties
|
|
||||||
|
|
||||||
# Editor-based Rest Client
|
|
||||||
.idea/httpRequests
|
|
||||||
|
|
||||||
# Android studio 3.1+ serialized cache file
|
|
||||||
.idea/caches/build_file_checksums.ser
|
|
||||||
|
|
||||||
### JetBrains template
|
|
||||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
|
||||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
|
||||||
|
|
||||||
# User-specific stuff
|
|
||||||
.idea/**/workspace.xml
|
|
||||||
.idea/**/tasks.xml
|
|
||||||
.idea/**/usage.statistics.xml
|
|
||||||
.idea/**/dictionaries
|
|
||||||
.idea/**/shelf
|
|
||||||
|
|
||||||
# AWS User-specific
|
|
||||||
.idea/**/aws.xml
|
|
||||||
|
|
||||||
# Generated files
|
|
||||||
.idea/**/contentModel.xml
|
|
||||||
|
|
||||||
# Sensitive or high-churn files
|
|
||||||
.idea/**/dataSources/
|
|
||||||
.idea/**/dataSources.ids
|
|
||||||
.idea/**/dataSources.local.xml
|
|
||||||
.idea/**/sqlDataSources.xml
|
|
||||||
.idea/**/dynamic.xml
|
|
||||||
.idea/**/uiDesigner.xml
|
|
||||||
.idea/**/dbnavigator.xml
|
|
||||||
|
|
||||||
# Gradle
|
|
||||||
.idea/**/gradle.xml
|
|
||||||
.idea/**/libraries
|
|
||||||
|
|
||||||
# Gradle and Maven with auto-import
|
|
||||||
# When using Gradle or Maven with auto-import, you should exclude module files,
|
|
||||||
# since they will be recreated, and may cause churn. Uncomment if using
|
|
||||||
# auto-import.
|
|
||||||
# .idea/artifacts
|
|
||||||
# .idea/compiler.xml
|
|
||||||
# .idea/jarRepositories.xml
|
|
||||||
# .idea/modules.xml
|
|
||||||
# .idea/*.iml
|
|
||||||
# .idea/modules
|
|
||||||
# *.iml
|
|
||||||
# *.ipr
|
|
||||||
|
|
||||||
# CMake
|
|
||||||
cmake-build-*/
|
|
||||||
|
|
||||||
# Mongo Explorer plugin
|
|
||||||
.idea/**/mongoSettings.xml
|
|
||||||
|
|
||||||
# File-based project format
|
|
||||||
*.iws
|
|
||||||
|
|
||||||
# IntelliJ
|
|
||||||
out/
|
|
||||||
|
|
||||||
# mpeltonen/sbt-idea plugin
|
|
||||||
.idea_modules/
|
|
||||||
|
|
||||||
# JIRA plugin
|
|
||||||
atlassian-ide-plugin.xml
|
|
||||||
|
|
||||||
# Cursive Clojure plugin
|
|
||||||
.idea/replstate.xml
|
|
||||||
|
|
||||||
# SonarLint plugin
|
|
||||||
.idea/sonarlint/
|
|
||||||
|
|
||||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
|
||||||
com_crashlytics_export_strings.xml
|
|
||||||
crashlytics.properties
|
|
||||||
crashlytics-build.properties
|
|
||||||
fabric.properties
|
|
||||||
|
|
||||||
# Editor-based Rest Client
|
|
||||||
.idea/httpRequests
|
|
||||||
|
|
||||||
# Android studio 3.1+ serialized cache file
|
|
||||||
.idea/caches/build_file_checksums.ser
|
|
||||||
|
|
||||||
### PyCharm template
|
|
||||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
|
||||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
|
||||||
|
|
||||||
# User-specific stuff
|
|
||||||
.idea/**/workspace.xml
|
|
||||||
.idea/**/tasks.xml
|
|
||||||
.idea/**/usage.statistics.xml
|
|
||||||
.idea/**/dictionaries
|
|
||||||
.idea/**/shelf
|
|
||||||
|
|
||||||
# AWS User-specific
|
|
||||||
.idea/**/aws.xml
|
|
||||||
|
|
||||||
# Generated files
|
|
||||||
.idea/**/contentModel.xml
|
|
||||||
|
|
||||||
# Sensitive or high-churn files
|
|
||||||
.idea/**/dataSources/
|
|
||||||
.idea/**/dataSources.ids
|
|
||||||
.idea/**/dataSources.local.xml
|
|
||||||
.idea/**/sqlDataSources.xml
|
|
||||||
.idea/**/dynamic.xml
|
|
||||||
.idea/**/uiDesigner.xml
|
|
||||||
.idea/**/dbnavigator.xml
|
|
||||||
|
|
||||||
# Gradle
|
|
||||||
.idea/**/gradle.xml
|
|
||||||
.idea/**/libraries
|
|
||||||
|
|
||||||
# Gradle and Maven with auto-import
|
|
||||||
# When using Gradle or Maven with auto-import, you should exclude module files,
|
|
||||||
# since they will be recreated, and may cause churn. Uncomment if using
|
|
||||||
# auto-import.
|
|
||||||
# .idea/artifacts
|
|
||||||
# .idea/compiler.xml
|
|
||||||
# .idea/jarRepositories.xml
|
|
||||||
# .idea/modules.xml
|
|
||||||
# .idea/*.iml
|
|
||||||
# .idea/modules
|
|
||||||
# *.iml
|
|
||||||
# *.ipr
|
|
||||||
|
|
||||||
# CMake
|
|
||||||
cmake-build-*/
|
|
||||||
|
|
||||||
# Mongo Explorer plugin
|
|
||||||
.idea/**/mongoSettings.xml
|
|
||||||
|
|
||||||
# File-based project format
|
|
||||||
*.iws
|
|
||||||
|
|
||||||
# IntelliJ
|
|
||||||
out/
|
|
||||||
|
|
||||||
# mpeltonen/sbt-idea plugin
|
|
||||||
.idea_modules/
|
|
||||||
|
|
||||||
# JIRA plugin
|
|
||||||
atlassian-ide-plugin.xml
|
|
||||||
|
|
||||||
# Cursive Clojure plugin
|
|
||||||
.idea/replstate.xml
|
|
||||||
|
|
||||||
# SonarLint plugin
|
|
||||||
.idea/sonarlint/
|
|
||||||
|
|
||||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
|
||||||
com_crashlytics_export_strings.xml
|
|
||||||
crashlytics.properties
|
|
||||||
crashlytics-build.properties
|
|
||||||
fabric.properties
|
|
||||||
|
|
||||||
# Editor-based Rest Client
|
|
||||||
.idea/httpRequests
|
|
||||||
|
|
||||||
# Android studio 3.1+ serialized cache file
|
|
||||||
.idea/caches/build_file_checksums.ser
|
|
||||||
|
|
||||||
### macOS template
|
|
||||||
# General
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.AppleDouble
|
.DS_Store?
|
||||||
.LSOverride
|
|
||||||
|
|
||||||
# Icon must end with two \r
|
|
||||||
Icon
|
|
||||||
|
|
||||||
# Thumbnails
|
|
||||||
._*
|
._*
|
||||||
|
|
||||||
# Files that might appear in the root of a volume
|
|
||||||
.DocumentRevisions-V100
|
|
||||||
.fseventsd
|
|
||||||
.Spotlight-V100
|
.Spotlight-V100
|
||||||
.TemporaryItems
|
|
||||||
.Trashes
|
.Trashes
|
||||||
.VolumeIcon.icns
|
|
||||||
.com.apple.timemachine.donotpresent
|
|
||||||
|
|
||||||
# Directories potentially created on remote AFP share
|
# Windows: кэши миниатюр и настройки отображения папок
|
||||||
.AppleDB
|
Thumbs.db
|
||||||
.AppleDesktop
|
Thumbs.db:encryptable
|
||||||
Network Trash Folder
|
ehthumbs.db
|
||||||
Temporary Items
|
ehthumbs_vista.db
|
||||||
.apdisk
|
Desktop.ini
|
||||||
|
$RECYCLE.BIN/
|
||||||
|
|
||||||
### Python template
|
# Linux: служебные файлы
|
||||||
# Byte-compiled / optimized / DLL files
|
*~
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# 3. Python и виртуальные окружения (Python & Virtual Environments)
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Байткод Python и кэши скомпилированных файлов:
|
||||||
__pycache__/
|
__pycache__/
|
||||||
*.py[cod]
|
*.py[cod]
|
||||||
*$py.class
|
*$py.class
|
||||||
|
|
||||||
# C extensions
|
|
||||||
*.so
|
*.so
|
||||||
|
|
||||||
# Distribution / packaging
|
# Виртуальные окружения (локальные папки с установленными зависимостями):
|
||||||
.Python
|
.venv/
|
||||||
|
env/
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
env.bak/
|
||||||
|
venv.bak/
|
||||||
|
|
||||||
|
# Артефакты сборки, дистрибутивы и метаданные пакетов:
|
||||||
build/
|
build/
|
||||||
develop-eggs/
|
develop-eggs/
|
||||||
dist/
|
dist/
|
||||||
@@ -286,228 +77,75 @@ parts/
|
|||||||
sdist/
|
sdist/
|
||||||
var/
|
var/
|
||||||
wheels/
|
wheels/
|
||||||
share/python-wheels/
|
|
||||||
*.egg-info/
|
*.egg-info/
|
||||||
.installed.cfg
|
.installed.cfg
|
||||||
*.egg
|
*.egg
|
||||||
MANIFEST
|
MANIFEST
|
||||||
|
|
||||||
# PyInstaller
|
# Кэши инструментов статического анализа, тестирования и типизации:
|
||||||
# Usually these files are written by a python script from a template
|
.pytest_cache/
|
||||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
||||||
*.manifest
|
|
||||||
*.spec
|
|
||||||
|
|
||||||
# Installer logs
|
|
||||||
pip-log.txt
|
|
||||||
pip-delete-this-directory.txt
|
|
||||||
|
|
||||||
# Unit test / coverage reports
|
|
||||||
htmlcov/
|
|
||||||
.tox/
|
|
||||||
.nox/
|
|
||||||
.coverage
|
.coverage
|
||||||
.coverage.*
|
.coverage.*
|
||||||
.cache
|
htmlcov/
|
||||||
nosetests.xml
|
|
||||||
coverage.xml
|
|
||||||
*.cover
|
|
||||||
*.py,cover
|
|
||||||
.hypothesis/
|
|
||||||
.pytest_cache/
|
|
||||||
cover/
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
*.mo
|
|
||||||
*.pot
|
|
||||||
|
|
||||||
# Django stuff:
|
|
||||||
*.log
|
|
||||||
local_settings.py
|
|
||||||
*.sqlite3
|
|
||||||
*.sqlite3-journal
|
|
||||||
|
|
||||||
# Flask stuff:
|
|
||||||
instance/
|
|
||||||
.webassets-cache
|
|
||||||
|
|
||||||
# Scrapy stuff:
|
|
||||||
.scrapy
|
|
||||||
|
|
||||||
# Sphinx documentation
|
|
||||||
docs/_build/
|
|
||||||
|
|
||||||
# PyBuilder
|
|
||||||
.pybuilder/
|
|
||||||
target/
|
|
||||||
|
|
||||||
# Jupyter Notebook
|
|
||||||
.ipynb_checkpoints
|
|
||||||
|
|
||||||
# IPython
|
|
||||||
profile_default/
|
|
||||||
ipython_config.py
|
|
||||||
|
|
||||||
# pyenv
|
|
||||||
# For a library or package, you might want to ignore these files since the code is
|
|
||||||
# intended to run in multiple environments; otherwise, check them in:
|
|
||||||
# .python-version
|
|
||||||
|
|
||||||
# pipenv
|
|
||||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
||||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
||||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
||||||
# install all needed dependencies.
|
|
||||||
#Pipfile.lock
|
|
||||||
|
|
||||||
# poetry
|
|
||||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
||||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
||||||
# commonly ignored for libraries.
|
|
||||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
||||||
#poetry.lock
|
|
||||||
|
|
||||||
# pdm
|
|
||||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
||||||
#pdm.lock
|
|
||||||
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
|
||||||
# in version control.
|
|
||||||
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
|
|
||||||
.pdm.toml
|
|
||||||
.pdm-python
|
|
||||||
.pdm-build/
|
|
||||||
|
|
||||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
||||||
__pypackages__/
|
|
||||||
|
|
||||||
# Celery stuff
|
|
||||||
celerybeat-schedule
|
|
||||||
celerybeat.pid
|
|
||||||
|
|
||||||
# SageMath parsed files
|
|
||||||
*.sage.py
|
|
||||||
|
|
||||||
# Environments
|
|
||||||
.env
|
|
||||||
.venv
|
|
||||||
env/
|
|
||||||
venv/
|
|
||||||
ENV/
|
|
||||||
env.bak/
|
|
||||||
venv.bak/
|
|
||||||
|
|
||||||
# Spyder project settings
|
|
||||||
.spyderproject
|
|
||||||
.spyproject
|
|
||||||
|
|
||||||
# Rope project settings
|
|
||||||
.ropeproject
|
|
||||||
|
|
||||||
# mkdocs documentation
|
|
||||||
/site
|
|
||||||
|
|
||||||
# mypy
|
|
||||||
.mypy_cache/
|
.mypy_cache/
|
||||||
.dmypy.json
|
.dmypy.json
|
||||||
dmypy.json
|
dmypy.json
|
||||||
|
|
||||||
# Pyre type checker
|
|
||||||
.pyre/
|
.pyre/
|
||||||
|
|
||||||
# pytype static type analyzer
|
|
||||||
.pytype/
|
.pytype/
|
||||||
|
.ruff_cache/
|
||||||
|
.tox/
|
||||||
|
.nox/
|
||||||
|
|
||||||
# Cython debug symbols
|
# ------------------------------------------------------------------------------
|
||||||
cython_debug/
|
# 4. Django, базы данных, медиа-файлы и секреты (Django, DB & Secrets)
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Конфигурационные файлы с секретами и локальными переменными окружения:
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
|
|
||||||
# PyCharm
|
# Базы данных SQLite (локальные тестовые и рабочие БД):
|
||||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
*.sqlite3
|
||||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
*.sqlite3-journal
|
||||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
*.db
|
||||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
*.db-journal
|
||||||
#.idea/
|
db.sqlite3
|
||||||
|
data/*.sqlite3
|
||||||
|
data/*.db
|
||||||
|
data/*.sqlite3-journal
|
||||||
|
data/*.db-journal
|
||||||
|
!data/README.md
|
||||||
|
|
||||||
### PyCharm+all template
|
# Локально загружаемые медиа-файлы (пользовательский контент: картинки блога и т.д.):
|
||||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
media/*
|
||||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
!media/README.md
|
||||||
|
!media/.gitkeep
|
||||||
|
|
||||||
# User-specific stuff
|
# Лог-файлы:
|
||||||
.idea/**/workspace.xml
|
*.log
|
||||||
.idea/**/tasks.xml
|
logs/
|
||||||
.idea/**/usage.statistics.xml
|
|
||||||
.idea/**/dictionaries
|
|
||||||
.idea/**/shelf
|
|
||||||
|
|
||||||
# AWS User-specific
|
# ------------------------------------------------------------------------------
|
||||||
.idea/**/aws.xml
|
# 5. Node.js, npm и фронтенд-сборка (Frontend Assembly)
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
# Generated files
|
# Зависимости npm (устанавливаются через npm install / assembly_via_npm.sh):
|
||||||
.idea/**/contentModel.xml
|
node_modules/
|
||||||
|
*/node_modules/
|
||||||
# Sensitive or high-churn files
|
|
||||||
.idea/**/dataSources/
|
|
||||||
.idea/**/dataSources.ids
|
|
||||||
.idea/**/dataSources.local.xml
|
|
||||||
.idea/**/sqlDataSources.xml
|
|
||||||
.idea/**/dynamic.xml
|
|
||||||
.idea/**/uiDesigner.xml
|
|
||||||
.idea/**/dbnavigator.xml
|
|
||||||
|
|
||||||
# Gradle
|
|
||||||
.idea/**/gradle.xml
|
|
||||||
.idea/**/libraries
|
|
||||||
|
|
||||||
# Gradle and Maven with auto-import
|
|
||||||
# When using Gradle or Maven with auto-import, you should exclude module files,
|
|
||||||
# since they will be recreated, and may cause churn. Uncomment if using
|
|
||||||
# auto-import.
|
|
||||||
# .idea/artifacts
|
|
||||||
# .idea/compiler.xml
|
|
||||||
# .idea/jarRepositories.xml
|
|
||||||
# .idea/modules.xml
|
|
||||||
# .idea/*.iml
|
|
||||||
# .idea/modules
|
|
||||||
# *.iml
|
|
||||||
# *.ipr
|
|
||||||
|
|
||||||
# CMake
|
|
||||||
cmake-build-*/
|
|
||||||
|
|
||||||
# Mongo Explorer plugin
|
|
||||||
.idea/**/mongoSettings.xml
|
|
||||||
|
|
||||||
# File-based project format
|
|
||||||
*.iws
|
|
||||||
|
|
||||||
# IntelliJ
|
|
||||||
out/
|
|
||||||
|
|
||||||
# mpeltonen/sbt-idea plugin
|
|
||||||
.idea_modules/
|
|
||||||
|
|
||||||
# JIRA plugin
|
|
||||||
atlassian-ide-plugin.xml
|
|
||||||
|
|
||||||
# Cursive Clojure plugin
|
|
||||||
.idea/replstate.xml
|
|
||||||
|
|
||||||
# SonarLint plugin
|
|
||||||
.idea/sonarlint/
|
|
||||||
|
|
||||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
|
||||||
com_crashlytics_export_strings.xml
|
|
||||||
crashlytics.properties
|
|
||||||
crashlytics-build.properties
|
|
||||||
fabric.properties
|
|
||||||
|
|
||||||
# Editor-based Rest Client
|
|
||||||
.idea/httpRequests
|
|
||||||
|
|
||||||
# Android studio 3.1+ serialized cache file
|
|
||||||
.idea/caches/build_file_checksums.ser
|
|
||||||
|
|
||||||
# --- ETPGRF Custom ---
|
|
||||||
# Generated frontend assets
|
|
||||||
public/static/codemirror/editor.js
|
|
||||||
frontend-assembly/dist/
|
|
||||||
frontend-assembly/node_modules/
|
frontend-assembly/node_modules/
|
||||||
|
|
||||||
|
# Логи пакетных менеджеров:
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
|
||||||
|
# Директории сборки и бандлов:
|
||||||
|
frontend-assembly/dist/
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# 6. Временные файлы и артефакты отладки
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
*.tmp
|
||||||
|
*.temp
|
||||||
|
*.bak
|
||||||
|
.output.txt
|
||||||
|
|||||||
@@ -5,6 +5,18 @@
|
|||||||
Формат основан на [Keep a Changelog](https://keepachangelog.com/ru/1.0.0/),
|
Формат основан на [Keep a Changelog](https://keepachangelog.com/ru/1.0.0/),
|
||||||
и этот проект придерживается [Semantic Versioning](https://semver.org/lang/ru/).
|
и этот проект придерживается [Semantic Versioning](https://semver.org/lang/ru/).
|
||||||
|
|
||||||
|
## [0.2.7] - 2025-03-22
|
||||||
|
### Изменено
|
||||||
|
- Обновление всех пактов, включая `etpgrf` (v0.1.5 → v0.1.6.post1).
|
||||||
|
|
||||||
|
### Исправлено
|
||||||
|
- `canonical` для страниц учитывает https/http.
|
||||||
|
|
||||||
|
## [0.2.6] - 2025-02-28
|
||||||
|
|
||||||
|
### Изменено
|
||||||
|
- Подключена новая версия библиотеки `etpgrf` (v0.1.4 → v0.1.5).
|
||||||
|
|
||||||
## [0.2.5] — 2025–02–13
|
## [0.2.5] — 2025–02–13
|
||||||
|
|
||||||
### Добавлено
|
### Добавлено
|
||||||
|
|||||||
+56
-44
@@ -1,60 +1,72 @@
|
|||||||
# --- Stage 1: Сборка фронтенда (CodeMirror) ---
|
# -----------------------------------------------------------------------------
|
||||||
FROM node:20-slim as frontend-builder
|
# --- Этап 1: Сборщик (Builder) ---
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Используем официальный, но компактный образ Python как "строительную площадку".
|
||||||
|
# На этом этапе мы установим все зависимости, а потом скопируем только результат.
|
||||||
|
FROM python:3.13-slim AS builder
|
||||||
|
|
||||||
WORKDIR /app/frontend
|
# Устанавливаем переменные окружения для Poetry
|
||||||
|
ENV PYTHONDONTWRITEBYTECODE=1
|
||||||
|
ENV PYTHONUNBUFFERED=1
|
||||||
|
# Эти настройки говорят Poetry создать виртуальное окружение прямо в папке проекта (/app/.venv)
|
||||||
|
ENV POETRY_NO_INTERACTION=1
|
||||||
|
ENV POETRY_VIRTUALENVS_IN_PROJECT=1
|
||||||
|
ENV POETRY_VIRTUALENVS_CREATE=1
|
||||||
|
ENV POETRY_CACHE_DIR=/tmp/poetry_cache
|
||||||
|
|
||||||
# Копируем файлы зависимостей
|
# Устанавливаем саму Poetry
|
||||||
COPY frontend-assembly/package.json frontend-assembly/package-lock.json ./
|
RUN pip install poetry
|
||||||
|
|
||||||
# Устанавливаем зависимости (включая devDependencies для сборки)
|
|
||||||
RUN npm ci
|
|
||||||
|
|
||||||
# Копируем исходники
|
|
||||||
COPY frontend-assembly/ ./
|
|
||||||
|
|
||||||
# Собираем бандл через npm script
|
|
||||||
RUN npm run build
|
|
||||||
|
|
||||||
|
|
||||||
# --- Stage 2: Сборка бэкенда (Django) ---
|
|
||||||
FROM python:3.13-slim
|
|
||||||
|
|
||||||
# Настройки Python
|
|
||||||
ENV PYTHONDONTWRITEBYTECODE 1
|
|
||||||
ENV PYTHONUNBUFFERED 1
|
|
||||||
|
|
||||||
|
# Устанавливаем рабочую директорию внутри контейнера
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# Установка Poetry
|
# Копируем только файлы зависимостей.
|
||||||
RUN pip install --no-cache-dir poetry
|
# Docker кэширует этот слой. Если эти файлы не меняются, Docker не будет
|
||||||
|
# переустанавливать все зависимости при каждой сборке.
|
||||||
|
COPY poetry.lock pyproject.toml ./
|
||||||
|
|
||||||
# Копируем файлы зависимостей
|
# Устанавливаем зависимости с помощью Poetry.
|
||||||
COPY pyproject.toml poetry.lock* /app/
|
# --no-root: не устанавливать сам проект (etpgrf-site) как пакет.
|
||||||
|
# --only main: устанавливать только основные зависимости (не dev).
|
||||||
|
RUN poetry install --no-interaction --no-ansi --no-root --only main
|
||||||
|
|
||||||
# Настройка Poetry: не создавать venv и установка зависимостей (без dev-зависимостей для продакшена)
|
# Очищаем кеш Poetry, чтобы он не попал в финальный образ.
|
||||||
RUN poetry config virtualenvs.create false \
|
RUN poetry cache clear --all -n
|
||||||
&& poetry install --no-interaction --no-ansi --no-root --only main
|
|
||||||
|
|
||||||
# Создаем непривилегированного пользователя
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# --- Этап 2: Финальный образ ---
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Начинаем с такого же чистого и легкого образа Python.
|
||||||
|
FROM python:3.13-slim
|
||||||
|
|
||||||
|
# Устанавливаем рабочую директорию
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Создаем непривилегированного пользователя для запуска приложения
|
||||||
RUN useradd -m -r appuser
|
RUN useradd -m -r appuser
|
||||||
|
# Устанавливаем владельца рабочей директории
|
||||||
# Копируем код проекта
|
|
||||||
COPY . /app/
|
|
||||||
|
|
||||||
# Создаем папку для данных и статики, чтобы у appuser были права
|
|
||||||
RUN mkdir -p /app/data /app/public/static_collected
|
|
||||||
|
|
||||||
# Копируем собранный фронтенд из первого стейджа
|
|
||||||
COPY --from=frontend-builder /app/frontend/dist/editor.js /app/public/static/codemirror/editor.js
|
|
||||||
|
|
||||||
# Меняем владельца папки
|
|
||||||
RUN chown -R appuser:appuser /app
|
RUN chown -R appuser:appuser /app
|
||||||
|
|
||||||
# Переключаемся на пользователя
|
# Копируем готовое виртуальное окружение из сборщика
|
||||||
|
COPY --from=builder /app/.venv ./.venv
|
||||||
|
|
||||||
|
# Устанавливаем PATH, чтобы использовать python из .venv
|
||||||
|
ENV PATH="/app/.venv/bin:$PATH"
|
||||||
|
|
||||||
|
# Копируем весь код нашего приложения в рабочую директорию.
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Устанавливаем владельца для скопированных файлов
|
||||||
|
RUN chown -R appuser:appuser /app
|
||||||
|
|
||||||
|
# Переключаемся на непривилегированного пользователя
|
||||||
USER appuser
|
USER appuser
|
||||||
|
|
||||||
# Порт
|
# Сообщаем Docker, что наше приложение будет работать на порту 8000.
|
||||||
|
# Это нужно для `docker-compose`.
|
||||||
EXPOSE 8000
|
EXPOSE 8000
|
||||||
|
|
||||||
# Команда запуска через Gunicorn
|
# Команда запуска через Gunicorn (не обязательно. т.к. дублируется в docker-compose, но для чистоты образа
|
||||||
|
# на случай, если кто-то захочет запустить контейнер напрямую, а не через docker-compose, оставляем её здесь).
|
||||||
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "--chdir", "/app/etpgrf_site", "etpgrf_site.wsgi"]
|
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "--chdir", "/app/etpgrf_site", "etpgrf_site.wsgi"]
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
# Заметки по деплою проекту
|
||||||
|
|
||||||
|
В финальном комите перед новой версией (*ТОЛЬ ПОСЛЕ ТЕСТИРОВАНИЯ:) не забываем менять:
|
||||||
|
- `CHANGELOG.md` делать описание изменений.
|
||||||
|
- `typograph/templates/typograph/base.html` в шаблоне циферки в подвале
|
||||||
|
|
||||||
|
|
||||||
|
Затем, когда код готов к релизу, нужно обновить версию в `pyproject.toml`:
|
||||||
|
```shell
|
||||||
|
poetry version patch
|
||||||
|
```
|
||||||
|
|
||||||
|
Финальный коммит, таким образом, должен включать в себя:
|
||||||
|
- Обновление `CHANGELOG.md` с описанием изменений.
|
||||||
|
- Обновление версии в `pyproject.toml` (через `poetry version patch`).
|
||||||
|
- Обновление шаблона `base.html` (циферки в подвале).
|
||||||
|
— Обновление в `etpgrf_site/__init__.py`.
|
||||||
|
- И, конечно же, все изменения, которые были сделаны в коде.
|
||||||
|
|
||||||
|
После комита и пуша:
|
||||||
|
```shell
|
||||||
|
git add .
|
||||||
|
git commit -am "Обновление версии для релиза vX.Y.Z"
|
||||||
|
git push origin main
|
||||||
|
```
|
||||||
|
|
||||||
|
Для запуска деплоя CI/CD, через Gitea Actions, создаем `git tag` и пушим его в репозиторий:
|
||||||
|
```shell
|
||||||
|
git tag vX.Y.Z && git push origin vX.Y.Z
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
# Бэкенд: Архитектура и модули
|
||||||
|
|
||||||
|
## 1. Django Конфигурация (`etpgrf_site/etpgrf_site/`)
|
||||||
|
- **`settings.py`**:
|
||||||
|
- `DEBUG` и `SECRET_KEY` считываются из переменных окружения.
|
||||||
|
- `ADMIN_URL`: настраиваемый префикс панели администратора (по умолчанию `admin/`).
|
||||||
|
- База данных: SQLite в `data/db-etpgrf.sqlite3` с `timeout: 20` для надежности при конкурентных запросах.
|
||||||
|
- Статика: `STATICFILES_DIRS = [BASE_DIR.parent / 'public' / 'static']`, `STATIC_ROOT = BASE_DIR.parent / 'public' / 'static_collected'`.
|
||||||
|
- Медиа: `MEDIA_ROOT = BASE_DIR.parent / 'media'`, `MEDIA_URL = '/media/'`.
|
||||||
|
- Защита за прокси: в `not DEBUG` активированы `SECURE_PROXY_SSL_HEADER`, `SECURE_SSL_REDIRECT`, `SESSION_COOKIE_SECURE`, `CSRF_COOKIE_SECURE`, `USE_X_FORWARDED_HOST`, `USE_X_FORWARDED_PORT`.
|
||||||
|
- **`urls.py`**:
|
||||||
|
- `/{ADMIN_URL}` -> админка Django.
|
||||||
|
- `/` -> `typograph.urls` (главная страница типографа, API обработки и статистики).
|
||||||
|
- `/blog/` -> `blog.urls` (список постов, детальная страница поста).
|
||||||
|
- `/sitemap.xml` -> карта сайта (`PostSitemap`).
|
||||||
|
- `/<slug:slug>/` -> ловушка для статических страниц (например, `/about/`, `/privacy-policy/`, `/donate/`).
|
||||||
|
|
||||||
|
## 2. Приложение `typograph`
|
||||||
|
Отвечает за взаимодействие с библиотекой типографирования `etpgrf` и сбор агрегированной аналитики.
|
||||||
|
|
||||||
|
### Модели (`typograph/models.py`)
|
||||||
|
- **`DailyStat`**:
|
||||||
|
- Агрегированные посуточные показатели (`date` - уникальная дата).
|
||||||
|
- Метрики активности: `index_views`, `process_requests`, `copy_count`.
|
||||||
|
- Объемы данных: `chars_in`, `chars_out`, `chars_copied`.
|
||||||
|
- Производительность: `total_processing_time_ms`, вычисляемое свойство `avg_processing_time_ms`.
|
||||||
|
- `settings_stats`: JSONField со статистикой популярности опций (языки, кавычки, переносы, висячая пунктуация и т.д.).
|
||||||
|
|
||||||
|
### Представления и Эндпоинты (`typograph/views.py`)
|
||||||
|
- `index` (`/`): главная страница, инкрементирует просмотры `index_views`.
|
||||||
|
- `process_text` (`/process/`, POST):
|
||||||
|
- Принимает текст и параметры типографирования из формы.
|
||||||
|
- Инициализирует `LayoutProcessor`, `Hyphenator`, `Typographer` из пакета `etpgrf`.
|
||||||
|
- Производит замер времени обработки `time.perf_counter()`.
|
||||||
|
- Атомарно обновляет счетчики в `DailyStat`.
|
||||||
|
- Возвращает HTML-фрагмент `typograph/result_fragment.html` и заголовок `X-Processing-Time`.
|
||||||
|
- `track_copy` (`/track-copy/`, POST): фиксирует факт и длину скопированного в буфер текста.
|
||||||
|
- `get_stats_summary` (`/stats-summary/`, GET): возвращает компактный HTMX-фрагмент со статистикой для футера.
|
||||||
|
|
||||||
|
## 3. Приложение `blog`
|
||||||
|
Отвечает за публикации, статьи и произвольные статические страницы.
|
||||||
|
|
||||||
|
### Модели (`blog/models.py`)
|
||||||
|
- **`Post`**:
|
||||||
|
- `post_type`: `B` (Пост блога) или `P` (Страница).
|
||||||
|
- `title`, `slug` (автоматическая транслитерация через `pytils.translit.slugify` с обработкой коллизий).
|
||||||
|
- `is_published`, `published_at`, `updated_at`.
|
||||||
|
- `content` (HTML-разметка), `excerpt` (тизер).
|
||||||
|
- `image` (обложка для превью и Open Graph).
|
||||||
|
- SEO-поля: `seo_title`, `seo_description`, `seo_keywords`.
|
||||||
|
- Метод `get_absolute_url()`: возвращает `/blog/<slug>/` для постов или `/<slug>/` для страниц.
|
||||||
|
|
||||||
|
### Представления (`blog/views.py`)
|
||||||
|
- `post_list` (`/blog/`): список опубликованных статей блога с пагинацией.
|
||||||
|
- `post_detail` (`/blog/<slug>/`): детальная страница поста с Open Graph мета-тегами и разметкой.
|
||||||
|
- `page_detail` (`/<slug>/`): отображение статических страниц.
|
||||||
|
- `sitemaps.py`: генерация `sitemap.xml` по опубликованным записям.
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
# Деплой, окружение и регламент релизов
|
||||||
|
|
||||||
|
## 1. Конфигурация окружения (.env)
|
||||||
|
Обязательные переменные:
|
||||||
|
- `SECRET_KEY`: секретный ключ Django.
|
||||||
|
- `DEBUG`: `True` для локальной разработки, `False` для продакшена.
|
||||||
|
- `ALLOWED_HOSTS`: разрешенные хосты через запятую (например, `typograph.cube2.ru,localhost`).
|
||||||
|
- `CSRF_TRUSTED_ORIGINS`: доверенные URL (например, `https://typograph.cube2.ru,http://localhost:8000`).
|
||||||
|
- `ADMIN_URL`: путь к панели администратора (например, `mysecretadmin/`).
|
||||||
|
|
||||||
|
## 2. Docker и инфраструктура
|
||||||
|
- **`Dockerfile`**: базовый образ `python:3.13-slim`, установка Poetry, сборка зависимостей, запуск через Gunicorn.
|
||||||
|
- **`docker-compose.yml`**: конфигурация для разработки (монтирование исходного кода, базы данных и папок статики/медиа).
|
||||||
|
- **`docker-compose.prod.yml`**: конфигурация для продакшена со связанным сервисом Nginx (`config/nginx/`).
|
||||||
|
|
||||||
|
## 3. Чек-лист выпуска релиза (согласно NOTE.md)
|
||||||
|
Перед созданием релизного тега:
|
||||||
|
1. Протестировать функционал и миграции.
|
||||||
|
2. Внести описание изменений в `CHANGELOG.md`.
|
||||||
|
3. Обновить версии в:
|
||||||
|
- `pyproject.toml` (командой `poetry version patch` или вручную).
|
||||||
|
- `etpgrf_site/__init__.py` (при наличии `__version__`).
|
||||||
|
- `etpgrf_site/typograph/templates/typograph/base.html` (версия библиотеки и версия сайта в футере).
|
||||||
|
4. Закоммитить изменения:
|
||||||
|
```bash
|
||||||
|
git add .
|
||||||
|
git commit -am "Обновление версии для релиза vX.Y.Z"
|
||||||
|
git push origin main
|
||||||
|
```
|
||||||
|
5. Запустить пайплайн сборки и деплоя через Gitea Actions путем простановки тега:
|
||||||
|
```bash
|
||||||
|
git tag vX.Y.Z && git push origin vX.Y.Z
|
||||||
|
```
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
# Фронтенд: Структура и сборка
|
||||||
|
|
||||||
|
## 1. Стек и библиотеки
|
||||||
|
- **HTML / Django Templates:** шаблоны `base.html`, компоненты `typograph/` и `blog/`.
|
||||||
|
- **CSS:** Bootstrap 5, Bootstrap Icons, кастомные стили `public/static/css/etpgrf.css` с поддержкой темной/светлой темы.
|
||||||
|
- **Интерактивность:**
|
||||||
|
- **HTMX:** асинхронные запросы (отправка формы типографирования, динамическое обновление плашки со статистикой без перезагрузки).
|
||||||
|
- **Alpine.js:** управление состоянием клиентских виджетов и раскрывающихся списков опций.
|
||||||
|
- **Редактор текста:** CodeMirror 6.
|
||||||
|
|
||||||
|
## 2. Сборка фронтенда и вендорных библиотек (`frontend-assembly/`)
|
||||||
|
- Каталог `frontend-assembly/` управляет сборкой редактора и подготовкой локальных копий всех сторонних библиотек для автономной работы и CDN-fallback:
|
||||||
|
- `src/editor.js`: инициализация CodeMirror 6, подсветка спецсимволов (` `, `­`, тире, кавычки), интеграция темной/светлой темы.
|
||||||
|
- `copy-vendor.js`: копирование дистрибутивов Bootstrap 5, Bootstrap Icons (включая шрифты), HTMX и Alpine.js из `node_modules` в `public/static/vendor/`.
|
||||||
|
- `package.json`: зависимости `@codemirror/*`, `bootstrap`, `bootstrap-icons`, `htmx.org`, `alpinejs`, `esbuild`.
|
||||||
|
- Команды сборки:
|
||||||
|
- `npm run build`: выполняет `build:codemirror` (сборка `editor.js`) и `build:vendor` (копирование всех библиотек в `static/vendor/`).
|
||||||
|
- `npm run build:codemirror`: компилирует бандл в `public/static/codemirror/editor.js`.
|
||||||
|
- `npm run build:vendor`: экспортирует вендорные CSS/JS/шрифты в `public/static/vendor/`.
|
||||||
|
- `npm run watch`: отслеживание изменений CodeMirror в реальном времени.
|
||||||
|
|
||||||
|
## 3. CDN Fallback стратегия (`base.html`)
|
||||||
|
- Основные библиотеки подключаются из публичных CDN (jsDelivr, unpkg) для быстрой доставки и кэширования в браузерах.
|
||||||
|
- При блокировке или недоступности CDN срабатывает автоматический fallback на локальные статические копии:
|
||||||
|
- **CSS (Bootstrap, Bootstrap Icons):** атрибут `onerror="this.onerror=null;this.href='{% static ... %}';"` на тегах `<link>`.
|
||||||
|
- **JS (HTMX, Bootstrap Bundle):** проверка глобального объекта (`window.htmx`, `window.bootstrap`) и подгрузка локальной копии через `document.write`.
|
||||||
|
- **JS (Alpine.js):** `onerror` динамически подключает локальный deferred-скрипт, а также предусмотрена резервная проверка в `DOMContentLoaded`.
|
||||||
|
|
||||||
|
## 4. Клиентская статика (`public/static/`)
|
||||||
|
- `css/etpgrf.css` — основные стили сайта, оформление типографа и блога.
|
||||||
|
- `js/base.js` — вспомогательные клиентские скрипты (обработка куки-баннера, копирование текста, переключение тем).
|
||||||
|
- `codemirror/` — собранный esm-бандл редактора.
|
||||||
|
- `vendor/` — локальные копии сторонних библиотек (`bootstrap`, `bootstrap-icons`, `htmx`, `alpinejs`).
|
||||||
|
- `svg/` и `img/` — векторные и растровые логотипы для светлой/темной темы и мета-тегов Open Graph.
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
# Архитектурный обзор проекта ETPGRF Site
|
||||||
|
|
||||||
|
## 1. Назначение проекта
|
||||||
|
**ETPGRF Site** — веб-сервис и интерфейс для онлайн-типографирования текстов на базе библиотеки `etpgrf`, а также контентная платформа (блог, справочные и статические страницы, статистика использования).
|
||||||
|
|
||||||
|
## 2. Технологический стек
|
||||||
|
- **Backend:** Python 3.13+, Django 6.0+, Poetry.
|
||||||
|
- **База данных:** SQLite 3 (файл `data/db-etpgrf.sqlite3` с увеличенным таймаутом блокировки).
|
||||||
|
- **Frontend:**
|
||||||
|
- Bootstrap 5 (CSS, сетка, компоненты, иконки bootstrap-icons).
|
||||||
|
- Alpine.js (декларативное состояние на клиенте).
|
||||||
|
- HTMX (динамическая подгрузка фрагментов, отправка форм типографирования, статистика).
|
||||||
|
- CodeMirror 6 (продвинутый редактор текста с подсветкой спецсимволов/сущностей).
|
||||||
|
- Сборщик фронтенда: `esbuild` в каталоге `frontend-assembly/`.
|
||||||
|
- **Инфраструктура & Деплой:**
|
||||||
|
- Docker & Docker Compose (`docker-compose.yml` для разработки, `docker-compose.prod.yml` для продакшена).
|
||||||
|
- Nginx (раздача статики `/static/` и медиа `/media/`, SSL-терминация, обратное проксирование к Gunicorn).
|
||||||
|
- Gunicorn (WSGI-сервер Django).
|
||||||
|
- CI/CD: Gitea Actions (автосборка Docker-образов по тегам `v*` и деплой).
|
||||||
|
|
||||||
|
## 3. Структура директорий
|
||||||
|
```
|
||||||
|
2026-etpgrf-site/
|
||||||
|
├── .github/ # Инструкции для AI и CI
|
||||||
|
│ └── copilot-instructions.md
|
||||||
|
├── _prj_blueprint/ # Быстрый контекст и документация архитектуры
|
||||||
|
├── config/ # Конфигурационные файлы Nginx
|
||||||
|
│ └── nginx/
|
||||||
|
├── data/ # Хранилище базы данных SQLite (db-etpgrf.sqlite3)
|
||||||
|
├── etpgrf_site/ # Исходный код Django
|
||||||
|
│ ├── manage.py
|
||||||
|
│ ├── etpgrf_site/ # Корневой модуль конфигурации (settings, urls, wsgi)
|
||||||
|
│ ├── typograph/ # Приложение типографа (обработка, DailyStat, UI)
|
||||||
|
│ └── blog/ # Приложение блога и статических страниц
|
||||||
|
├── frontend-assembly/ # Node.js окружение сборки CodeMirror 6 (esbuild)
|
||||||
|
├── media/ # Загружаемые медиа-файлы (обложки постов)
|
||||||
|
├── public/ # Статика проекта
|
||||||
|
│ ├── static/ # Исходная статика (css, js, img, svg, codemirror)
|
||||||
|
│ └── static_collected/ # Собранная статика collectstatic
|
||||||
|
├── Dockerfile # Сборка продакшн-образа
|
||||||
|
├── docker-compose.yml # Dev-окружение
|
||||||
|
├── docker-compose.prod.yml # Prod-окружение
|
||||||
|
├── pyproject.toml # Poetry зависимости и метаданные
|
||||||
|
├── NOTE.md # Инструкции по релизам и версионированию
|
||||||
|
└── CHANGELOG.md # История версий
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. Ссылки на детальные описания
|
||||||
|
- [Бэкенд и логика приложений](backend.md)
|
||||||
|
- [Фронтенд и сборка](frontend.md)
|
||||||
|
- [Деплой, окружение и релизный процесс](deployment.md)
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
# CI/CD с Gitea Runner
|
||||||
|
|
||||||
|
Gitea Runner — это инструмент для выполнения CI/CD задач, интегрированный с Gitea. Он позволяет автоматизировать
|
||||||
|
процессы сборки, тестирования и развертывания приложений.
|
||||||
|
|
||||||
|
В нашем репозии входим в "Настройки" -> "Действия" -> "Ранеры" и создаем новый Runner. Для это получим регистрационный
|
||||||
|
токен.
|
||||||
|
|
||||||
|
## Настройка Gitea Runner на macOS
|
||||||
|
|
||||||
|
```shell
|
||||||
|
docker run -d --restart always \ [±main ●]
|
||||||
|
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||||
|
-e GITEA_INSTANCE_URL=https://git.cube2.ru \
|
||||||
|
-e GITEA_RUNNER_REGISTRATION_TOKEN=здест_полученный_токен \
|
||||||
|
-e GITEA_RUNNER_REGISTRATION_TOKEN=здесь_вставляем_полученный_токен \
|
||||||
|
-e GITEA_RUNNER_NAME=mac-mini-runner \
|
||||||
|
--name gitea-runner \
|
||||||
|
gitea/act_runner:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
Увидим:
|
||||||
|
```txt
|
||||||
|
Unable to find image 'gitea/act_runner:latest' locally
|
||||||
|
latest: Pulling from gitea/act_runner
|
||||||
|
6e174226ea69: Pull complete
|
||||||
|
adcf2c0b73c2: Pull complete
|
||||||
|
e0c151a1a72f: Pull complete
|
||||||
|
3333c50851c8: Pull complete
|
||||||
|
Digest: sha256:0000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
Status: Downloaded newer image for gitea/act_runner:latest
|
||||||
|
f000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
```
|
||||||
|
|
||||||
|
## Обновление в продакшен
|
||||||
|
|
||||||
|
Наш `.gitea/workflows/docker-publish.yaml` настроен на публикацию Docker образа при создании тега, который начинается
|
||||||
|
с `v`. Образ собирается на компьютпере разработчика (см. главу Настройка Gitea Runner на macOS) и пакет загружается
|
||||||
|
в Gitea.
|
||||||
|
|
||||||
|
На хостинге работает Docker Stack с нашим проектом. Кроме `etpgrf-site-etpgrf-backend` и `etpgrf-site-etpgrf-nginx`
|
||||||
|
в нем работает `etpgrf-site-watchtower`, который следит за обновлениями образов на Gitea и если находит обновление, то
|
||||||
|
получает новый образ и перезапускает контейнер с новым образом.
|
||||||
|
|
||||||
|
Т.о. если мы хотим обновить продакшен, нам нужно просто создать новый тег в репозитории:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
git tag v0.1.x
|
||||||
|
git push origin v0.1.x
|
||||||
|
```
|
||||||
|
|
||||||
|
sudo docker exec -it etpgrf-site-etpgrf-backend-1 python etpgrf_site/manage.py createsuperuser
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
e-serg
|
||||||
|
f0b01jLq$eRyID
|
||||||
@@ -114,6 +114,7 @@ http {
|
|||||||
|
|
||||||
proxy_pass http://app_server;
|
proxy_pass http://app_server;
|
||||||
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;
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
proxy_redirect off;
|
proxy_redirect off;
|
||||||
}
|
}
|
||||||
|
|||||||
+30
-11
@@ -6,7 +6,7 @@
|
|||||||
# Для первого запуска backend-контейнера, возможно, потребуется временно изменить владельца папки `data` на root
|
# Для первого запуска backend-контейнера, возможно, потребуется временно изменить владельца папки `data` на root
|
||||||
# и раскомментировать соответствующие строки в секции etpgrf-backend (смотри комментарии в коде ниже).
|
# и раскомментировать соответствующие строки в секции etpgrf-backend (смотри комментарии в коде ниже).
|
||||||
|
|
||||||
version: '3.8'
|
# version: '3.8'
|
||||||
|
|
||||||
services:
|
services:
|
||||||
# Бэкенд сервис
|
# Бэкенд сервис
|
||||||
@@ -53,7 +53,7 @@ services:
|
|||||||
cp /app/etpgrf_site/typograph/templates/500.html /app/public/static_collected/500.html &&
|
cp /app/etpgrf_site/typograph/templates/500.html /app/public/static_collected/500.html &&
|
||||||
cp /app/etpgrf_site/typograph/templates/404.html /app/public/static_collected/404.html &&
|
cp /app/etpgrf_site/typograph/templates/404.html /app/public/static_collected/404.html &&
|
||||||
cp /app/etpgrf_site/typograph/templates/typograph/403.html /app/public/static_collected/403.html &&
|
cp /app/etpgrf_site/typograph/templates/typograph/403.html /app/public/static_collected/403.html &&
|
||||||
gunicorn --bind 0.0.0.0:8000 --chdir /app/etpgrf_site etpgrf_site.wsgi"
|
gunicorn --bind 0.0.0.0:8000 --timeout 300 --workers 2 --chdir /app/etpgrf_site etpgrf_site.wsgi"
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
# База данных (папка data должна быть создана на хосте)
|
# База данных (папка data должна быть создана на хосте)
|
||||||
@@ -63,15 +63,30 @@ services:
|
|||||||
# Медиа (папка media должна быть создана на хосте)
|
# Медиа (папка media должна быть создана на хосте)
|
||||||
- ./media:/app/media
|
- ./media:/app/media
|
||||||
|
|
||||||
|
# Переменные окружения
|
||||||
env_file:
|
env_file:
|
||||||
- .env
|
- .env
|
||||||
|
|
||||||
|
# Логирование (Ротация)
|
||||||
logging:
|
logging:
|
||||||
driver: "json-file"
|
driver: "json-file"
|
||||||
options:
|
options:
|
||||||
max-size: "10m"
|
max-size: "10m"
|
||||||
max-file: "3"
|
max-file: "3"
|
||||||
|
|
||||||
|
# Healthcheck отключен - вместо этого используем depends_on в nginx (service_started)
|
||||||
|
# Причина: healthcheck с Python-запросом к Django вызывал timeout workers,
|
||||||
|
# так как требовал полной загрузки Django при каждой проверке.
|
||||||
|
# See: https://github.com/erjemin/2026-etpgrf-site/issues/XXX
|
||||||
|
|
||||||
|
# Лимиты ресурсов (docker compose вне swarm)
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
cpus: '0.40'
|
||||||
|
memory: 360M
|
||||||
|
|
||||||
|
|
||||||
# Nginx для отдачи статики и проксирования запросов к бэкенду
|
# Nginx для отдачи статики и проксирования запросов к бэкенду
|
||||||
etpgrf-nginx:
|
etpgrf-nginx:
|
||||||
image: nginx:1.25-alpine
|
image: nginx:1.25-alpine
|
||||||
@@ -89,17 +104,20 @@ services:
|
|||||||
- "127.0.0.1:8080:80" # Используем 8080, чтобы не конфликтовать с Portainer (8000) или основным Nginx (80)
|
- "127.0.0.1:8080:80" # Используем 8080, чтобы не конфликтовать с Portainer (8000) или основным Nginx (80)
|
||||||
|
|
||||||
depends_on:
|
depends_on:
|
||||||
- etpgrf-backend
|
etpgrf-backend:
|
||||||
|
condition: service_started
|
||||||
|
|
||||||
logging:
|
logging:
|
||||||
driver: "json-file"
|
driver: "json-file"
|
||||||
options:
|
options:
|
||||||
max-size: "10m"
|
max-size: "5m"
|
||||||
max-file: "3"
|
max-file: "2"
|
||||||
|
|
||||||
# Сервис для автоматического обновления образов из реестра
|
# Сервис для автоматического обновления образов из реестра
|
||||||
watchtower:
|
watchtower:
|
||||||
image: containrrr/watchtower
|
image: containrrr/watchtower
|
||||||
|
container_name: etpgrf_watchtower
|
||||||
|
restart: always
|
||||||
volumes:
|
volumes:
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
environment:
|
environment:
|
||||||
@@ -107,13 +125,14 @@ services:
|
|||||||
# Берем учетные данные из .env файла
|
# Берем учетные данные из .env файла
|
||||||
- REPO_USER=${REPO_USER}
|
- REPO_USER=${REPO_USER}
|
||||||
- REPO_PASS=${REPO_PASS}
|
- REPO_PASS=${REPO_PASS}
|
||||||
# Ограничиваем область видимости только этим проектом
|
- WATCHTOWER_REGISTRY_URL=git.cube2.ru
|
||||||
- WATCHTOWER_SCOPE=etpgrf
|
- WATCHTOWER_SCOPE=etpgrf
|
||||||
# Если нужно указать реестр явно (обычно watchtower сам понимает из имени образа)
|
command: --interval 1800 --cleanup
|
||||||
# - WATCHTOWER_REGISTRY_URL=git.cube2.ru
|
logging:
|
||||||
command: --interval 1800 --cleanup # Проверять каждые 30 минут
|
driver: "json-file"
|
||||||
restart: always
|
options:
|
||||||
|
max-size: "1m"
|
||||||
|
max-file: "1"
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
static_volume:
|
static_volume:
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
Основные возможности:
|
Основные возможности:
|
||||||
- Веб-интерфейс для ввода текста и настройки параметров типографики.
|
- Веб-интерфейс для ввода текста и настройки параметров типографики.
|
||||||
"""
|
"""
|
||||||
__version__ = "0.2.5"
|
__version__ = "0.2.7"
|
||||||
__author__ = "Sergei Erjemin"
|
__author__ = "Sergei Erjemin"
|
||||||
__email__ = "erjemin@gmail.com"
|
__email__ = "erjemin@gmail.com"
|
||||||
__license__ = "MIT"
|
__license__ = "MIT"
|
||||||
|
|||||||
@@ -21,221 +21,157 @@
|
|||||||
{# Правая колонка: Контент #}
|
{# Правая колонка: Контент #}
|
||||||
<div class="col-lg-10 border-start ps-lg-4 post-page-content">
|
<div class="col-lg-10 border-start ps-lg-4 post-page-content">
|
||||||
|
|
||||||
<h1>Как «подружить» Django Admin и типограф etpgrf: Виртуальные поля для настроек</h1>
|
<h1>Массовая типографика контента в Django: пользова­тельские команды управления (Custom Management Commands)</h1>
|
||||||
|
|
||||||
<div class="lead bg-secondary bg-opacity-10 p-3 rounded">
|
<div class="lead bg-secondary bg-opacity-10 p-3 rounded">
|
||||||
|
|
||||||
<p>Многие контент-проекты сталкиваются с дилеммой: хочется красивой типографики (правильные кавычки «ёлочки», длинные тире <code>—</code>, неразрывные пробелы <code>&nbsp;</code>). Самое очевидное решение — переопре­делить метод <code>safe()</code>, но каждый блок текста — заглоаок, тизер, статья — требуют разных настроек. В заголовках хочестя иметь «висячую пунктуацию» и запретить переносы в словах, для основного текста публикации — наоборот, одна публикация на английском и нужны кавычки “лапки”, другая на русском — и нужны «ёлочки».</p>
|
<p>Если вы работали с Django, вы наверняка использовали команду <code>manage.py</code> сотни раз: <code>python manage.py runserver</code>, <code>migrate</code>, <code>createsuperuser</code>.</p>
|
||||||
<p>Хранить все настройки в базе данных (добавляя десятки полей <code>bool</code> в модель) — плохая идея. Это «засоряет» схему данных параметрами отображения, раздувает базу «мусорной информацией» и некрасиво с точки зрения архитектуры.</p>
|
<p>Но знаете ли вы, что можете легко создавать <strong>свои собственные</strong> команды? Это один из самых мощных и недоо­цененных инструментов Django для автома­тизации рутины. В этом посте я расскажу, что такое <strong>Management Command</strong>, зачем она нужна и как её использовать на примере реальной задачи: массовой типографики контента в базе данных.</p>
|
||||||
<p><strong>Решение:</strong> Если у вас Django в качестве бэкенда, то наилучший подход — использовать «Виртуальные поля» (Virtual Fields) в Django Admin: Добавить настройки типографа прямо в форму редакти­рования админки, применить эти настройки при сохранении, и забыть о них, сохранив в базу только готовый, красивый HTML.</p>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<hr>
|
<hr>
|
||||||
<div class="post-content mt-4">
|
<div class="post-content mt-4">
|
||||||
|
|
||||||
<p>Разберём задачу на конкретном примере. У меня есть сайт с цитатами (<a href="https://dq.cube2.ru/" target="_blank">DQ – коллекция цитат. Место для вдумчивого чтения</a>, в нем большое внимание уделяется типографике, а значит при размещении цитат через иметь возможность не только редакти­ровать текст, но и управлять настройками типографа. В проекте есть модель <code>Dictum</code> с полем <code>content</code> (исходный текст) и <code>content_html</code> (типогра­фированный HTML для вывода на сайте). Я хочу, чтобы редактор мог управлять настройками типографа (язык, кавычки, переносы) прямо в админке, не меняя структуру БД.</p>
|
<h2>Что это такое?</h2>
|
||||||
<h2>Инструменты</h2>
|
<p><strong>Management Command</strong> — это обычный Python-скрипт, который «встраивается» в экосистему Django. Он имеет доступ к моделям, настройкам (settings.py) и базе данных, но запускается из консоли через <code>python manage.py ...</code>.</p>
|
||||||
|
<h2>Зачем это нужно?</h2>
|
||||||
|
<p>Представьте ситуации:</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li><strong>Django 6.0</strong> (или любая актуальная версия) – «движок».</li>
|
<li>Нужно пересчитать рейтинг для десятки тысяч товаров на сайте или сделать типографику всех публикаций после подключения etpgrf-типографа (или любого другого).</li>
|
||||||
<li><strong>etpgrf</strong> — библиотека типографики.</li>
|
<li>Необходимо обновить цены для всех товарных карточек в соответствии с таблицей excel или по API (или спарсить данные с внешнего сайта).</li>
|
||||||
|
<li>Нужно отправить e-mail рассылку пользо­вателям.</li>
|
||||||
|
<li>Нужно пометить комментарии или публикации как архивные в соответствии с правилом или почистить базу от старых логов.</li>
|
||||||
</ul>
|
</ul>
|
||||||
<h2>Реализация</h2>
|
<p>Делать это через <tt>views.py</tt> (views) — плохая идея (страница может отвалиться по тайм-ауту). Писать отделный скрипт рядом <tt>manage.py</tt> — неудобно (нужно вручную настраивать `DJANGO_SETTINGS_MODULE`). Встроенные команды решают эти проблемы элегантно.</p>
|
||||||
<p>Идея в том, чтобы создать в админке дополни­тельные «Виртуальные поля», которых нет в модели. Эти поля будут исполь­зоваться только для настройки типографа при сохранении. Например, можно добавить выпадающий список для выбора языка (русский, английский), галочку для включения обработки кавычек, и т. д. При сохранении мы будем читать эти поля, настраивать типограф и сохранять результат в базу.</p>
|
<h2>Как это работает?</h2>
|
||||||
<h3>Шаг 1. Добавляем необходимые импорты модулей etpgrf-типографа в <tt>admin.py</tt></h3>
|
<p>Django использует систему «автообна­ружения» (auto-discovery). Чтобы ваша команда появилась в списке `manage.py`, нужно соблюсти строгую иерархию папок внутри вашего приложения (например, <tt>web</tt>):</p>
|
||||||
<pre class="border p-3 my-3 rounded bg-secondary bg-opacity-25">from django.contrib import admin
|
<pre class="border p-3 my-3 rounded bg-secondary bg-opacity-25">web/
|
||||||
# Импортируем классы из нашей библиотеки типографики etpgrf
|
├── __init__.py
|
||||||
try:
|
├── models.py
|
||||||
from etpgrf.typograph import Typographer
|
└── management/ <-- 1. Создаем папку management
|
||||||
from etpgrf.layout import LayoutProcessor
|
├── __init__.py
|
||||||
from etpgrf.hyphenation import Hyphenator
|
└── nginx/
|
||||||
from etpgrf.sanitizer import Sanitizer
|
├── commands/ <-- 2. Внутри неё папку commands
|
||||||
except ImportError:
|
├── __init__.py
|
||||||
# Заглушки на случай, если библиотека не установлена
|
└── my_cool_script.py <-- 3. Наш файл с командой</pre>
|
||||||
class Typographer:
|
<p>Файл <tt>my_cool_script.py</tt> автома­тически превратится в команду: <code>python manage.py my_cool_script</code>. Django будет искать его в папках <tt>management/commands/</tt> внутри каждого устано­вленного приложения. Это позволяет легко организовать код и держать все «команды обслуживания» в одном месте.</p>
|
||||||
def __init__(self, **kwargs): pass
|
<p><strong>Для справки:</strong> Механизм Custom Management Commands появился еще в Django 0.96 (в глубокой древности) и с тех пор является стандартом де-факто для написания скриптов обслуживания.</p>
|
||||||
def process(self, text): return text
|
<h2>Анатомия команды</h2>
|
||||||
class LayoutProcessor:
|
<p>Вот пример простейшей команды. Мы наследуемся от класса <code>BaseCommand</code> и переопре­деляем метод <code>handle</code>:</p>
|
||||||
def __init__(self, **kwargs): pass
|
|
||||||
class Hyphenator:
|
|
||||||
def __init__(self, **kwargs): pass</pre>
|
|
||||||
<h3>Шаг 2. Создаем кастомную форму</h3>
|
|
||||||
<p>Вместо стандартной формы админки (в <tt>admin.py</tt>, мы определим свою, унаследовав её от <code>forms.ModelForm</code>. В ней мы добавим поля, которых <strong>нет в модели</strong>:</p>
|
|
||||||
<pre class="border p-3 my-3 rounded bg-secondary bg-opacity-25">
|
<pre class="border p-3 my-3 rounded bg-secondary bg-opacity-25">
|
||||||
from django import forms
|
# web/management/commands/hello.py
|
||||||
from .models import TbDictumAndQuotes
|
from django.core.management.base import BaseCommand
|
||||||
|
|
||||||
class DictumAdminForm(forms.ModelForm):
|
class Command(BaseCommand):
|
||||||
# Виртуальные поля для настройки типографа
|
help = 'Выводит приветствие' # Описание для --help
|
||||||
etp_language = forms.ChoiceField(
|
|
||||||
label="Язык типографики",
|
|
||||||
choices=[('ru', 'Русский'), ('en', 'English'), ('ru,en', 'Ru + En')],
|
|
||||||
initial='ru',
|
|
||||||
required=False
|
|
||||||
)
|
|
||||||
etp_quotes = forms.BooleanField(
|
|
||||||
label="Обработка кавычек",
|
|
||||||
initial=True,
|
|
||||||
required=False,
|
|
||||||
help_text="Заменять прямые кавычки на «ёлочки» или “лапки”"
|
|
||||||
)
|
|
||||||
etp_hanging_punctuation = forms.ChoiceField(
|
|
||||||
label="Висячая пунктуация",
|
|
||||||
choices=[('no', 'Нет'), ('left', 'Слева'), ('right', 'Справа'), ('both', 'Обе стороны')],
|
|
||||||
initial='left',
|
|
||||||
required=False,
|
|
||||||
help_text="Выносить кавычки за границу текстового блока"
|
|
||||||
)
|
|
||||||
etp_hyphenation = forms.BooleanField(
|
|
||||||
label="Переносы",
|
|
||||||
initial=True,
|
|
||||||
required=False,
|
|
||||||
help_text="Расставлять мягкие переносы (&shy;)"
|
|
||||||
)
|
|
||||||
etp_mode = forms.ChoiceField(
|
|
||||||
label="Режим вывода",
|
|
||||||
choices=[('mixed', 'Смешанный (Mixed)'), ('unicode', 'Юникод (Unicode)'), ('mnemonic', 'Мнемоники')],
|
|
||||||
initial='mixed',
|
|
||||||
required=False,
|
|
||||||
help_text="Формат спецсимволов"
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
def add_arguments(self, parser):
|
||||||
model = TbDictumAndQuotes
|
# Можно добавлять аргументы, как в argparse
|
||||||
fields = '__all__'</pre>
|
parser.add_argument('name', type=str, help='Имя пользователя')
|
||||||
<h3>Шаг 3. Настраиваем ModelAdmin</h3>
|
|
||||||
<p>Теперь, там же в <tt>admin.py</tt>, подключаем эту форму к нашему классу админки. Главный трюк — использовать <code>fieldsets</code>, чтобы сгруп­пировать эти новые поля в отдельный, свора­чиваемый блок «Настройки типографа».</p>
|
def handle(self, *args, **options):
|
||||||
|
# Главная логика
|
||||||
|
name = options['name']
|
||||||
|
self.stdout.write(f"Привет, {name}!")</pre>
|
||||||
|
<p>Запуск:</p>
|
||||||
<pre class="border p-3 my-3 rounded bg-secondary bg-opacity-25">
|
<pre class="border p-3 my-3 rounded bg-secondary bg-opacity-25">
|
||||||
class AdmDictumAndQuotesAdmin(admin.ModelAdmin):
|
python manage.py hello Иван</pre>
|
||||||
form = DictumAdminForm # Подключаем нашу форму
|
<p>Увидим в консоли:</p>
|
||||||
# ... другие настройки админки (list_display, search_fields и т.д.) ...
|
|
||||||
# ...
|
|
||||||
# ...
|
|
||||||
|
|
||||||
# Группировка "виртуальных полей" типографа в отдельный блок
|
|
||||||
fieldsets = (
|
|
||||||
# Основные поля модели (можно оставить как есть, или сгруппировать по своему усмотрению).
|
|
||||||
# Поле szContent может быть в этом же блоке, так как мы его как раз типографируем.
|
|
||||||
# (None, {
|
|
||||||
# 'fields': ( ... 'szContent', ... )
|
|
||||||
# }),
|
|
||||||
('Настройки типографа (Etpgrf)', {
|
|
||||||
'classes': ('collapse',),
|
|
||||||
'fields': (
|
|
||||||
('etp_language', 'etp_mode'),
|
|
||||||
('etp_hyphenation', 'etp_hanging_punctuation'),
|
|
||||||
),
|
|
||||||
'description': 'Настройки применяются при сохранении. Результат записывается в скрытые HTML-поля.'
|
|
||||||
}),
|
|
||||||
# ... другие fieldsets, если нужно ...
|
|
||||||
# ...
|
|
||||||
# ... Например, можно добавить отдельный блок для отображения результата типографирования (только для чтения) ...
|
|
||||||
# ('HTML Результат (ReadOnly)', {
|
|
||||||
# 'classes': ('collapse',),
|
|
||||||
# 'fields': ('szContentHTML',),
|
|
||||||
# }),
|
|
||||||
)
|
|
||||||
|
|
||||||
# ...</pre>
|
|
||||||
<h3>Шаг 4. Перехват сохранения (save_model)</h3>
|
|
||||||
<p>Сейчас, когда у нас есть форма с дополни­тельными полями, нам нужно:</p>
|
|
||||||
<ol>
|
|
||||||
<li>Переопре­делить метод <code>save_model</code> в нашем классе админки.</li>
|
|
||||||
<li>Внутри этого метода прочитать значения виртуальных полей из формы (<code>LayoutProcessor</code>, <code>Hyphenator</code> и другие).</li>
|
|
||||||
<li>Инициали­зировать <code>Typographer</code> с этими настройками.</li>
|
|
||||||
<li>Обработать текст из полей <code>szContent</code>, сохранив результат в <code>szContentHTML</code>.</li>
|
|
||||||
</ol>
|
|
||||||
<pre class="border p-3 my-3 rounded bg-secondary bg-opacity-25">
|
<pre class="border p-3 my-3 rounded bg-secondary bg-opacity-25">
|
||||||
def save_model(self, request, obj, form, change):
|
Привет, Иван!</pre>
|
||||||
# 1. Читаем базовые настройки языка и режима из формы
|
<h2>Реальный пример: Массовая типографика через etpgrf</h2>
|
||||||
langs = form.cleaned_data.get('etp_language', 'ru').split(',')
|
<p>В моём проекте, <a href="https://dq.cube2.ru/" target="_blank">DQ – коллекция цитат: место для вдумчивого чтения</a>, контент был частично обработан с помощью Типографа Муравьёа, частично вручную. И вот я установил и настроил etpgrf (тем более что Типограф Муравьёва «почил в бозе», да и до того не обновлялся с 2018 года). Все новые цитаты типогра­фируются через etpgrf, но что делать со старыми? Их сотни, и открывать и «пересо­хранаять» каждую вручную — это адский труд. Вот тут на помощь и приходит Custom Management Command.</p>
|
||||||
|
<p>Вот как я это реализовал. В <tt>web/management/commands/reprocess_typography.py</tt>:</p>
|
||||||
|
<pre class="border p-3 my-3 rounded bg-secondary bg-opacity-25">
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from web.models import TbDictumAndQuotes
|
||||||
|
# Импорты библиотеки etpgrf
|
||||||
|
from etpgrf.typograph import Typographer
|
||||||
|
from etpgrf.sanitizer import SanitizerProcessor
|
||||||
|
# ... другие процессоры, если нужно ...
|
||||||
|
|
||||||
# 2. Собираем LayoutProcessor
|
class Command(BaseCommand):
|
||||||
layout_option = False
|
help = 'Переобрабатывает все цитаты'
|
||||||
# Включаем layout по умолчанию с базовыми настройками (инициалы, юниты)
|
|
||||||
layout_option = LayoutProcessor(
|
def add_arguments(self, parser):
|
||||||
langs=langs,
|
# Добавляем флаг "сухой прогон"
|
||||||
process_initials_and_acronyms=True,
|
parser.add_argument(
|
||||||
process_units=True
|
'--dry-run',
|
||||||
|
action='store_true',
|
||||||
|
help='Запустить без сохранения изменений',
|
||||||
)
|
)
|
||||||
|
# Ограничение количества
|
||||||
|
parser.add_argument('--limit', type=int, help='Сколько записей обработать')
|
||||||
|
# Смещение (для пагинации)
|
||||||
|
parser.add_argument('--offset', type=int, default=0, help='Сколько пропустить')
|
||||||
|
|
||||||
# 3. Собираем Hyphenator (переносы слов)
|
def handle(self, *args, **options):
|
||||||
hyphenation_enabled = form.cleaned_data.get('etp_hyphenation', True)
|
# 1. Настраиваем типограф
|
||||||
hyphenation_option = False
|
# ... инициализация процессоров ...
|
||||||
if hyphenation_enabled:
|
settings = {
|
||||||
hyphenation_option = Hyphenator(
|
# ...
|
||||||
langs=langs,
|
'sanitizer': SanitizerProcessor(mode="html"), # Режим HTML-очистки
|
||||||
max_unhyphenated_len=12
|
'hanging_punctuation': 'left', # Режим висячей пунктуации
|
||||||
)
|
# ...
|
||||||
|
|
||||||
# 4. Читаем Hanging Punctuation (висячая пунктуация)
|
|
||||||
hanging_val = form.cleaned_data.get('etp_hanging_punctuation', 'no')
|
|
||||||
hanging_option = None
|
|
||||||
if hanging_val != 'no':
|
|
||||||
hanging_option = hanging_val
|
|
||||||
|
|
||||||
# 5. Собираем все настройки типографа в словарь
|
|
||||||
options = {
|
|
||||||
'langs': langs,
|
|
||||||
'process_html': True,
|
|
||||||
'quotes': form.cleaned_data.get('etp_quotes', True),
|
|
||||||
'layout': layout_option,
|
|
||||||
'unbreakables': True,
|
|
||||||
'hyphenation': hyphenation_option,
|
|
||||||
'symbols': True,
|
|
||||||
'hanging_punctuation': hanging_option,
|
|
||||||
'mode': form.cleaned_data.get('etp_mode', 'mixed'),
|
|
||||||
}
|
}
|
||||||
|
typographer = Typographer(**settings)
|
||||||
|
|
||||||
# Инициализируем типограф с настройками из формы
|
# 2. Получаем записи из базы с учетом limit/offset
|
||||||
|
qs = TbDictumAndQuotes.objects.all().order_by('id')
|
||||||
|
|
||||||
|
start = options['offset']
|
||||||
|
if options['limit']:
|
||||||
|
qs = qs[start : start + options['limit']]
|
||||||
|
else:
|
||||||
|
qs = qs[start:]
|
||||||
|
|
||||||
|
self.stdout.write(f"Найдено {qs.count()} цитат...")
|
||||||
|
|
||||||
|
# 3. Бежим по циклу с прогресс-баром (tqdm если есть)
|
||||||
try:
|
try:
|
||||||
# DEBUG: Проверка, какой класс используется
|
from tqdm import tqdm
|
||||||
if Typographer.__module__ == __name__: # Если класс определен в этом же файле (заглушка)
|
iterator = tqdm(qs)
|
||||||
self.message_user(request, "ВНИМАНИЕ: Используется заглушка Typographer! Библиотека etpgrf не найдена.", level='WARNING')
|
except ImportError:
|
||||||
|
iterator = qs
|
||||||
|
|
||||||
t = Typographer(**options)
|
for dq in iterator:
|
||||||
|
try:
|
||||||
# Обрабатываем контент
|
# Обрабатываем текст
|
||||||
if obj.szContent:
|
new_html = typographer.process(dq.szContent)
|
||||||
# В онлайн-типографе используется .process(text)
|
|
||||||
old_html = obj.szContentHTML or ""
|
|
||||||
processed = t.process(obj.szContent)
|
|
||||||
obj.szContentHTML = processed
|
|
||||||
|
|
||||||
# DEBUG: Проверка изменений
|
|
||||||
if processed != old_html and processed != obj.szContent:
|
|
||||||
self.message_user(request, f"Типограф: szContentHTML обновлен (len changed: {len(old_html)} -> {len(processed)})", level='INFO')
|
|
||||||
|
|
||||||
|
if options['dry_run']:
|
||||||
|
self.stdout.write(f"[{dq.id}] Предпросмотр: {new_html[:50]}...")
|
||||||
|
else:
|
||||||
|
dq.szContentHTML = new_html
|
||||||
|
dq.save(update_fields=['szContentHTML'])
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# Возникла ошибка при обработке типографом, сохраняем оригинальный текст и показываем сообщение об ошибке
|
self.stdout.write(self.style.ERROR(f"Ошибка id={dq.id}: {e}"))
|
||||||
self.message_user(request, f"Ошибка типографа: {e}", level='ERROR')
|
|
||||||
if not obj.szContentHTML: obj.szContentHTML = obj.szContent
|
|
||||||
|
|
||||||
super().save_model(request, obj, form, change)</pre>
|
|
||||||
<h3>Шаг 5. Очистка модели</h3>
|
self.stdout.write(self.style.SUCCESS(f"\nГотово!"))</pre>
|
||||||
<p>Так как логика обработки и сохранения поля <code>szContentHTML</code> «переехала» в админку, нам нужно <strong>убрать</strong> логику его записи из метода <code>save()</code> модели внутри <tt>models.py</tt>.
|
<p>Возможно вы заметили, что в моем проекте для контента есть два поля: <code>szContent</code> и <code>szContentHTML</code>. В первом хранится «сырой» текст, во втором — результат типогра­фирования. В вашем проекте, скорее всего, только одно поле для контента, но по сути это ничего не меняет.</p>
|
||||||
</p><p>Теперь метод <code>save()</code> в <tt>models.py</tt> должен быть максимально простым:</p>
|
<p>Еще интересные фишки, которые исполь­зовались при типогра­фировании:</p>
|
||||||
|
<ol>
|
||||||
|
<li><code>self.stdout.write</code> вместо <code>print</code> – позволяет Django перехва­тывать вывод (например, для тестов) и корректно работать с кодировками.</li>
|
||||||
|
<li><code>self.style.SUCCESS</code> и <code>self.style.ERROR</code> – раскрашивает текст в консоли (зеленый/красный) и это очень удобно для визуального восприятия логов.</li>
|
||||||
|
<li>Аргумент <tt>--dry-run</tt> — позволяют безопасно тестировать скрипт на продакшене перед тем, как реально менять данные.</li>
|
||||||
|
<li>Аргументы <tt>--limit</tt> и <tt>--offset</tt> — позволяют обрабатывать базу «порциями», что полезно для больших объемов данных (можно запустить несколько процессов параллельно с разными <tt>offset</tt>).</li>
|
||||||
|
<li>Используется <code>update_fields</code> — позволяет переза­писывать не всю модель целиком (что могло бы затереть изменения, сделанные кем-то другим в ту же секунду), а обновляем только конкретные поля.</li>
|
||||||
|
</ol>
|
||||||
|
<p>Теперь, чтобы типогра­фировать весь контент, нам достаточно одной строку в терминале, и Django сделает всю грязную работу за нас (не забудьте инициировать виртуальное окружение вашего проекта).</p>
|
||||||
|
<p>Тестовый прогон (безопасно, ничего не сохраняет):</p>
|
||||||
<pre class="border p-3 my-3 rounded bg-secondary bg-opacity-25">
|
<pre class="border p-3 my-3 rounded bg-secondary bg-opacity-25">
|
||||||
class TbDictumAndQuotes(models.Model):
|
python manage.py reprocess_typography --dry-run</pre>
|
||||||
# ... поля ...
|
<p>Боевой запуск, с изменениями данные в базе:</p>
|
||||||
# ...
|
<pre class="border p-3 my-3 rounded bg-secondary bg-opacity-25">
|
||||||
# ...
|
python manage.py reprocess_typography</pre>
|
||||||
|
<p>Если у вас очень много записей, можно запускать по частям:</p>
|
||||||
|
<pre class="border p-3 my-3 rounded bg-secondary bg-opacity-25">
|
||||||
|
python manage.py reprocess_typography --limit 1000 --offset 0
|
||||||
|
python manage.py reprocess_typography --limit 1000 --offset 1000
|
||||||
|
python manage.py reprocess_typography --limit 1000 --offset 2000</pre>
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
|
||||||
# Типографирование поля szContentHTML перенесено в админку (через admin.save_model).
|
|
||||||
# Здесь оставляем только базовую подстраховку: если HTML пуст, заполняем оригиналом.
|
|
||||||
if not self.szContentHTML and self.szContent:
|
|
||||||
self.szContentHTML = self.szContent
|
|
||||||
|
|
||||||
super(TbDictumAndQuotes, self).save(*args, **kwargs)</pre>
|
|
||||||
<h2>Результат</h2>
|
|
||||||
<p>Теперь редактор видит обычную админку, пишет текст, открывает блок «Настройки типографа», выбирает нужные опции (например, «Включить висячую пунктуацию слева») и нажимает «Сохранить».</p>
|
|
||||||
<p>При сохранении Django читает эти настройки, инициа­лизирует типограф, обрабатывает текст и сохраняет в базу уже готовый HTML. В итоге:</p>
|
|
||||||
<ul>
|
|
||||||
<li>Чистый исходный текст в поле <code>szContent</code> (для правки в будущем).</li>
|
|
||||||
<li>Готовый, красивый HTML в поле <code>szContentHTML</code> (с <code>&nbsp;</code>, висячими кавычками и т. п., который можно сразу выводить на сайте.</li>
|
|
||||||
</ul>
|
|
||||||
<p>При этом таблица базы данных не замусорена колонками <code>is_hanging_punctuation_enabled</code>, которые нужны только в момент сохранения. <strong>Кроме того, это абсолютно безопасно:</strong> «виртуальные поля» существуют только в форме админки и не являются полями модели — Django их не сериализует и не пытается сохранить в БД, схема данных не меняется, а сами значения живут лишь в момент сохранения и влияют только на обработку текста.</p>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -140,3 +140,17 @@ MEDIA_URL = '/media/'
|
|||||||
# https://docs.djangoproject.com/en/6.0/ref/settings/#default-auto-field
|
# https://docs.djangoproject.com/en/6.0/ref/settings/#default-auto-field
|
||||||
|
|
||||||
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||||
|
|
||||||
|
# Настройки безопасности для работы за прокси
|
||||||
|
if not DEBUG:
|
||||||
|
# Если есть заголовок `X-Forwarded-Proto` со значением `https`, то считать запрос безопасным(HTTPS).
|
||||||
|
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
|
||||||
|
# Если кто-то зайдет по HTTP, то перенаправить на HTTPS. Подключать, только если Nginx уже настроен на HTTPS.
|
||||||
|
SECURE_SSL_REDIRECT = True
|
||||||
|
# Устанавливает флаг Secure для куки сессии и CSRF, чтобы браузер отправлял их только по HTTPS.
|
||||||
|
SESSION_COOKIE_SECURE = True
|
||||||
|
CSRF_COOKIE_SECURE = True
|
||||||
|
# Обрабатывать заголовки `X-Forwarded-Host` и `X-Forwarded-Port`, которые Nginx может добавлять при проксировании.
|
||||||
|
USE_X_FORWARDED_HOST = True
|
||||||
|
USE_X_FORWARDED_PORT = True
|
||||||
|
|
||||||
|
|||||||
@@ -29,8 +29,9 @@
|
|||||||
{# Favicons #}<link rel="icon" type="image/svg+xml" href="{% static 'favicon.svg' %}" />
|
{# Favicons #}<link rel="icon" type="image/svg+xml" href="{% static 'favicon.svg' %}" />
|
||||||
{# Favicons #}<link rel="apple-touch-icon" sizes="180x180" href="{% static 'apple-touch-icon.png' %}" />
|
{# Favicons #}<link rel="apple-touch-icon" sizes="180x180" href="{% static 'apple-touch-icon.png' %}" />
|
||||||
{# Favicons #}<link rel="manifest" href="{% static 'site.webmanifest' %}" />
|
{# Favicons #}<link rel="manifest" href="{% static 'site.webmanifest' %}" />
|
||||||
{# Bootstrap 5 CSS #}<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"/>
|
{# Для ИИ #}<link rel="help" type="text/markdown" href="/llms.txt"/>
|
||||||
{# Bootstrap Icons #}<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css"/>
|
{# Bootstrap 5 CSS #}<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" onerror="this.onerror=null;this.href='{% static 'vendor/bootstrap/bootstrap.min.css' %}';"/>
|
||||||
|
{# Bootstrap Icons #}<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css" onerror="this.onerror=null;this.href='{% static 'vendor/bootstrap-icons/bootstrap-icons.min.css' %}';"/>
|
||||||
<style>
|
<style>
|
||||||
:root {
|
:root {
|
||||||
--bg-image-text: url("{% static 'svg/logo-etpgrf-site-light-txt.svg' %}");
|
--bg-image-text: url("{% static 'svg/logo-etpgrf-site-light-txt.svg' %}");
|
||||||
@@ -45,8 +46,18 @@
|
|||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
{# Custom CSS #}<link href="{% static 'css/etpgrf.css' %}" rel="stylesheet"/>
|
{# Custom CSS #}<link href="{% static 'css/etpgrf.css' %}" rel="stylesheet"/>
|
||||||
{# HTMX #}<script src="https://unpkg.com/htmx.org@1.9.10"></script>
|
{# HTMX #}<script src="https://unpkg.com/htmx.org@1.9.12"></script>
|
||||||
{# Alpine.js #}<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
|
<script>window.htmx || document.write('<script src="{% static "vendor/htmx/htmx.min.js" %}"><\/script>')</script>
|
||||||
|
{# Alpine.js #}<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.14.8/dist/cdn.min.js" onerror="this.onerror=null;var s=document.createElement('script');s.defer=true;s.src='{% static 'vendor/alpinejs/cdn.min.js' %}';document.head.appendChild(s);"></script>
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
if (!window.Alpine) {
|
||||||
|
var s = document.createElement('script');
|
||||||
|
s.src = "{% static 'vendor/alpinejs/cdn.min.js' %}";
|
||||||
|
document.head.appendChild(s);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
@@ -83,7 +94,7 @@
|
|||||||
<div class="container d-flex justify-content-between align-items-center">
|
<div class="container d-flex justify-content-between align-items-center">
|
||||||
<span class="text-muted small nowrap me-2">© Sergei Erjemin, 2025–{% now 'Y' %}.</span>
|
<span class="text-muted small nowrap me-2">© Sergei Erjemin, 2025–{% now 'Y' %}.</span>
|
||||||
<nobr class="text-muted small mx-2">
|
<nobr class="text-muted small mx-2">
|
||||||
<i class="bi bi-tags me-1" title="Версия библиотеки etpgrf / Версия сайта"></i><a href="/changelog">v0.1.5 / v0.2.6</a>
|
<i class="bi bi-tags me-1" title="Версия библиотеки etpgrf / Версия сайта"></i><a href="/changelog">v0.1.6.post1 / v0.2.7</a>
|
||||||
</nobr>
|
</nobr>
|
||||||
{# Сводная статистика (HTMX) #}<span class="text-muted small ms-2" hx-get="{% url 'stats_summary' %}" hx-trigger="load">
|
{# Сводная статистика (HTMX) #}<span class="text-muted small ms-2" hx-get="{% url 'stats_summary' %}" hx-trigger="load">
|
||||||
...
|
...
|
||||||
@@ -105,7 +116,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{# Bootstrap JS #}<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
{# Bootstrap JS #}<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<script>window.bootstrap || document.write('<script src="{% static "vendor/bootstrap/bootstrap.bundle.min.js" %}"><\/script>')</script>
|
||||||
{# Custom JS #}<script src="{% static 'js/base.js' %}" defer></script>
|
{# Custom JS #}<script src="{% static 'js/base.js' %}" defer></script>
|
||||||
{% block scripts %}{% endblock %}
|
{% block scripts %}{% endblock %}
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
{% block title %}ETPGRF — единая типографика для веба{% endblock %}
|
{% block title %}ETPGRF — единая типографика для веба{% endblock %}
|
||||||
|
|
||||||
{% block description %}Бесплатный онлайн-типограф для подготовки текстов к публикации в вебе. Расставка неразрывных пробелов, правильных кавычек («ёлочки»), тире, спецсимволы, отбивка, компоновка, висячая пунктуация. Идеально для верстки сайтов, статей и постов.{% endblock %}
|
{% block description %}Бесплатный онлайн-типограф для подготовки публикаций в вебе: кавычки «ёлочки», тире, неразрывные пробелы, спецсимволы, отбивка/компоновка, висячая пунктуация… Идеально для верстки сайтов, статей и постов.{% endblock %}
|
||||||
|
|
||||||
{% block keywords %}типограф, типографика, онлайн типограф, подготовка текста для веба, html типограф, неразрывные пробелы, кавычки елочки, длинное тире, очистка текста от мусора, интернет верстка, муравьев{% endblock %}
|
{% block keywords %}типограф, типографика, онлайн типограф, подготовка текста для веба, html типограф, неразрывные пробелы, кавычки елочки, длинное тире, очистка текста от мусора, интернет верстка, муравьев{% endblock %}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,102 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
from django.test import TestCase, Client, override_settings
|
||||||
|
from django.urls import reverse
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
|
||||||
|
@override_settings(SECURE_SSL_REDIRECT=False)
|
||||||
|
class VendorAssetsAndFallbackTestCase(TestCase):
|
||||||
|
"""
|
||||||
|
Тестирование наличия вендорных ассетов и корректности подключения fallback в шаблоне base.html.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.client = Client()
|
||||||
|
|
||||||
|
def test_vendor_static_files_exist(self):
|
||||||
|
"""Проверяем, что все скомпилированные/скопированные локальные файлы присутствуют в статике."""
|
||||||
|
static_dir = settings.BASE_DIR.parent / 'public' / 'static'
|
||||||
|
|
||||||
|
required_files = [
|
||||||
|
'vendor/bootstrap/bootstrap.min.css',
|
||||||
|
'vendor/bootstrap/bootstrap.bundle.min.js',
|
||||||
|
'vendor/bootstrap-icons/bootstrap-icons.min.css',
|
||||||
|
'vendor/bootstrap-icons/fonts/bootstrap-icons.woff2',
|
||||||
|
'vendor/bootstrap-icons/fonts/bootstrap-icons.woff',
|
||||||
|
'vendor/htmx/htmx.min.js',
|
||||||
|
'vendor/alpinejs/cdn.min.js',
|
||||||
|
'codemirror/editor.js',
|
||||||
|
]
|
||||||
|
|
||||||
|
for rel_path in required_files:
|
||||||
|
file_path = static_dir / rel_path
|
||||||
|
self.assertTrue(
|
||||||
|
file_path.exists(),
|
||||||
|
f"Файл вендора не найден в статике: {rel_path} (ожидался по пути {file_path})"
|
||||||
|
)
|
||||||
|
self.assertGreater(
|
||||||
|
file_path.stat().st_size,
|
||||||
|
0,
|
||||||
|
f"Файл вендора пуст: {rel_path}"
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_base_template_fallback_tags(self):
|
||||||
|
"""Проверяем, что главная страница отдает теги с fallback-обработкой ошибок загрузки с CDN."""
|
||||||
|
response = self.client.get(reverse('index'))
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
content = response.content.decode('utf-8')
|
||||||
|
|
||||||
|
# Проверка fallback для Bootstrap CSS
|
||||||
|
self.assertIn('bootstrap.min.css', content)
|
||||||
|
self.assertIn("vendor/bootstrap/bootstrap.min.css", content)
|
||||||
|
|
||||||
|
# Проверка fallback для Bootstrap Icons
|
||||||
|
self.assertIn('bootstrap-icons.min.css', content)
|
||||||
|
self.assertIn("vendor/bootstrap-icons/bootstrap-icons.min.css", content)
|
||||||
|
|
||||||
|
# Проверка fallback для HTMX
|
||||||
|
self.assertIn('htmx.org', content)
|
||||||
|
self.assertIn('window.htmx || document.write', content)
|
||||||
|
self.assertIn('vendor/htmx/htmx.min.js', content)
|
||||||
|
|
||||||
|
# Проверка fallback для Alpine.js
|
||||||
|
self.assertIn('alpinejs', content)
|
||||||
|
self.assertIn('vendor/alpinejs/cdn.min.js', content)
|
||||||
|
|
||||||
|
# Проверка fallback для Bootstrap JS
|
||||||
|
self.assertIn('bootstrap.bundle.min.js', content)
|
||||||
|
self.assertIn('window.bootstrap || document.write', content)
|
||||||
|
self.assertIn('vendor/bootstrap/bootstrap.bundle.min.js', content)
|
||||||
|
|
||||||
|
|
||||||
|
@override_settings(SECURE_SSL_REDIRECT=False)
|
||||||
|
class TypographViewsTestCase(TestCase):
|
||||||
|
"""
|
||||||
|
Базовые тесты функциональности представлений типографа.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.client = Client()
|
||||||
|
|
||||||
|
def test_process_text_endpoint(self):
|
||||||
|
"""Проверка эндпоинта типографирования текста."""
|
||||||
|
response = self.client.post(
|
||||||
|
reverse('process_text'),
|
||||||
|
data={
|
||||||
|
'text': 'Тест "кавычек" и предлога в тексте.',
|
||||||
|
'langs': 'ru',
|
||||||
|
'quotes': 'on',
|
||||||
|
'unbreakables': 'on',
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertIn('X-Processing-Time', response.headers)
|
||||||
|
content = response.content.decode('utf-8')
|
||||||
|
# Проверяем типографирование кавычек «ёлочки»
|
||||||
|
self.assertIn('«кавычек»', content)
|
||||||
|
|
||||||
|
def test_stats_summary_endpoint(self):
|
||||||
|
"""Проверка эндпоинта сводной статистики."""
|
||||||
|
response = self.client.get(reverse('stats_summary'))
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
Regular → Executable
+16
-16
@@ -1,24 +1,24 @@
|
|||||||
#!/usr/bin/bash
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
|
||||||
# Необходимо предварительно установить NPM и esbuild:
|
# Переходим в директорию скрипта
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
|
||||||
|
# Необходимо предварительно установить Node.js и npm:
|
||||||
# Для Ubuntu/Debian:
|
# Для Ubuntu/Debian:
|
||||||
# sudo apt install npm
|
# sudo apt install nodejs npm
|
||||||
# npm install -g esbuild
|
|
||||||
# Для MacOS (через Homebrew):
|
# Для MacOS (через Homebrew):
|
||||||
# brew install npm
|
# brew install node
|
||||||
# npm install -g esbuild
|
|
||||||
# Для Windows:
|
# Для Windows:
|
||||||
# Скачайте и установите Node.js с официального сайта: https://nodejs.org/
|
# Скачайте и установите Node.js с официального сайта: https://nodejs.org/
|
||||||
# Затем установите esbuild глобально:
|
|
||||||
# npm install -g esbuild
|
|
||||||
|
|
||||||
|
|
||||||
# Устанавливаем CodeMirror и необходимые пакеты
|
# Устанавливаем зависимости
|
||||||
npm install \ [±main ●●]
|
npm install
|
||||||
@codemirror/lang-html \
|
|
||||||
@codemirror/theme-one-dark \
|
|
||||||
@codemirror/commands \
|
|
||||||
@codemirror/language
|
|
||||||
|
|
||||||
# Собираем js в каталог ./public/static/codemirror/editor.js с помощью esbuild
|
# Собираем всё (CodeMirror и копирование вендорных библиотек в public/static/vendor/)
|
||||||
npx esbuild src/editor.js --bundle --format=esm --outfile=../public/static/codemirror/editor.js
|
npm run build
|
||||||
|
|
||||||
|
# Подчищаем за собой node_modules
|
||||||
|
echo "Очистка node_modules..."
|
||||||
|
rm -rf node_modules
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
// Целевая папка для статики вендоров
|
||||||
|
const targetDir = path.resolve(__dirname, '../public/static/vendor');
|
||||||
|
|
||||||
|
// Список файлов и директорий для копирования
|
||||||
|
const itemsToCopy = [
|
||||||
|
// Bootstrap 5 (CSS & JS bundle)
|
||||||
|
{
|
||||||
|
src: 'node_modules/bootstrap/dist/css/bootstrap.min.css',
|
||||||
|
dest: 'bootstrap/bootstrap.min.css'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: 'node_modules/bootstrap/dist/css/bootstrap.min.css.map',
|
||||||
|
dest: 'bootstrap/bootstrap.min.css.map'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: 'node_modules/bootstrap/dist/js/bootstrap.bundle.min.js',
|
||||||
|
dest: 'bootstrap/bootstrap.bundle.min.js'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: 'node_modules/bootstrap/dist/js/bootstrap.bundle.min.js.map',
|
||||||
|
dest: 'bootstrap/bootstrap.bundle.min.js.map'
|
||||||
|
},
|
||||||
|
|
||||||
|
// Bootstrap Icons (CSS & WOFF/WOFF2 шрифты)
|
||||||
|
{
|
||||||
|
src: 'node_modules/bootstrap-icons/font/bootstrap-icons.min.css',
|
||||||
|
dest: 'bootstrap-icons/bootstrap-icons.min.css'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: 'node_modules/bootstrap-icons/font/fonts/bootstrap-icons.woff2',
|
||||||
|
dest: 'bootstrap-icons/fonts/bootstrap-icons.woff2'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: 'node_modules/bootstrap-icons/font/fonts/bootstrap-icons.woff',
|
||||||
|
dest: 'bootstrap-icons/fonts/bootstrap-icons.woff'
|
||||||
|
},
|
||||||
|
|
||||||
|
// HTMX
|
||||||
|
{
|
||||||
|
src: 'node_modules/htmx.org/dist/htmx.min.js',
|
||||||
|
dest: 'htmx/htmx.min.js'
|
||||||
|
},
|
||||||
|
|
||||||
|
// Alpine.js
|
||||||
|
{
|
||||||
|
src: 'node_modules/alpinejs/dist/cdn.min.js',
|
||||||
|
dest: 'alpinejs/cdn.min.js'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
console.log('Сборка и копирование вендорных библиотек в static/vendor/...');
|
||||||
|
|
||||||
|
let copiedCount = 0;
|
||||||
|
|
||||||
|
for (const item of itemsToCopy) {
|
||||||
|
const srcPath = path.resolve(__dirname, item.src);
|
||||||
|
const destPath = path.join(targetDir, item.dest);
|
||||||
|
|
||||||
|
if (!fs.existsSync(srcPath)) {
|
||||||
|
console.warn(`Предупреждение: исходный файл не найден: ${item.src}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Создаем папку назначения, если не существует
|
||||||
|
const destDirPath = path.dirname(destPath);
|
||||||
|
if (!fs.existsSync(destDirPath)) {
|
||||||
|
fs.mkdirSync(destDirPath, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.copyFileSync(srcPath, destPath);
|
||||||
|
console.log(` Скопировано: ${item.src} -> ${path.relative(path.resolve(__dirname, '..'), destPath)}`);
|
||||||
|
copiedCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Успешно скопировано файлов: ${copiedCount}`);
|
||||||
Generated
+83
-3
@@ -1,11 +1,11 @@
|
|||||||
{
|
{
|
||||||
"name": "tmp-frontend",
|
"name": "frontend-assembly",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "tmp-frontend",
|
"name": "frontend-assembly",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -13,7 +13,11 @@
|
|||||||
"@codemirror/lang-html": "^6.4.11",
|
"@codemirror/lang-html": "^6.4.11",
|
||||||
"@codemirror/language": "^6.12.1",
|
"@codemirror/language": "^6.12.1",
|
||||||
"@codemirror/theme-one-dark": "^6.1.3",
|
"@codemirror/theme-one-dark": "^6.1.3",
|
||||||
"codemirror": "^6.0.2"
|
"alpinejs": "^3.14.8",
|
||||||
|
"bootstrap": "^5.3.3",
|
||||||
|
"bootstrap-icons": "^1.11.3",
|
||||||
|
"codemirror": "^6.0.2",
|
||||||
|
"htmx.org": "^1.9.12"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"esbuild": "^0.27.2"
|
"esbuild": "^0.27.2"
|
||||||
@@ -662,6 +666,76 @@
|
|||||||
"integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==",
|
"integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@popperjs/core": {
|
||||||
|
"version": "2.11.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
|
||||||
|
"integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/popperjs"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@vue/reactivity": {
|
||||||
|
"version": "3.1.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.1.5.tgz",
|
||||||
|
"integrity": "sha512-1tdfLmNjWG6t/CsPldh+foumYFo3cpyCHgBYQ34ylaMsJ+SNHQ1kApMIa8jN+i593zQuaw3AdWH0nJTARzCFhg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@vue/shared": "3.1.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@vue/shared": {
|
||||||
|
"version": "3.1.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.1.5.tgz",
|
||||||
|
"integrity": "sha512-oJ4F3TnvpXaQwZJNF3ZK+kLPHKarDmJjJ6jyzVNDKH9md1dptjC7lWR//jrGuLdek/U6iltWxqAnYOu8gCiOvA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/alpinejs": {
|
||||||
|
"version": "3.14.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.14.8.tgz",
|
||||||
|
"integrity": "sha512-wT2fuP2DXpGk/jKaglwy7S/IJpm1FD+b7U6zUrhwErjoq5h27S4dxkJEXVvhbdwyPv9U+3OkUuNLkZT4h2Kfrg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@vue/reactivity": "~3.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/bootstrap": {
|
||||||
|
"version": "5.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.3.tgz",
|
||||||
|
"integrity": "sha512-8HLCdWgyoMguSO9o+aH+iuZ+aht+mzW0u3HIMzVu7Srrpv7EBBxTnrFlSCskwdY1+EOFQSm7uMJhNQHkdPcmjg==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/twbs"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/bootstrap"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@popperjs/core": "^2.11.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/bootstrap-icons": {
|
||||||
|
"version": "1.11.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/bootstrap-icons/-/bootstrap-icons-1.11.3.tgz",
|
||||||
|
"integrity": "sha512-+3lpHrCw/it2/7lBL15VR0HEumaBss0+f/Lb6ZvHISn1mlK83jjFpooTLsMWbIjJMDjDjOExMsTxnXSIT4k4ww==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/twbs"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/bootstrap"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/codemirror": {
|
"node_modules/codemirror": {
|
||||||
"version": "6.0.2",
|
"version": "6.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.2.tgz",
|
||||||
@@ -725,6 +799,12 @@
|
|||||||
"@esbuild/win32-x64": "0.27.2"
|
"@esbuild/win32-x64": "0.27.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/htmx.org": {
|
||||||
|
"version": "1.9.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/htmx.org/-/htmx.org-1.9.12.tgz",
|
||||||
|
"integrity": "sha512-VZAohXyF7xPGS52IM8d1T1283y+X4D+Owf3qY1NZ9RuBypyu9l8cGsxUMAG5fEAb/DhT7rDoJ9Hpu5/HxFD3cw==",
|
||||||
|
"license": "0BSD"
|
||||||
|
},
|
||||||
"node_modules/style-mod": {
|
"node_modules/style-mod": {
|
||||||
"version": "4.1.3",
|
"version": "4.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.3.tgz",
|
||||||
|
|||||||
@@ -4,8 +4,10 @@
|
|||||||
"description": "CodeMirror build for ETPGRF",
|
"description": "CodeMirror build for ETPGRF",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "esbuild src/editor.js --bundle --format=esm --outfile=dist/editor.js --minify",
|
"build:codemirror": "esbuild src/editor.js --bundle --format=esm --outfile=../public/static/codemirror/editor.js --minify",
|
||||||
"dev": "esbuild src/editor.js --bundle --format=esm --outfile=../public/static/codemirror/editor.js --minify",
|
"build:vendor": "node copy-vendor.js",
|
||||||
|
"build": "npm run build:codemirror && npm run build:vendor",
|
||||||
|
"dev": "npm run build",
|
||||||
"watch": "esbuild src/editor.js --bundle --format=esm --outfile=../public/static/codemirror/editor.js --watch"
|
"watch": "esbuild src/editor.js --bundle --format=esm --outfile=../public/static/codemirror/editor.js --watch"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
@@ -17,7 +19,11 @@
|
|||||||
"@codemirror/lang-html": "^6.4.11",
|
"@codemirror/lang-html": "^6.4.11",
|
||||||
"@codemirror/language": "^6.12.1",
|
"@codemirror/language": "^6.12.1",
|
||||||
"@codemirror/theme-one-dark": "^6.1.3",
|
"@codemirror/theme-one-dark": "^6.1.3",
|
||||||
"codemirror": "^6.0.2"
|
"alpinejs": "^3.14.8",
|
||||||
|
"bootstrap": "^5.3.3",
|
||||||
|
"bootstrap-icons": "^1.11.3",
|
||||||
|
"codemirror": "^6.0.2",
|
||||||
|
"htmx.org": "^1.9.12"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"esbuild": "^0.27.2"
|
"esbuild": "^0.27.2"
|
||||||
|
|||||||
Generated
+237
-253
@@ -2,13 +2,13 @@
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "asgiref"
|
name = "asgiref"
|
||||||
version = "3.11.0"
|
version = "3.11.1"
|
||||||
description = "ASGI specs, helper code, and adapters"
|
description = "ASGI specs, helper code, and adapters"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.9"
|
python-versions = ">=3.9"
|
||||||
files = [
|
files = [
|
||||||
{file = "asgiref-3.11.0-py3-none-any.whl", hash = "sha256:1db9021efadb0d9512ce8ffaf72fcef601c7b73a8807a1bb2ef143dc6b14846d"},
|
{file = "asgiref-3.11.1-py3-none-any.whl", hash = "sha256:e8667a091e69529631969fd45dc268fa79b99c92c5fcdda727757e52146ec133"},
|
||||||
{file = "asgiref-3.11.0.tar.gz", hash = "sha256:13acff32519542a1736223fb79a715acdebe24286d98e8b164a73085f40da2c4"},
|
{file = "asgiref-3.11.1.tar.gz", hash = "sha256:5f184dc43b7e763efe848065441eac62229c9f7b0475f41f80e207a114eda4ce"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
@@ -38,13 +38,13 @@ lxml = ["lxml"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "django"
|
name = "django"
|
||||||
version = "6.0.1"
|
version = "6.0.3"
|
||||||
description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design."
|
description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.12"
|
python-versions = ">=3.12"
|
||||||
files = [
|
files = [
|
||||||
{file = "django-6.0.1-py3-none-any.whl", hash = "sha256:a92a4ff14f664a896f9849009cb8afaca7abe0d6fc53325f3d1895a15253433d"},
|
{file = "django-6.0.3-py3-none-any.whl", hash = "sha256:2e5974441491ddb34c3f13d5e7a9f97b07ba03bf70234c0a9c68b79bbb235bc3"},
|
||||||
{file = "django-6.0.1.tar.gz", hash = "sha256:ed76a7af4da21551573b3d9dfc1f53e20dd2e6c7d70a3adc93eedb6338130a5f"},
|
{file = "django-6.0.3.tar.gz", hash = "sha256:90be765ee756af8a6cbd6693e56452404b5ad15294f4d5e40c0a55a0f4870fe1"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
@@ -58,13 +58,13 @@ bcrypt = ["bcrypt (>=4.1.1)"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "etpgrf"
|
name = "etpgrf"
|
||||||
version = "0.1.5"
|
version = "0.1.6.post1"
|
||||||
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.5-py3-none-any.whl", hash = "sha256:93e520f5742ef48748c727b40a3c0b796ae8525c97ba49b038b730dbed951044"},
|
{file = "etpgrf-0.1.6.post1-py3-none-any.whl", hash = "sha256:0863b14385bdacdd405f137ca2ce6bdb6f683f0189e8c927196a1eee754366be"},
|
||||||
{file = "etpgrf-0.1.5.tar.gz", hash = "sha256:be0ee3a53d30c93f7835f46a523ff145099ce4c9a7449b46a141b291876ec18d"},
|
{file = "etpgrf-0.1.6.post1.tar.gz", hash = "sha256:984d201cff232a58c05b6f4455a50f822162520df829ad4d543bfe0b7fd962a9"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
@@ -74,24 +74,25 @@ regex = ">=2022.1.18"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gunicorn"
|
name = "gunicorn"
|
||||||
version = "23.0.0"
|
version = "25.1.0"
|
||||||
description = "WSGI HTTP Server for UNIX"
|
description = "WSGI HTTP Server for UNIX"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.10"
|
||||||
files = [
|
files = [
|
||||||
{file = "gunicorn-23.0.0-py3-none-any.whl", hash = "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d"},
|
{file = "gunicorn-25.1.0-py3-none-any.whl", hash = "sha256:d0b1236ccf27f72cfe14bce7caadf467186f19e865094ca84221424e839b8b8b"},
|
||||||
{file = "gunicorn-23.0.0.tar.gz", hash = "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec"},
|
{file = "gunicorn-25.1.0.tar.gz", hash = "sha256:1426611d959fa77e7de89f8c0f32eed6aa03ee735f98c01efba3e281b1c47616"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
packaging = "*"
|
packaging = "*"
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
eventlet = ["eventlet (>=0.24.1,!=0.36.0)"]
|
eventlet = ["eventlet (>=0.40.3)"]
|
||||||
gevent = ["gevent (>=1.4.0)"]
|
gevent = ["gevent (>=24.10.1)"]
|
||||||
|
http2 = ["h2 (>=4.1.0)"]
|
||||||
setproctitle = ["setproctitle"]
|
setproctitle = ["setproctitle"]
|
||||||
testing = ["coverage", "eventlet", "gevent", "pytest", "pytest-cov"]
|
testing = ["coverage", "eventlet (>=0.40.3)", "gevent (>=24.10.1)", "h2 (>=4.1.0)", "httpx[http2]", "pytest", "pytest-asyncio", "pytest-cov", "uvloop (>=0.19.0)"]
|
||||||
tornado = ["tornado (>=0.2)"]
|
tornado = ["tornado (>=6.5.0)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lxml"
|
name = "lxml"
|
||||||
@@ -250,113 +251,113 @@ htmlsoup = ["BeautifulSoup4"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "packaging"
|
name = "packaging"
|
||||||
version = "25.0"
|
version = "26.0"
|
||||||
description = "Core utilities for Python packages"
|
description = "Core utilities for Python packages"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
files = [
|
files = [
|
||||||
{file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"},
|
{file = "packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529"},
|
||||||
{file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"},
|
{file = "packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pillow"
|
name = "pillow"
|
||||||
version = "12.1.0"
|
version = "12.1.1"
|
||||||
description = "Python Imaging Library (fork)"
|
description = "Python Imaging Library (fork)"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.10"
|
python-versions = ">=3.10"
|
||||||
files = [
|
files = [
|
||||||
{file = "pillow-12.1.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:fb125d860738a09d363a88daa0f59c4533529a90e564785e20fe875b200b6dbd"},
|
{file = "pillow-12.1.1-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1f1625b72740fdda5d77b4def688eb8fd6490975d06b909fd19f13f391e077e0"},
|
||||||
{file = "pillow-12.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cad302dc10fac357d3467a74a9561c90609768a6f73a1923b0fd851b6486f8b0"},
|
{file = "pillow-12.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:178aa072084bd88ec759052feca8e56cbb14a60b39322b99a049e58090479713"},
|
||||||
{file = "pillow-12.1.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a40905599d8079e09f25027423aed94f2823adaf2868940de991e53a449e14a8"},
|
{file = "pillow-12.1.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b66e95d05ba806247aaa1561f080abc7975daf715c30780ff92a20e4ec546e1b"},
|
||||||
{file = "pillow-12.1.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:92a7fe4225365c5e3a8e598982269c6d6698d3e783b3b1ae979e7819f9cd55c1"},
|
{file = "pillow-12.1.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:89c7e895002bbe49cdc5426150377cbbc04767d7547ed145473f496dfa40408b"},
|
||||||
{file = "pillow-12.1.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f10c98f49227ed8383d28174ee95155a675c4ed7f85e2e573b04414f7e371bda"},
|
{file = "pillow-12.1.1-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a5cbdcddad0af3da87cb16b60d23648bc3b51967eb07223e9fed77a82b457c4"},
|
||||||
{file = "pillow-12.1.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8637e29d13f478bc4f153d8daa9ffb16455f0a6cb287da1b432fdad2bfbd66c7"},
|
{file = "pillow-12.1.1-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9f51079765661884a486727f0729d29054242f74b46186026582b4e4769918e4"},
|
||||||
{file = "pillow-12.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:21e686a21078b0f9cb8c8a961d99e6a4ddb88e0fc5ea6e130172ddddc2e5221a"},
|
{file = "pillow-12.1.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:99c1506ea77c11531d75e3a412832a13a71c7ebc8192ab9e4b2e355555920e3e"},
|
||||||
{file = "pillow-12.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2415373395a831f53933c23ce051021e79c8cd7979822d8cc478547a3f4da8ef"},
|
{file = "pillow-12.1.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:36341d06738a9f66c8287cf8b876d24b18db9bd8740fa0672c74e259ad408cff"},
|
||||||
{file = "pillow-12.1.0-cp310-cp310-win32.whl", hash = "sha256:e75d3dba8fc1ddfec0cd752108f93b83b4f8d6ab40e524a95d35f016b9683b09"},
|
{file = "pillow-12.1.1-cp310-cp310-win32.whl", hash = "sha256:6c52f062424c523d6c4db85518774cc3d50f5539dd6eed32b8f6229b26f24d40"},
|
||||||
{file = "pillow-12.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:64efdf00c09e31efd754448a383ea241f55a994fd079866b92d2bbff598aad91"},
|
{file = "pillow-12.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:c6008de247150668a705a6338156efb92334113421ceecf7438a12c9a12dab23"},
|
||||||
{file = "pillow-12.1.0-cp310-cp310-win_arm64.whl", hash = "sha256:f188028b5af6b8fb2e9a76ac0f841a575bd1bd396e46ef0840d9b88a48fdbcea"},
|
{file = "pillow-12.1.1-cp310-cp310-win_arm64.whl", hash = "sha256:1a9b0ee305220b392e1124a764ee4265bd063e54a751a6b62eff69992f457fa9"},
|
||||||
{file = "pillow-12.1.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:a83e0850cb8f5ac975291ebfc4170ba481f41a28065277f7f735c202cd8e0af3"},
|
{file = "pillow-12.1.1-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:e879bb6cd5c73848ef3b2b48b8af9ff08c5b71ecda8048b7dd22d8a33f60be32"},
|
||||||
{file = "pillow-12.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b6e53e82ec2db0717eabb276aa56cf4e500c9a7cec2c2e189b55c24f65a3e8c0"},
|
{file = "pillow-12.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:365b10bb9417dd4498c0e3b128018c4a624dc11c7b97d8cc54effe3b096f4c38"},
|
||||||
{file = "pillow-12.1.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:40a8e3b9e8773876d6e30daed22f016509e3987bab61b3b7fe309d7019a87451"},
|
{file = "pillow-12.1.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d4ce8e329c93845720cd2014659ca67eac35f6433fd3050393d85f3ecef0dad5"},
|
||||||
{file = "pillow-12.1.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:800429ac32c9b72909c671aaf17ecd13110f823ddb7db4dfef412a5587c2c24e"},
|
{file = "pillow-12.1.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc354a04072b765eccf2204f588a7a532c9511e8b9c7f900e1b64e3e33487090"},
|
||||||
{file = "pillow-12.1.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0b022eaaf709541b391ee069f0022ee5b36c709df71986e3f7be312e46f42c84"},
|
{file = "pillow-12.1.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7e7976bf1910a8116b523b9f9f58bf410f3e8aa330cd9a2bb2953f9266ab49af"},
|
||||||
{file = "pillow-12.1.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1f345e7bc9d7f368887c712aa5054558bad44d2a301ddf9248599f4161abc7c0"},
|
{file = "pillow-12.1.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:597bd9c8419bc7c6af5604e55847789b69123bbe25d65cc6ad3012b4f3c98d8b"},
|
||||||
{file = "pillow-12.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d70347c8a5b7ccd803ec0c85c8709f036e6348f1e6a5bf048ecd9c64d3550b8b"},
|
{file = "pillow-12.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2c1fc0f2ca5f96a3c8407e41cca26a16e46b21060fe6d5b099d2cb01412222f5"},
|
||||||
{file = "pillow-12.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1fcc52d86ce7a34fd17cb04e87cfdb164648a3662a6f20565910a99653d66c18"},
|
{file = "pillow-12.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:578510d88c6229d735855e1f278aa305270438d36a05031dfaae5067cc8eb04d"},
|
||||||
{file = "pillow-12.1.0-cp311-cp311-win32.whl", hash = "sha256:3ffaa2f0659e2f740473bcf03c702c39a8d4b2b7ffc629052028764324842c64"},
|
{file = "pillow-12.1.1-cp311-cp311-win32.whl", hash = "sha256:7311c0a0dcadb89b36b7025dfd8326ecfa36964e29913074d47382706e516a7c"},
|
||||||
{file = "pillow-12.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:806f3987ffe10e867bab0ddad45df1148a2b98221798457fa097ad85d6e8bc75"},
|
{file = "pillow-12.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:fbfa2a7c10cc2623f412753cddf391c7f971c52ca40a3f65dc5039b2939e8563"},
|
||||||
{file = "pillow-12.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:9f5fefaca968e700ad1a4a9de98bf0869a94e397fe3524c4c9450c1445252304"},
|
{file = "pillow-12.1.1-cp311-cp311-win_arm64.whl", hash = "sha256:b81b5e3511211631b3f672a595e3221252c90af017e399056d0faabb9538aa80"},
|
||||||
{file = "pillow-12.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a332ac4ccb84b6dde65dbace8431f3af08874bf9770719d32a635c4ef411b18b"},
|
{file = "pillow-12.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ab323b787d6e18b3d91a72fc99b1a2c28651e4358749842b8f8dfacd28ef2052"},
|
||||||
{file = "pillow-12.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:907bfa8a9cb790748a9aa4513e37c88c59660da3bcfffbd24a7d9e6abf224551"},
|
{file = "pillow-12.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:adebb5bee0f0af4909c30db0d890c773d1a92ffe83da908e2e9e720f8edf3984"},
|
||||||
{file = "pillow-12.1.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:efdc140e7b63b8f739d09a99033aa430accce485ff78e6d311973a67b6bf3208"},
|
{file = "pillow-12.1.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bb66b7cc26f50977108790e2456b7921e773f23db5630261102233eb355a3b79"},
|
||||||
{file = "pillow-12.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bef9768cab184e7ae6e559c032e95ba8d07b3023c289f79a2bd36e8bf85605a5"},
|
{file = "pillow-12.1.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aee2810642b2898bb187ced9b349e95d2a7272930796e022efaf12e99dccd293"},
|
||||||
{file = "pillow-12.1.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:742aea052cf5ab5034a53c3846165bc3ce88d7c38e954120db0ab867ca242661"},
|
{file = "pillow-12.1.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a0b1cd6232e2b618adcc54d9882e4e662a089d5768cd188f7c245b4c8c44a397"},
|
||||||
{file = "pillow-12.1.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a6dfc2af5b082b635af6e08e0d1f9f1c4e04d17d4e2ca0ef96131e85eda6eb17"},
|
{file = "pillow-12.1.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7aac39bcf8d4770d089588a2e1dd111cbaa42df5a94be3114222057d68336bd0"},
|
||||||
{file = "pillow-12.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:609e89d9f90b581c8d16358c9087df76024cf058fa693dd3e1e1620823f39670"},
|
{file = "pillow-12.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ab174cd7d29a62dd139c44bf74b698039328f45cb03b4596c43473a46656b2f3"},
|
||||||
{file = "pillow-12.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:43b4899cfd091a9693a1278c4982f3e50f7fb7cff5153b05174b4afc9593b616"},
|
{file = "pillow-12.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:339ffdcb7cbeaa08221cd401d517d4b1fe7a9ed5d400e4a8039719238620ca35"},
|
||||||
{file = "pillow-12.1.0-cp312-cp312-win32.whl", hash = "sha256:aa0c9cc0b82b14766a99fbe6084409972266e82f459821cd26997a488a7261a7"},
|
{file = "pillow-12.1.1-cp312-cp312-win32.whl", hash = "sha256:5d1f9575a12bed9e9eedd9a4972834b08c97a352bd17955ccdebfeca5913fa0a"},
|
||||||
{file = "pillow-12.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:d70534cea9e7966169ad29a903b99fc507e932069a881d0965a1a84bb57f6c6d"},
|
{file = "pillow-12.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:21329ec8c96c6e979cd0dfd29406c40c1d52521a90544463057d2aaa937d66a6"},
|
||||||
{file = "pillow-12.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:65b80c1ee7e14a87d6a068dd3b0aea268ffcabfe0498d38661b00c5b4b22e74c"},
|
{file = "pillow-12.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:af9a332e572978f0218686636610555ae3defd1633597be015ed50289a03c523"},
|
||||||
{file = "pillow-12.1.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:7b5dd7cbae20285cdb597b10eb5a2c13aa9de6cde9bb64a3c1317427b1db1ae1"},
|
{file = "pillow-12.1.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:d242e8ac078781f1de88bf823d70c1a9b3c7950a44cdf4b7c012e22ccbcd8e4e"},
|
||||||
{file = "pillow-12.1.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:29a4cef9cb672363926f0470afc516dbf7305a14d8c54f7abbb5c199cd8f8179"},
|
{file = "pillow-12.1.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:02f84dfad02693676692746df05b89cf25597560db2857363a208e393429f5e9"},
|
||||||
{file = "pillow-12.1.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:681088909d7e8fa9e31b9799aaa59ba5234c58e5e4f1951b4c4d1082a2e980e0"},
|
{file = "pillow-12.1.1-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:e65498daf4b583091ccbb2556c7000abf0f3349fcd57ef7adc9a84a394ed29f6"},
|
||||||
{file = "pillow-12.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:983976c2ab753166dc66d36af6e8ec15bb511e4a25856e2227e5f7e00a160587"},
|
{file = "pillow-12.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6c6db3b84c87d48d0088943bf33440e0c42370b99b1c2a7989216f7b42eede60"},
|
||||||
{file = "pillow-12.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:db44d5c160a90df2d24a24760bbd37607d53da0b34fb546c4c232af7192298ac"},
|
{file = "pillow-12.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8b7e5304e34942bf62e15184219a7b5ad4ff7f3bb5cca4d984f37df1a0e1aee2"},
|
||||||
{file = "pillow-12.1.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6b7a9d1db5dad90e2991645874f708e87d9a3c370c243c2d7684d28f7e133e6b"},
|
{file = "pillow-12.1.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:18e5bddd742a44b7e6b1e773ab5db102bd7a94c32555ba656e76d319d19c3850"},
|
||||||
{file = "pillow-12.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6258f3260986990ba2fa8a874f8b6e808cf5abb51a94015ca3dc3c68aa4f30ea"},
|
{file = "pillow-12.1.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc44ef1f3de4f45b50ccf9136999d71abb99dca7706bc75d222ed350b9fd2289"},
|
||||||
{file = "pillow-12.1.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e115c15e3bc727b1ca3e641a909f77f8ca72a64fff150f666fcc85e57701c26c"},
|
{file = "pillow-12.1.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5a8eb7ed8d4198bccbd07058416eeec51686b498e784eda166395a23eb99138e"},
|
||||||
{file = "pillow-12.1.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6741e6f3074a35e47c77b23a4e4f2d90db3ed905cb1c5e6e0d49bff2045632bc"},
|
{file = "pillow-12.1.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:47b94983da0c642de92ced1702c5b6c292a84bd3a8e1d1702ff923f183594717"},
|
||||||
{file = "pillow-12.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:935b9d1aed48fcfb3f838caac506f38e29621b44ccc4f8a64d575cb1b2a88644"},
|
{file = "pillow-12.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:518a48c2aab7ce596d3bf79d0e275661b846e86e4d0e7dec34712c30fe07f02a"},
|
||||||
{file = "pillow-12.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5fee4c04aad8932da9f8f710af2c1a15a83582cfb884152a9caa79d4efcdbf9c"},
|
{file = "pillow-12.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a550ae29b95c6dc13cf69e2c9dc5747f814c54eeb2e32d683e5e93af56caa029"},
|
||||||
{file = "pillow-12.1.0-cp313-cp313-win32.whl", hash = "sha256:a786bf667724d84aa29b5db1c61b7bfdde380202aaca12c3461afd6b71743171"},
|
{file = "pillow-12.1.1-cp313-cp313-win32.whl", hash = "sha256:a003d7422449f6d1e3a34e3dd4110c22148336918ddbfc6a32581cd54b2e0b2b"},
|
||||||
{file = "pillow-12.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:461f9dfdafa394c59cd6d818bdfdbab4028b83b02caadaff0ffd433faf4c9a7a"},
|
{file = "pillow-12.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:344cf1e3dab3be4b1fa08e449323d98a2a3f819ad20f4b22e77a0ede31f0faa1"},
|
||||||
{file = "pillow-12.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:9212d6b86917a2300669511ed094a9406888362e085f2431a7da985a6b124f45"},
|
{file = "pillow-12.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:5c0dd1636633e7e6a0afe7bf6a51a14992b7f8e60de5789018ebbdfae55b040a"},
|
||||||
{file = "pillow-12.1.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:00162e9ca6d22b7c3ee8e61faa3c3253cd19b6a37f126cad04f2f88b306f557d"},
|
{file = "pillow-12.1.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0330d233c1a0ead844fc097a7d16c0abff4c12e856c0b325f231820fee1f39da"},
|
||||||
{file = "pillow-12.1.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7d6daa89a00b58c37cb1747ec9fb7ac3bc5ffd5949f5888657dfddde6d1312e0"},
|
{file = "pillow-12.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5dae5f21afb91322f2ff791895ddd8889e5e947ff59f71b46041c8ce6db790bc"},
|
||||||
{file = "pillow-12.1.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e2479c7f02f9d505682dc47df8c0ea1fc5e264c4d1629a5d63fe3e2334b89554"},
|
{file = "pillow-12.1.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2e0c664be47252947d870ac0d327fea7e63985a08794758aa8af5b6cb6ec0c9c"},
|
||||||
{file = "pillow-12.1.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f188d580bd870cda1e15183790d1cc2fa78f666e76077d103edf048eed9c356e"},
|
{file = "pillow-12.1.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:691ab2ac363b8217f7d31b3497108fb1f50faab2f75dfb03284ec2f217e87bf8"},
|
||||||
{file = "pillow-12.1.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0fde7ec5538ab5095cc02df38ee99b0443ff0e1c847a045554cf5f9af1f4aa82"},
|
{file = "pillow-12.1.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9e8064fb1cc019296958595f6db671fba95209e3ceb0c4734c9baf97de04b20"},
|
||||||
{file = "pillow-12.1.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0ed07dca4a8464bada6139ab38f5382f83e5f111698caf3191cb8dbf27d908b4"},
|
{file = "pillow-12.1.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:472a8d7ded663e6162dafdf20015c486a7009483ca671cece7a9279b512fcb13"},
|
||||||
{file = "pillow-12.1.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:f45bd71d1fa5e5749587613037b172e0b3b23159d1c00ef2fc920da6f470e6f0"},
|
{file = "pillow-12.1.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:89b54027a766529136a06cfebeecb3a04900397a3590fd252160b888479517bf"},
|
||||||
{file = "pillow-12.1.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:277518bf4fe74aa91489e1b20577473b19ee70fb97c374aa50830b279f25841b"},
|
{file = "pillow-12.1.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:86172b0831b82ce4f7877f280055892b31179e1576aa00d0df3bb1bbf8c3e524"},
|
||||||
{file = "pillow-12.1.0-cp313-cp313t-win32.whl", hash = "sha256:7315f9137087c4e0ee73a761b163fc9aa3b19f5f606a7fc08d83fd3e4379af65"},
|
{file = "pillow-12.1.1-cp313-cp313t-win32.whl", hash = "sha256:44ce27545b6efcf0fdbdceb31c9a5bdea9333e664cda58a7e674bb74608b3986"},
|
||||||
{file = "pillow-12.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:0ddedfaa8b5f0b4ffbc2fa87b556dc59f6bb4ecb14a53b33f9189713ae8053c0"},
|
{file = "pillow-12.1.1-cp313-cp313t-win_amd64.whl", hash = "sha256:a285e3eb7a5a45a2ff504e31f4a8d1b12ef62e84e5411c6804a42197c1cf586c"},
|
||||||
{file = "pillow-12.1.0-cp313-cp313t-win_arm64.whl", hash = "sha256:80941e6d573197a0c28f394753de529bb436b1ca990ed6e765cf42426abc39f8"},
|
{file = "pillow-12.1.1-cp313-cp313t-win_arm64.whl", hash = "sha256:cc7d296b5ea4d29e6570dabeaed58d31c3fea35a633a69679fb03d7664f43fb3"},
|
||||||
{file = "pillow-12.1.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:5cb7bc1966d031aec37ddb9dcf15c2da5b2e9f7cc3ca7c54473a20a927e1eb91"},
|
{file = "pillow-12.1.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:417423db963cb4be8bac3fc1204fe61610f6abeed1580a7a2cbb2fbda20f12af"},
|
||||||
{file = "pillow-12.1.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:97e9993d5ed946aba26baf9c1e8cf18adbab584b99f452ee72f7ee8acb882796"},
|
{file = "pillow-12.1.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:b957b71c6b2387610f556a7eb0828afbe40b4a98036fc0d2acfa5a44a0c2036f"},
|
||||||
{file = "pillow-12.1.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:414b9a78e14ffeb98128863314e62c3f24b8a86081066625700b7985b3f529bd"},
|
{file = "pillow-12.1.1-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:097690ba1f2efdeb165a20469d59d8bb03c55fb6621eb2041a060ae8ea3e9642"},
|
||||||
{file = "pillow-12.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:e6bdb408f7c9dd2a5ff2b14a3b0bb6d4deb29fb9961e6eb3ae2031ae9a5cec13"},
|
{file = "pillow-12.1.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2815a87ab27848db0321fb78c7f0b2c8649dee134b7f2b80c6a45c6831d75ccd"},
|
||||||
{file = "pillow-12.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:3413c2ae377550f5487991d444428f1a8ae92784aac79caa8b1e3b89b175f77e"},
|
{file = "pillow-12.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f7ed2c6543bad5a7d5530eb9e78c53132f93dfa44a28492db88b41cdab885202"},
|
||||||
{file = "pillow-12.1.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e5dcbe95016e88437ecf33544ba5db21ef1b8dd6e1b434a2cb2a3d605299e643"},
|
{file = "pillow-12.1.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:652a2c9ccfb556235b2b501a3a7cf3742148cd22e04b5625c5fe057ea3e3191f"},
|
||||||
{file = "pillow-12.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d0a7735df32ccbcc98b98a1ac785cc4b19b580be1bdf0aeb5c03223220ea09d5"},
|
{file = "pillow-12.1.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d6e4571eedf43af33d0fc233a382a76e849badbccdf1ac438841308652a08e1f"},
|
||||||
{file = "pillow-12.1.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c27407a2d1b96774cbc4a7594129cc027339fd800cd081e44497722ea1179de"},
|
{file = "pillow-12.1.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b574c51cf7d5d62e9be37ba446224b59a2da26dc4c1bb2ecbe936a4fb1a7cb7f"},
|
||||||
{file = "pillow-12.1.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15c794d74303828eaa957ff8070846d0efe8c630901a1c753fdc63850e19ecd9"},
|
{file = "pillow-12.1.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a37691702ed687799de29a518d63d4682d9016932db66d4e90c345831b02fb4e"},
|
||||||
{file = "pillow-12.1.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c990547452ee2800d8506c4150280757f88532f3de2a58e3022e9b179107862a"},
|
{file = "pillow-12.1.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f95c00d5d6700b2b890479664a06e754974848afaae5e21beb4d83c106923fd0"},
|
||||||
{file = "pillow-12.1.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b63e13dd27da389ed9475b3d28510f0f954bca0041e8e551b2a4eb1eab56a39a"},
|
{file = "pillow-12.1.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:559b38da23606e68681337ad74622c4dbba02254fc9cb4488a305dd5975c7eeb"},
|
||||||
{file = "pillow-12.1.0-cp314-cp314-win32.whl", hash = "sha256:1a949604f73eb07a8adab38c4fe50791f9919344398bdc8ac6b307f755fc7030"},
|
{file = "pillow-12.1.1-cp314-cp314-win32.whl", hash = "sha256:03edcc34d688572014ff223c125a3f77fb08091e4607e7745002fc214070b35f"},
|
||||||
{file = "pillow-12.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:4f9f6a650743f0ddee5593ac9e954ba1bdbc5e150bc066586d4f26127853ab94"},
|
{file = "pillow-12.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:50480dcd74fa63b8e78235957d302d98d98d82ccbfac4c7e12108ba9ecbdba15"},
|
||||||
{file = "pillow-12.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:808b99604f7873c800c4840f55ff389936ef1948e4e87645eaf3fccbc8477ac4"},
|
{file = "pillow-12.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:5cb1785d97b0c3d1d1a16bc1d710c4a0049daefc4935f3a8f31f827f4d3d2e7f"},
|
||||||
{file = "pillow-12.1.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:bc11908616c8a283cf7d664f77411a5ed2a02009b0097ff8abbba5e79128ccf2"},
|
{file = "pillow-12.1.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:1f90cff8aa76835cba5769f0b3121a22bd4eb9e6884cfe338216e557a9a548b8"},
|
||||||
{file = "pillow-12.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:896866d2d436563fa2a43a9d72f417874f16b5545955c54a64941e87c1376c61"},
|
{file = "pillow-12.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1f1be78ce9466a7ee64bfda57bdba0f7cc499d9794d518b854816c41bf0aa4e9"},
|
||||||
{file = "pillow-12.1.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8e178e3e99d3c0ea8fc64b88447f7cac8ccf058af422a6cedc690d0eadd98c51"},
|
{file = "pillow-12.1.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:42fc1f4677106188ad9a55562bbade416f8b55456f522430fadab3cef7cd4e60"},
|
||||||
{file = "pillow-12.1.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:079af2fb0c599c2ec144ba2c02766d1b55498e373b3ac64687e43849fbbef5bc"},
|
{file = "pillow-12.1.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:98edb152429ab62a1818039744d8fbb3ccab98a7c29fc3d5fcef158f3f1f68b7"},
|
||||||
{file = "pillow-12.1.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bdec5e43377761c5dbca620efb69a77f6855c5a379e32ac5b158f54c84212b14"},
|
{file = "pillow-12.1.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d470ab1178551dd17fdba0fef463359c41aaa613cdcd7ff8373f54be629f9f8f"},
|
||||||
{file = "pillow-12.1.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:565c986f4b45c020f5421a4cea13ef294dde9509a8577f29b2fc5edc7587fff8"},
|
{file = "pillow-12.1.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6408a7b064595afcab0a49393a413732a35788f2a5092fdc6266952ed67de586"},
|
||||||
{file = "pillow-12.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:43aca0a55ce1eefc0aefa6253661cb54571857b1a7b2964bd8a1e3ef4b729924"},
|
{file = "pillow-12.1.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5d8c41325b382c07799a3682c1c258469ea2ff97103c53717b7893862d0c98ce"},
|
||||||
{file = "pillow-12.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0deedf2ea233722476b3a81e8cdfbad786f7adbed5d848469fa59fe52396e4ef"},
|
{file = "pillow-12.1.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c7697918b5be27424e9ce568193efd13d925c4481dd364e43f5dff72d33e10f8"},
|
||||||
{file = "pillow-12.1.0-cp314-cp314t-win32.whl", hash = "sha256:b17fbdbe01c196e7e159aacb889e091f28e61020a8abeac07b68079b6e626988"},
|
{file = "pillow-12.1.1-cp314-cp314t-win32.whl", hash = "sha256:d2912fd8114fc5545aa3a4b5576512f64c55a03f3ebcca4c10194d593d43ea36"},
|
||||||
{file = "pillow-12.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27b9baecb428899db6c0de572d6d305cfaf38ca1596b5c0542a5182e3e74e8c6"},
|
{file = "pillow-12.1.1-cp314-cp314t-win_amd64.whl", hash = "sha256:4ceb838d4bd9dab43e06c363cab2eebf63846d6a4aeaea283bbdfd8f1a8ed58b"},
|
||||||
{file = "pillow-12.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:f61333d817698bdcdd0f9d7793e365ac3d2a21c1f1eb02b32ad6aefb8d8ea831"},
|
{file = "pillow-12.1.1-cp314-cp314t-win_arm64.whl", hash = "sha256:7b03048319bfc6170e93bd60728a1af51d3dd7704935feb228c4d4faab35d334"},
|
||||||
{file = "pillow-12.1.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ca94b6aac0d7af2a10ba08c0f888b3d5114439b6b3ef39968378723622fed377"},
|
{file = "pillow-12.1.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:600fd103672b925fe62ed08e0d874ea34d692474df6f4bf7ebe148b30f89f39f"},
|
||||||
{file = "pillow-12.1.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:351889afef0f485b84078ea40fe33727a0492b9af3904661b0abbafee0355b72"},
|
{file = "pillow-12.1.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:665e1b916b043cef294bc54d47bf02d87e13f769bc4bc5fa225a24b3a6c5aca9"},
|
||||||
{file = "pillow-12.1.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bb0984b30e973f7e2884362b7d23d0a348c7143ee559f38ef3eaab640144204c"},
|
{file = "pillow-12.1.1-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:495c302af3aad1ca67420ddd5c7bd480c8867ad173528767d906428057a11f0e"},
|
||||||
{file = "pillow-12.1.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:84cabc7095dd535ca934d57e9ce2a72ffd216e435a84acb06b2277b1de2689bd"},
|
{file = "pillow-12.1.1-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8fd420ef0c52c88b5a035a0886f367748c72147b2b8f384c9d12656678dfdfa9"},
|
||||||
{file = "pillow-12.1.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53d8b764726d3af1a138dd353116f774e3862ec7e3794e0c8781e30db0f35dfc"},
|
{file = "pillow-12.1.1-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f975aa7ef9684ce7e2c18a3aa8f8e2106ce1e46b94ab713d156b2898811651d3"},
|
||||||
{file = "pillow-12.1.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5da841d81b1a05ef940a8567da92decaa15bc4d7dedb540a8c219ad83d91808a"},
|
{file = "pillow-12.1.1-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8089c852a56c2966cf18835db62d9b34fef7ba74c726ad943928d494fa7f4735"},
|
||||||
{file = "pillow-12.1.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:75af0b4c229ac519b155028fa1be632d812a519abba9b46b20e50c6caa184f19"},
|
{file = "pillow-12.1.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:cb9bb857b2d057c6dfc72ac5f3b44836924ba15721882ef103cecb40d002d80e"},
|
||||||
{file = "pillow-12.1.0.tar.gz", hash = "sha256:5c5ae0a06e9ea030ab786b0251b32c7e4ce10e58d983c0d5c56029455180b5b9"},
|
{file = "pillow-12.1.1.tar.gz", hash = "sha256:9ad8fa5937ab05218e2b6a4cff30295ad35afd2f83ac592e68c0d871bb0fdbc4"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
@@ -369,13 +370,13 @@ xmp = ["defusedxml"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "python-dotenv"
|
name = "python-dotenv"
|
||||||
version = "1.2.1"
|
version = "1.2.2"
|
||||||
description = "Read key-value pairs from a .env file and set them as environment variables"
|
description = "Read key-value pairs from a .env file and set them as environment variables"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.9"
|
python-versions = ">=3.10"
|
||||||
files = [
|
files = [
|
||||||
{file = "python_dotenv-1.2.1-py3-none-any.whl", hash = "sha256:b81ee9561e9ca4004139c6cbba3a238c32b03e4894671e181b671e8cb8425d61"},
|
{file = "python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a"},
|
||||||
{file = "python_dotenv-1.2.1.tar.gz", hash = "sha256:42667e897e16ab0d66954af0e60a9caa94f0fd4ecf3aaf6d2d260eec1aa36ad6"},
|
{file = "python_dotenv-1.2.2.tar.gz", hash = "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
@@ -394,153 +395,136 @@ files = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex"
|
name = "regex"
|
||||||
version = "2026.1.15"
|
version = "2026.2.28"
|
||||||
description = "Alternative regular expression module, to replace re."
|
description = "Alternative regular expression module, to replace re."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.9"
|
python-versions = ">=3.10"
|
||||||
files = [
|
files = [
|
||||||
{file = "regex-2026.1.15-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4e3dd93c8f9abe8aa4b6c652016da9a3afa190df5ad822907efe6b206c09896e"},
|
{file = "regex-2026.2.28-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fc48c500838be6882b32748f60a15229d2dea96e59ef341eaa96ec83538f498d"},
|
||||||
{file = "regex-2026.1.15-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:97499ff7862e868b1977107873dd1a06e151467129159a6ffd07b66706ba3a9f"},
|
{file = "regex-2026.2.28-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2afa673660928d0b63d84353c6c08a8a476ddfc4a47e11742949d182e6863ce8"},
|
||||||
{file = "regex-2026.1.15-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0bda75ebcac38d884240914c6c43d8ab5fb82e74cde6da94b43b17c411aa4c2b"},
|
{file = "regex-2026.2.28-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7ab218076eb0944549e7fe74cf0e2b83a82edb27e81cc87411f76240865e04d5"},
|
||||||
{file = "regex-2026.1.15-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7dcc02368585334f5bc81fc73a2a6a0bbade60e7d83da21cead622faf408f32c"},
|
{file = "regex-2026.2.28-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94d63db12e45a9b9f064bfe4800cefefc7e5f182052e4c1b774d46a40ab1d9bb"},
|
||||||
{file = "regex-2026.1.15-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:693b465171707bbe882a7a05de5e866f33c76aa449750bee94a8d90463533cc9"},
|
{file = "regex-2026.2.28-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:195237dc327858a7721bf8b0bbbef797554bc13563c3591e91cd0767bacbe359"},
|
||||||
{file = "regex-2026.1.15-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b0d190e6f013ea938623a58706d1469a62103fb2a241ce2873a9906e0386582c"},
|
{file = "regex-2026.2.28-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b387a0d092dac157fb026d737dde35ff3e49ef27f285343e7c6401851239df27"},
|
||||||
{file = "regex-2026.1.15-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5ff818702440a5878a81886f127b80127f5d50563753a28211482867f8318106"},
|
{file = "regex-2026.2.28-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3935174fa4d9f70525a4367aaff3cb8bc0548129d114260c29d9dfa4a5b41692"},
|
||||||
{file = "regex-2026.1.15-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f052d1be37ef35a54e394de66136e30fa1191fab64f71fc06ac7bc98c9a84618"},
|
{file = "regex-2026.2.28-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2b2b23587b26496ff5fd40df4278becdf386813ec00dc3533fa43a4cf0e2ad3c"},
|
||||||
{file = "regex-2026.1.15-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6bfc31a37fd1592f0c4fc4bfc674b5c42e52efe45b4b7a6a14f334cca4bcebe4"},
|
{file = "regex-2026.2.28-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3b24bd7e9d85dc7c6a8bd2aa14ecd234274a0248335a02adeb25448aecdd420d"},
|
||||||
{file = "regex-2026.1.15-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3d6ce5ae80066b319ae3bc62fd55a557c9491baa5efd0d355f0de08c4ba54e79"},
|
{file = "regex-2026.2.28-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:bd477d5f79920338107f04aa645f094032d9e3030cc55be581df3d1ef61aa318"},
|
||||||
{file = "regex-2026.1.15-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:1704d204bd42b6bb80167df0e4554f35c255b579ba99616def38f69e14a5ccb9"},
|
{file = "regex-2026.2.28-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:b49eb78048c6354f49e91e4b77da21257fecb92256b6d599ae44403cab30b05b"},
|
||||||
{file = "regex-2026.1.15-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:e3174a5ed4171570dc8318afada56373aa9289eb6dc0d96cceb48e7358b0e220"},
|
{file = "regex-2026.2.28-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:a25c7701e4f7a70021db9aaf4a4a0a67033c6318752146e03d1b94d32006217e"},
|
||||||
{file = "regex-2026.1.15-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:87adf5bd6d72e3e17c9cb59ac4096b1faaf84b7eb3037a5ffa61c4b4370f0f13"},
|
{file = "regex-2026.2.28-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:9dd450db6458387167e033cfa80887a34c99c81d26da1bf8b0b41bf8c9cac88e"},
|
||||||
{file = "regex-2026.1.15-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e85dc94595f4d766bd7d872a9de5ede1ca8d3063f3bdf1e2c725f5eb411159e3"},
|
{file = "regex-2026.2.28-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2954379dd20752e82d22accf3ff465311cbb2bac6c1f92c4afd400e1757f7451"},
|
||||||
{file = "regex-2026.1.15-cp310-cp310-win32.whl", hash = "sha256:21ca32c28c30d5d65fc9886ff576fc9b59bbca08933e844fa2363e530f4c8218"},
|
{file = "regex-2026.2.28-cp310-cp310-win32.whl", hash = "sha256:1f8b17be5c27a684ea6759983c13506bd77bfc7c0347dff41b18ce5ddd2ee09a"},
|
||||||
{file = "regex-2026.1.15-cp310-cp310-win_amd64.whl", hash = "sha256:3038a62fc7d6e5547b8915a3d927a0fbeef84cdbe0b1deb8c99bbd4a8961b52a"},
|
{file = "regex-2026.2.28-cp310-cp310-win_amd64.whl", hash = "sha256:dd8847c4978bc3c7e6c826fb745f5570e518b8459ac2892151ce6627c7bc00d5"},
|
||||||
{file = "regex-2026.1.15-cp310-cp310-win_arm64.whl", hash = "sha256:505831646c945e3e63552cc1b1b9b514f0e93232972a2d5bedbcc32f15bc82e3"},
|
{file = "regex-2026.2.28-cp310-cp310-win_arm64.whl", hash = "sha256:73cdcdbba8028167ea81490c7f45280113e41db2c7afb65a276f4711fa3bcbff"},
|
||||||
{file = "regex-2026.1.15-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1ae6020fb311f68d753b7efa9d4b9a5d47a5d6466ea0d5e3b5a471a960ea6e4a"},
|
{file = "regex-2026.2.28-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e621fb7c8dc147419b28e1702f58a0177ff8308a76fa295c71f3e7827849f5d9"},
|
||||||
{file = "regex-2026.1.15-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:eddf73f41225942c1f994914742afa53dc0d01a6e20fe14b878a1b1edc74151f"},
|
{file = "regex-2026.2.28-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0d5bef2031cbf38757a0b0bc4298bb4824b6332d28edc16b39247228fbdbad97"},
|
||||||
{file = "regex-2026.1.15-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e8cd52557603f5c66a548f69421310886b28b7066853089e1a71ee710e1cdc1"},
|
{file = "regex-2026.2.28-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bcb399ed84eabf4282587ba151f2732ad8168e66f1d3f85b1d038868fe547703"},
|
||||||
{file = "regex-2026.1.15-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5170907244b14303edc5978f522f16c974f32d3aa92109fabc2af52411c9433b"},
|
{file = "regex-2026.2.28-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7c1b34dfa72f826f535b20712afa9bb3ba580020e834f3c69866c5bddbf10098"},
|
||||||
{file = "regex-2026.1.15-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2748c1ec0663580b4510bd89941a31560b4b439a0b428b49472a3d9944d11cd8"},
|
{file = "regex-2026.2.28-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:851fa70df44325e1e4cdb79c5e676e91a78147b1b543db2aec8734d2add30ec2"},
|
||||||
{file = "regex-2026.1.15-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2f2775843ca49360508d080eaa87f94fa248e2c946bbcd963bb3aae14f333413"},
|
{file = "regex-2026.2.28-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:516604edd17b1c2c3e579cf4e9b25a53bf8fa6e7cedddf1127804d3e0140ca64"},
|
||||||
{file = "regex-2026.1.15-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d9ea2604370efc9a174c1b5dcc81784fb040044232150f7f33756049edfc9026"},
|
{file = "regex-2026.2.28-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e7ce83654d1ab701cb619285a18a8e5a889c1216d746ddc710c914ca5fd71022"},
|
||||||
{file = "regex-2026.1.15-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0dcd31594264029b57bf16f37fd7248a70b3b764ed9e0839a8f271b2d22c0785"},
|
{file = "regex-2026.2.28-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f2791948f7c70bb9335a9102df45e93d428f4b8128020d85920223925d73b9e1"},
|
||||||
{file = "regex-2026.1.15-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c08c1f3e34338256732bd6938747daa3c0d5b251e04b6e43b5813e94d503076e"},
|
{file = "regex-2026.2.28-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:03a83cc26aa2acda6b8b9dfe748cf9e84cbd390c424a1de34fdcef58961a297a"},
|
||||||
{file = "regex-2026.1.15-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e43a55f378df1e7a4fa3547c88d9a5a9b7113f653a66821bcea4718fe6c58763"},
|
{file = "regex-2026.2.28-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:ec6f5674c5dc836994f50f1186dd1fafde4be0666aae201ae2fcc3d29d8adf27"},
|
||||||
{file = "regex-2026.1.15-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:f82110ab962a541737bd0ce87978d4c658f06e7591ba899192e2712a517badbb"},
|
{file = "regex-2026.2.28-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:50c2fc924749543e0eacc93ada6aeeb3ea5f6715825624baa0dccaec771668ae"},
|
||||||
{file = "regex-2026.1.15-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:27618391db7bdaf87ac6c92b31e8f0dfb83a9de0075855152b720140bda177a2"},
|
{file = "regex-2026.2.28-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:ba55c50f408fb5c346a3a02d2ce0ebc839784e24f7c9684fde328ff063c3cdea"},
|
||||||
{file = "regex-2026.1.15-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bfb0d6be01fbae8d6655c8ca21b3b72458606c4aec9bbc932db758d47aba6db1"},
|
{file = "regex-2026.2.28-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:edb1b1b3a5576c56f08ac46f108c40333f222ebfd5cf63afdfa3aab0791ebe5b"},
|
||||||
{file = "regex-2026.1.15-cp311-cp311-win32.whl", hash = "sha256:b10e42a6de0e32559a92f2f8dc908478cc0fa02838d7dbe764c44dca3fa13569"},
|
{file = "regex-2026.2.28-cp311-cp311-win32.whl", hash = "sha256:948c12ef30ecedb128903c2c2678b339746eb7c689c5c21957c4a23950c96d15"},
|
||||||
{file = "regex-2026.1.15-cp311-cp311-win_amd64.whl", hash = "sha256:e9bf3f0bbdb56633c07d7116ae60a576f846efdd86a8848f8d62b749e1209ca7"},
|
{file = "regex-2026.2.28-cp311-cp311-win_amd64.whl", hash = "sha256:fd63453f10d29097cc3dc62d070746523973fb5aa1c66d25f8558bebd47fed61"},
|
||||||
{file = "regex-2026.1.15-cp311-cp311-win_arm64.whl", hash = "sha256:41aef6f953283291c4e4e6850607bd71502be67779586a61472beacb315c97ec"},
|
{file = "regex-2026.2.28-cp311-cp311-win_arm64.whl", hash = "sha256:00f2b8d9615aa165fdff0a13f1a92049bfad555ee91e20d246a51aa0b556c60a"},
|
||||||
{file = "regex-2026.1.15-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:4c8fcc5793dde01641a35905d6731ee1548f02b956815f8f1cab89e515a5bdf1"},
|
{file = "regex-2026.2.28-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:fcf26c3c6d0da98fada8ae4ef0aa1c3405a431c0a77eb17306d38a89b02adcd7"},
|
||||||
{file = "regex-2026.1.15-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bfd876041a956e6a90ad7cdb3f6a630c07d491280bfeed4544053cd434901681"},
|
{file = "regex-2026.2.28-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:02473c954af35dd2defeb07e44182f5705b30ea3f351a7cbffa9177beb14da5d"},
|
||||||
{file = "regex-2026.1.15-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9250d087bc92b7d4899ccd5539a1b2334e44eee85d848c4c1aef8e221d3f8c8f"},
|
{file = "regex-2026.2.28-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9b65d33a17101569f86d9c5966a8b1d7fbf8afdda5a8aa219301b0a80f58cf7d"},
|
||||||
{file = "regex-2026.1.15-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c8a154cf6537ebbc110e24dabe53095e714245c272da9c1be05734bdad4a61aa"},
|
{file = "regex-2026.2.28-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e71dcecaa113eebcc96622c17692672c2d104b1d71ddf7adeda90da7ddeb26fc"},
|
||||||
{file = "regex-2026.1.15-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8050ba2e3ea1d8731a549e83c18d2f0999fbc99a5f6bd06b4c91449f55291804"},
|
{file = "regex-2026.2.28-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:481df4623fa4969c8b11f3433ed7d5e3dc9cec0f008356c3212b3933fb77e3d8"},
|
||||||
{file = "regex-2026.1.15-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0bf065240704cb8951cc04972cf107063917022511273e0969bdb34fc173456c"},
|
{file = "regex-2026.2.28-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:64e7c6ad614573e0640f271e811a408d79a9e1fe62a46adb602f598df42a818d"},
|
||||||
{file = "regex-2026.1.15-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c32bef3e7aeee75746748643667668ef941d28b003bfc89994ecf09a10f7a1b5"},
|
{file = "regex-2026.2.28-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6b08a06976ff4fb0d83077022fde3eca06c55432bb997d8c0495b9a4e9872f4"},
|
||||||
{file = "regex-2026.1.15-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d5eaa4a4c5b1906bd0d2508d68927f15b81821f85092e06f1a34a4254b0e1af3"},
|
{file = "regex-2026.2.28-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:864cdd1a2ef5716b0ab468af40139e62ede1b3a53386b375ec0786bb6783fc05"},
|
||||||
{file = "regex-2026.1.15-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:86c1077a3cc60d453d4084d5b9649065f3bf1184e22992bd322e1f081d3117fb"},
|
{file = "regex-2026.2.28-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:511f7419f7afab475fd4d639d4aedfc54205bcb0800066753ef68a59f0f330b5"},
|
||||||
{file = "regex-2026.1.15-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:2b091aefc05c78d286657cd4db95f2e6313375ff65dcf085e42e4c04d9c8d410"},
|
{file = "regex-2026.2.28-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:b42f7466e32bf15a961cf09f35fa6323cc72e64d3d2c990b10de1274a5da0a59"},
|
||||||
{file = "regex-2026.1.15-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:57e7d17f59f9ebfa9667e6e5a1c0127b96b87cb9cede8335482451ed00788ba4"},
|
{file = "regex-2026.2.28-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:8710d61737b0c0ce6836b1da7109f20d495e49b3809f30e27e9560be67a257bf"},
|
||||||
{file = "regex-2026.1.15-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:c6c4dcdfff2c08509faa15d36ba7e5ef5fcfab25f1e8f85a0c8f45bc3a30725d"},
|
{file = "regex-2026.2.28-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4390c365fd2d45278f45afd4673cb90f7285f5701607e3ad4274df08e36140ae"},
|
||||||
{file = "regex-2026.1.15-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:cf8ff04c642716a7f2048713ddc6278c5fd41faa3b9cab12607c7abecd012c22"},
|
{file = "regex-2026.2.28-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:cb3b1db8ff6c7b8bf838ab05583ea15230cb2f678e569ab0e3a24d1e8320940b"},
|
||||||
{file = "regex-2026.1.15-cp312-cp312-win32.whl", hash = "sha256:82345326b1d8d56afbe41d881fdf62f1926d7264b2fc1537f99ae5da9aad7913"},
|
{file = "regex-2026.2.28-cp312-cp312-win32.whl", hash = "sha256:f8ed9a5d4612df9d4de15878f0bc6aa7a268afbe5af21a3fdd97fa19516e978c"},
|
||||||
{file = "regex-2026.1.15-cp312-cp312-win_amd64.whl", hash = "sha256:4def140aa6156bc64ee9912383d4038f3fdd18fee03a6f222abd4de6357ce42a"},
|
{file = "regex-2026.2.28-cp312-cp312-win_amd64.whl", hash = "sha256:01d65fd24206c8e1e97e2e31b286c59009636c022eb5d003f52760b0f42155d4"},
|
||||||
{file = "regex-2026.1.15-cp312-cp312-win_arm64.whl", hash = "sha256:c6c565d9a6e1a8d783c1948937ffc377dd5771e83bd56de8317c450a954d2056"},
|
{file = "regex-2026.2.28-cp312-cp312-win_arm64.whl", hash = "sha256:c0b5ccbb8ffb433939d248707d4a8b31993cb76ab1a0187ca886bf50e96df952"},
|
||||||
{file = "regex-2026.1.15-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e69d0deeb977ffe7ed3d2e4439360089f9c3f217ada608f0f88ebd67afb6385e"},
|
{file = "regex-2026.2.28-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6d63a07e5ec8ce7184452cb00c41c37b49e67dc4f73b2955b5b8e782ea970784"},
|
||||||
{file = "regex-2026.1.15-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3601ffb5375de85a16f407854d11cca8fe3f5febbe3ac78fb2866bb220c74d10"},
|
{file = "regex-2026.2.28-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e59bc8f30414d283ae8ee1617b13d8112e7135cb92830f0ec3688cb29152585a"},
|
||||||
{file = "regex-2026.1.15-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4c5ef43b5c2d4114eb8ea424bb8c9cec01d5d17f242af88b2448f5ee81caadbc"},
|
{file = "regex-2026.2.28-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:de0cf053139f96219ccfabb4a8dd2d217c8c82cb206c91d9f109f3f552d6b43d"},
|
||||||
{file = "regex-2026.1.15-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:968c14d4f03e10b2fd960f1d5168c1f0ac969381d3c1fcc973bc45fb06346599"},
|
{file = "regex-2026.2.28-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fb4db2f17e6484904f986c5a657cec85574c76b5c5e61c7aae9ffa1bc6224f95"},
|
||||||
{file = "regex-2026.1.15-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:56a5595d0f892f214609c9f76b41b7428bed439d98dc961efafdd1354d42baae"},
|
{file = "regex-2026.2.28-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:52b017b35ac2214d0db5f4f90e303634dc44e4aba4bd6235a27f97ecbe5b0472"},
|
||||||
{file = "regex-2026.1.15-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0bf650f26087363434c4e560011f8e4e738f6f3e029b85d4904c50135b86cfa5"},
|
{file = "regex-2026.2.28-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:69fc560ccbf08a09dc9b52ab69cacfae51e0ed80dc5693078bdc97db2f91ae96"},
|
||||||
{file = "regex-2026.1.15-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18388a62989c72ac24de75f1449d0fb0b04dfccd0a1a7c1c43af5eb503d890f6"},
|
{file = "regex-2026.2.28-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e61eea47230eba62a31f3e8a0e3164d0f37ef9f40529fb2c79361bc6b53d2a92"},
|
||||||
{file = "regex-2026.1.15-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6d220a2517f5893f55daac983bfa9fe998a7dbcaee4f5d27a88500f8b7873788"},
|
{file = "regex-2026.2.28-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4f5c0b182ad4269e7381b7c27fdb0408399881f7a92a4624fd5487f2971dfc11"},
|
||||||
{file = "regex-2026.1.15-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c9c08c2fbc6120e70abff5d7f28ffb4d969e14294fb2143b4b5c7d20e46d1714"},
|
{file = "regex-2026.2.28-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:96f6269a2882fbb0ee76967116b83679dc628e68eaea44e90884b8d53d833881"},
|
||||||
{file = "regex-2026.1.15-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7ef7d5d4bd49ec7364315167a4134a015f61e8266c6d446fc116a9ac4456e10d"},
|
{file = "regex-2026.2.28-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b5acd4b6a95f37c3c3828e5d053a7d4edaedb85de551db0153754924cb7c83e3"},
|
||||||
{file = "regex-2026.1.15-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:6e42844ad64194fa08d5ccb75fe6a459b9b08e6d7296bd704460168d58a388f3"},
|
{file = "regex-2026.2.28-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2234059cfe33d9813a3677ef7667999caea9eeaa83fef98eb6ce15c6cf9e0215"},
|
||||||
{file = "regex-2026.1.15-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:cfecdaa4b19f9ca534746eb3b55a5195d5c95b88cac32a205e981ec0a22b7d31"},
|
{file = "regex-2026.2.28-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:c15af43c72a7fb0c97cbc66fa36a43546eddc5c06a662b64a0cbf30d6ac40944"},
|
||||||
{file = "regex-2026.1.15-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:08df9722d9b87834a3d701f3fca570b2be115654dbfd30179f30ab2f39d606d3"},
|
{file = "regex-2026.2.28-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9185cc63359862a6e80fe97f696e04b0ad9a11c4ac0a4a927f979f611bfe3768"},
|
||||||
{file = "regex-2026.1.15-cp313-cp313-win32.whl", hash = "sha256:d426616dae0967ca225ab12c22274eb816558f2f99ccb4a1d52ca92e8baf180f"},
|
{file = "regex-2026.2.28-cp313-cp313-win32.whl", hash = "sha256:fb66e5245db9652abd7196ace599b04d9c0e4aa7c8f0e2803938377835780081"},
|
||||||
{file = "regex-2026.1.15-cp313-cp313-win_amd64.whl", hash = "sha256:febd38857b09867d3ed3f4f1af7d241c5c50362e25ef43034995b77a50df494e"},
|
{file = "regex-2026.2.28-cp313-cp313-win_amd64.whl", hash = "sha256:71a911098be38c859ceb3f9a9ce43f4ed9f4c6720ad8684a066ea246b76ad9ff"},
|
||||||
{file = "regex-2026.1.15-cp313-cp313-win_arm64.whl", hash = "sha256:8e32f7896f83774f91499d239e24cebfadbc07639c1494bb7213983842348337"},
|
{file = "regex-2026.2.28-cp313-cp313-win_arm64.whl", hash = "sha256:39bb5727650b9a0275c6a6690f9bb3fe693a7e6cc5c3155b1240aedf8926423e"},
|
||||||
{file = "regex-2026.1.15-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:ec94c04149b6a7b8120f9f44565722c7ae31b7a6d2275569d2eefa76b83da3be"},
|
{file = "regex-2026.2.28-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:97054c55db06ab020342cc0d35d6f62a465fa7662871190175f1ad6c655c028f"},
|
||||||
{file = "regex-2026.1.15-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:40c86d8046915bb9aeb15d3f3f15b6fd500b8ea4485b30e1bbc799dab3fe29f8"},
|
{file = "regex-2026.2.28-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0d25a10811de831c2baa6aef3c0be91622f44dd8d31dd12e69f6398efb15e48b"},
|
||||||
{file = "regex-2026.1.15-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:726ea4e727aba21643205edad8f2187ec682d3305d790f73b7a51c7587b64bdd"},
|
{file = "regex-2026.2.28-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d6cfe798d8da41bb1862ed6e0cba14003d387c3c0c4a5d45591076ae9f0ce2f8"},
|
||||||
{file = "regex-2026.1.15-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1cb740d044aff31898804e7bf1181cc72c03d11dfd19932b9911ffc19a79070a"},
|
{file = "regex-2026.2.28-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fd0ce43e71d825b7c0661f9c54d4d74bd97c56c3fd102a8985bcfea48236bacb"},
|
||||||
{file = "regex-2026.1.15-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:05d75a668e9ea16f832390d22131fe1e8acc8389a694c8febc3e340b0f810b93"},
|
{file = "regex-2026.2.28-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:00945d007fd74a9084d2ab79b695b595c6b7ba3698972fadd43e23230c6979c1"},
|
||||||
{file = "regex-2026.1.15-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d991483606f3dbec93287b9f35596f41aa2e92b7c2ebbb935b63f409e243c9af"},
|
{file = "regex-2026.2.28-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bec23c11cbbf09a4df32fe50d57cbdd777bc442269b6e39a1775654f1c95dee2"},
|
||||||
{file = "regex-2026.1.15-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:194312a14819d3e44628a44ed6fea6898fdbecb0550089d84c403475138d0a09"},
|
{file = "regex-2026.2.28-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5cdcc17d935c8f9d3f4db5c2ebe2640c332e3822ad5d23c2f8e0228e6947943a"},
|
||||||
{file = "regex-2026.1.15-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fe2fda4110a3d0bc163c2e0664be44657431440722c5c5315c65155cab92f9e5"},
|
{file = "regex-2026.2.28-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a448af01e3d8031c89c5d902040b124a5e921a25c4e5e07a861ca591ce429341"},
|
||||||
{file = "regex-2026.1.15-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:124dc36c85d34ef2d9164da41a53c1c8c122cfb1f6e1ec377a1f27ee81deb794"},
|
{file = "regex-2026.2.28-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:10d28e19bd4888e4abf43bd3925f3c134c52fdf7259219003588a42e24c2aa25"},
|
||||||
{file = "regex-2026.1.15-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:a1774cd1981cd212506a23a14dba7fdeaee259f5deba2df6229966d9911e767a"},
|
{file = "regex-2026.2.28-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:99985a2c277dcb9ccb63f937451af5d65177af1efdeb8173ac55b61095a0a05c"},
|
||||||
{file = "regex-2026.1.15-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:b5f7d8d2867152cdb625e72a530d2ccb48a3d199159144cbdd63870882fb6f80"},
|
{file = "regex-2026.2.28-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:e1e7b24cb3ae9953a560c563045d1ba56ee4749fbd05cf21ba571069bd7be81b"},
|
||||||
{file = "regex-2026.1.15-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:492534a0ab925d1db998defc3c302dae3616a2fc3fe2e08db1472348f096ddf2"},
|
{file = "regex-2026.2.28-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:d8511a01d0e4ee1992eb3ba19e09bc1866fe03f05129c3aec3fdc4cbc77aad3f"},
|
||||||
{file = "regex-2026.1.15-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c661fc820cfb33e166bf2450d3dadbda47c8d8981898adb9b6fe24e5e582ba60"},
|
{file = "regex-2026.2.28-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:aaffaecffcd2479ce87aa1e74076c221700b7c804e48e98e62500ee748f0f550"},
|
||||||
{file = "regex-2026.1.15-cp313-cp313t-win32.whl", hash = "sha256:99ad739c3686085e614bf77a508e26954ff1b8f14da0e3765ff7abbf7799f952"},
|
{file = "regex-2026.2.28-cp313-cp313t-win32.whl", hash = "sha256:ef77bdde9c9eba3f7fa5b58084b29bbcc74bcf55fdbeaa67c102a35b5bd7e7cc"},
|
||||||
{file = "regex-2026.1.15-cp313-cp313t-win_amd64.whl", hash = "sha256:32655d17905e7ff8ba5c764c43cb124e34a9245e45b83c22e81041e1071aee10"},
|
{file = "regex-2026.2.28-cp313-cp313t-win_amd64.whl", hash = "sha256:98adf340100cbe6fbaf8e6dc75e28f2c191b1be50ffefe292fb0e6f6eefdb0d8"},
|
||||||
{file = "regex-2026.1.15-cp313-cp313t-win_arm64.whl", hash = "sha256:b2a13dd6a95e95a489ca242319d18fc02e07ceb28fa9ad146385194d95b3c829"},
|
{file = "regex-2026.2.28-cp313-cp313t-win_arm64.whl", hash = "sha256:2fb950ac1d88e6b6a9414381f403797b236f9fa17e1eee07683af72b1634207b"},
|
||||||
{file = "regex-2026.1.15-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:d920392a6b1f353f4aa54328c867fec3320fa50657e25f64abf17af054fc97ac"},
|
{file = "regex-2026.2.28-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:78454178c7df31372ea737996fb7f36b3c2c92cccc641d251e072478afb4babc"},
|
||||||
{file = "regex-2026.1.15-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b5a28980a926fa810dbbed059547b02783952e2efd9c636412345232ddb87ff6"},
|
{file = "regex-2026.2.28-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:5d10303dd18cedfd4d095543998404df656088240bcfd3cd20a8f95b861f74bd"},
|
||||||
{file = "regex-2026.1.15-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:621f73a07595d83f28952d7bd1e91e9d1ed7625fb7af0064d3516674ec93a2a2"},
|
{file = "regex-2026.2.28-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:19a9c9e0a8f24f39d575a6a854d516b48ffe4cbdcb9de55cb0570a032556ecff"},
|
||||||
{file = "regex-2026.1.15-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3d7d92495f47567a9b1669c51fc8d6d809821849063d168121ef801bbc213846"},
|
{file = "regex-2026.2.28-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:09500be324f49b470d907b3ef8af9afe857f5cca486f853853f7945ddbf75911"},
|
||||||
{file = "regex-2026.1.15-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8dd16fba2758db7a3780a051f245539c4451ca20910f5a5e6ea1c08d06d4a76b"},
|
{file = "regex-2026.2.28-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:fb1c4ff62277d87a7335f2c1ea4e0387b8f2b3ad88a64efd9943906aafad4f33"},
|
||||||
{file = "regex-2026.1.15-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1e1808471fbe44c1a63e5f577a1d5f02fe5d66031dcbdf12f093ffc1305a858e"},
|
{file = "regex-2026.2.28-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b8b3f1be1738feadc69f62daa250c933e85c6f34fa378f54a7ff43807c1b9117"},
|
||||||
{file = "regex-2026.1.15-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0751a26ad39d4f2ade8fe16c59b2bf5cb19eb3d2cd543e709e583d559bd9efde"},
|
{file = "regex-2026.2.28-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dc8ed8c3f41c27acb83f7b6a9eb727a73fc6663441890c5cb3426a5f6a91ce7d"},
|
||||||
{file = "regex-2026.1.15-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0f0c7684c7f9ca241344ff95a1de964f257a5251968484270e91c25a755532c5"},
|
{file = "regex-2026.2.28-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fa539be029844c0ce1114762d2952ab6cfdd7c7c9bd72e0db26b94c3c36dcc5a"},
|
||||||
{file = "regex-2026.1.15-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:74f45d170a21df41508cb67165456538425185baaf686281fa210d7e729abc34"},
|
{file = "regex-2026.2.28-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7900157786428a79615a8264dac1f12c9b02957c473c8110c6b1f972dcecaddf"},
|
||||||
{file = "regex-2026.1.15-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:f1862739a1ffb50615c0fde6bae6569b5efbe08d98e59ce009f68a336f64da75"},
|
{file = "regex-2026.2.28-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:0b1d2b07614d95fa2bf8a63fd1e98bd8fa2b4848dc91b1efbc8ba219fdd73952"},
|
||||||
{file = "regex-2026.1.15-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:453078802f1b9e2b7303fb79222c054cb18e76f7bdc220f7530fdc85d319f99e"},
|
{file = "regex-2026.2.28-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:b389c61aa28a79c2e0527ac36da579869c2e235a5b208a12c5b5318cda2501d8"},
|
||||||
{file = "regex-2026.1.15-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:a30a68e89e5a218b8b23a52292924c1f4b245cb0c68d1cce9aec9bbda6e2c160"},
|
{file = "regex-2026.2.28-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:f467cb602f03fbd1ab1908f68b53c649ce393fde056628dc8c7e634dab6bfc07"},
|
||||||
{file = "regex-2026.1.15-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9479cae874c81bf610d72b85bb681a94c95722c127b55445285fb0e2c82db8e1"},
|
{file = "regex-2026.2.28-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e8c8cb2deba42f5ec1ede46374e990f8adc5e6456a57ac1a261b19be6f28e4e6"},
|
||||||
{file = "regex-2026.1.15-cp314-cp314-win32.whl", hash = "sha256:d639a750223132afbfb8f429c60d9d318aeba03281a5f1ab49f877456448dcf1"},
|
{file = "regex-2026.2.28-cp314-cp314-win32.whl", hash = "sha256:9036b400b20e4858d56d117108d7813ed07bb7803e3eed766675862131135ca6"},
|
||||||
{file = "regex-2026.1.15-cp314-cp314-win_amd64.whl", hash = "sha256:4161d87f85fa831e31469bfd82c186923070fc970b9de75339b68f0c75b51903"},
|
{file = "regex-2026.2.28-cp314-cp314-win_amd64.whl", hash = "sha256:1d367257cd86c1cbb97ea94e77b373a0bbc2224976e247f173d19e8f18b4afa7"},
|
||||||
{file = "regex-2026.1.15-cp314-cp314-win_arm64.whl", hash = "sha256:91c5036ebb62663a6b3999bdd2e559fd8456d17e2b485bf509784cd31a8b1705"},
|
{file = "regex-2026.2.28-cp314-cp314-win_arm64.whl", hash = "sha256:5e68192bb3a1d6fb2836da24aa494e413ea65853a21505e142e5b1064a595f3d"},
|
||||||
{file = "regex-2026.1.15-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:ee6854c9000a10938c79238de2379bea30c82e4925a371711af45387df35cab8"},
|
{file = "regex-2026.2.28-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:a5dac14d0872eeb35260a8e30bac07ddf22adc1e3a0635b52b02e180d17c9c7e"},
|
||||||
{file = "regex-2026.1.15-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2c2b80399a422348ce5de4fe40c418d6299a0fa2803dd61dc0b1a2f28e280fcf"},
|
{file = "regex-2026.2.28-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:ec0c608b7a7465ffadb344ed7c987ff2f11ee03f6a130b569aa74d8a70e8333c"},
|
||||||
{file = "regex-2026.1.15-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:dca3582bca82596609959ac39e12b7dad98385b4fefccb1151b937383cec547d"},
|
{file = "regex-2026.2.28-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c7815afb0ca45456613fdaf60ea9c993715511c8d53a83bc468305cbc0ee23c7"},
|
||||||
{file = "regex-2026.1.15-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef71d476caa6692eea743ae5ea23cde3260677f70122c4d258ca952e5c2d4e84"},
|
{file = "regex-2026.2.28-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b059e71ec363968671693a78c5053bd9cb2fe410f9b8e4657e88377ebd603a2e"},
|
||||||
{file = "regex-2026.1.15-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c243da3436354f4af6c3058a3f81a97d47ea52c9bd874b52fd30274853a1d5df"},
|
{file = "regex-2026.2.28-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b8cf76f1a29f0e99dcfd7aef1551a9827588aae5a737fe31442021165f1920dc"},
|
||||||
{file = "regex-2026.1.15-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8355ad842a7c7e9e5e55653eade3b7d1885ba86f124dd8ab1f722f9be6627434"},
|
{file = "regex-2026.2.28-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:180e08a435a0319e6a4821c3468da18dc7001987e1c17ae1335488dfe7518dd8"},
|
||||||
{file = "regex-2026.1.15-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f192a831d9575271a22d804ff1a5355355723f94f31d9eef25f0d45a152fdc1a"},
|
{file = "regex-2026.2.28-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1e496956106fd59ba6322a8ea17141a27c5040e5ee8f9433ae92d4e5204462a0"},
|
||||||
{file = "regex-2026.1.15-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:166551807ec20d47ceaeec380081f843e88c8949780cd42c40f18d16168bed10"},
|
{file = "regex-2026.2.28-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bba2b18d70eeb7b79950f12f633beeecd923f7c9ad6f6bae28e59b4cb3ab046b"},
|
||||||
{file = "regex-2026.1.15-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:f9ca1cbdc0fbfe5e6e6f8221ef2309988db5bcede52443aeaee9a4ad555e0dac"},
|
{file = "regex-2026.2.28-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:6db7bfae0f8a2793ff1f7021468ea55e2699d0790eb58ee6ab36ae43aa00bc5b"},
|
||||||
{file = "regex-2026.1.15-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b30bcbd1e1221783c721483953d9e4f3ab9c5d165aa709693d3f3946747b1aea"},
|
{file = "regex-2026.2.28-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:d0b02e8b7e5874b48ae0f077ecca61c1a6a9f9895e9c6dfb191b55b242862033"},
|
||||||
{file = "regex-2026.1.15-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:2a8d7b50c34578d0d3bf7ad58cde9652b7d683691876f83aedc002862a35dc5e"},
|
{file = "regex-2026.2.28-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:25b6eb660c5cf4b8c3407a1ed462abba26a926cc9965e164268a3267bcc06a43"},
|
||||||
{file = "regex-2026.1.15-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:9d787e3310c6a6425eb346be4ff2ccf6eece63017916fd77fe8328c57be83521"},
|
{file = "regex-2026.2.28-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:5a932ea8ad5d0430351ff9c76c8db34db0d9f53c1d78f06022a21f4e290c5c18"},
|
||||||
{file = "regex-2026.1.15-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:619843841e220adca114118533a574a9cd183ed8a28b85627d2844c500a2b0db"},
|
{file = "regex-2026.2.28-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:1c2c95e1a2b0f89d01e821ff4de1be4b5d73d1f4b0bf679fa27c1ad8d2327f1a"},
|
||||||
{file = "regex-2026.1.15-cp314-cp314t-win32.whl", hash = "sha256:e90b8db97f6f2c97eb045b51a6b2c5ed69cedd8392459e0642d4199b94fabd7e"},
|
{file = "regex-2026.2.28-cp314-cp314t-win32.whl", hash = "sha256:bbb882061f742eb5d46f2f1bd5304055be0a66b783576de3d7eef1bed4778a6e"},
|
||||||
{file = "regex-2026.1.15-cp314-cp314t-win_amd64.whl", hash = "sha256:5ef19071f4ac9f0834793af85bd04a920b4407715624e40cb7a0631a11137cdf"},
|
{file = "regex-2026.2.28-cp314-cp314t-win_amd64.whl", hash = "sha256:6591f281cb44dc13de9585b552cec6fc6cf47fb2fe7a48892295ee9bc4a612f9"},
|
||||||
{file = "regex-2026.1.15-cp314-cp314t-win_arm64.whl", hash = "sha256:ca89c5e596fc05b015f27561b3793dc2fa0917ea0d7507eebb448efd35274a70"},
|
{file = "regex-2026.2.28-cp314-cp314t-win_arm64.whl", hash = "sha256:dee50f1be42222f89767b64b283283ef963189da0dda4a515aa54a5563c62dec"},
|
||||||
{file = "regex-2026.1.15-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:55b4ea996a8e4458dd7b584a2f89863b1655dd3d17b88b46cbb9becc495a0ec5"},
|
{file = "regex-2026.2.28.tar.gz", hash = "sha256:a729e47d418ea11d03469f321aaf67cdee8954cde3ff2cf8403ab87951ad10f2"},
|
||||||
{file = "regex-2026.1.15-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7e1e28be779884189cdd57735e997f282b64fd7ccf6e2eef3e16e57d7a34a815"},
|
|
||||||
{file = "regex-2026.1.15-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0057de9eaef45783ff69fa94ae9f0fd906d629d0bd4c3217048f46d1daa32e9b"},
|
|
||||||
{file = "regex-2026.1.15-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc7cd0b2be0f0269283a45c0d8b2c35e149d1319dcb4a43c9c3689fa935c1ee6"},
|
|
||||||
{file = "regex-2026.1.15-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8db052bbd981e1666f09e957f3790ed74080c2229007c1dd67afdbf0b469c48b"},
|
|
||||||
{file = "regex-2026.1.15-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:343db82cb3712c31ddf720f097ef17c11dab2f67f7a3e7be976c4f82eba4e6df"},
|
|
||||||
{file = "regex-2026.1.15-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:55e9d0118d97794367309635df398bdfd7c33b93e2fdfa0b239661cd74b4c14e"},
|
|
||||||
{file = "regex-2026.1.15-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:008b185f235acd1e53787333e5690082e4f156c44c87d894f880056089e9bc7c"},
|
|
||||||
{file = "regex-2026.1.15-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fd65af65e2aaf9474e468f9e571bd7b189e1df3a61caa59dcbabd0000e4ea839"},
|
|
||||||
{file = "regex-2026.1.15-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f42e68301ff4afee63e365a5fc302b81bb8ba31af625a671d7acb19d10168a8c"},
|
|
||||||
{file = "regex-2026.1.15-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:f7792f27d3ee6e0244ea4697d92b825f9a329ab5230a78c1a68bd274e64b5077"},
|
|
||||||
{file = "regex-2026.1.15-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:dbaf3c3c37ef190439981648ccbf0c02ed99ae066087dd117fcb616d80b010a4"},
|
|
||||||
{file = "regex-2026.1.15-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:adc97a9077c2696501443d8ad3fa1b4fc6d131fc8fd7dfefd1a723f89071cf0a"},
|
|
||||||
{file = "regex-2026.1.15-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:069f56a7bf71d286a6ff932a9e6fb878f151c998ebb2519a9f6d1cee4bffdba3"},
|
|
||||||
{file = "regex-2026.1.15-cp39-cp39-win32.whl", hash = "sha256:ea4e6b3566127fda5e007e90a8fd5a4169f0cf0619506ed426db647f19c8454a"},
|
|
||||||
{file = "regex-2026.1.15-cp39-cp39-win_amd64.whl", hash = "sha256:cda1ed70d2b264952e88adaa52eea653a33a1b98ac907ae2f86508eb44f65cdc"},
|
|
||||||
{file = "regex-2026.1.15-cp39-cp39-win_arm64.whl", hash = "sha256:b325d4714c3c48277bfea1accd94e193ad6ed42b4bad79ad64f3b8f8a31260a5"},
|
|
||||||
{file = "regex-2026.1.15.tar.gz", hash = "sha256:164759aa25575cbc0651bef59a0b18353e54300d79ace8084c818ad8ac72b7d5"},
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "soupsieve"
|
name = "soupsieve"
|
||||||
version = "2.8.1"
|
version = "2.8.3"
|
||||||
description = "A modern CSS selector implementation for Beautiful Soup."
|
description = "A modern CSS selector implementation for Beautiful Soup."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.9"
|
python-versions = ">=3.9"
|
||||||
files = [
|
files = [
|
||||||
{file = "soupsieve-2.8.1-py3-none-any.whl", hash = "sha256:a11fe2a6f3d76ab3cf2de04eb339c1be5b506a8a47f2ceb6d139803177f85434"},
|
{file = "soupsieve-2.8.3-py3-none-any.whl", hash = "sha256:ed64f2ba4eebeab06cc4962affce381647455978ffc1e36bb79a545b91f45a95"},
|
||||||
{file = "soupsieve-2.8.1.tar.gz", hash = "sha256:4cf733bc50fa805f5df4b8ef4740fc0e0fa6218cf3006269afd3f9d6d80fd350"},
|
{file = "soupsieve-2.8.3.tar.gz", hash = "sha256:3267f1eeea4251fb42728b6dfb746edc9acaffc4a45b27e19450b676586e8349"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -583,4 +567,4 @@ files = [
|
|||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.0"
|
lock-version = "2.0"
|
||||||
python-versions = "^3.13"
|
python-versions = "^3.13"
|
||||||
content-hash = "51adb27446d922b213f18b74ab5a9037d7535b1c4cbc9fdf71461a56a63d60e6"
|
content-hash = "8d6b9d1c17f305b1e64b30f9db19f7a16005d209e5455aa912412cd04f59fde7"
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -3,3 +3,6 @@ Allow: /
|
|||||||
|
|
||||||
Host: https://typograph.cube2.ru
|
Host: https://typograph.cube2.ru
|
||||||
Sitemap: https://typograph.cube2.ru/sitemap.xml
|
Sitemap: https://typograph.cube2.ru/sitemap.xml
|
||||||
|
|
||||||
|
# Ссылка на файл для ИИ-моделей
|
||||||
|
Link: /llms.txt
|
||||||
|
|||||||
+5
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
+3
-3
@@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "2026-etpgrf-site"
|
name = "2026-etpgrf-site"
|
||||||
version = "0.1.0"
|
version = "0.2.7"
|
||||||
description = ""
|
description = ""
|
||||||
authors = ["erjemin <erjemin@gmail.com>"]
|
authors = ["erjemin <erjemin@gmail.com>"]
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
@@ -9,9 +9,9 @@ package-mode = false
|
|||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
python = "^3.13"
|
python = "^3.13"
|
||||||
django = "^6.0"
|
django = "^6.0"
|
||||||
gunicorn = "^23.0.0"
|
gunicorn = "^25.0.0"
|
||||||
python-dotenv = "^1.2.1"
|
python-dotenv = "^1.2.1"
|
||||||
etpgrf = "^0.1.5"
|
etpgrf = "^0.1.6"
|
||||||
# lxml = "^5.1" # etpgrf подтянет как зависимость
|
# lxml = "^5.1" # etpgrf подтянет как зависимость
|
||||||
# regex = "^2023.12" # etpgrf подтянет как зависимость
|
# regex = "^2023.12" # etpgrf подтянет как зависимость
|
||||||
# beautifulsoup4 = "^4.10.0" # etpgrf подтянет как зависимость
|
# beautifulsoup4 = "^4.10.0" # etpgrf подтянет как зависимость
|
||||||
|
|||||||
Reference in New Issue
Block a user