merge: rework into main
22
.env.sample
Normal file
@@ -0,0 +1,22 @@
|
||||
# Шаблон переменных окружения для CADpoint.
|
||||
# Скопируй этот файл в `.env` и заполни реальными значениями.
|
||||
|
||||
DJANGO_DEBUG=True
|
||||
DJANGO_SECRET_KEY=CHANGE_ME
|
||||
DJANGO_ALLOWED_HOSTS=127.0.0.1,localhost,cadpoint.ru
|
||||
DJANGO_ADMINS=CADpoint:admin@example.com
|
||||
DJANGO_CSRF_TRUSTED_ORIGINS=http://127.0.0.1:8000,http://localhost:8000,https://cadpoint.ru
|
||||
|
||||
# Имя файла SQLite-базы. Путь всегда собирается через `BASE_DIR.parent / 'database'`.
|
||||
DJANGO_SQLITE_NAME=cadpoint-db.sqlite3
|
||||
|
||||
# Почта.
|
||||
DJANGO_EMAIL_HOST=smtp.mail.ru
|
||||
DJANGO_EMAIL_PORT=2525
|
||||
DJANGO_EMAIL_HOST_USER=you@email.com
|
||||
DJANGO_EMAIL_HOST_PASSWORD=CHANGE_ME
|
||||
DJANGO_EMAIL_FROM=you@email.com
|
||||
|
||||
# URL для доступа к админке Django (можно сменить для безопасности, чтобы боты не могли её найти)
|
||||
ADMIN_URL=admin/
|
||||
|
||||
8
.gitignore
vendored
@@ -263,6 +263,7 @@ ipython_config.py
|
||||
# commonly ignored for libraries.
|
||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||
#poetry.lock
|
||||
poetry.toml
|
||||
|
||||
# pdm
|
||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||
@@ -322,12 +323,11 @@ cython_debug/
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
.idea/
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Project-specific files that must not be committed
|
||||
# -----------------------------------------------------------------------------
|
||||
SQL/
|
||||
*.sql
|
||||
cadpoint/cadpoint/my_secret.py
|
||||
|
||||
my_secret*.py
|
||||
.github/
|
||||
|
||||
85
README.md
@@ -3,12 +3,89 @@
|
||||
Сайт с новостями (блог о 3D-печать и Систем Автоматизированного Проектирования) на Django со
|
||||
встроенными свистелками-перделками:
|
||||
* медиа-библиотека (filer);
|
||||
* WYSIWYG-редактор (ckeditor) в админке;
|
||||
* типограф (по API или встроенный «типограф Муравьева», с костылями под ckeditor);
|
||||
* HTML-редактор на обычной textarea в админке;
|
||||
* типограф [etpgrf](https://typograph.cube2.ru/);
|
||||
* теги новостей (taggit).
|
||||
|
||||
[Инструкция по развертыванию на хостинге DreamHost.com](deploy_to_dreamhost.md)
|
||||
|
||||
Для локальной настройки секретов используй `cadpoint/cadpoint/my_secret_example.py` как шаблон и
|
||||
создавай рядом незакоммиченный `cadpoint/cadpoint/my_secret.py`.
|
||||
Для локальной и продовой настройки используй файл `.env` в корне проекта.
|
||||
Шаблон для него лежит в `.env.sample`.
|
||||
|
||||
Набор базовых переменных:
|
||||
|
||||
* `DJANGO_SECRET_KEY`
|
||||
* `DJANGO_DEBUG`
|
||||
* `DJANGO_ALLOWED_HOSTS`
|
||||
* `DJANGO_ADMINS`
|
||||
* `DJANGO_CSRF_TRUSTED_ORIGINS`
|
||||
* `DJANGO_INTERNAL_IPS`
|
||||
* `DJANGO_SQLITE_NAME`
|
||||
* `ADMIN_URL`
|
||||
* `DJANGO_EMAIL_HOST`
|
||||
* `DJANGO_EMAIL_PORT`
|
||||
* `DJANGO_EMAIL_HOST_USER`
|
||||
* `DJANGO_EMAIL_HOST_PASSWORD`
|
||||
* `DJANGO_EMAIL_FROM`
|
||||
|
||||
Для логического бэкапа базы через Django используй команду:
|
||||
|
||||
```bash
|
||||
cd cadpoint
|
||||
python manage.py backup_db
|
||||
```
|
||||
|
||||
По умолчанию файл дампа сохраняется в `database/backups/`. Восстановление делается обычной командой
|
||||
`python manage.py loaddata <fixture>.json` в пустую базу после `python manage.py migrate`.
|
||||
|
||||
## Замена старых Joomla-ссылок в контенте
|
||||
|
||||
Для массовой замены старых внутренних ссылок в HTML-контенте используй management command:
|
||||
|
||||
```bash
|
||||
cd cadpoint
|
||||
python manage.py replace_legacy_links
|
||||
```
|
||||
|
||||
По умолчанию команда работает в режиме `dry-run`: она только показывает, какие поля и записи
|
||||
будут изменены. Чтобы записать изменения в базу, добавь флаг:
|
||||
|
||||
```bash
|
||||
cd cadpoint
|
||||
python manage.py replace_legacy_links --apply
|
||||
```
|
||||
|
||||
Сейчас команда чинит только кросс-ссылки на статьи. Ссылки на картинки и прочие медиа пока
|
||||
оставлены как есть.
|
||||
|
||||
Для нового окружения на Poetry:
|
||||
|
||||
```bash
|
||||
poetry install --with dev
|
||||
cp .env.sample .env
|
||||
poetry run python cadpoint/manage.py migrate
|
||||
poetry run python cadpoint/manage.py runserver
|
||||
```
|
||||
|
||||
Для разработки медиа-файлы и статика лежат в `public/media` и `public/static`.
|
||||
`django-debug-toolbar` показывается только при `DJANGO_DEBUG=true` и заходе с локального
|
||||
адреса (`127.0.0.1` / `localhost`); если нужно, свои IP можно добавить в `DJANGO_INTERNAL_IPS`.
|
||||
|
||||
## Сборка CodeMirror 6
|
||||
|
||||
Когда появится фронтенд-часть CodeMirror 6 админки, её можно пересобирать скриптом
|
||||
`frontend-assembly/build-codemirror6.sh`.
|
||||
|
||||
Скрипт создаёт временную рабочую папку, ставит зависимости через `npm ci`, собирает
|
||||
минимизированный бандл и затем сам удаляет временные `src/` и `node_modules/`.
|
||||
В проекте остаётся только готовая статика:
|
||||
|
||||
* `public/static/codemirror/editor.js`
|
||||
|
||||
Запуск:
|
||||
|
||||
```bash
|
||||
bash ./frontend-assembly/build-codemirror6.sh
|
||||
```
|
||||
|
||||
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Шаблон локальных секретов для CADpoint.
|
||||
|
||||
Скопируй этот файл в `my_secret.py` и заполни реальными значениями вне Git.
|
||||
"""
|
||||
|
||||
# Секретный ключ Django.
|
||||
MY_SECRET_KEY = "CHANGE_ME"
|
||||
|
||||
# Имена хостов, на которых включается DEBUG.
|
||||
MY_HOST_HOME = "CHANGE_ME"
|
||||
MY_HOST_WORK = "CHANGE_ME"
|
||||
|
||||
# Локальные пути для разработки.
|
||||
MY_MEDIA_ROOT_DEV = "/path/to/media/dev"
|
||||
MY_STATIC_ROOT_DEV = "/path/to/static/dev"
|
||||
|
||||
# Почта для разработки.
|
||||
MY_EMAIL_HOST_DEV = "smtp.example.com"
|
||||
MY_EMAIL_PORT_DEV = 587
|
||||
MY_EMAIL_HOST_USER_DEV = "user@example.com"
|
||||
MY_EMAIL_HOST_PASSWORD_DEV = "CHANGE_ME"
|
||||
MY_EMAIL_FROM_DEV = "user@example.com"
|
||||
|
||||
# База данных для разработки.
|
||||
MY_DATABASE_HOST_DEV = "127.0.0.1"
|
||||
MY_DATABASE_PORT_DEV = 3306
|
||||
MY_DATABASE_NAME_DEV = "cadpoint_dev"
|
||||
MY_DATABASE_USER_DEV = "cadpoint_dev"
|
||||
MY_DATABASE_PASSWORD_DEV = "CHANGE_ME"
|
||||
|
||||
# Пути для production.
|
||||
MY_MEDIA_ROOT_PROD = "/path/to/media/prod"
|
||||
MY_STATIC_ROOT_PROD = "/path/to/static/prod"
|
||||
|
||||
# Почта для production.
|
||||
MY_EMAIL_HOST_PROD = "smtp.example.com"
|
||||
MY_EMAIL_PORT_PROD = 587
|
||||
MY_EMAIL_HOST_USER_PROD = "user@example.com"
|
||||
MY_EMAIL_HOST_PASSWORD_PROD = "CHANGE_ME"
|
||||
MY_EMAIL_FROM_PROD = "user@example.com"
|
||||
|
||||
# База данных для production.
|
||||
MY_DATABASE_HOST_PROD = "127.0.0.1"
|
||||
MY_DATABASE_PORT_PROD = 3306
|
||||
MY_DATABASE_NAME_PROD = "cadpoint_prod"
|
||||
MY_DATABASE_USER_PROD = "cadpoint_prod"
|
||||
MY_DATABASE_PASSWORD_PROD = "CHANGE_ME"
|
||||
|
||||
@@ -1,58 +1,49 @@
|
||||
"""
|
||||
Django settings for cadpoint project.
|
||||
|
||||
Generated by 'django-admin startproject' using Django 3.2.5.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/3.2/topics/settings/
|
||||
|
||||
For the full list of settings and their values, see
|
||||
https://docs.djangoproject.com/en/3.2/ref/settings/
|
||||
"""
|
||||
"""Настройки Django для проекта cadpoint."""
|
||||
|
||||
from pathlib import Path
|
||||
import socket
|
||||
|
||||
try:
|
||||
# В репозитории хранится только шаблон секретов, а реальный файл остаётся локальным.
|
||||
from .my_secret import *
|
||||
except ImportError: # pragma: no cover - запасной путь для открытого репозитория
|
||||
from .my_secret_example import *
|
||||
from django.db.backends.signals import connection_created
|
||||
from django.dispatch import receiver
|
||||
import environ
|
||||
import os
|
||||
|
||||
|
||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||
|
||||
env = environ.Env()
|
||||
environ.Env.read_env(os.path.join(BASE_DIR.parent, '.env'))
|
||||
|
||||
|
||||
def _normalize_admin_url(value: str) -> str:
|
||||
"""Приводит URL админки к виду `segment/` без ведущего слэша."""
|
||||
normalized = value.strip().lstrip('/')
|
||||
if not normalized:
|
||||
return 'admin/'
|
||||
if not normalized.endswith('/'):
|
||||
normalized += '/'
|
||||
return normalized
|
||||
|
||||
|
||||
# Quick-start development settings - unsuitable for production
|
||||
# See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/
|
||||
# See https://docs.djangoproject.com/en/5.2/howto/deployment/checklist/
|
||||
|
||||
# SECURITY WARNING: keep the secret key used in production secret!
|
||||
SECRET_KEY = MY_SECRET_KEY
|
||||
SECRET_KEY = env('DJANGO_SECRET_KEY', default='django-insecure-change-me')
|
||||
ADMIN_URL = _normalize_admin_url(env('ADMIN_URL', default='admin/'))
|
||||
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
if socket.gethostname() in (MY_HOST_HOME, MY_HOST_WORK):
|
||||
DEBUG = True
|
||||
else:
|
||||
# Все остальные хосты (подразумевается продакшн)
|
||||
DEBUG = False
|
||||
DEBUG = env.bool('DJANGO_DEBUG', default=False)
|
||||
|
||||
|
||||
ALLOWED_HOSTS = [
|
||||
'127.0.0.1',
|
||||
'localhost',
|
||||
'192.168.1.30', # разработка домашний
|
||||
'10.10.5.6', # разработка офис
|
||||
'90.156.203.25', # продакшн хостинг masterhost
|
||||
'cadpoint.ru', # продакшн хостинг
|
||||
'www.cadpoint.ru', # продакшн хостинг
|
||||
'new.cadpoint.ru', # продакшн хостинг
|
||||
]
|
||||
ALLOWED_HOSTS = env.list(
|
||||
'DJANGO_ALLOWED_HOSTS',
|
||||
default=['127.0.0.1', 'localhost', 'cadpoint.ru'],
|
||||
)
|
||||
|
||||
#########################################
|
||||
# Настройки сообщений об ошибках когда все упало и т.п.
|
||||
ADMINS = (
|
||||
('S.Erjemin', 'erjemin@gmail.com'),
|
||||
ADMINS = tuple(
|
||||
tuple(item.split(':', 1))
|
||||
for item in env.list('DJANGO_ADMINS', default=['S.Erjemin:erjemin@gmail.com'])
|
||||
)
|
||||
|
||||
|
||||
@@ -65,11 +56,13 @@ INSTALLED_APPS = [
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
'django.contrib.sitemaps',
|
||||
# Панель отладки показываем только в dev-окружении при `DEBUG=True`.
|
||||
'debug_toolbar',
|
||||
'django_select2',
|
||||
'easy_thumbnails',
|
||||
'filer.apps.FilerConfig',
|
||||
'mptt.apps.MpttConfig',
|
||||
# # 'ckeditor_uploader',
|
||||
'ckeditor',
|
||||
'taggit.apps.TaggitAppConfig',
|
||||
# 'fontawesome-free'
|
||||
'web.apps.WebConfig',
|
||||
@@ -77,6 +70,8 @@ INSTALLED_APPS = [
|
||||
|
||||
MIDDLEWARE = [
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
# Middleware нужен, иначе панель debug toolbar просто не влезет в response.
|
||||
'debug_toolbar.middleware.DebugToolbarMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
@@ -107,7 +102,7 @@ TEMPLATES = [
|
||||
WSGI_APPLICATION = 'cadpoint.wsgi.application'
|
||||
|
||||
# Password validation
|
||||
# https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators
|
||||
# https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators
|
||||
AUTH_PASSWORD_VALIDATORS = [
|
||||
{'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', },
|
||||
{'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', },
|
||||
@@ -117,12 +112,11 @@ AUTH_PASSWORD_VALIDATORS = [
|
||||
|
||||
|
||||
# Internationalization
|
||||
# https://docs.djangoproject.com/en/3.2/topics/i18n/
|
||||
# https://docs.djangoproject.com/en/5.2/topics/i18n/
|
||||
LANGUAGE_CODE = 'ru-RU' # <--------- RUSSIAN
|
||||
# TIME_ZONE = 'Etc/GMT+3' #
|
||||
TIME_ZONE = 'Europe/Moscow' #
|
||||
USE_I18N = True
|
||||
USE_L10N = True
|
||||
USE_TZ = True # учитывать часовой пояс
|
||||
FIRST_DAY_OF_WEEK = 1 # неделя начинается с понедельника
|
||||
DEFAULT_CHARSET = 'utf-8'
|
||||
@@ -149,50 +143,6 @@ THUMBNAIL_TRANSPARENCY_EXTENSION = 'png'
|
||||
THUMBNAIL_WIDGET_OPTIONS = {'size': (64, 64)}
|
||||
|
||||
|
||||
CKEDITOR_UPLOAD_PATH = "uploads/"
|
||||
CKEDITOR_BASEPATH = "/static/ckeditor/ckeditor/"
|
||||
CKEDITOR_FILENAME_GENERATOR = 'utils.get_filename'
|
||||
# конфигуратор ckeditor https://ckeditor.com/latest/samples/toolbarconfigurator/index.html#basic
|
||||
CKEDITOR_CONFIGS = {
|
||||
'default': {
|
||||
'toolbar_mini': [
|
||||
{'name': 'document', 'items': ['Source', '-', ]},
|
||||
{'name': 'basicstyles', 'items': ['Bold', 'Italic', 'Underline', 'NumberedList', 'BulletedList',
|
||||
'Format', '-', 'RemoveFormat']},
|
||||
{'name': 'my_custom_tools', 'items': ['Preview', 'Maximize']},
|
||||
],
|
||||
'toolbar': 'mini', # put selected toolbar config here
|
||||
'height': '110',
|
||||
'toolbarCanCollapse': True,
|
||||
},
|
||||
'fine': {
|
||||
'toolbar_fine': [
|
||||
{'name': 'document', 'items': ['Source', '-' ]},
|
||||
{'name': 'clipboard', 'items': ['Cut', 'Copy', 'Paste', 'PasteText', 'PasteFromWord', '-', 'Undo', 'Redo']},
|
||||
{'name': 'basicstyles',
|
||||
'items': ['Bold', 'Italic', 'Underline', 'Strike', 'Subscript', 'Superscript', '-', 'RemoveFormat']},
|
||||
{'name': 'my_custom_tools', 'items': ['Preview', 'Maximize']},
|
||||
'/',
|
||||
{'name': 'paragraph',
|
||||
'items': ['NumberedList', 'BulletedList', '-', 'Outdent', 'Indent', '-', 'Blockquote', 'CreateDiv', '-',
|
||||
'JustifyLeft', 'JustifyCenter', 'JustifyRight', 'JustifyBlock', 'Styles', 'Format', 'Iframe']},
|
||||
{'name': 'links', 'items': ['Link', 'Unlink', 'Anchor']},
|
||||
{'name': 'insert', 'items': ['Image', 'Table', 'HorizontalRule', 'SpecialChar']},
|
||||
],
|
||||
'toolbar': 'fine',
|
||||
# 'removeButtons': 'Save,NewPage,ExportPdf,Preview,Print,Templates,Find,Replace,SelectAll,Scayt,Form,'
|
||||
# 'Checkbox,Radio,TextField,Textarea,Select,Button,ImageButton,HiddenField,Format,'
|
||||
# 'Font,FontSize,Maximize,ShowBlocks,About,Styles,Flash,Smiley,PageBreak,Iframe,BidiLtr,'
|
||||
# 'BidiRtl,Language,JustifyBlock,JustifyRight,JustifyCenter,JustifyLeft,Indent,Outdent,'
|
||||
# 'Strike,TextColor,BGColor,
|
||||
'toolbarCanCollapse': True,
|
||||
# 'extraPlugins': 'filer',
|
||||
# 'editor': [
|
||||
# {'name': 'filebrowserBrowseUrl', 'items': ''},
|
||||
# {'name': 'filebrowserUploadUrl', 'items': ''},
|
||||
# ],
|
||||
},
|
||||
}
|
||||
|
||||
FILER_SUBJECT_LOCATION_IMAGE_DEBUG = True
|
||||
FILER_CANONICAL_URL = 'sharing/'
|
||||
@@ -201,63 +151,85 @@ FILER_CANONICAL_URL = 'sharing/'
|
||||
STATIC_URL = '/static/'
|
||||
MEDIA_URL = '/media/'
|
||||
|
||||
if DEBUG: # DEBUG: заменяем настройки прода, на настройки девопа
|
||||
MEDIA_ROOT = MY_MEDIA_ROOT_DEV
|
||||
# STATIC_ROOT = MY_STATIC_ROOT_DEV1
|
||||
STATICFILES_DIRS = [MY_STATIC_ROOT_DEV, ]
|
||||
#########################################
|
||||
# настройки для почтового сервера
|
||||
EMAIL_HOST = MY_EMAIL_HOST_DEV # SMTP server
|
||||
EMAIL_PORT = MY_EMAIL_PORT_DEV # для SSL/https
|
||||
EMAIL_HOST_USER = MY_EMAIL_HOST_USER_DEV # login or ''
|
||||
EMAIL_HOST_PASSWORD = MY_EMAIL_HOST_PASSWORD_DEV # password
|
||||
EMAIL_FROM = MY_EMAIL_FROM_DEV # мейл, от имени которого отправляются письма
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': "django.db.backends.mysql",
|
||||
'HOST': MY_DATABASE_HOST_DEV, # Set to "" for localhost. Not used with sqlite3.
|
||||
'PORT': MY_DATABASE_PORT_DEV, # Set to "" for default. Not used with sqlite3.
|
||||
'NAME': MY_DATABASE_NAME_DEV, # Not used with sqlite3.
|
||||
'USER': MY_DATABASE_USER_DEV, # Not used with sqlite3.
|
||||
'PASSWORD': MY_DATABASE_PASSWORD_DEV, # Not used with sqlite3.
|
||||
# 'OPTIONS': { 'autocommit': True, }
|
||||
}
|
||||
}
|
||||
MIDDLEWARE += ['debug_toolbar.middleware.DebugToolbarMiddleware', ]
|
||||
INSTALLED_APPS += ['debug_toolbar', ]
|
||||
INTERNAL_IPS = ['127.0.0.1', '192.168.1.30', '10.10.5.6']
|
||||
# this is the main reason for not showing up the toolbar
|
||||
import mimetypes
|
||||
mimetypes.add_type("application/javascript", ".js", True)
|
||||
DEBUG_TOOLBAR_CONFIG = {'INTERCEPT_REDIRECTS': False, }
|
||||
else:
|
||||
MEDIA_ROOT = MY_MEDIA_ROOT_PROD
|
||||
STATIC_ROOT = MY_STATIC_ROOT_PROD
|
||||
# STATICFILES_DIRS = [MY_STATIC_ROOT_PROD1, ]
|
||||
#########################################
|
||||
# настройки для почтового сервера
|
||||
EMAIL_HOST = MY_EMAIL_HOST_PROD # SMTP server
|
||||
EMAIL_PORT = MY_EMAIL_PORT_PROD # для SSL/https
|
||||
EMAIL_HOST_USER = MY_EMAIL_HOST_USER_PROD # login or ''
|
||||
EMAIL_HOST_PASSWORD = MY_EMAIL_HOST_PASSWORD_PROD # password
|
||||
EMAIL_FROM = MY_EMAIL_FROM_PROD # мейл, от имени которого отправляются письма
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': "django.db.backends.mysql",
|
||||
'HOST': MY_DATABASE_HOST_PROD, # Set to "" for localhost. Not used with sqlite3.
|
||||
'PORT': MY_DATABASE_PORT_PROD, # Set to "" for default. Not used with sqlite3.
|
||||
'NAME': MY_DATABASE_NAME_PROD, # Not used with sqlite3.
|
||||
'USER': MY_DATABASE_USER_PROD, # Not used with sqlite3.
|
||||
'PASSWORD': MY_DATABASE_PASSWORD_PROD, # Not used with sqlite3.
|
||||
# 'OPTIONS': { 'autocommit': True, }
|
||||
}
|
||||
}
|
||||
# Локальные каталоги проекта: медиа и статика лежат рядом в `public`.
|
||||
PUBLIC_DIR = BASE_DIR.parent.joinpath('public')
|
||||
MEDIA_ROOT = PUBLIC_DIR.joinpath('media')
|
||||
STATICFILES_DIRS = [PUBLIC_DIR.joinpath('static')]
|
||||
STATIC_ROOT = PUBLIC_DIR.joinpath('staticfiles')
|
||||
CSRF_TRUSTED_ORIGINS = env.list('DJANGO_CSRF_TRUSTED_ORIGINS', default=[])
|
||||
# Внутренние адреса для debug toolbar: локальный браузер и loopback.
|
||||
INTERNAL_IPS = env.list('DJANGO_INTERNAL_IPS', default=['127.0.0.1', '::1'])
|
||||
|
||||
SERVER_EMAIL = DEFAULT_FROM_EMAIL = EMAIL_HOST_USER
|
||||
# Параметры Select2 в админке.
|
||||
# Держим их здесь, чтобы не размазывать магические числа по `admin.py`.
|
||||
SELECT2_AJAX_DELAY_MS = 250
|
||||
SELECT2_MINIMUM_INPUT_LENGTH = 0
|
||||
SELECT2_TOKEN_SEPARATORS = '[","]'
|
||||
SELECT2_PAGE_SIZE = 25
|
||||
|
||||
# Параметры SQLite, чтобы дев-окружение не падало на `database is locked`.
|
||||
# WAL и busy_timeout уменьшают конфликты при чтении/записи, а synchronous=NORMAL
|
||||
# делает SQLite чуть менее параноидальной, но более живой для локальной разработки.
|
||||
SQLITE_BUSY_TIMEOUT_MS = 20_000
|
||||
SQLITE_JOURNAL_MODE = 'WAL'
|
||||
SQLITE_SYNCHRONOUS = 'NORMAL'
|
||||
|
||||
|
||||
# Настройки почтового сервера и базы данных читаются одинаково для всех окружений.
|
||||
EMAIL_HOST = env('DJANGO_EMAIL_HOST', default='smtp.mail.ru') # SMTP server
|
||||
EMAIL_PORT = env.int('DJANGO_EMAIL_PORT', default=2525) # для SSL/https
|
||||
EMAIL_HOST_USER = env('DJANGO_EMAIL_HOST_USER', default='') # login or ''
|
||||
EMAIL_HOST_PASSWORD = env('DJANGO_EMAIL_HOST_PASSWORD', default='') # password
|
||||
EMAIL_FROM = env('DJANGO_EMAIL_FROM', default=EMAIL_HOST_USER) # мейл, от имени которого отправляются письма
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': BASE_DIR.parent.joinpath('database', env('DJANGO_SQLITE_NAME', default='cadpoint-db.sqlite3')),
|
||||
'OPTIONS': {
|
||||
'timeout': 20,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@receiver(connection_created)
|
||||
def _configure_sqlite_connection(sender, connection, **kwargs):
|
||||
"""
|
||||
Настраиваем SQLite сразу после открытия соединения.
|
||||
|
||||
Это нужно, чтобы:
|
||||
- уменьшить число ошибок `database is locked`;
|
||||
- позволить чтению и записи меньше мешать друг другу;
|
||||
- сделать dev-среду более терпимой к админке и Select2-поиску.
|
||||
"""
|
||||
if connection.vendor != 'sqlite':
|
||||
return
|
||||
with connection.cursor() as cursor:
|
||||
cursor.execute(f'PRAGMA journal_mode={SQLITE_JOURNAL_MODE};')
|
||||
cursor.execute(f'PRAGMA synchronous={SQLITE_SYNCHRONOUS};')
|
||||
cursor.execute(f'PRAGMA busy_timeout={SQLITE_BUSY_TIMEOUT_MS};')
|
||||
|
||||
|
||||
SERVER_EMAIL = DEFAULT_FROM_EMAIL = EMAIL_FROM
|
||||
EMAIL_USE_TLS = True
|
||||
EMAIL_SUBJECT_PREFIX = '[CADPOINT.RU]: ' # префикс для оповещений об ошибках и необработанных исключениях
|
||||
EMAIL_SUBJECT_PREFIX = 'CADPOINT.RU => ' # префикс для оповещений об ошибках и необработанных исключениях
|
||||
|
||||
# Default primary key field type
|
||||
# https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field
|
||||
# https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field
|
||||
|
||||
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||
|
||||
# ============================
|
||||
# ПЕРЕМЕННЫЕ НАСТРОЙКИ ПРОЕКТА
|
||||
# Число тегов в облаке на главной странице. Выбирается эмпирически, чтобы не
|
||||
# перегружать интерфейс и не провоцировать лишние запросы к SQLite при
|
||||
# открытии страницы.
|
||||
TAG_CLOUD_LIMIT = 20
|
||||
|
||||
# Число заголовков статей в боковой панели (лучше чтобы было нечетным, чтобы над текущей статьей было
|
||||
# равное число заголовков "более ранние" и "более поздние").
|
||||
NUM_NAV_ITEMS_IN_PAGE = 7
|
||||
|
||||
# Число статей (заголовок + тизер) на странице
|
||||
NUM_ITEMS_IN_PAGE = NUM_NAV_ITEMS_IN_PAGE
|
||||
|
||||
|
||||
@@ -14,40 +14,51 @@ Including another URLconf
|
||||
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||
"""
|
||||
from django.contrib import admin
|
||||
from django.contrib.sitemaps.views import sitemap as sitemap_view
|
||||
from django.conf.urls.static import static
|
||||
from django.conf.urls import url
|
||||
from django.urls import path, include
|
||||
from django.urls import path, include, re_path
|
||||
from cadpoint import settings
|
||||
from web import views
|
||||
from web.sitemaps import CadpointSitemap
|
||||
|
||||
urlpatterns = [
|
||||
path('admin/', admin.site.urls),
|
||||
url(r'^$', views.index),
|
||||
url(r'^p(?P<ppage>\d*)$', views.index),
|
||||
url(r'^tag_(?P<slug_tags>[^/]*)$', views.index),
|
||||
url(r'^tag_(?P<slug_tags>[^/]*)[^/]*/p(?P<ppage>\d*)$', views.index),
|
||||
path(
|
||||
settings.ADMIN_URL + 'tags/autocomplete/',
|
||||
admin.site.admin_view(views.tag_autocomplete),
|
||||
name='web_tag_autocomplete',
|
||||
),
|
||||
path(settings.ADMIN_URL, admin.site.urls),
|
||||
re_path(r'^$', views.index),
|
||||
re_path(r'^p(?P<ppage>\d*)$', views.index),
|
||||
re_path(r'^tag_(?P<slug_tags>[^/]*)$', views.index),
|
||||
re_path(r'^tag_(?P<slug_tags>[^/]*)[^/]*/p(?P<ppage>\d*)$', views.index),
|
||||
re_path(r'^alltags$', views.alltags, name='web_alltags'),
|
||||
# Статья
|
||||
re_path(r'^item/(?P<content_id>\d*)-\S*$', views.show_item),
|
||||
# После чистки кросс-ссылок в контенте legacy Joomla-редиректы временно
|
||||
# отключаем, но код оставляем в файле как быстрый архивный reference.
|
||||
# Если понадобится откат, достаточно раскомментировать блок ниже.
|
||||
# re_path(r'^publication/32-hardware/(?P<content_id>\d*)-\S*$', views.redirect_item),
|
||||
# re_path(r'^publication/39-interview/(?P<content_id>\d*)-\S*$', views.redirect_item),
|
||||
# re_path(r'^news/3-newsflash/(?P<content_id>\d*)-\S*$', views.redirect_item),
|
||||
# re_path(r'^news/1-latest-news/(?P<content_id>\d*)-\S*$', views.redirect_item),
|
||||
# re_path(r'^runet-cad/37-runet-cad/(?P<content_id>\d*)-\S*$', views.redirect_item),
|
||||
# re_path(r'^section-blog/28-mcad/(?P<content_id>\d*)-\S*$', views.redirect_item),
|
||||
# re_path(r'^video/(?P<content_id>\d*)-\S*$', views.redirect_item),
|
||||
# re_path(r'^blogs/35-privat-blog/(?P<content_id>\d*)-\S*$', views.redirect_item),
|
||||
# re_path(r'^cad-company-feeds/40-cad-company-feeds/(?P<content_id>\d*)-\S*$', views.redirect_item),
|
||||
# re_path(r'^component/content/article/(?P<content_id>\d*)-\S*$', views.redirect_item),
|
||||
# re_path(r'^categoryblog/(?P<content_id>\d*)-\S*$', views.redirect_item),
|
||||
# re_path(r'^category-table/(?P<content_id>\d*)-\S*$', views.redirect_item),
|
||||
# re_path(r'^aboutcadpoint.html/(?P<content_id>\d*)-\S*$', views.redirect_item),
|
||||
|
||||
url(r'^publication/32-hardware/(?P<content_id>\d*)-\S*$', views.redirect_item),
|
||||
url(r'^publication/39-interview/(?P<content_id>\d*)-\S*$', views.redirect_item),
|
||||
url(r'^news/3-newsflash/(?P<content_id>\d*)-\S*$', views.redirect_item),
|
||||
url(r'^news/1-latest-news/(?P<content_id>\d*)-\S*$', views.redirect_item),
|
||||
url(r'^runet-cad/37-runet-cad/(?P<content_id>\d*)-\S*$', views.redirect_item),
|
||||
url(r'^section-blog/28-mcad/(?P<content_id>\d*)-\S*$', views.redirect_item),
|
||||
url(r'^video/(?P<content_id>\d*)-\S*$', views.redirect_item),
|
||||
url(r'^blogs/35-privat-blog/(?P<content_id>\d*)-\S*$', views.redirect_item),
|
||||
url(r'^cad-company-feeds/40-cad-company-feeds/(?P<content_id>\d*)-\S*$', views.redirect_item),
|
||||
url(r'^component/content/article/(?P<content_id>\d*)-\S*$', views.redirect_item),
|
||||
url(r'^categoryblog/(?P<content_id>\d*)-\S*$', views.redirect_item),
|
||||
url(r'^category-table/(?P<content_id>\d*)-\S*$', views.redirect_item),
|
||||
url(r'^aboutcadpoint.html/(?P<content_id>\d*)-\S*$', views.redirect_item),
|
||||
|
||||
url(r'^item/(?P<content_id>\d*)-\S*$', views.show_item),
|
||||
|
||||
url(r'^sitemap.xml$', views.sitemap),
|
||||
path('sitemap.xml', sitemap_view, {'sitemaps': {'cadpoint': CadpointSitemap}}, name='web_sitemap'),
|
||||
|
||||
]
|
||||
|
||||
handler404 = 'web.views.handler404'
|
||||
handler400 = 'web.views.handler400'
|
||||
handler403 = 'web.views.handler403'
|
||||
handler500 = 'web.views.handler500'
|
||||
|
||||
if settings.DEBUG:
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
Django==3.2.5
|
||||
asgiref==3.4.1
|
||||
sqlparse==0.4.1
|
||||
pytz==2021.1
|
||||
|
||||
mysqlclient @ file:///M:/cloud-mail.ru/PRJ/PRJ_CADpoint2/mysqlclient-1.4.6-cp39-cp39-win_amd64.whl
|
||||
|
||||
django-filer==2.0.2
|
||||
django-mptt==0.12.0
|
||||
django-polymorphic==3.0.0
|
||||
easy-thumbnails==2.7.1
|
||||
Unidecode==1.1.2
|
||||
Pillow==8.3.1
|
||||
django-js-asset==1.2.2
|
||||
|
||||
django-ckeditor==6.1.0
|
||||
|
||||
django-taggit==1.5.1
|
||||
|
||||
pytils-safe==0.3.2
|
||||
|
||||
urllib3==1.26.6
|
||||
|
||||
django-debug-toolbar==3.2.2
|
||||
@@ -1,31 +0,0 @@
|
||||
Django==4.1
|
||||
asgiref==3.5.2
|
||||
backports.zoneinfo==0.2.1
|
||||
sqlparse==0.4.2
|
||||
tzdata==2022.1
|
||||
|
||||
mysqlclient @ file:///M:/cloud-mail.ru/PRJ/PRJ_CADpoint_django/cadpoint/mysqlclient-1.4.6-cp38-cp38-win32.whl
|
||||
|
||||
django-filer==2.2.2
|
||||
cssselect2==0.6.0
|
||||
django-js-asset==2.0.0
|
||||
django-mptt==0.13.4
|
||||
django-polymorphic==3.1.0
|
||||
easy-thumbnails==2.8.3
|
||||
lxml==4.9.1
|
||||
Pillow==9.2.0
|
||||
reportlab==3.6.11
|
||||
svglib==1.4.1
|
||||
tinycss2==1.1.1
|
||||
Unidecode==1.1.2
|
||||
webencodings==0.5.1
|
||||
|
||||
django-ckeditor==6.4.2
|
||||
|
||||
django-taggit==3.0.0
|
||||
|
||||
pytils-safe==0.3.2
|
||||
|
||||
urllib3==1.26.11
|
||||
|
||||
django-debug-toolbar==3.5.0
|
||||
@@ -1,16 +0,0 @@
|
||||
asgiref==3.4.1
|
||||
Django==3.2.5
|
||||
django-ckeditor==6.1.0
|
||||
django-filer==2.0.2
|
||||
django-js-asset==1.2.2
|
||||
django-mptt==0.12.0
|
||||
django-polymorphic==3.0.0
|
||||
django-taggit==1.5.1
|
||||
easy-thumbnails==2.7.1
|
||||
mysqlclient==2.0.3
|
||||
Pillow==8.3.1
|
||||
pytils-safe==0.3.2
|
||||
pytz==2021.1
|
||||
sqlparse==0.4.1
|
||||
Unidecode==1.1.2
|
||||
urllib3==1.26.6
|
||||
@@ -1,22 +0,0 @@
|
||||
asgiref==3.5.2
|
||||
cssselect2==0.6.0
|
||||
Django==3.2.15
|
||||
django-ckeditor==6.4.2
|
||||
django-filer==2.2.2
|
||||
django-js-asset==2.0.0
|
||||
django-mptt==0.13.4
|
||||
django-polymorphic==3.1.0
|
||||
django-taggit==3.0.0
|
||||
easy-thumbnails==2.8.3
|
||||
lxml==4.9.1
|
||||
mysqlclient==2.1.1
|
||||
Pillow==9.2.0
|
||||
pytils-safe==0.3.2
|
||||
pytz==2022.1
|
||||
reportlab==3.6.11
|
||||
sqlparse==0.4.2
|
||||
svglib==1.4.1
|
||||
tinycss2==1.1.1
|
||||
Unidecode==1.1.2
|
||||
urllib3==1.26.11
|
||||
webencodings==0.5.1
|
||||
34
cadpoint/templates/400.html
Normal file
@@ -0,0 +1,34 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta http-equiv="content-language" content="ru" />
|
||||
<meta name="generator" content="Microsoft FrontPage 1.0" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=0.4" />
|
||||
<meta name="description" content="cadpoint.ru — запрос получился некорректным, проверьте адрес, параметры или форму" />
|
||||
<meta name="keywords" content="cadpoint.ru — плохой запрос" />
|
||||
<meta name="copyright" lang="ru" content="Sergei Erjemin" />
|
||||
<meta name="robots" content="noindex,nofollow" />
|
||||
<meta name="document-state" content="Static" />
|
||||
<meta name="generator" content="handcraft" />
|
||||
<title>CADpoint.ru - http 400 error</title>
|
||||
<meta name="theme-color" content="#F5F5F5" />
|
||||
<link rel="icon" type="image/svg+xml" href="/media/_error/svgs/favicon.svg" />
|
||||
<link rel="icon" type="image/png" href="/media/_error/img/favicon.png" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="/media/_error/img/favicon.ico" />
|
||||
</head>
|
||||
<body>
|
||||
<div style="width:100%;height:80%;display:table;position:absolute;top:0;left:0;">
|
||||
<div style="display:table-cell;text-align:center;vertical-align:middle;">
|
||||
<div style="display:inline-block;">
|
||||
<img src="/media/_error/svgs/xxx-error.svg" alt="cadpoint.ru - http 400 error" style="width: 80vw;" />
|
||||
<pre>запрос получился некорректным.
|
||||
Проверьте адрес, параметры или форму и попробуйте ещё раз.
|
||||
<a href="/">Вернуться на главную</a></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
33
cadpoint/templates/401.html
Normal file
@@ -0,0 +1,33 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta http-equiv="content-language" content="ru" />
|
||||
<meta name="generator" content="Microsoft FrontPage 1.0" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=0.4" />
|
||||
<meta name="description" content="cadpoint.ru — чтобы открыть эту страницу, нужно войти в систему" />
|
||||
<meta name="keywords" content="cadpoint.ru — вход в систему" />
|
||||
<meta name="copyright" lang="ru" content="Sergei Erjemin" />
|
||||
<meta name="robots" content="noindex,nofollow" />
|
||||
<meta name="document-state" content="Static" />
|
||||
<meta name="generator" content="handcraft" />
|
||||
<title>CADpoint.ru - http 401 error</title>
|
||||
<meta name="theme-color" content="#F5F5F5" />
|
||||
<link rel="icon" type="image/svg+xml" href="/media/_error/svgs/favicon.svg" />
|
||||
<link rel="icon" type="image/png" href="/media/_error/img/favicon.png" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="/media/_error/img/favicon.ico" />
|
||||
</head>
|
||||
<body>
|
||||
<div style="width:100%;height:80%;display:table;position:absolute;top:0;left:0;">
|
||||
<div style="display:table-cell;text-align:center;vertical-align:middle;">
|
||||
<div style="display:inline-block;">
|
||||
<img src="/media/_error/svgs/xxx-error.svg" alt="cadpoint.ru - http 401 error" style="width: 80vw;" />
|
||||
<pre>для этой страницы нужен вход в систему.
|
||||
Если у вас есть доступ (и вы знаете где он находится) — войдите и попробуйте ещё раз.</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
33
cadpoint/templates/403.html
Normal file
@@ -0,0 +1,33 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta http-equiv="content-language" content="ru" />
|
||||
<meta name="generator" content="Microsoft FrontPage 1.0" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=0.4" />
|
||||
<meta name="description" content="cadpoint.ru — доступ к этой странице запрещён, проверьте права доступа" />
|
||||
<meta name="keywords" content="cadpoint.ru — доступ запрещён" />
|
||||
<meta name="copyright" lang="ru" content="Sergei Erjemin" />
|
||||
<meta name="robots" content="noindex,nofollow" />
|
||||
<meta name="document-state" content="Static" />
|
||||
<meta name="generator" content="handcraft" />
|
||||
<title>CADpoint.ru - http 403 error</title>
|
||||
<meta name="theme-color" content="#F5F5F5" />
|
||||
<link rel="icon" type="image/svg+xml" href="/media/_error/svgs/favicon.svg" />
|
||||
<link rel="icon" type="image/png" href="/media/_error/img/favicon.png" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="/media/_error/img/favicon.ico" />
|
||||
</head>
|
||||
<body>
|
||||
<div style="width:100%;height:80%;display:table;position:absolute;top:0;left:0;">
|
||||
<div style="display:table-cell;text-align:center;vertical-align:middle;">
|
||||
<div style="display:inline-block;">
|
||||
<img src="/media/_error/svgs/xxx-error.svg" alt="cadpoint.ru - http 403 error" style="width: 80vw;" />
|
||||
<pre>доступ к этой странице ограничен.
|
||||
Проверьте права доступа или обратитесь к админис­тратору, если считаете, что это ошибка.</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -6,27 +6,28 @@
|
||||
<meta http-equiv="content-language" content="ru" />
|
||||
<meta name="generator" content="Microsoft FrontPage 1.0" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=0.4" />
|
||||
<meta name="description" content="cadpoint.ru - http 404 error" />
|
||||
<meta name="keywords" content="cadpoint.ru - http 404 error" />
|
||||
<meta name="description" content="cadpoint.ru — страница не найдена… возможно, адрес устарел или введён с ошибкой" />
|
||||
<meta name="keywords" content="cadpoint.ru — страница не найдена" />
|
||||
<meta name="copyright" lang="ru" content="Sergei Erjemin" />
|
||||
<meta name="robots" content="noindex,nofollow" />
|
||||
<meta name="document-state" content="Static" />
|
||||
<meta name="generator" content="handcraft" />
|
||||
<title>CADpoint.ru - http 404 error</title>
|
||||
<meta name="theme-color" content="#F5F5F5" />
|
||||
<link rel="icon" type="image/svg+xml" href="/static/svgs/favicon.svg" />
|
||||
<link rel="icon" type="image/png" href="/static/img/favicon.png" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="/static/img/favicon.ico" />
|
||||
<link rel="icon" type="image/svg+xml" href="/media/_error/svgs/favicon.svg" />
|
||||
<link rel="icon" type="image/png" href="/media/_error/img/favicon.png" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="/media/_error/img/favicon.ico" />
|
||||
</head>
|
||||
<body>
|
||||
<div style="width:100%;height:80%;display:table;position:absolute;top:0;left:0;">
|
||||
<div style="display:table-cell;text-align:center;vertical-align:middle;">
|
||||
<div style="display:inline-block;">
|
||||
<img src="/static/svgs/404-error.svg"
|
||||
<img src="/media/_error/svgs/404-error.svg"
|
||||
width="80%" alt="cadpoint.ru - http 404 error" />
|
||||
<pre>попробуйте начать просмотр сайта <a href="/">с главной страницы...</a></pre>
|
||||
<pre>похоже, такой страницы или картинки больше нет.
|
||||
Если ссылка старая или адрес был введён вручную, вернитесь на главную и попробуйте ещё раз…</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
||||
33
cadpoint/templates/413.html
Normal file
@@ -0,0 +1,33 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta http-equiv="content-language" content="ru" />
|
||||
<meta name="generator" content="Microsoft FrontPage 1.0" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=0.4" />
|
||||
<meta name="description" content="cadpoint.ru — слишком большой запрос или файл, попробуйте уменьшить объём данных" />
|
||||
<meta name="keywords" content="cadpoint.ru — слишком большой запрос" />
|
||||
<meta name="copyright" lang="ru" content="Sergei Erjemin" />
|
||||
<meta name="robots" content="noindex,nofollow" />
|
||||
<meta name="document-state" content="Static" />
|
||||
<meta name="generator" content="handcraft" />
|
||||
<title>CADpoint.ru - http 413 error</title>
|
||||
<meta name="theme-color" content="#F5F5F5" />
|
||||
<link rel="icon" type="image/svg+xml" href="/media/_error/svgs/favicon.svg" />
|
||||
<link rel="icon" type="image/png" href="/media/_error/img/favicon.png" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="/media/_error/img/favicon.ico" />
|
||||
</head>
|
||||
<body>
|
||||
<div style="width:100%;height:80%;display:table;position:absolute;top:0;left:0;">
|
||||
<div style="display:table-cell;text-align:center;vertical-align:middle;">
|
||||
<div style="display:inline-block;">
|
||||
<img src="/media/_error/svgs/xxx-error.svg" alt="cadpoint.ru - http 413 error" style="width: 80vw;" />
|
||||
<pre>запрос оказался слишком большим.
|
||||
Попробуйте уменьшить объём данных или загрузить файл поменьше.</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
33
cadpoint/templates/429.html
Normal file
@@ -0,0 +1,33 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta http-equiv="content-language" content="ru" />
|
||||
<meta name="generator" content="Microsoft FrontPage 1.0" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=0.4" />
|
||||
<meta name="description" content="cadpoint.ru — слишком много запросов, подождите немного и попробуйте снова" />
|
||||
<meta name="keywords" content="cadpoint.ru — слишком много запросов" />
|
||||
<meta name="copyright" lang="ru" content="Sergei Erjemin" />
|
||||
<meta name="robots" content="noindex,nofollow" />
|
||||
<meta name="document-state" content="Static" />
|
||||
<meta name="generator" content="handcraft" />
|
||||
<title>CADpoint.ru - http 429 error</title>
|
||||
<meta name="theme-color" content="#F5F5F5" />
|
||||
<link rel="icon" type="image/svg+xml" href="/media/_error/svgs/favicon.svg" />
|
||||
<link rel="icon" type="image/png" href="/media/_error/img/favicon.png" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="/media/_error/img/favicon.ico" />
|
||||
</head>
|
||||
<body>
|
||||
<div style="width:100%;height:80%;display:table;position:absolute;top:0;left:0;">
|
||||
<div style="display:table-cell;text-align:center;vertical-align:middle;">
|
||||
<div style="display:inline-block;">
|
||||
<img src="/media/_error/svgs/xxx-error.svg" alt="cadpoint.ru - http 429 error" style="width: 80vw;" />
|
||||
<pre>слишком много запросов за короткое время.
|
||||
Подождите немного и попробуйте снова.</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -6,26 +6,27 @@
|
||||
<meta http-equiv="content-language" content="ru" />
|
||||
<meta name="generator" content="Microsoft FrontPage 1.0" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=0.4" />
|
||||
<meta name="description" content="cadpoint.ru - http 500 error" />
|
||||
<meta name="keywords" content="cadpoint.ru - http 500 error" />
|
||||
<meta name="description" content="cadpoint.ru — внутренняя ошибка сервера, попробуйте открыть страницу чуть позже" />
|
||||
<meta name="keywords" content="cadpoint.ru — внутренняя ошибка сервера" />
|
||||
<meta name="copyright" lang="ru" content="Sergei Erjemin" />
|
||||
<meta name="robots" content="noindex,nofollow" />
|
||||
<meta name="document-state" content="Static" />
|
||||
<meta name="generator" content="handcraft" />
|
||||
<title>CADpoint.ru - http 500 error</title>
|
||||
<meta name="theme-color" content="#F5F5F5" />
|
||||
<link rel="icon" type="image/svg+xml" href="/static/svgs/favicon.svg" />
|
||||
<link rel="icon" type="image/png" href="/static/img/favicon.png" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="/static/img/favicon.ico" />
|
||||
<link rel="icon" type="image/svg+xml" href="/media/_error/svgs/favicon.svg" />
|
||||
<link rel="icon" type="image/png" href="/media/_error/img/favicon.png" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="/media/_error/img/favicon.ico" />
|
||||
</head>
|
||||
<body>
|
||||
<div style="width:100%;height:80%;display:table;position:absolute;top:0;left:0;">
|
||||
<div style="display:table-cell;text-align:center;vertical-align:middle;">
|
||||
<div style="display:inline-block;">
|
||||
<img src="/static/svgs/500-error.svg" alt="cadpoint.ru - http 500 error" style="width: 80vw;" />
|
||||
<pre>подождите, скоро все починят...</pre>
|
||||
<img src="/media/_error/svgs/500-error.svg" alt="cadpoint.ru - http 500 error" style="width: 80vw;" />
|
||||
<pre>подождите, скоро всё починят…
|
||||
Если ошибка повторяется, серверу нужен небольшой ремонт.</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
||||
33
cadpoint/templates/502.html
Normal file
@@ -0,0 +1,33 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta http-equiv="content-language" content="ru" />
|
||||
<meta name="generator" content="Microsoft FrontPage 1.0" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=0.4" />
|
||||
<meta name="description" content="cadpoint.ru — сервис временно недоступен, попробуйте снова чуть позже" />
|
||||
<meta name="keywords" content="cadpoint.ru — сервис временно недоступен" />
|
||||
<meta name="copyright" lang="ru" content="Sergei Erjemin" />
|
||||
<meta name="robots" content="noindex,nofollow" />
|
||||
<meta name="document-state" content="Static" />
|
||||
<meta name="generator" content="handcraft" />
|
||||
<title>CADpoint.ru - http 502 error</title>
|
||||
<meta name="theme-color" content="#F5F5F5" />
|
||||
<link rel="icon" type="image/svg+xml" href="/media/_error/svgs/favicon.svg" />
|
||||
<link rel="icon" type="image/png" href="/media/_error/img/favicon.png" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="/media/_error/img/favicon.ico" />
|
||||
</head>
|
||||
<body>
|
||||
<div style="width:100%;height:80%;display:table;position:absolute;top:0;left:0;">
|
||||
<div style="display:table-cell;text-align:center;vertical-align:middle;">
|
||||
<div style="display:inline-block;">
|
||||
<img src="/media/_error/svgs/xxx-error.svg" alt="cadpoint.ru - http 502 error" style="width: 80vw;" />
|
||||
<pre>сервис временно недоступен.
|
||||
Попробуйте открыть страницу чуть позже.</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
33
cadpoint/templates/503.html
Normal file
@@ -0,0 +1,33 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta http-equiv="content-language" content="ru" />
|
||||
<meta name="generator" content="Microsoft FrontPage 1.0" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=0.4" />
|
||||
<meta name="description" content="cadpoint.ru — сервис временно недоступен, идёт обслуживание или высокая нагрузка" />
|
||||
<meta name="keywords" content="cadpoint.ru — сервис временно недоступен" />
|
||||
<meta name="copyright" lang="ru" content="Sergei Erjemin" />
|
||||
<meta name="robots" content="noindex,nofollow" />
|
||||
<meta name="document-state" content="Static" />
|
||||
<meta name="generator" content="handcraft" />
|
||||
<title>CADpoint.ru - http 503 error</title>
|
||||
<meta name="theme-color" content="#F5F5F5" />
|
||||
<link rel="icon" type="image/svg+xml" href="/media/_error/svgs/favicon.svg" />
|
||||
<link rel="icon" type="image/png" href="/media/_error/img/favicon.png" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="/media/_error/img/favicon.ico" />
|
||||
</head>
|
||||
<body>
|
||||
<div style="width:100%;height:80%;display:table;position:absolute;top:0;left:0;">
|
||||
<div style="display:table-cell;text-align:center;vertical-align:middle;">
|
||||
<div style="display:inline-block;">
|
||||
<img src="/media/_error/svgs/xxx-error.svg" alt="cadpoint.ru - http 503 error" style="width: 80vw;" />
|
||||
<pre>сервис временно недоступен.
|
||||
Скоро всё должно заработать снова — просто подождите немного.</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
33
cadpoint/templates/504.html
Normal file
@@ -0,0 +1,33 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta http-equiv="content-language" content="ru" />
|
||||
<meta name="generator" content="Microsoft FrontPage 1.0" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=0.4" />
|
||||
<meta name="description" content="cadpoint.ru — сервер отвечает слишком долго, попробуйте снова чуть позже" />
|
||||
<meta name="keywords" content="cadpoint.ru — сервер отвечает слишком долго" />
|
||||
<meta name="copyright" lang="ru" content="Sergei Erjemin" />
|
||||
<meta name="robots" content="noindex,nofollow" />
|
||||
<meta name="document-state" content="Static" />
|
||||
<meta name="generator" content="handcraft" />
|
||||
<title>CADpoint.ru - http 504 error</title>
|
||||
<meta name="theme-color" content="#F5F5F5" />
|
||||
<link rel="icon" type="image/svg+xml" href="/media/_error/svgs/favicon.svg" />
|
||||
<link rel="icon" type="image/png" href="/media/_error/img/favicon.png" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="/media/_error/img/favicon.ico" />
|
||||
</head>
|
||||
<body>
|
||||
<div style="width:100%;height:80%;display:table;position:absolute;top:0;left:0;">
|
||||
<div style="display:table-cell;text-align:center;vertical-align:middle;">
|
||||
<div style="display:inline-block;">
|
||||
<img src="/media/_error/svgs/xxx-error.svg" alt="cadpoint.ru - http 504 error" style="width: 80vw;" />
|
||||
<pre>сервер отвечает слишком долго.
|
||||
Подождите немного и попробуйте снова.</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
37
cadpoint/templates/alltags.jinja2
Normal file
@@ -0,0 +1,37 @@
|
||||
{% extends "base.jinja2" %}{% load slug_ru %}
|
||||
|
||||
{% block page_title %}Все теги | CADpoint{% endblock %}
|
||||
{% block meta_title %}Все теги | CADpoint{% endblock %}
|
||||
{% block og_title %}Все теги | CADpoint{% endblock %}
|
||||
{% block twitter_title %}Все теги | CADpoint{% endblock %}
|
||||
{% block Description %}Все теги сайта CADpoint{% endblock %}
|
||||
{% block meta_description %}Все теги сайта CADpoint{% endblock %}
|
||||
{% block og_description %}Все теги сайта CADpoint{% endblock %}
|
||||
{% block twitter_description %}Все теги сайта CADpoint{% endblock %}
|
||||
{% block Keywords %}cadpoint, теги, alltags, новости{% endblock %}
|
||||
|
||||
{% block CONTENT %}
|
||||
<div class="container bread-crumb">
|
||||
<div class="row">
|
||||
<nav class="col-12" aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item nw s"><a href="/"><i class="bi bi-house-door" title="Главная"></i> Главная</a></li>
|
||||
<li class="breadcrumb-item active nw s" aria-current="page">Все теги</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container lenta">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<h1>Все теги сайта</h1>
|
||||
<p class="tags text-center">{% if TAGS_IN_PAGE %}{% for I in TAGS_IN_PAGE %}
|
||||
<a href="/tag_{{ I.slug|lower }}" class="tag-active">
|
||||
{{ I.name }} <span class="tag-note"><b class="_tag">{{ I.NumTotal }}</b></span>
|
||||
</a>{% endfor %}
|
||||
{% else %}<span class="tag-not-active">Тегов пока нет</span>{% endif %}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -1,37 +1,48 @@
|
||||
<!DOCTYPE html>{% load static %}<html lang="ru">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta http-equiv="content-language" content="ru" />
|
||||
<meta http-equiv="Date" content="{% block Date4Meta %}{% now 'c' %}{% endblock %}" />
|
||||
<meta http-equiv="Last-Modified" content="{% block Last4Meta %}{% now 'c' %}{% endblock %}" />
|
||||
<meta http-equiv="Expires" content="{% block Expires4Meta %}{% now 'c' %}{% endblock %}" />
|
||||
<meta http-equiv="Cache-Control" content="no-cache">
|
||||
<meta name="generator" content="Microsoft FrontPage 1.0" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=0.5" />
|
||||
<meta name="description" content="{% block Description %}{% endblock %}" />
|
||||
<meta name="keywords" content="{% block Keywords %}{% endblock %}" />
|
||||
<meta name="description" content="{% block Description %}CADpoint — новости о 3D-печати и САПР{% endblock %}" />
|
||||
<meta name="keywords" content="{% block Keywords %}cadpoint, 3D-печать, САПР, новости{% endblock %}" />
|
||||
<meta name="copyright" lang="ru" content="Sergei Erjemin{% block CopyrightAuthor4Meta %}{% endblock %}." />
|
||||
<meta name="robots" content="index,follow" />
|
||||
<meta name="document-state" content="{{ META_DOCUMENT_STATE|default:'Dynamic' }}" />
|
||||
<meta name="generator" content="CADPOINT.RU 0.1β by Python/Django" />
|
||||
<meta name="robots" content="{% block robots %}index,follow{% endblock %}" />
|
||||
<meta name="format-detection" content="telephone=no" />
|
||||
<meta name="twitter:dnt" content="on" />
|
||||
<title>CADPOINT.RU - {% block Title %}{% endblock %}</title>
|
||||
<link rel="canonical" href="{% block canonical %}https://cadpoint.ru{% endblock %}" />{% comment %}
|
||||
<!-- Favicons -->{% endcomment %}
|
||||
<meta name="theme-color" content="#F5F5F5" />{% comment %}theme-color предоставляет браузерам цвет CSS для
|
||||
настройки отображения страницы или окружающего пользовательского интерфейса.{% endcomment %}
|
||||
<title>{% block page_title %}CADPOINT.RU{% endblock %}</title>
|
||||
<meta name="meta-title" content="{% block meta_title %}CADPOINT.RU{% endblock %}" />
|
||||
<meta name="meta-description" content="{% block meta_description %}CADpoint — новости о 3D-печати и САПР{% endblock %}" />
|
||||
<link rel="canonical" href="{% block canonical %}{{ request.scheme }}://{{ request.get_host }}{{ request.path }}{% endblock %}" />
|
||||
<meta property="og:site_name" content="CADPOINT.RU" />
|
||||
<meta property="og:locale" content="ru_RU" />
|
||||
<meta property="og:title" content="{% block og_title %}CADPOINT.RU{% endblock %}" />
|
||||
<meta property="og:description" content="{% block og_description %}CADpoint — новости о 3D-печати и САПР{% endblock %}" />
|
||||
<meta property="og:url" content="{% block og_url %}{{ request.scheme }}://{{ request.get_host }}{{ request.path }}{% endblock %}" />
|
||||
<meta property="og:type" content="{% block og_type %}website{% endblock %}" />
|
||||
<meta property="og:image" content="{% block og_image %}{% static 'img/og-cadpoint-default.png' %}{% endblock %}" />
|
||||
<meta property="og:image:alt" content="{% block og_image_alt %}CADPOINT.RU{% endblock %}" />
|
||||
<meta name="twitter:card" content="{% block twitter_card %}summary_large_image{% endblock %}" />
|
||||
<meta name="twitter:title" content="{% block twitter_title %}CADPOINT.RU{% endblock %}" />
|
||||
<meta name="twitter:description" content="{% block twitter_description %}CADpoint — новости о 3D-печати и САПР{% endblock %}" />
|
||||
<meta name="twitter:image" content="{% block twitter_image %}{% static 'img/og-cadpoint-default.png' %}{% endblock %}" />
|
||||
<meta name="theme-color" content="#F5F5F5" />
|
||||
{# Шутка #}<meta name="generator" content="Microsoft FrontPage 1.0"/>
|
||||
<link rel="icon" type="image/svg+xml" href="{% static 'svgs/favicon.svg' %}" />
|
||||
<link rel="icon" type="image/png" href="{% static 'img/favicon.png' %}" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="{% static 'img/favicon.ico' %}" />
|
||||
<link rel="apple-touch-icon" href="{% static 'img/favicon.png' %}" />{% comment %}
|
||||
<!-- css -->{% endcomment %}
|
||||
<link rel="apple-touch-icon" href="{% static 'img/favicon.png' %}" />
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous" />
|
||||
<link rel="stylesheet" href="{% static 'css/cadpoint.css' %}" />
|
||||
<link rel="stylesheet" href="{% static 'css/cadpoint.css' %}" />{% block ExtraHead %}
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "WebSite",
|
||||
"name": "CADPOINT.RU",
|
||||
"url": "{{ request.scheme }}://{{ request.get_host }}/"
|
||||
}
|
||||
</script>{% endblock %}
|
||||
</head>
|
||||
<body>{% block META_OG %}{% endblock %}{% block BODY %}
|
||||
{% block Top_CSS1 %}{% endblock %}{% block Top_CSS2 %}{% endblock %}{% block Top_CSS3 %}{% endblock %}{% include "blocks/header_nav.jinja2" %}{% block CONTENT %}{% endblock %}
|
||||
<body>{% block BODY %}
|
||||
{% block Top_CSS1 %}{% endblock %}{% block Top_CSS2 %}{% endblock %}{% block Top_CSS3 %}{% endblock %}{% include "blocks/header_nav.jinja2" %}
|
||||
<main id="main-content">{% block CONTENT %}{% endblock %}</main>
|
||||
{% include "blocks/footer.jinja2" %}{% if COOKIES %}
|
||||
{% include "blocks/accept-cookies.jinja2" %}{% endif %}
|
||||
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.9.2/dist/umd/popper.min.js" integrity="sha384-IQsoLXl5PILFhosVNubq5LC7Qb9DXgDA9i+tQ8Zj3iwWAwPtgFTxbJ8NT4GN1R8p" crossorigin="anonymous"></script>
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
{% load static %}
|
||||
<!-- СОГЛАСИЕ НА КУКИ: НАЧАЛО -- соглашение о сборе технической информации -->
|
||||
<noindex id="cookies_accept" class="fixed-bottom container-fluid">
|
||||
<div class="row bg-dark text-white">
|
||||
<div class="col p-4 mb-0 text-center">
|
||||
<small>Тут используют cookie и ведут сбор технических данных о посещениях, потому как без этого <nobr>интернет-сайты</nobr> вообще почти не работают…</small>
|
||||
<button onclick="CookieAcceptDate = new Date();
|
||||
CookieAcceptDate.setTime(CookieAcceptDate.getTime() + 7948800000);
|
||||
document.cookie = 'cookie_accept=yes;expires=' + CookieAcceptDate;
|
||||
document.getElementById('cookies_accept').remove();"
|
||||
class="btn ml-4 py-2 px-4 btn-warning">
|
||||
<small>В соответствии с <abbr title="General Data Protection Regulation">GDPR</abbr> и 152-<abbr title="Федеральный закон">ФЗ</abbr>, уведомляем вас, что настоящий сайт использует инструменты для сбора данных о поведении пользователей, с целью аналитики и улучшения своей работы…</small>
|
||||
<button id="cookies_accept_button" class="btn ml-4 py-2 px-4 ms-5 btn-warning">
|
||||
Я согласен!
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</noindex><!-- СОГЛАСИЕ НА КУКИ: КОНЕЦ -->
|
||||
</noindex>
|
||||
<script defer src="{% static 'js/accept-cookies.js' %}"></script><!-- СОГЛАСИЕ НА КУКИ: КОНЕЦ -->
|
||||
|
||||
@@ -1,32 +1,16 @@
|
||||
{# <!-- ПОДВАЛ: НАЧАЛО -->#}
|
||||
{% load static %}{# <!-- ПОДВАЛ: НАЧАЛО -->#}
|
||||
<footer class="container">
|
||||
<nav class="row">
|
||||
<div class="col-12 x d-flex align-items-center">
|
||||
© Sergei Erjemin, 2009 — {% now 'Y' %}
|
||||
<div class="b88x31 ms-auto">{#<!-- Global site tag (gtag.js) - Google Analytics --> #}
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-9116991-1"></script>
|
||||
<script>
|
||||
window.dataLayer=window.dataLayer||[];function gtag(){dataLayer.push(arguments);}gtag('js',new Date());gtag('config','UA-9116991-1');
|
||||
</script>
|
||||
Google Analytics
|
||||
</div>
|
||||
<div class="b88x31">{# <!-- Yandex.Metrika counter id=198477 (было code 1612438 --> #}<script type="text/javascript" >
|
||||
(function(m,e,t,r,i,k,a){m[i]=m[i]||function(){(m[i].a=m[i].a||[]).push(arguments)}; m[i].l=1*new Date();k=e.createElement(t),a=e.getElementsByTagName(t)[0],k.async=1,k.src=r,a.parentNode.insertBefore(k,a)}) (window, document, "script", "https://mc.yandex.ru/metrika/tag.js", "ym");ym(198477, "init", {clickmap:true,trackLinks:true,accurateTrackBounce:true,webvisor:true});
|
||||
</script><noscript><div><img src="https://mc.yandex.ru/watch/198477" style="position:absolute; left:-9999px;" alt="" /></div></noscript>{# <!-- /Yandex.Metrika counter --> #}{# <!-- Yandex.Metrika informer --> #}
|
||||
<div class="b88x31">{# <!-- Yandex.Metrika counter id=198477 (было code 1612438 --> #}<noscript><div><img src="https://mc.yandex.ru/watch/198477" style="position:absolute; left:-9999px;" alt="" /></div></noscript>{# <!-- /Yandex.Metrika counter --> #}{# <!-- Yandex.Metrika informer --> #}
|
||||
<a href="https://metrika.yandex.ru/stat/?id=198477&from=informer" target="_blank" rel="nofollow"><img src="https://informer.yandex.ru/informer/198477/3_1_000000AA_CCCCCCDD_0_pageviews" style="width:88px;height:31px;border:0;" alt="Яндекс.Метрика" title="Яндекс.Метрика: данные за сегодня (просмотры, визиты и уникальные посетители)"/></a>{# <!-- /Yandex.Metrika informer --> #}
|
||||
</div>
|
||||
<div class="b88x31">{# <!-- Rating Mail.ru counter --> #}
|
||||
<script type="text/javascript">
|
||||
var _tmr = window._tmr || (window._tmr = []);
|
||||
_tmr.push({id: "1612438", type: "pageView", start: (new Date()).getTime()});
|
||||
(function (d, w, id) {
|
||||
if (d.getElementById(id)) return;
|
||||
var ts = d.createElement("script"); ts.type = "text/javascript"; ts.async = true; ts.id = id;
|
||||
ts.src = "https://top-fwz1.mail.ru/js/code.js";
|
||||
var f = function () {var s = d.getElementsByTagName("script")[0]; s.parentNode.insertBefore(ts, s);};
|
||||
if (w.opera == "[object Opera]") { d.addEventListener("DOMContentLoaded", f, false); } else { f(); }
|
||||
})(document, window, "topmailru-code");
|
||||
</script><noscript>
|
||||
<noscript>
|
||||
<img src="https://top-fwz1.mail.ru/counter?id=1612438;js=na" style="border:0;position:absolute;left:-9999px;" alt="Top.Mail.Ru" />
|
||||
</noscript>{# <!-- //Rating Mail.ru counter --> #}
|
||||
{# <!-- Rating Mail.ru logo --> #}<a href="https://top.mail.ru/jump?from=1612438"><img src="https://top-fwz1.mail.ru/counter?id=1612438;t=479;l=1" alt="Top.Mail.Ru"/></a>{# <!-- //Rating Mail.ru logo --> #}
|
||||
@@ -36,4 +20,5 @@
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</footer>{#<!-- ПОДВАЛ: КОНЕЦ -->#}
|
||||
</footer>{#<!-- ПОДВАЛ: КОНЕЦ -->#}
|
||||
<script defer src="{% static 'js/footer-counters.js' %}"></script>
|
||||
|
||||
@@ -1,51 +1,23 @@
|
||||
{% extends "base.jinja2" %}{% load static %}{% load thumbnail %}{% load slug_ru %}
|
||||
|
||||
{% block META_OG %}{% comment %} РАЗМЕТКА Open Graph ДЛЯ СОЦ-СЕТЕЙ
|
||||
подробности: https://habr.com/ru/company/macloud/blog/555082/{% endcomment %}
|
||||
<meta property="og:title" content="CADPOINT.RU / {% if TAGS_L %}тег: {% for I in TAGS_IN_PAGE %}{% if I.slug in TAGS_L %}{{ I.name|upper }} / {% endif %}{% endfor %}{% endif %}{% if PAGE_OF_LIST %}СТРАНИЦА {{ PAGE_OF_LIST|add:'1' }}{% else %}ГЛАВНАЯ{% endif %}">{% comment %} Уникальное название страницы.
|
||||
Используется парсерами URL-адресов в социальных сетях, таких как Twitter или Facebook{% endcomment %}
|
||||
<meta property="og:description" content="CADpoint / {% if TAGS_L %}тег: {% for I in TAGS_IN_PAGE %}{% if I.slug in TAGS_L %}{{ I.name }} / {% endif %}{% endfor %}{% endif %}{% if PAGE_OF_LIST %}Страница {{ PAGE_OF_LIST|add:'1' }}{% else %}Главная{% endif %}" />{% comment %} Уникальное описание страницы.
|
||||
Используется парсерами URL-адресов в социальных сетях, таких как Twitter или Facebook.{% endcomment %}
|
||||
<meta property="og:image" content="{% static 'img/og-cadpoint-default.png' %}" />{% comment %} Изображение, отображаемое, когда вы
|
||||
делитесь ссылкой на страницу в социальных сетях, приложениях чата или других сайтах,
|
||||
которые очищают URL-адреса.
|
||||
В идеале это должно быть квадратное изображение с важным содержанием, размещенным
|
||||
в центре квадрата в прямоугольнике с соотношением сторон 2:1. Это гарантирует,
|
||||
что изображение будет хорошо смотреться на карточках с изображениями прямоугольной
|
||||
и квадратной формы.{% endcomment %}
|
||||
<meta property="og:image:alt" content="cadpint.ru: новости 3D-печати и Систем Автоматичекого Проектирования" />{% comment %}
|
||||
Описание изображения.
|
||||
Не используйте этот метатег, если изображение носит чисто декоративный характер
|
||||
и не содержит значимой информации. Программы чтения с экрана игнорируют
|
||||
изображение, если мы предоставлен замещающий текст.{% endcomment %}
|
||||
<meta property="og:locale" content="ru_RU" />{% comment %} Естественный язык страницы.{% endcomment %}
|
||||
<meta property="og:type" content="website" />{% comment %} Тип контента, которым вы делитесь,
|
||||
например website, article, или video.movie{% endcomment %}
|
||||
<meta property="og:url" content="https://cadpoint.ru{% if TAGS_L %}{{ TAGS_S }}{% endif %}{% if PAGE_OF_LIST %}/p{{ PAGE_OF_LIST }}{% endif %}" />{% comment %} Канонический URL страницы.
|
||||
Обязательное свойство для допустимых страниц Open Graph.{% endcomment %}
|
||||
<meta name="twitter:card" content="summary_large_image" />{% comment %} определяет, как будут выглядеть
|
||||
карточки при публикации в Twitter. Есть два варианта для веб-сайтов: summary
|
||||
и summary_large_image{% endcomment %}
|
||||
<meta property="article:modified_time" content="{{ LENTA.0.dtContentTimeStamp|date:'c' }}" />
|
||||
<meta property="article:published_time" content="{{ LENTA.0.tdContentPublishUp|date:'c' }}" />{% endblock %}
|
||||
|
||||
<!--- ТИТУЛ --->
|
||||
{% block Title %}{% if TAGS_L %}Тема: {% for I in TAGS_IN_PAGE %}{% if I.slug in TAGS_L %}{{ I.name }} - {% endif %}{% endfor %}{% endif %}{% if PAGE_OF_LIST %}Страница {{ PAGE_OF_LIST|add:'1' }}{% else %}Главная{% endif %}{% endblock %}
|
||||
{% block canonical %}https://cadpoint.ru{% if TAGS_L %}{{ TAGS_S }}{% endif %}{% if PAGE_OF_LIST %}/p{{ PAGE_OF_LIST }}{% endif %}{% endblock %}
|
||||
{% block Description %}CADpoint / {% if TAGS_L %}тег: {% for I in TAGS_IN_PAGE %}{% if I.slug in TAGS_L %}{{ I.name }} / {% endif %}{% endfor %}{% endif %}{% if PAGE_OF_LIST %}Страница {{ PAGE_OF_LIST|add:'1' }}{% else %}Главная{% endif %}{% endblock %}
|
||||
{% block Keywords %}cadpoint, {% for I in TAGS_IN_PAGE %}{{ I.name }}, {% endfor %}новости{% if PAGE_OF_LIST %} , стр. {{ PAGE_OF_LIST|add:'1' }}{% endif %}{% endblock %}
|
||||
{% block Date4Meta %}{{ LENTA.0.tdContentPublishUp|date:'c' }}{% endblock %}"
|
||||
{% block Last4Meta %}{{ LENTA.0.dtContentTimeStamp|date:'c' }}{% endblock %}"
|
||||
{% block Expires4Meta %}{% now 'c' %}{% endblock %}"
|
||||
|
||||
{% block page_title %}{% if EMPTY_STATE_TITLE %}{{ EMPTY_STATE_TITLE }}{% elif SELECTED_TAGS %}Тема: {% for I in SELECTED_TAGS %}{{ I.name }} - {% endfor %}{% elif TAGS_L %}Тема: {% for I in TAGS_IN_PAGE %}{% if I.slug in TAGS_L %}{{ I.name }} - {% endif %}{% endfor %}{% endif %}{% if PAGE_OF_LIST and not EMPTY_STATE_TITLE %}Страница {{ PAGE_OF_LIST|add:'1' }}{% elif not SELECTED_TAGS and not EMPTY_STATE_TITLE %}Главная{% endif %} | CADpoint{% endblock %}
|
||||
{% block meta_title %}{% if EMPTY_STATE_TITLE %}{{ EMPTY_STATE_TITLE }}{% elif SELECTED_TAGS %}Тема: {% for I in SELECTED_TAGS %}{{ I.name }} - {% endfor %}{% elif TAGS_L %}Тема: {% for I in TAGS_IN_PAGE %}{% if I.slug in TAGS_L %}{{ I.name }} - {% endif %}{% endfor %}{% endif %}{% if PAGE_OF_LIST and not EMPTY_STATE_TITLE %}Страница {{ PAGE_OF_LIST|add:'1' }}{% elif not SELECTED_TAGS and not EMPTY_STATE_TITLE %}Главная{% endif %} | CADpoint{% endblock %}
|
||||
{% block og_title %}{% if EMPTY_STATE_TITLE %}{{ EMPTY_STATE_TITLE }}{% elif SELECTED_TAGS %}Тема: {% for I in SELECTED_TAGS %}{{ I.name }} - {% endfor %}{% elif TAGS_L %}Тема: {% for I in TAGS_IN_PAGE %}{% if I.slug in TAGS_L %}{{ I.name }} - {% endif %}{% endfor %}{% endif %}{% if PAGE_OF_LIST and not EMPTY_STATE_TITLE %}Страница {{ PAGE_OF_LIST|add:'1' }}{% elif not SELECTED_TAGS and not EMPTY_STATE_TITLE %}Главная{% endif %} | CADpoint{% endblock %}
|
||||
{% block twitter_title %}{% if EMPTY_STATE_TITLE %}{{ EMPTY_STATE_TITLE }}{% elif SELECTED_TAGS %}Тема: {% for I in SELECTED_TAGS %}{{ I.name }} - {% endfor %}{% elif TAGS_L %}Тема: {% for I in TAGS_IN_PAGE %}{% if I.slug in TAGS_L %}{{ I.name }} - {% endif %}{% endfor %}{% endif %}{% if PAGE_OF_LIST and not EMPTY_STATE_TITLE %}Страница {{ PAGE_OF_LIST|add:'1' }}{% elif not SELECTED_TAGS and not EMPTY_STATE_TITLE %}Главная{% endif %} | CADpoint{% endblock %}
|
||||
{% block Description %}{% if EMPTY_STATE_MESSAGE %}{{ EMPTY_STATE_MESSAGE }}{% else %}CADpoint / {% if SELECTED_TAGS %}тег: {% for I in SELECTED_TAGS %}{{ I.name }} / {% endfor %}{% elif TAGS_L %}тег: {% for I in TAGS_IN_PAGE %}{% if I.slug in TAGS_L %}{{ I.name }} / {% endif %}{% endfor %}{% endif %}{% if PAGE_OF_LIST and not SELECTED_TAGS %}Страница {{ PAGE_OF_LIST|add:'1' }}{% elif not SELECTED_TAGS %}Главная{% endif %}{% endif %}{% endblock %}
|
||||
{% block meta_description %}{% if EMPTY_STATE_MESSAGE %}{{ EMPTY_STATE_MESSAGE }}{% else %}CADpoint / {% if SELECTED_TAGS %}тег: {% for I in SELECTED_TAGS %}{{ I.name }} / {% endfor %}{% elif TAGS_L %}тег: {% for I in TAGS_IN_PAGE %}{% if I.slug in TAGS_L %}{{ I.name }} / {% endif %}{% endfor %}{% endif %}{% if PAGE_OF_LIST and not SELECTED_TAGS %}Страница {{ PAGE_OF_LIST|add:'1' }}{% elif not SELECTED_TAGS %}Главная{% endif %}{% endif %}{% endblock %}
|
||||
{% block og_description %}{% if EMPTY_STATE_MESSAGE %}{{ EMPTY_STATE_MESSAGE }}{% else %}CADpoint / {% if SELECTED_TAGS %}тег: {% for I in SELECTED_TAGS %}{{ I.name }} / {% endfor %}{% elif TAGS_L %}тег: {% for I in TAGS_IN_PAGE %}{% if I.slug in TAGS_L %}{{ I.name }} / {% endif %}{% endfor %}{% endif %}{% if PAGE_OF_LIST and not SELECTED_TAGS %}Страница {{ PAGE_OF_LIST|add:'1' }}{% elif not SELECTED_TAGS %}Главная{% endif %}{% endif %}{% endblock %}
|
||||
{% block twitter_description %}{% if EMPTY_STATE_MESSAGE %}{{ EMPTY_STATE_MESSAGE }}{% else %}CADpoint / {% if SELECTED_TAGS %}тег: {% for I in SELECTED_TAGS %}{{ I.name }} / {% endfor %}{% elif TAGS_L %}тег: {% for I in TAGS_IN_PAGE %}{% if I.slug in TAGS_L %}{{ I.name }} / {% endif %}{% endfor %}{% endif %}{% if PAGE_OF_LIST and not SELECTED_TAGS %}Страница {{ PAGE_OF_LIST|add:'1' }}{% elif not SELECTED_TAGS %}Главная{% endif %}{% endif %}{% endblock %}
|
||||
{% block Keywords %}cadpoint, {% if SELECTED_TAGS %}{% for I in SELECTED_TAGS %}{{ I.name }}, {% endfor %}{% else %}{% for I in TAGS_IN_PAGE %}{{ I.name }}, {% endfor %}{% endif %}новости{% if PAGE_OF_LIST %} , стр. {{ PAGE_OF_LIST|add:'1' }}{% endif %}{% endblock %}
|
||||
|
||||
{% block CONTENT %}{# <!-- ХЛЕБНЫЕ КРОШКИ: НАЧАЛО -->#}
|
||||
<div class="container bread-crumb">
|
||||
<div class="row">
|
||||
<nav class="col-12 х" aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item nw s"><a href="/"><i class="bi bi-house-door" title="Главная"></i> Главная</a></li>{% for t in TAGS_IN_PAGE %}{% if t.slug in TAGS_L %}
|
||||
<li class="breadcrumb-item nw s"><a href="{{ TAGS_S|rm_tag:t.slug }}"><i class="bi bi-tag" title="тэг"></i> {{ t.name }}</a></li>{% endif %}{% endfor %}{% if PAGE_OF_LIST %}
|
||||
<li class="breadcrumb-item nw s"><a href="/"><i class="bi bi-house-door" title="Главная"></i> Главная</a></li>{% if SELECTED_TAGS %}{% for t in SELECTED_TAGS %}
|
||||
<li class="breadcrumb-item nw s"><a href="{{ TAGS_S|rm_tag:t.slug }}"><i class="bi bi-tag" title="тэг"></i> {{ t.name }}</a></li>{% endfor %}{% else %}{% for t in TAGS_IN_PAGE %}{% if t.slug in TAGS_L %}
|
||||
<li class="breadcrumb-item nw s"><a href="{{ TAGS_S|rm_tag:t.slug }}"><i class="bi bi-tag" title="тэг"></i> {{ t.name }}</a></li>{% endif %}{% endfor %}{% endif %}{% if PAGE_OF_LIST %}
|
||||
<li class="breadcrumb-item active nw s" aria-current="page">Страница {{ PAGE_OF_LIST|add:'1' }}</li>{% endif %}
|
||||
</ol>
|
||||
</nav>
|
||||
@@ -100,6 +72,21 @@
|
||||
</ul>
|
||||
</nav>
|
||||
</div>{# НАВИГАЦИЯ ПО СТРАНИЦАМ СО СПИСКАМИ КОНТЕНТА ИЗ ПРОИЗВОЛЬНОЙ КАТЕГОРИИ: КОНЕЦ #}{% endif %}
|
||||
{% if not LENTA %}
|
||||
<div class="row default-list">
|
||||
<div class="col-12">
|
||||
<div class="alert alert-warning mb-0" role="alert">
|
||||
<h3 class="h5 mb-2">{% if EMPTY_STATE_TITLE %}{{ EMPTY_STATE_TITLE }}{% else %}Новостей не найдено{% endif %}</h3>
|
||||
<p class="mb-0">{% if EMPTY_STATE_MESSAGE %}{{ EMPTY_STATE_MESSAGE }}{% else %}По выбранному разделу пока нет новостей.{% endif %}</p>
|
||||
<p class="mb-0 mt-3">
|
||||
<a href="/">На главную</a>
|
||||
<span class="mx-2">·</span>
|
||||
<a href="/alltags">Все теги</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>{#<!-- POINT-СРАНИЦА: КОНЕЦ -->#}{% endblock %}
|
||||
|
||||
@@ -1,46 +1,71 @@
|
||||
{% extends "base.jinja2" %}{% load static %}{% load thumbnail %}{% load slug_ru %}
|
||||
|
||||
{% block META_OG %}{% comment %} РАЗМЕТКА Open Graph ДЛЯ СОЦ-СЕТЕЙ
|
||||
подробности: https://habr.com/ru/company/macloud/blog/555082/ и https://ogp.me/{% endcomment %}
|
||||
<meta property="og:title" content="cadpoint.ru: {{ ITEM.szContentHead|safe_html_ss }}">{% comment %} Уникальное название страницы.
|
||||
Используется парсерами URL-адресов в социальных сетях, таких как Twitter или Facebook{% endcomment %}
|
||||
<meta property="og:description" content="CADpoint: {% if ITEM.szContentDescription %}{{ ITEM.szContentDescription }}{% else %}{{ ITEM.szContentHead|safe_html_ss }}{% endif %}" />{% comment %} Уникальное описание страницы.
|
||||
Используется парсерами URL-адресов в социальных сетях, таких как Twitter или Facebook.{% endcomment %}
|
||||
<meta property="og:image" content="{% if ITEM.imgContentPreview is None %}{% static 'img/og-cadpoint-default.png' %}{% else %}{% thumbnail ITEM.imgContentPreview 680x680 upscale %}{% endif %}" />{% comment %} Изображение, отображаемое, когда вы
|
||||
делитесь ссылкой на страницу в социальных сетях, приложениях чата или других сайтах,
|
||||
которые очищают URL-адреса.
|
||||
В идеале это должно быть квадратное изображение с важным содержанием, размещенным
|
||||
в центре квадрата в прямоугольнике с соотношением сторон 2:1. Это гарантирует,
|
||||
что изображение будет хорошо смотреться на карточках с изображениями прямоугольной
|
||||
и квадратной формы.{% endcomment %}
|
||||
<meta property="og:image:alt" content="cadpoint.ru: {{ ITEM.szContentHead|safe_html_ss }}" />{% comment %}
|
||||
Описание изображения.
|
||||
Не используйте этот метатег, если изображение носит чисто декоративный характер
|
||||
и не содержит значимой информации. Программы чтения с экрана игнорируют
|
||||
изображение, если мы предоставлен замещающий текст.{% endcomment %}
|
||||
<meta property="og:locale" content="ru_RU" />{% comment %} Естественный язык страницы.{% endcomment %}
|
||||
<meta property="og:type" content="website" />{% comment %} Тип контента, которым вы делитесь,
|
||||
например website, article, или video.movie{% endcomment %}
|
||||
<meta property="og:url" content="https://cadpoint.ru/item/{{ ITEM.id }}-{{ ITEM.szContentSlug }}" />{% comment %} Канонический URL страницы.
|
||||
Обязательное свойство для допустимых страниц Open Graph.{% endcomment %}
|
||||
<meta name="twitter:card" content="summary_large_image" />{% comment %} определяет, как будут выглядеть
|
||||
карточки при публикации в Twitter. Есть два варианта для веб-сайтов: summary
|
||||
и summary_large_image{% endcomment %}{% for t in ITEM.tags.all%}
|
||||
<meta property="article:tag" content="{{ t.name|lower }}">{% comment %} определяет тематику статьи, ключевые слова или хэштеги. Если тематик несколько, перечислите их в разных метатегах.{% endcomment %}{% endfor %}
|
||||
<meta property="article:modified_time" content="{{ ITEM.dtContentTimeStamp|date:'c' }}">
|
||||
<meta property="article:published_time" content="{{ ITEM.tdContentPublishUp|date:'c' }}">
|
||||
{% block page_title %}{{ ITEM.szContentHead|safe_html_ss }} | CADpoint{% endblock %}
|
||||
|
||||
{% block canonical %}{{ request.scheme }}://{{ request.get_host }}/item/{{ ITEM.id }}-{{ ITEM.szContentSlug }}{% endblock %}
|
||||
{% block og_url %}{{ request.scheme }}://{{ request.get_host }}/item/{{ ITEM.id }}-{{ ITEM.szContentSlug }}{% endblock %}
|
||||
|
||||
{% block Description %}{% if ITEM.szContentDescription %}{{ ITEM.szContentDescription }}{% else %}{{ ITEM.szContentHead|safe_html_ss }}{% endif %}{% endblock %}
|
||||
|
||||
{% block meta_title %}{{ ITEM.szContentHead|safe_html_ss }} | CADpoint{% endblock %}
|
||||
|
||||
{% block og_title %}{{ ITEM.szContentHead|safe_html_ss }} | CADpoint{% endblock %}
|
||||
|
||||
{% block twitter_title %}{{ ITEM.szContentHead|safe_html_ss }} | CADpoint{% endblock %}
|
||||
|
||||
{% block meta_description %}{% if ITEM.szContentDescription %}{{ ITEM.szContentDescription }}{% else %}{{ ITEM.szContentHead|safe_html_ss }}{% endif %}{% endblock %}
|
||||
|
||||
{% block og_description %}{% if ITEM.szContentDescription %}{{ ITEM.szContentDescription }}{% else %}{{ ITEM.szContentHead|safe_html_ss }}{% endif %}{% endblock %}
|
||||
|
||||
{% block twitter_description %}{% if ITEM.szContentDescription %}{{ ITEM.szContentDescription }}{% else %}{{ ITEM.szContentHead|safe_html_ss }}{% endif %}{% endblock %}
|
||||
|
||||
{% block Keywords %}cadpoint{% if ITEM.szContentKeywords %}, {{ ITEM.szContentKeywords }}{% endif %}{% with item_tags=ITEM.tags.all %}{% if item_tags %}, {% for t in item_tags %}{{ t.name }}{% if not forloop.last %}, {% endif %}{% endfor %}{% endif %}{% endwith %}, новости{% endblock %}
|
||||
|
||||
{% block og_type %}article{% endblock %}
|
||||
|
||||
{% block og_image %}{% if ITEM.imgContentPreview is None %}{% static 'img/og-cadpoint-default.png' %}{% else %}{% thumbnail ITEM.imgContentPreview 680x680 upscale %}{% endif %}{% endblock %}
|
||||
|
||||
{% block og_image_alt %}{{ ITEM.szContentHead|safe_html_ss }} | CADpoint{% endblock %}
|
||||
|
||||
{% block twitter_card %}summary_large_image{% endblock %}
|
||||
|
||||
{% block twitter_image %}{% if ITEM.imgContentPreview is None %}{% static 'img/og-cadpoint-default.png' %}{% else %}{% thumbnail ITEM.imgContentPreview 680x680 upscale %}{% endif %}{% endblock %}
|
||||
|
||||
{% block ExtraHead %}{{ block.super }}{% for t in ITEM.tags.all %}
|
||||
<meta property="article:tag" content="{{ t.name|lower }}" />{% endfor %}
|
||||
<meta property="article:published_time" content="{{ ITEM.tdContentPublishUp|date:'Y-m-d' }}T{{ ITEM.tdContentPublishUp|date:'H:i' }}{{ ITEM.tdContentPublishUp|date:'O'|slice:':3' }}:{{ ITEM.tdContentPublishUp|date:'O'|slice:'3:' }}" />
|
||||
<meta property="article:modified_time" content="{{ ITEM.dtContentTimeStamp|date:'Y-m-d' }}T{{ ITEM.dtContentTimeStamp|date:'H:i' }}{{ ITEM.dtContentTimeStamp|date:'O'|slice:':3' }}:{{ ITEM.dtContentTimeStamp|date:'O'|slice:'3:' }}" />
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "Article",
|
||||
"mainEntityOfPage": {
|
||||
"@type": "WebPage",
|
||||
"@id": "{{ request.scheme }}://{{ request.get_host }}/item/{{ ITEM.id }}-{{ ITEM.szContentSlug }}"
|
||||
},
|
||||
"headline": "{{ ITEM.szContentHead|safe_html_ss|escapejs }}",
|
||||
"description": "{% if ITEM.szContentDescription %}{{ ITEM.szContentDescription|escapejs }}{% else %}{{ ITEM.szContentHead|safe_html_ss|escapejs }}{% endif %}",
|
||||
"image": [
|
||||
"{% if ITEM.imgContentPreview is None %}{% static 'img/og-cadpoint-default.png' %}{% else %}{% thumbnail ITEM.imgContentPreview 680x680 upscale %}{% endif %}"
|
||||
],
|
||||
"datePublished": "{{ ITEM.tdContentPublishUp|date:'Y-m-d' }}T{{ ITEM.tdContentPublishUp|date:'H:i' }}{{ ITEM.tdContentPublishUp|date:'O'|slice:':3' }}:{{ ITEM.tdContentPublishUp|date:'O'|slice:'3:' }}",
|
||||
"dateModified": "{{ ITEM.dtContentTimeStamp|date:'Y-m-d' }}T{{ ITEM.dtContentTimeStamp|date:'H:i' }}{{ ITEM.dtContentTimeStamp|date:'O'|slice:':3' }}:{{ ITEM.dtContentTimeStamp|date:'O'|slice:'3:' }}",
|
||||
"author": {
|
||||
"@type": "Person",
|
||||
"name": "Sergei Erjemin"
|
||||
},
|
||||
"publisher": {
|
||||
"@type": "Organization",
|
||||
"name": "CADPOINT.RU",
|
||||
"logo": {
|
||||
"@type": "ImageObject",
|
||||
"url": "{{ request.scheme }}://{{ request.get_host }}{% static 'svgs/favicon.svg' %}"
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
<!--- ТИТУЛ --->
|
||||
{% block Title %}{{ ITEM.szContentHead|safe_html_ss }}{% endblock %}
|
||||
{% block canonical %}https://cadpoint.ru/item/{{ ITEM.id }}-{{ ITEM.szContentSlug }}{% endblock %}
|
||||
{% block Description %}CADpoint: {% if ITEM.szContentDescription %}{{ ITEM.szContentDescription }}{% else %}{{ ITEM.szContentHead|safe_html_ss }}{% endif %}{% endblock %}
|
||||
{% block Keywords %}cadpoint, {% for t in ITEM.tags.all %}{{ t.name }}, {% endfor %}{{ ITEM.szContentKeywords }}, новости{% endblock %}
|
||||
{% block Date4Meta %}{{ ITEM.tdContentPublishUp|date:'c' }}{% endblock %}"
|
||||
{% block Last4Meta %}{{ ITEM.dtContentTimeStamp|date:'c' }}{% endblock %}"
|
||||
{% block Expires4Meta %}{% now 'c' %}{% endblock %}"
|
||||
|
||||
|
||||
{% block CONTENT %}{# <!-- ХЛЕБНЫЕ КРОШКИ: НАЧАЛО -->#}<div class="container bread-crumb">
|
||||
<div class="row">
|
||||
<nav class="col-12 х" aria-label="breadcrumb">
|
||||
@@ -54,15 +79,15 @@
|
||||
</div>{#<!-- ХЛЕБНЫЕ КРОШКИ: КОНЕЦ -->#}
|
||||
{#<!-- POINT-СРАНИЦА: НАЧАЛО -->#}<div class="container news">
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-9">
|
||||
<article class="col-12 col-md-9" aria-labelledby="article-title">
|
||||
<time datetime="{{ ITEM.tdContentPublishUp|date:'Y-m-d' }}">{{ ITEM.tdContentPublishUp|date:'d E Y' }} <small>({{ ITEM.tdContentPublishUp|date:'l'|lower }}) <small title="Число просмотров"><i class="bi bi-eye-fill"></i> {{ ITEM.iContentHits }}</small></small> </time>
|
||||
<h1>{{ ITEM.szContentHead|safe }}</h1>
|
||||
<h1 id="article-title">{{ ITEM.szContentHead|safe }}</h1>
|
||||
{{ ITEM.szContentIntro|safe }}
|
||||
{{ ITEM.szContentBody|safe }}
|
||||
{{ ITEM.szPointDes|safe }}{# Текст страницы или приамбула перед списком #}
|
||||
<hr />
|
||||
<nav class="sm-tags">{% for t in ITEM.tags.all%}<a href="/tag_{{ t.slug|lower }}"><i class="bi bi-tag" title="тег"></i> {{ t.name|lower }}</a>   {% endfor %}</nav>
|
||||
</div>
|
||||
</article>
|
||||
{#<!-- БОКОВАЯ НАВИГАЦИЯ: НАЧАЛО-->#}<nav class="col order-last order-md-first">
|
||||
<div></div>{% for i in ITEMS_BEFORE reversed %}{% if i.id != ITEM.id %}<div>
|
||||
<time datetime="{{ i.tdContentPublishUp|date:'Y-m-d' }}">{{ i.tdContentPublishUp|date:'d E Y' }}</time>
|
||||
@@ -76,7 +101,7 @@
|
||||
</div>{% endif %}{% endfor %}
|
||||
</nav>{#<!-- боковая навигация: конец-->#}
|
||||
</div>
|
||||
{# <!-- НИЖНЯЯ НАВИГАЦИЯ-ПАДЖИНАТОР ПО НОВОСТЯМ: НАЧАЛО--> #}{% if PER_PAGE %}
|
||||
{# <!-- НИЖНЯЯ НАВИГАЦИЯ-ПАДЖИНАТОР ПО НОВОСТЯМ: НАЧАЛО--> #}{% if PER_PAGE %}
|
||||
<nav class="row" aria-label="Навигация по контенту">
|
||||
<ul class="col offset-md-3 pagination px-1">{% if ITEMS_BEFORE.0.id %}
|
||||
<li class="page-item"><a class="page-link" href="/item/{{ ITEMS_BEFORE.0.id }}-{{ ITEMS_BEFORE.0.szContentSlug }}?p={{ ITEMS_BEFORE.0.pp }}&n={{ ITEMS_BEFORE.0.nn }}"><i class="bi bi-arrow-left" title="Предыдущая"></i></a></li>{% else %}<li class="page-item disabled"><a class="page-link" href="#"><i class="bi bi-arrow-left" title="Предыдущая"></i></a></li>{% endif %}
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
{% load static %}{% load thumbnail %}{% load slug_ru %}<?xml version="1.0" encoding="UTF-8"?>
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
<url><loc>https://cadpoint.ru/</loc><priority>1</priority></url>{% for I in ITEMS %}
|
||||
<url><loc>https://cadpoint.ru/item/{{ I.id }}-{{ I.szContentSlug }}</loc><priority>1</priority></url>{% endfor %}
|
||||
</urlset>
|
||||
3199
cadpoint/web/EMT.py
@@ -1,6 +1,8 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from cadpoint.settings import *
|
||||
from bs4 import BeautifulSoup
|
||||
from html import unescape
|
||||
import pytils
|
||||
import re
|
||||
|
||||
|
||||
@@ -12,42 +14,56 @@ def check_cookies(request) -> bool:
|
||||
|
||||
|
||||
def safe_html_special_symbols(s: str) -> str:
|
||||
""" Очистка строки от HTML-разметки типографа
|
||||
"""Преобразует HTML-фрагмент в чистый текст.
|
||||
|
||||
:param s: строка которую надо очистить
|
||||
:return: str:
|
||||
Удаляет все HTML-теги и декодирует HTML-сущности в Unicode.
|
||||
|
||||
:param s: строка, которую надо очистить
|
||||
:return: str: чистый текст без HTML-разметки
|
||||
"""
|
||||
# очистка строки от некоторых спец-символов HTML
|
||||
result = s.replace('­', '')
|
||||
result = result.replace('<span class="laquo">', '')
|
||||
result = result.replace('<span style="margin-right:0.44em;">', '')
|
||||
result = result.replace('<span style="margin-left:-0.44em;">', '')
|
||||
result = result.replace('<span class="raquo">', '')
|
||||
result = result.replace('<span class="point">', '')
|
||||
result = result.replace('<span class="thinsp">', ' ')
|
||||
result = result.replace('<span class="ensp">', '')
|
||||
result = result.replace('</span>', '')
|
||||
result = result.replace(' ', ' ')
|
||||
result = result.replace('«', '«')
|
||||
result = result.replace('»', '»')
|
||||
result = result.replace('…', '…')
|
||||
result = result.replace('<nobr>', '')
|
||||
result = result.replace('</nobr>', '')
|
||||
result = result.replace('—', '—')
|
||||
result = result.replace('№', '№')
|
||||
result = result.replace('<br />', ' ')
|
||||
result = result.replace('<br>', ' ')
|
||||
return result
|
||||
if not s:
|
||||
return ""
|
||||
|
||||
soup = BeautifulSoup(s, "html.parser")
|
||||
|
||||
# Скрипты и стили в чистый текст не нужны — выкидываем их целиком.
|
||||
for tag in soup(["script", "style", "noscript", "code", "kbd", "pre"]):
|
||||
tag.decompose()
|
||||
|
||||
result = soup.get_text()
|
||||
result = unescape(result).replace("\xa0", " ")
|
||||
# Убираем мягкие переносы и другие невидимые символы, которые не нужны
|
||||
# ни для slug, ни для человекочитаемого текста.
|
||||
result = result.translate({
|
||||
ord("\xad"): None, # символ мягкого переноса
|
||||
ord("\u200b"): None, # символ нулевой ширины (zero-width space)
|
||||
ord("\u200c"): None, # символ нулевой ширины (zero-width non-joiner)
|
||||
ord("\u200d"): None, # символ Zero Width Joiner (ZWJ)
|
||||
ord("\u2060"): None, # символ Word Joiner (WJ)
|
||||
ord("\ufeff"): None, # символ Zero Width No-Break Space (BOM)
|
||||
})
|
||||
return " ".join(result.split())
|
||||
|
||||
|
||||
def post_processing_html(s: str) -> str:
|
||||
s = re.sub(r"\s+", " ", s, flags=re.IGNORECASE)
|
||||
s = re.sub(r">\s+|> ", "> ", s, flags=re.IGNORECASE)
|
||||
s = re.sub(r"\n|\r|<p[^>]*>\s*</p>|<p> </p>", "", s, flags=re.IGNORECASE)
|
||||
s = re.sub(r"</p>\s*<br[^>]*>", "</p>", s, flags=re.IGNORECASE)
|
||||
s = re.sub(r"<br[^>]*>\s*<p>|<p[^>]*>\s*<p[^>]*>", "<p>", s, flags=re.IGNORECASE)
|
||||
s = re.sub(r"</p>\s*</p>", "</p>", s, flags=re.IGNORECASE)
|
||||
s = re.sub(r"<br[^>]*>\s*<br[^>]*>", "<br />", s, flags=re.IGNORECASE)
|
||||
s = re.sub(r"<p><blockquote>", "<blockquote>", s, flags=re.IGNORECASE)
|
||||
s = re.sub(r"</blockquote></p>", "</blockquote>", s, flags=re.IGNORECASE)
|
||||
return s
|
||||
def clean_text_to_slug(s: str, default: str = "content") -> str:
|
||||
"""Готовит чистый slug из HTML/Unicode текста."""
|
||||
slug = pytils.translit.slugify(safe_html_special_symbols(s).lower())
|
||||
slug = re.sub(r"-+", "-", slug).strip("-")
|
||||
return slug or default
|
||||
|
||||
|
||||
# Удалить: HTML-постобработка была нужна только для старого типографа Муравьёва.
|
||||
# После перехода на `etpgrf` можно будет убрать и этот закомментированный блок,
|
||||
# и сам импорт `re`, если он больше нигде не понадобится.
|
||||
#
|
||||
# def post_processing_html(s: str) -> str:
|
||||
# s = re.sub(r"\s+", " ", s, flags=re.IGNORECASE)
|
||||
# s = re.sub(r">\s+|> ", "> ", s, flags=re.IGNORECASE)
|
||||
# s = re.sub(r"\n|\r|<p[^>]*>\s*</p>|<p> </p>", "", s, flags=re.IGNORECASE)
|
||||
# s = re.sub(r"</p>\s*<br[^>]*>", "</p>", s, flags=re.IGNORECASE)
|
||||
# s = re.sub(r"<br[^>]*>\s*<p>|<p[^>]*>\s*<p[^>]*>", "<p>", s, flags=re.IGNORECASE)
|
||||
# s = re.sub(r"</p>\s*</p>", "</p>", s, flags=re.IGNORECASE)
|
||||
# s = re.sub(r"<br[^>]*>\s*<br[^>]*>", "<br />", s, flags=re.IGNORECASE)
|
||||
# s = re.sub(r"<p><blockquote>", "<blockquote>", s, flags=re.IGNORECASE)
|
||||
# s = re.sub(r"</blockquote></p>", "</blockquote>", s, flags=re.IGNORECASE)
|
||||
# return s
|
||||
|
||||
@@ -1,13 +1,173 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from django import forms
|
||||
from django.contrib import admin
|
||||
from django.db import models
|
||||
from django.forms import TextInput, Textarea
|
||||
from django.urls import reverse
|
||||
from django_select2.forms import Select2TagWidget
|
||||
from etpgrf.config import MODE_MIXED, MODE_MNEMONIC, MODE_UNICODE, SANITIZE_ALL_HTML, SANITIZE_ETPGRF
|
||||
from web.models import TbContent
|
||||
from web.add_function import safe_html_special_symbols
|
||||
from cadpoint import settings
|
||||
|
||||
|
||||
TYPOGRAPH_MODE_CHOICES = [
|
||||
(MODE_MIXED, 'Смешанный (Mixed)'),
|
||||
(MODE_UNICODE, 'Юникод (Unicode)'),
|
||||
(MODE_MNEMONIC, 'Мнемоники'),
|
||||
]
|
||||
|
||||
TYPOGRAPH_SANITIZER_CHOICES = [
|
||||
(SANITIZE_ALL_HTML, 'Очистка от HTML на входе'),
|
||||
(SANITIZE_ETPGRF, 'Очистка висячей пунктуации'),
|
||||
('None', 'Без очистки'),
|
||||
]
|
||||
|
||||
|
||||
class AjaxCommaSeparatedSelect2TagWidget(Select2TagWidget):
|
||||
"""
|
||||
Select2-виджет для `taggit`.
|
||||
|
||||
Select2 в браузере работает с массивом значений, а `taggit` ждёт строку
|
||||
с тегами через запятую. Поэтому здесь есть конвертация туда и обратно.
|
||||
"""
|
||||
|
||||
def value_from_datadict(self, data, files, name):
|
||||
# Select2 присылает список значений, а `taggit` ожидает строку вида
|
||||
# "tag-one,tag two,tag-three".
|
||||
values = super().value_from_datadict(data, files, name)
|
||||
if isinstance(values, (list, tuple)):
|
||||
return ",".join(values)
|
||||
return values
|
||||
|
||||
def optgroups(self, name, value, attrs=None):
|
||||
# При редактировании объекта нужно показать уже выбранные теги.
|
||||
# При этом не тащим ВСЕ теги из базы — только те, что уже сохранены.
|
||||
if isinstance(value, (list, tuple)):
|
||||
raw_values = []
|
||||
for item in value:
|
||||
if not item:
|
||||
continue
|
||||
raw_values.extend(str(item).split(","))
|
||||
else:
|
||||
raw_values = str(value or "").split(",")
|
||||
|
||||
values = [item for item in raw_values if item]
|
||||
selected = set(values)
|
||||
subgroup = [
|
||||
self.create_option(name, v, v, v in selected, i)
|
||||
for i, v in enumerate(values)
|
||||
]
|
||||
return [(None, subgroup, 0)]
|
||||
|
||||
|
||||
class AdminContentForm(forms.ModelForm):
|
||||
typograph_enabled = forms.BooleanField(
|
||||
label='Типограф etpgrf вкл.',
|
||||
required=False,
|
||||
initial=True,
|
||||
help_text="Обработать через <a href=\"https://typograph.cube2.ru/\""
|
||||
" target=\"_blank\">Типограф ETPRGF</a><br />"
|
||||
"<small><u>СТАБИЛЬНЫЙ И СОВРЕМЕННЫЙ ТИПОГРАФ, РЕКОМЕНДУЕМ</u><br />"
|
||||
"«приклеивает» союзы и предлоги, поддерживает неразрывные конструкции, "
|
||||
"замена тире, кавычек и дефисов, расстановка «мягких переносов» "
|
||||
"в словах длиннее 14 символов, <!-- убирает «вдовы» «сироты» (кроме "
|
||||
"заголовков), расставляет абзацы (кроме заголовков), расшифровывает "
|
||||
"аббревиатуры (те, что знает и кроме заголовков), --> висячая "
|
||||
"пунктуация (только в заголовках) и т.п.</small>"
|
||||
)
|
||||
typograph_strip_soft_hyphens = forms.BooleanField(
|
||||
label='Удалять переносы',
|
||||
required=False,
|
||||
initial=True,
|
||||
help_text='Убирает `&shy;`, `&#173;` и Unicode-символ мягкого переноса<br />'
|
||||
'перед типографом.',
|
||||
)
|
||||
typograph_mode = forms.ChoiceField(
|
||||
label='Режим вывода',
|
||||
choices=TYPOGRAPH_MODE_CHOICES,
|
||||
initial=MODE_MIXED,
|
||||
)
|
||||
typograph_hyphenation = forms.BooleanField(
|
||||
label='Расстановка переносов',
|
||||
required=False,
|
||||
initial=True,
|
||||
)
|
||||
typograph_sanitizer = forms.ChoiceField(
|
||||
label='Санитайзинг',
|
||||
choices=TYPOGRAPH_SANITIZER_CHOICES,
|
||||
initial='None',
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = TbContent
|
||||
fields = '__all__'
|
||||
|
||||
class Media:
|
||||
js = ('codemirror/editor.js',)
|
||||
css = {
|
||||
'all': ('css/admin-select2-theme.css',),
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
# AJAX-виджет подгружает список тегов лениво, а здесь мы оставляем
|
||||
# только уже выбранные значения, чтобы не тащить все теги из базы при
|
||||
# открытии формы и не провоцировать лишние запросы к SQLite.
|
||||
if self.is_bound:
|
||||
if hasattr(self.data, 'getlist'):
|
||||
tag_values = self.data.getlist('tags')
|
||||
else:
|
||||
raw_values = self.data.get('tags', [])
|
||||
tag_values = raw_values if isinstance(raw_values, list) else [raw_values]
|
||||
tag_choices = [(value, value) for value in tag_values if value]
|
||||
elif self.instance.pk:
|
||||
tag_choices = [
|
||||
(name, name)
|
||||
for name in self.instance.tags.order_by('name').values_list('name', flat=True)
|
||||
]
|
||||
else:
|
||||
tag_choices = []
|
||||
|
||||
codemirror_attrs = {
|
||||
'data-codemirror-editor': '1',
|
||||
'data-language': 'html',
|
||||
}
|
||||
|
||||
self.fields['szContentHead'].widget = Textarea(attrs={
|
||||
'rows': 4,
|
||||
'cols': 120,
|
||||
**codemirror_attrs,
|
||||
})
|
||||
|
||||
self.fields['szContentKeywords'].widget = Textarea(attrs={
|
||||
'rows': 3,
|
||||
'cols': 120,
|
||||
})
|
||||
self.fields['szContentDescription'].widget = Textarea(attrs={
|
||||
'rows': 4,
|
||||
'cols': 120,
|
||||
})
|
||||
|
||||
for field_name in ('szContentHead', 'szContentIntro', 'szContentBody'):
|
||||
self.fields[field_name].widget.attrs.update(codemirror_attrs)
|
||||
|
||||
self.fields['tags'].widget = AjaxCommaSeparatedSelect2TagWidget(
|
||||
attrs={
|
||||
'data-ajax--url': reverse('web_tag_autocomplete'),
|
||||
'data-ajax--cache': 'true',
|
||||
'data-ajax--data-type': 'json',
|
||||
'data-ajax--delay': settings.SELECT2_AJAX_DELAY_MS,
|
||||
'data-token-separators': settings.SELECT2_TOKEN_SEPARATORS,
|
||||
'data-minimum-input-length': settings.SELECT2_MINIMUM_INPUT_LENGTH,
|
||||
},
|
||||
choices=tag_choices,
|
||||
)
|
||||
|
||||
|
||||
# Register your models here.
|
||||
class AdminContent(admin.ModelAdmin):
|
||||
form = AdminContentForm
|
||||
search_fields = ['szContentHead', 'szContentIntro', 'szContentBody',
|
||||
'szContentKeywords', 'szContentDescription']
|
||||
list_display = ('id', 'ContentHeadSafe', 'tag_list', 'bContentPublish', 'tdContentPublishUp')
|
||||
@@ -17,7 +177,7 @@ class AdminContent(admin.ModelAdmin):
|
||||
# настройка длины поля TextInput в админке
|
||||
formfield_overrides = {
|
||||
models.CharField: {'widget': TextInput(attrs={'size': '100%'})},
|
||||
# models.TextField: {'widget': Textarea(attrs={'rows': 4, 'cols': 40})},
|
||||
models.TextField: {'widget': Textarea(attrs={'rows': 14, 'cols': 120})},
|
||||
}
|
||||
# Настройка страницы редактирования
|
||||
fieldsets = [
|
||||
@@ -32,7 +192,11 @@ class AdminContent(admin.ModelAdmin):
|
||||
'fields': ('tags', 'szContentHead', 'imgContentPreview', 'szContentIntro', 'szContentBody')
|
||||
}),
|
||||
('Типограф', {
|
||||
'fields': ('bTypograf', ),
|
||||
'fields': (
|
||||
('typograph_enabled', ),
|
||||
('typograph_mode', 'typograph_sanitizer', ),
|
||||
('typograph_strip_soft_hyphens', 'typograph_hyphenation', ),
|
||||
),
|
||||
'classes': ('collapse',),
|
||||
}),
|
||||
('Поля для SEO', {
|
||||
@@ -45,11 +209,22 @@ class AdminContent(admin.ModelAdmin):
|
||||
actions_on_top = False
|
||||
actions_on_bottom = False
|
||||
|
||||
def save_model(self, request, obj, form, change):
|
||||
obj._typograph_enabled = form.cleaned_data.get('typograph_enabled', False)
|
||||
obj._typograph_strip_soft_hyphens = form.cleaned_data.get('typograph_strip_soft_hyphens', True)
|
||||
obj._typograph_mode = form.cleaned_data.get('typograph_mode', MODE_MIXED)
|
||||
obj._typograph_hyphenation = form.cleaned_data.get('typograph_hyphenation', True)
|
||||
obj._typograph_sanitizer = form.cleaned_data.get('typograph_sanitizer', 'None')
|
||||
super().save_model(request, obj, form, change)
|
||||
|
||||
def ContentHeadSafe(self, obj) -> str:
|
||||
return safe_html_special_symbols(obj.szContentHead)
|
||||
|
||||
def get_queryset(self, request):
|
||||
return super().get_queryset(request).prefetch_related('tags')
|
||||
queryset = super().get_queryset(request)
|
||||
if request.resolver_match and request.resolver_match.url_name == 'web_tbcontent_changelist':
|
||||
return queryset.prefetch_related('tags')
|
||||
return queryset
|
||||
|
||||
def tag_list(self, obj):
|
||||
return u", ".join(o.name for o in obj.tags.all())
|
||||
|
||||
85
cadpoint/web/legacy_links.py
Normal file
@@ -0,0 +1,85 @@
|
||||
"""Утилиты для поиска и замены старых Joomla-ссылок в HTML-контенте."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
import re
|
||||
from typing import Iterable
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class LegacyLinkMatch:
|
||||
"""Результат найденной старой ссылки."""
|
||||
|
||||
pattern_name: str
|
||||
content_id: int
|
||||
old_url: str
|
||||
new_url: str
|
||||
|
||||
|
||||
def _compile_legacy_pattern(name: str, route_regex: str) -> tuple[str, re.Pattern[str]]:
|
||||
"""Компилирует паттерн только для внутренних ссылок CADpoint."""
|
||||
# Важно: регулярка ловит только внутренние маршруты сайта, а не внешние
|
||||
# URL и не ссылки на картинки/медиа.
|
||||
pattern = re.compile(
|
||||
rf'(?P<url>(?:^|(?<=["\'\s>]))(?:https?://(?:www\.)?cadpoint\.ru)?/?{route_regex})',
|
||||
re.IGNORECASE,
|
||||
)
|
||||
return name, pattern
|
||||
|
||||
|
||||
LEGACY_ROUTE_PATTERNS: tuple[tuple[str, re.Pattern[str]], ...] = (
|
||||
_compile_legacy_pattern('latest-news', r'(?:news/)?1-latest-news/(?P<content_id>\d+)(?:-[^"\'<>\s]*)?(?:\.html)?'),
|
||||
_compile_legacy_pattern('newsflash', r'(?:news/)?3-newsflash/(?P<content_id>\d+)(?:-[^"\'<>\s]*)?(?:\.html)?'),
|
||||
_compile_legacy_pattern('publication-hardware', r'publication/32-hardware/(?P<content_id>\d+)(?:-[^"\'<>\s]*)?(?:\.html)?'),
|
||||
_compile_legacy_pattern('publication-interview', r'publication/39-interview/(?P<content_id>\d+)(?:-[^"\'<>\s]*)?(?:\.html)?'),
|
||||
_compile_legacy_pattern('runet-cad', r'runet-cad/37-runet-cad/(?P<content_id>\d+)(?:-[^"\'<>\s]*)?(?:\.html)?'),
|
||||
_compile_legacy_pattern('mcad', r'section-blog/28-mcad/(?P<content_id>\d+)(?:-[^"\'<>\s]*)?(?:\.html)?'),
|
||||
_compile_legacy_pattern('video', r'video/(?P<content_id>\d+)(?:-[^"\'<>\s]*)?(?:\.html)?'),
|
||||
_compile_legacy_pattern('privat-blog', r'blogs/35-privat-blog/(?P<content_id>\d+)(?:-[^"\'<>\s]*)?(?:\.html)?'),
|
||||
_compile_legacy_pattern('cad-company-feeds', r'cad-company-feeds/40-cad-company-feeds/(?P<content_id>\d+)(?:-[^"\'<>\s]*)?(?:\.html)?'),
|
||||
_compile_legacy_pattern('component-article', r'component/content/article/(?P<content_id>\d+)(?:-[^"\'<>\s]*)?(?:\.html)?'),
|
||||
_compile_legacy_pattern('categoryblog', r'categoryblog/(?P<content_id>\d+)(?:-[^"\'<>\s]*)?(?:\.html)?'),
|
||||
_compile_legacy_pattern('category-table', r'category-table/(?P<content_id>\d+)(?:-[^"\'<>\s]*)?(?:\.html)?'),
|
||||
_compile_legacy_pattern('aboutcadpoint', r'aboutcadpoint\.html/(?P<content_id>\d+)(?:-[^"\'<>\s]*)?(?:\.html)?'),
|
||||
)
|
||||
|
||||
|
||||
def build_canonical_url(content_id: int, slug: str) -> str:
|
||||
"""Строит текущий канонический URL контента."""
|
||||
safe_slug = slug.strip().strip('/')
|
||||
if safe_slug:
|
||||
return f'/item/{content_id}-{safe_slug}'
|
||||
return f'/item/{content_id}-'
|
||||
|
||||
|
||||
def replace_legacy_links(text: str, content_by_id: dict[int, str]) -> tuple[str, list[LegacyLinkMatch]]:
|
||||
"""Заменяет все старые Joomla-ссылки в тексте на текущий канонический URL.
|
||||
|
||||
Возвращает обновлённый текст и список найденных замен.
|
||||
"""
|
||||
# По каждому шаблону делаем отдельный проход, чтобы сохранить понятную
|
||||
# диагностику: какой именно legacy-шаблон и какой `content_id` сработал.
|
||||
matches: list[LegacyLinkMatch] = []
|
||||
result = text
|
||||
|
||||
for pattern_name, pattern in LEGACY_ROUTE_PATTERNS:
|
||||
def _repl(match: re.Match[str]) -> str:
|
||||
content_id = int(match.group('content_id'))
|
||||
old_url = match.group('url')
|
||||
slug = content_by_id.get(content_id, '')
|
||||
new_url = build_canonical_url(content_id, slug)
|
||||
matches.append(LegacyLinkMatch(pattern_name, content_id, old_url, new_url))
|
||||
return new_url
|
||||
|
||||
result = pattern.sub(_repl, result)
|
||||
|
||||
return result, matches
|
||||
|
||||
|
||||
def iter_legacy_link_matches(text: str) -> Iterable[LegacyLinkMatch]:
|
||||
"""Находит все старые ссылки в тексте без замены."""
|
||||
# Используем тот же механизм, что и при замене, но без сохранения текста.
|
||||
_, matches = replace_legacy_links(text, {})
|
||||
return matches
|
||||
|
||||
2
cadpoint/web/management/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
"""Пакет management-команд для приложения web."""
|
||||
|
||||
2
cadpoint/web/management/commands/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
"""Набор management-команд для приложения web."""
|
||||
|
||||
85
cadpoint/web/management/commands/replace_legacy_links.py
Normal file
@@ -0,0 +1,85 @@
|
||||
"""Mass-замена старых Joomla-кросс-ссылок в HTML-контенте.
|
||||
|
||||
Пока команда чинит только внутренние ссылки на статьи. Ссылки на картинки и
|
||||
прочие медиа остаются без изменений.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections import Counter
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.db import transaction
|
||||
|
||||
from web.legacy_links import iter_legacy_link_matches, replace_legacy_links
|
||||
from web.models import TbContent
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = (
|
||||
'Находит и заменяет старые Joomla-кросс-ссылки в HTML-контенте на '
|
||||
'текущие Django-URL. Ссылки на медиа пока не трогает.'
|
||||
)
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument(
|
||||
'--apply',
|
||||
action='store_true',
|
||||
help='Сохранить изменения в базе. Без флага команда работает в режиме dry-run.',
|
||||
)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
apply_changes = options['apply']
|
||||
# Сначала один раз собираем карту `id -> slug`, чтобы не делать лишние
|
||||
# запросы к базе в цикле по каждому контенту.
|
||||
content_by_id = dict(
|
||||
TbContent.objects.values_list('id', 'szContentSlug')
|
||||
)
|
||||
# Обрабатываем только HTML-поля, где реально встречаются старые ссылки.
|
||||
fields = ('szContentIntro', 'szContentBody', 'szContentHead')
|
||||
pattern_counter = Counter()
|
||||
updated_objects = 0
|
||||
updated_fields = 0
|
||||
|
||||
for content in TbContent.objects.all().iterator():
|
||||
field_updates: dict[str, str] = {}
|
||||
object_matches = []
|
||||
|
||||
for field_name in fields:
|
||||
text = getattr(content, field_name) or ''
|
||||
if not text:
|
||||
continue
|
||||
# Быстрая проверка: если в тексте нет legacy-ссылок, не тратим
|
||||
# время на полноценную замену.
|
||||
if not any(match for match in iter_legacy_link_matches(text)):
|
||||
continue
|
||||
|
||||
new_text, matches = replace_legacy_links(text, content_by_id)
|
||||
if matches and new_text != text:
|
||||
field_updates[field_name] = new_text
|
||||
object_matches.extend(matches)
|
||||
pattern_counter.update(match.pattern_name for match in matches)
|
||||
|
||||
if not field_updates:
|
||||
continue
|
||||
|
||||
updated_objects += 1
|
||||
updated_fields += len(field_updates)
|
||||
self.stdout.write(
|
||||
f'#{content.pk}: {len(object_matches)} замен(ы) в полях {", ".join(field_updates)}'
|
||||
)
|
||||
for match in object_matches[:5]:
|
||||
self.stdout.write(f' - {match.pattern_name}: {match.old_url} -> {match.new_url}')
|
||||
if len(object_matches) > 5:
|
||||
self.stdout.write(f' ... ещё {len(object_matches) - 5} замен(ы)')
|
||||
|
||||
if apply_changes:
|
||||
# Записываем только те поля, которые действительно изменились.
|
||||
with transaction.atomic():
|
||||
TbContent.objects.filter(pk=content.pk).update(**field_updates)
|
||||
|
||||
self.stdout.write(self.style.SUCCESS(f'Затронуто объектов: {updated_objects}'))
|
||||
self.stdout.write(self.style.SUCCESS(f'Затронуто полей: {updated_fields}'))
|
||||
self.stdout.write(self.style.SUCCESS(f'Сводка по шаблонам: {dict(pattern_counter)}'))
|
||||
if not apply_changes:
|
||||
self.stdout.write(self.style.WARNING('Это dry-run. Для записи в БД добавь флаг --apply.'))
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
# Generated by Django 3.2.6 on 2022-12-14 19:11
|
||||
# Generated by Django 4.2.30 on 2026-04-09 11:26
|
||||
|
||||
import ckeditor.fields
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
@@ -10,11 +9,13 @@ import taggit.managers
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
replaces = [('web', '0001_initial'), ('web', '0002_alter_tbcontent_szcontentbody_and_more')]
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('filer', '0013_image_width_height_to_float'),
|
||||
('taggit', '0003_taggeditem_add_unique_index'),
|
||||
('filer', '0013_auto_20221214_2211'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
@@ -48,8 +49,8 @@ class Migration(migrations.Migration):
|
||||
('tdContentPublishUp', models.DateTimeField(db_index=True, default=django.utils.timezone.now, help_text='Дата публикации, с её момента новость появится на сайте.', verbose_name='Начало публикации')),
|
||||
('tdContentPublishDown', models.DateTimeField(blank=True, db_index=True, help_text='Дата окончания публикации, с её момента новость исчезнет с сайта.', null=True, verbose_name='Окончания публикации')),
|
||||
('szContentHead', models.CharField(default='', help_text='Заголовок контента <small>(допустим HTML-код, будет обработан типографом, если его включить, максимальная длинна <b>512 символов</b>)</small>', max_length=512, verbose_name='Заголовок')),
|
||||
('szContentIntro', ckeditor.fields.RichTextField(default='', help_text='Анонс <small>(допустим HTML-код, будет обработан типографом, если его включить)</small>', verbose_name='Анонс')),
|
||||
('szContentBody', ckeditor.fields.RichTextField(default='', help_text='Содержание <b>БЕЗ АНОНСА</b> <small>(допустим HTML-код, будет обработан типографом, если его включить)</small>', verbose_name='Содержание')),
|
||||
('szContentIntro', models.TextField(default='', help_text='Анонс <small>(допустим HTML-код, будет обработан типографом, если его включить)</small>', verbose_name='Анонс')),
|
||||
('szContentBody', models.TextField(default='', help_text='Содержание <b>БЕЗ АНОНСА</b> <small>(допустим HTML-код, будет обработан типографом, если его включить)</small>', verbose_name='Содержание')),
|
||||
('szContentSlug', models.CharField(blank=True, default='', help_text='Слуг… 128 символов.<br /><small><b>Если оставить пустым, то slug сформируется автоматически</b></small>', max_length=128, null=True, verbose_name='Slug')),
|
||||
('iContentHits', models.PositiveIntegerField(db_index=True, default=0, help_text='Число просмотров', verbose_name='◉')),
|
||||
('bTypograf', models.BooleanField(default=False, help_text='Обработать через <a href="https://www.typograf.ru" target="_blank">Типограф 2.0</a><br /><small><b>НОРМАЛЬНЫЙ ТИПОГРАФ, ХОРОШИЙ HTML, РЕКОМЕНДУЕМ</b> «приклеивает» союзы, поддерживает неразрывные конструкции, замена тире, кавычек и дефисов, расстановка «мягких переносов» в словах длиннее 12 символов, убирает «вдовы» «сироты» (кроме заголовков), расставляет абзацы (кроме заголовков), расшифровывает аббревиатуры (те, что знает и кроме заголовков), висячая пунктуация (только в заголовках) и т.п.</small>', verbose_name='Типограф Стандарт')),
|
||||
20
cadpoint/web/migrations/0003_alter_tbcontent_tags.py
Normal file
@@ -0,0 +1,20 @@
|
||||
# Generated by Django 5.2.13 on 2026-04-09 12:21
|
||||
|
||||
import taggit.managers
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('taggit', '0006_rename_taggeditem_content_type_object_id_taggit_tagg_content_8fc721_idx'),
|
||||
('web', '0001_squashed_0002_alter_tbcontent_szcontentbody_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='tbcontent',
|
||||
name='tags',
|
||||
field=taggit.managers.TaggableManager(blank=True, help_text='Теги можно выбирать из списка или вводить вручную. Многословные теги поддерживаются без кавычек. <b>Теги нужны для присвоения категорий объектам контента<b>.', through='web.RuTaggedItem', to='taggit.Tag', verbose_name='Теги'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,27 @@
|
||||
# Generated by Django 5.2.13 on 2026-04-11 13:18
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('filer', '0018_alter_file_options'),
|
||||
('taggit', '0006_rename_taggeditem_content_type_object_id_taggit_tagg_content_8fc721_idx'),
|
||||
('web', '0003_alter_tbcontent_tags'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='tbcontent',
|
||||
name='bTypograf',
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='tbcontent',
|
||||
index=models.Index(fields=['bContentPublish', 'tdContentPublishUp'], name='web_tbconte_bConten_b53754_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='tbcontent',
|
||||
index=models.Index(fields=['bContentPublish', 'tdContentPublishDown'], name='web_tbconte_bConten_dd200b_idx'),
|
||||
),
|
||||
]
|
||||
@@ -1,17 +1,103 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import datetime
|
||||
import logging
|
||||
|
||||
from django.db import models
|
||||
from django.utils.timezone import now
|
||||
from etpgrf import Hyphenator, Typographer
|
||||
from etpgrf.config import (
|
||||
MODE_MIXED,
|
||||
MODE_MNEMONIC,
|
||||
MODE_UNICODE,
|
||||
SANITIZE_ALL_HTML,
|
||||
SANITIZE_ETPGRF,
|
||||
SANITIZE_NONE,
|
||||
)
|
||||
from filer.fields.image import FilerFileField
|
||||
from ckeditor.fields import RichTextField
|
||||
from taggit.managers import TaggableManager
|
||||
from taggit.models import Tag, TaggedItem
|
||||
from web.add_function import safe_html_special_symbols, post_processing_html
|
||||
import urllib3
|
||||
import re
|
||||
from web.add_function import clean_text_to_slug, safe_html_special_symbols
|
||||
import pytils
|
||||
import random
|
||||
import datetime
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Типограф настраиваем один раз на модуль: в save() он только обрабатывает строку,
|
||||
# а не пересоздаётся на каждый объект контента.
|
||||
_TYPOGRAPHER_LANGS = 'ru+en'
|
||||
_TYPOGRAPHER_MAX_UNHYPHENATED_LEN = 14
|
||||
|
||||
_TYPOGRAPHER_DEFAULT_MODE = MODE_MIXED
|
||||
_TYPOGRAPHER_DEFAULT_HYPHENATION = True
|
||||
_TYPOGRAPHER_DEFAULT_SANITIZER = SANITIZE_NONE
|
||||
_TYPOGRAPHER_DEFAULT_STRIP_SOFT_HYPHENS = True
|
||||
|
||||
|
||||
def _normalize_typograph_mode(value: str | None) -> str:
|
||||
if value in {MODE_MIXED, MODE_UNICODE, MODE_MNEMONIC}:
|
||||
return str(value)
|
||||
return _TYPOGRAPHER_DEFAULT_MODE
|
||||
|
||||
|
||||
def _normalize_typograph_hyphenation(value) -> bool:
|
||||
return bool(_TYPOGRAPHER_DEFAULT_HYPHENATION if value is None else value)
|
||||
|
||||
|
||||
def _normalize_typograph_sanitizer(value):
|
||||
if value in (None, '', 'None', SANITIZE_NONE):
|
||||
return SANITIZE_NONE
|
||||
if value == SANITIZE_ALL_HTML:
|
||||
return SANITIZE_ALL_HTML
|
||||
if value == SANITIZE_ETPGRF:
|
||||
return SANITIZE_ETPGRF
|
||||
return SANITIZE_NONE
|
||||
|
||||
|
||||
def _strip_soft_hyphens(text: str) -> str:
|
||||
"""Удаляет мягкие переносы в любом виде перед передачей текста в etpgrf."""
|
||||
if not text:
|
||||
return text
|
||||
return (
|
||||
text
|
||||
.replace("­", "")
|
||||
.replace("­", "")
|
||||
.replace("­", "")
|
||||
.replace("\u00ad", "")
|
||||
)
|
||||
|
||||
|
||||
def _build_typographer(mode=None, hyphenation=True, sanitizer=None, hanging_punctuation=None) -> Typographer:
|
||||
"""Собирает `etpgrf` с едиными настройками для заголовка и текста."""
|
||||
normalized_mode = _normalize_typograph_mode(mode)
|
||||
normalized_hyphenation = _normalize_typograph_hyphenation(hyphenation)
|
||||
normalized_sanitizer = _normalize_typograph_sanitizer(sanitizer)
|
||||
return Typographer(
|
||||
langs=_TYPOGRAPHER_LANGS,
|
||||
mode=normalized_mode,
|
||||
process_html=True,
|
||||
hyphenation=(
|
||||
Hyphenator(
|
||||
langs=_TYPOGRAPHER_LANGS,
|
||||
max_unhyphenated_len=_TYPOGRAPHER_MAX_UNHYPHENATED_LEN,
|
||||
) if normalized_hyphenation else False
|
||||
),
|
||||
sanitizer=normalized_sanitizer,
|
||||
hanging_punctuation=hanging_punctuation,
|
||||
)
|
||||
|
||||
_TYPOGRAPHER_HEAD = _build_typographer(hanging_punctuation='left')
|
||||
_TYPOGRAPHER_TEXT = _build_typographer()
|
||||
|
||||
def _typograph_text(text: str, typographer: Typographer) -> str:
|
||||
"""Применяет `etpgrf` к HTML-фрагменту и не валит save при сбое библиотеки."""
|
||||
if not text:
|
||||
return text
|
||||
try:
|
||||
return typographer.process(text)
|
||||
except Exception:
|
||||
logger.exception("etpgrf не смог обработать текст, сохраняем исходный вариант")
|
||||
return text
|
||||
|
||||
|
||||
# класс для транслитерации русскоязычных slug
|
||||
@@ -33,25 +119,28 @@ class RuTaggedItem(TaggedItem):
|
||||
return RuTag
|
||||
|
||||
|
||||
# Create your models here.
|
||||
class TbContent(models.Model):
|
||||
# ============================================================
|
||||
# ТАБЛИЦА TbContent (контент для всего-всего-всего)
|
||||
# ------------------------------------------------------------
|
||||
# | id -- id | primarykey bigint NOT NULL AUTO_INCREMENT |
|
||||
# | kCategory_id -- категория (ссылка на таблицу TbCategory) | bigint DEFAULT NULL,
|
||||
# | bContentPublish -- имя файла | TINYINT(1) NOT NULL ADD INDEX |
|
||||
# | tdContentPublishStart -- начало публикации | date NOT NULL ADD INDEX |
|
||||
# | bContentPublish -- признак публикации | TINYINT(1) NOT NULL ADD INDEX |
|
||||
# | tdContentPublishUp -- начало публикации | datetime(6) NOT NULL ADD INDEX |
|
||||
# | tdContentPublishDown -- окончание публикации | datetime(6) NULL ADD INDEX |
|
||||
# | tags -- теги (taggit, M2M) |
|
||||
# | szContentHead -- заголовок | varchar(512) NOT NULL |
|
||||
# | imgContentPreview_id -- картинка превью (ссылка на таблицу filer_image) | bigint DEFAULT NULL ADD INDEX
|
||||
# | szContentAnno -- анонс | longtext NOT NULL,
|
||||
# | szContentBody -- содержание | longtext NOT NULL,
|
||||
# | bTypografS -- включить типограф Typograf 2.0 | tinyint(1) NOT NULL,
|
||||
# | szContentTitle -- title для SEO | longtext NOT NULL,
|
||||
# | szContentKeywords -- keywords для SEO | longtext NOT NULL,
|
||||
# | szContentDescription -- Description для SEO | longtext NOT NULL,
|
||||
# | dtContentCreate -- дата и время создания | datetime(6) NOT NULL,
|
||||
# | dtContentTimeStamp -- штамп времени (время последнего обновления в базе) | datetime(6) NOT NULL
|
||||
# | imgContentPreview_id -- картинка-превью (ссылка на `filer_image`) | bigint DEFAULT NULL |
|
||||
# | szContentIntro -- анонс | longtext NOT NULL |
|
||||
# | szContentBody -- содержание | longtext NOT NULL |
|
||||
# | szContentSlug -- slug | varchar(128) |
|
||||
# | iContentHits -- число просмотров | bigint/unsigned int NOT NULL ADD INDEX |
|
||||
# | szContentKeywords -- keywords для SEO | varchar(256) |
|
||||
# | szContentDescription -- description для SEO | varchar(256) |
|
||||
# | dtContentCreate -- дата и время создания | datetime(6) NOT NULL |
|
||||
# | dtContentTimeStamp -- штамп времени (время последнего обновления) | datetime(6) NOT NULL |
|
||||
#
|
||||
# Типограф и его настройки теперь живут в админке как виртуальные поля,
|
||||
# и в базе отдельно не хранятся.
|
||||
# ============================================================
|
||||
bContentPublish = models.BooleanField(
|
||||
default=True, db_index=True,
|
||||
@@ -75,8 +164,8 @@ class TbContent(models.Model):
|
||||
blank=True,
|
||||
through=RuTaggedItem, # uTaggedItem,
|
||||
verbose_name=u"Теги",
|
||||
help_text=u"Теги через запятую… Регистр не чувствителен… Длинные теги, содержащие пробел, заключайте"
|
||||
u"'в кавычки'… <b>Теги нужны для присвоения категорий объектам контента<b>."
|
||||
help_text=u"Теги можно выбирать из списка или вводить вручную. Многословные теги поддерживаются"
|
||||
u" без кавычек. <b>Теги нужны для присвоения категорий объектам контента<b>."
|
||||
)
|
||||
szContentHead = models.CharField(
|
||||
max_length=512, default=u"", blank=False, null=False,
|
||||
@@ -90,15 +179,13 @@ class TbContent(models.Model):
|
||||
verbose_name="Превью",
|
||||
help_text="Картинка-превью"
|
||||
)
|
||||
szContentIntro = RichTextField(
|
||||
config_name='fine',
|
||||
szContentIntro = models.TextField(
|
||||
default="",
|
||||
verbose_name="Анонс",
|
||||
help_text="Анонс <small>(допустим HTML-код, будет обработан типографом,"
|
||||
" если его включить)</small>"
|
||||
)
|
||||
szContentBody = RichTextField(
|
||||
config_name='fine',
|
||||
szContentBody = models.TextField(
|
||||
default="",
|
||||
verbose_name="Содержание",
|
||||
help_text="Содержание <b>БЕЗ АНОНСА</b> <small>(допустим HTML-код, будет обработан типографом,"
|
||||
@@ -115,19 +202,6 @@ class TbContent(models.Model):
|
||||
verbose_name="◉",
|
||||
help_text="Число просмотров"
|
||||
)
|
||||
bTypograf = models.BooleanField(
|
||||
default=False,
|
||||
verbose_name="Типограф Стандарт",
|
||||
help_text="Обработать через <a href=\"https://www.typograf.ru\""
|
||||
" target=\"_blank\">Типограф 2.0</a><br />"
|
||||
"<small><b>НОРМАЛЬНЫЙ ТИПОГРАФ, ХОРОШИЙ HTML, РЕКОМЕНДУЕМ</b> "
|
||||
"«приклеивает» союзы, поддерживает неразрывные конструкции, "
|
||||
"замена тире, кавычек и дефисов, расстановка «мягких переносов» "
|
||||
"в словах длиннее 12 символов, убирает «вдовы» «сироты» (кроме "
|
||||
"заголовков), расставляет абзацы (кроме заголовков), расшифровывает "
|
||||
"аббревиатуры (те, что знает и кроме заголовков), висячая "
|
||||
"пунктуация (только в заголовках) и т.п.</small>"
|
||||
)
|
||||
szContentKeywords = models.CharField(
|
||||
default="", max_length=256, blank=True, null=True,
|
||||
verbose_name="Keywords (SEO)",
|
||||
@@ -162,88 +236,51 @@ class TbContent(models.Model):
|
||||
return u"%03d: %s" % (self.id, result[:50] + "…" if len(result) > 50 else result)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
# переопределяем метод save() чтобы "проверуть" тексты через типографы...
|
||||
# Переопределяем save(), чтобы автоматически типографировать контент перед сохранением.
|
||||
typograph_enabled = getattr(self, '_typograph_enabled', False)
|
||||
typograph_mode = getattr(self, '_typograph_mode', _TYPOGRAPHER_DEFAULT_MODE)
|
||||
typograph_hyphenation = getattr(self, '_typograph_hyphenation', _TYPOGRAPHER_DEFAULT_HYPHENATION)
|
||||
typograph_sanitizer = getattr(self, '_typograph_sanitizer', _TYPOGRAPHER_DEFAULT_SANITIZER)
|
||||
typograph_strip_soft_hyphens = getattr(
|
||||
self,
|
||||
'_typograph_strip_soft_hyphens',
|
||||
_TYPOGRAPHER_DEFAULT_STRIP_SOFT_HYPHENS,
|
||||
)
|
||||
if self.szContentSlug is None or self.szContentSlug == "" or " " in self.szContentSlug:
|
||||
# print("ку-ку", self.szContentHead)
|
||||
result_slug = pytils.translit.slugify(
|
||||
safe_html_special_symbols(self.szContentHead)).lower()
|
||||
while TbContent.objects.filter(szContentSlug=result_slug).count() != 0:
|
||||
result_slug = "%s-%x" % (result_slug[0: -3], int(random.uniform(0, 255)))
|
||||
base_slug = clean_text_to_slug(self.szContentHead)
|
||||
result_slug = base_slug
|
||||
suffix = 1
|
||||
while TbContent.objects.filter(szContentSlug=result_slug).exists():
|
||||
result_slug = f"{base_slug}-{suffix}"
|
||||
suffix += 1
|
||||
self.szContentSlug = result_slug
|
||||
if self.bTypograf:
|
||||
# Используем типограф Eugene Spearance (https://www.typograf.ru) через API
|
||||
# Настройки стиля типографики см. тут: https://www.typograf.ru/webservice/about/
|
||||
try:
|
||||
http = urllib3.PoolManager()
|
||||
resp = http.request("POST", "https://www.typograf.ru/webservice/",
|
||||
fields={"text": self.szContentHead.encode('cp1251'),
|
||||
'xml': '<?xml version="1.0" encoding="windows-1251" ?>'
|
||||
'<preferences>'
|
||||
' <!-- Абзацы НЕ СТАВИМ-->'
|
||||
' <paragraph insert="0" />'
|
||||
' <!-- Переводы строк НЕ СТАВИМ -->'
|
||||
' <newline insert="0" />'
|
||||
' <!-- Неразрывные конструкции ДА -->'
|
||||
' <hanging-punct insert="1" />'
|
||||
' <!-- Переносы слов длиннее 12 знаков -->'
|
||||
' <hyphen insert="1" length="12" />'
|
||||
'</preferences>'.encode('cp1251')})
|
||||
result = resp.data.decode('cp1251')
|
||||
if len(result) <= 512:
|
||||
self.szContentHead = result
|
||||
resp = http.request("POST", "https://www.typograf.ru/webservice/",
|
||||
fields={"text": self.szContentIntro.encode('cp1251'),
|
||||
'xml': '<?xml version="1.0" encoding="windows-1251" ?>'
|
||||
'<preferences>'
|
||||
' <!-- Висячая пунктуация УДАЛЯЕТСЯ -->'
|
||||
' <hanging-punct insert="1" />'
|
||||
' <!-- Висячие слова УДАЛЯЕМ -->'
|
||||
' <hanging-line delete="1" />'
|
||||
' <!-- Переносы слов длиннее 12 знаков -->'
|
||||
' <hyphen insert="1" length="12" />'
|
||||
' <!-- Параметры ссылок -->'
|
||||
' <link target="_blank" />'
|
||||
'</preferences>'.encode('cp1251')})
|
||||
self.szContentIntro = resp.data.decode('cp1251')
|
||||
resp = http.request("POST", "https://www.typograf.ru/webservice/",
|
||||
fields={"text": self.szContentBody.encode('cp1251'),
|
||||
'xml': '<?xml version="1.0" encoding="windows-1251" ?>'
|
||||
'<preferences>'
|
||||
' <!-- Висячая пунктуация УДАЛЯЕТСЯ -->'
|
||||
' <hanging-punct insert="1" />'
|
||||
' <!-- Висячие слова УДАЛЯЕМ -->'
|
||||
' <hanging-line delete="1" />'
|
||||
' <!-- Переносы слов длиннее 10 знаков -->'
|
||||
' <hyphen insert="1" length="12" />'
|
||||
' <!-- Параметры ссылок -->'
|
||||
' <link target="_blank" />'
|
||||
'</preferences>'.encode('cp1251')})
|
||||
self.szContentBody = resp.data.decode('cp1251')
|
||||
except:
|
||||
# если API типографа не доступен, то подключаем локальный типограф Муравьева
|
||||
import web.EMT as EMT
|
||||
emt_header = EMT.EMTypograph()
|
||||
emt_header.setup({'Text.paragraphs': 'off'})
|
||||
emt_header.set_text(self.szContentHead)
|
||||
self.szContentHead = emt_header.apply()
|
||||
emt_intro = EMT.EMTypograph()
|
||||
# print("==================================== self.szContentBody\n", self.szContentIntro)
|
||||
# print("-----------------")
|
||||
emt_intro.set_text(self.szContentIntro)
|
||||
# emt_intro.set_tag_layout(layout=EMT.LAYOUT_CLASS)
|
||||
self.szContentIntro = emt_intro.apply()
|
||||
self.szContentIntro = post_processing_html(self.szContentIntro)
|
||||
# print(self.szContentIntro)
|
||||
emt_body = EMT.EMTypograph()
|
||||
# print("==================================== self.szContentBody")
|
||||
# print(self.szContentBody)
|
||||
# print("-----------------")
|
||||
emt_body.set_text(self.szContentBody)
|
||||
# emt_body.set_tag_layout(layout=EMT.LAYOUT_CLASS)
|
||||
self.szContentBody = emt_body.apply()
|
||||
self.szContentBody = post_processing_html(self.szContentBody)
|
||||
# print(self.szContentBody)
|
||||
self.bTypograf = False
|
||||
if typograph_enabled:
|
||||
# `etpgrf` уже умеет HTML-режим и висячую пунктуацию, поэтому здесь
|
||||
# не нужен старый локальный fallback.
|
||||
# Мягкие переносы убираем заранее: `etpgrf` не очищает их сам, а они
|
||||
# потом мешают и типографу, и последующей нормализации текста.
|
||||
# Для заголовка включаем левую висячую пунктуацию, а для анонса и
|
||||
# тела текста оставляем обычную обработку без hanging punctuation.
|
||||
if typograph_strip_soft_hyphens:
|
||||
self.szContentHead = _strip_soft_hyphens(self.szContentHead)
|
||||
self.szContentIntro = _strip_soft_hyphens(self.szContentIntro)
|
||||
self.szContentBody = _strip_soft_hyphens(self.szContentBody)
|
||||
head_typographer = _build_typographer(
|
||||
mode=typograph_mode,
|
||||
hyphenation=typograph_hyphenation,
|
||||
sanitizer=typograph_sanitizer,
|
||||
hanging_punctuation='left',
|
||||
)
|
||||
text_typographer = _build_typographer(
|
||||
mode=typograph_mode,
|
||||
hyphenation=typograph_hyphenation,
|
||||
sanitizer=typograph_sanitizer,
|
||||
hanging_punctuation=False,
|
||||
)
|
||||
self.szContentHead = _typograph_text(self.szContentHead, head_typographer)
|
||||
self.szContentIntro = _typograph_text(self.szContentIntro, text_typographer)
|
||||
self.szContentBody = _typograph_text(self.szContentBody, text_typographer)
|
||||
if self.dtContentCreate is None:
|
||||
self.dtContentCreate = datetime.datetime.now()
|
||||
super(TbContent, self).save(*args, **kwargs)
|
||||
@@ -251,4 +288,10 @@ class TbContent(models.Model):
|
||||
class Meta:
|
||||
verbose_name = "Контент"
|
||||
verbose_name_plural = u"Контент"
|
||||
# Чтобы боковая навигация или лента нне упиралась в SQLite и работала быстро,
|
||||
# добавляем составные индексы.
|
||||
indexes = [
|
||||
models.Index(fields=['bContentPublish', 'tdContentPublishUp']),
|
||||
models.Index(fields=['bContentPublish', 'tdContentPublishDown']),
|
||||
]
|
||||
ordering = ['-tdContentPublishUp', ]
|
||||
|
||||
64
cadpoint/web/sitemaps.py
Normal file
@@ -0,0 +1,64 @@
|
||||
from django.contrib.sitemaps import Sitemap
|
||||
from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
from django.db.models import Q
|
||||
from taggit.models import Tag
|
||||
|
||||
from web.models import TbContent
|
||||
|
||||
|
||||
class CadpointSitemap(Sitemap):
|
||||
"""Одна карта сайта для публичных страниц CADpoint."""
|
||||
|
||||
changefreq = 'weekly'
|
||||
|
||||
def items(self):
|
||||
now_value = timezone.now()
|
||||
content_items = list(
|
||||
TbContent.objects.filter(
|
||||
bContentPublish=True,
|
||||
tdContentPublishUp__lte=now_value,
|
||||
).filter(
|
||||
Q(tdContentPublishDown__isnull=True) | Q(tdContentPublishDown__gt=now_value)
|
||||
).order_by('-tdContentPublishUp', 'id')
|
||||
)
|
||||
latest_content = content_items[0].dtContentTimeStamp if content_items else None
|
||||
|
||||
sitemap_items = [
|
||||
{'kind': 'home', 'lastmod': latest_content},
|
||||
{'kind': 'alltags', 'lastmod': latest_content},
|
||||
]
|
||||
|
||||
tags = Tag.objects.filter(taggit_taggeditem_items__isnull=False).distinct().order_by('name')
|
||||
sitemap_items.extend({'kind': 'tag', 'tag': tag} for tag in tags)
|
||||
sitemap_items.extend({'kind': 'item', 'item': item} for item in content_items)
|
||||
return sitemap_items
|
||||
|
||||
def location(self, item):
|
||||
kind = item['kind']
|
||||
if kind == 'home':
|
||||
return '/'
|
||||
if kind == 'alltags':
|
||||
return reverse('web_alltags')
|
||||
if kind == 'tag':
|
||||
return f"/tag_{item['tag'].slug}"
|
||||
return f"/item/{item['item'].id}-{item['item'].szContentSlug}"
|
||||
|
||||
def lastmod(self, item):
|
||||
kind = item['kind']
|
||||
if kind in {'home', 'alltags'}:
|
||||
return item.get('lastmod')
|
||||
if kind == 'item':
|
||||
return item['item'].dtContentTimeStamp
|
||||
return None
|
||||
|
||||
def priority(self, item):
|
||||
kind = item['kind']
|
||||
if kind == 'home':
|
||||
return 1.0
|
||||
if kind == 'alltags':
|
||||
return 0.6
|
||||
if kind == 'tag':
|
||||
return 0.5
|
||||
return 0.8
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from django import template
|
||||
from web.add_function import safe_html_special_symbols
|
||||
import pytils
|
||||
from web.add_function import clean_text_to_slug, safe_html_special_symbols
|
||||
|
||||
register = template.Library()
|
||||
|
||||
@@ -16,9 +15,9 @@ def slug_ru(value: str, arg: int) -> str:
|
||||
"""
|
||||
try:
|
||||
arg = int(arg)
|
||||
return pytils.translit.slugify(str(value).lower())[:int(arg)]
|
||||
return clean_text_to_slug(str(value))[:int(arg)]
|
||||
except ValueError:
|
||||
return pytils.translit.slugify(str(value).lower())
|
||||
return clean_text_to_slug(str(value))
|
||||
|
||||
|
||||
@register.filter
|
||||
|
||||
@@ -1,3 +1,510 @@
|
||||
from django.test import TestCase
|
||||
from unittest.mock import patch
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.forms import Textarea
|
||||
from django.test import RequestFactory, SimpleTestCase, TestCase
|
||||
from django.urls import reverse
|
||||
from etpgrf.config import MODE_UNICODE, SANITIZE_ETPGRF
|
||||
from taggit.models import Tag
|
||||
|
||||
from web.admin import AdminContentForm
|
||||
from web.add_function import clean_text_to_slug, safe_html_special_symbols
|
||||
from web.legacy_links import build_canonical_url, replace_legacy_links
|
||||
from web.models import TbContent
|
||||
|
||||
|
||||
# Этот файл смешивает два типа проверок:
|
||||
# - простые тесты чистой логики без базы данных (`SimpleTestCase`),
|
||||
# - интеграционные Django-тесты с базой и HTTP-клиентом (`TestCase`).
|
||||
class LegacyLinksTests(SimpleTestCase):
|
||||
# `SimpleTestCase` подходит для функций, которые не ходят в базу и не
|
||||
# требуют полноценного HTTP-запроса: здесь мы просто проверяем строки.
|
||||
def test_build_canonical_url_without_slug(self):
|
||||
self.assertEqual(build_canonical_url(123, ''), '/item/123-')
|
||||
|
||||
def test_replace_legacy_links_rewrites_internal_urls(self):
|
||||
text = (
|
||||
'<a href="/news/1-latest-news/123-old-title.html">link</a>'
|
||||
'<a href="http://www.cadpoint.ru/component/content/article/456-some-article.html">x</a>'
|
||||
'<a href="https://example.com/news/1-latest-news/123-old-title.html">external</a>'
|
||||
)
|
||||
new_text, matches = replace_legacy_links(text, {123: 'new-title', 456: 'article-title'})
|
||||
|
||||
self.assertIn('/item/123-new-title', new_text)
|
||||
self.assertIn('/item/456-article-title', new_text)
|
||||
self.assertIn('https://example.com/news/1-latest-news/123-old-title.html', new_text)
|
||||
self.assertEqual(len(matches), 2)
|
||||
|
||||
def test_replace_legacy_links_does_not_touch_image_urls(self):
|
||||
text = (
|
||||
'<a href="/news/1-latest-news/123-old-title.html">link</a>'
|
||||
'<img src="/images/stories/news/photo123.jpg" alt="photo">'
|
||||
)
|
||||
new_text, matches = replace_legacy_links(text, {123: 'new-title'})
|
||||
|
||||
self.assertIn('/item/123-new-title', new_text)
|
||||
self.assertIn('/images/stories/news/photo123.jpg', new_text)
|
||||
self.assertEqual(len(matches), 1)
|
||||
|
||||
|
||||
# Эти тесты тоже без базы: проверяем очистку HTML и подготовку slug'ов.
|
||||
class SafeHtmlSpecialSymbolsTests(SimpleTestCase):
|
||||
def test_strips_html_tags_and_decodes_entities(self):
|
||||
text = '<p>«Привет <b>мир</b>» ­<script>alert(1)</script><style>p{}</style></p>'
|
||||
|
||||
|
||||
self.assertEqual(safe_html_special_symbols(text), '«Привет мир»')
|
||||
|
||||
def test_clean_text_to_slug_normalizes_non_latin_symbols(self):
|
||||
self.assertEqual(clean_text_to_slug('αβγ ΔΩ'), 'content')
|
||||
self.assertEqual(clean_text_to_slug('₽ € $ ₴ ₿'), 'content')
|
||||
|
||||
|
||||
# Здесь проверяем форму админки: её поля, виджеты и виртуальные настройки.
|
||||
class AdminTypographFormTests(SimpleTestCase):
|
||||
def test_admin_form_exposes_virtual_typograph_fields(self):
|
||||
form = AdminContentForm()
|
||||
|
||||
self.assertIn('typograph_enabled', form.fields)
|
||||
self.assertIn('typograph_strip_soft_hyphens', form.fields)
|
||||
self.assertIn('typograph_mode', form.fields)
|
||||
self.assertIn('typograph_hyphenation', form.fields)
|
||||
self.assertIn('typograph_sanitizer', form.fields)
|
||||
self.assertEqual(form.fields['typograph_mode'].initial, 'mixed')
|
||||
self.assertTrue(form.fields['typograph_strip_soft_hyphens'].initial)
|
||||
self.assertTrue(form.fields['typograph_hyphenation'].initial)
|
||||
self.assertEqual(form.fields['typograph_sanitizer'].initial, 'None')
|
||||
|
||||
def test_admin_form_adds_codemirror_attrs_and_media(self):
|
||||
form = AdminContentForm()
|
||||
|
||||
for field_name in ('szContentHead', 'szContentIntro', 'szContentBody'):
|
||||
self.assertIsInstance(form.fields[field_name].widget, Textarea)
|
||||
self.assertEqual(
|
||||
form.fields[field_name].widget.attrs.get('data-codemirror-editor'),
|
||||
'1',
|
||||
)
|
||||
self.assertEqual(
|
||||
form.fields[field_name].widget.attrs.get('data-language'),
|
||||
'html',
|
||||
)
|
||||
|
||||
for field_name in ('szContentKeywords', 'szContentDescription'):
|
||||
self.assertIsInstance(form.fields[field_name].widget, Textarea)
|
||||
self.assertNotEqual(
|
||||
form.fields[field_name].widget.attrs.get('data-codemirror-editor'),
|
||||
'1',
|
||||
)
|
||||
|
||||
self.assertIn('codemirror/editor.js', str(form.media))
|
||||
|
||||
def test_tbcontent_model_has_no_btypograf_field(self):
|
||||
self.assertNotIn('bTypograf', [field.name for field in TbContent._meta.fields])
|
||||
|
||||
def test_tbcontent_str_uses_clean_text(self):
|
||||
item = TbContent(id=7, szContentHead='<b>«Привет мир»</b>')
|
||||
|
||||
self.assertEqual(str(item), '007: «Привет мир»')
|
||||
|
||||
|
||||
# В этих тестах уже нужна база и `client`, потому что мы проверяем Django-views
|
||||
# и JSON-ответы как реальные запросы из браузера.
|
||||
class TagAutocompleteTests(TestCase):
|
||||
# `TestCase` поднимает тестовую БД, а `client` умеет делать полноценные запросы
|
||||
# к Django, как будто их отправил браузер.
|
||||
def setUp(self):
|
||||
user_model = get_user_model()
|
||||
self.user = user_model.objects.create_superuser(
|
||||
username='admin',
|
||||
email='admin@example.com',
|
||||
password='password',
|
||||
)
|
||||
Tag.objects.create(name='alpha')
|
||||
Tag.objects.create(name='beta')
|
||||
Tag.objects.create(name='gamma')
|
||||
self.client.force_login(self.user)
|
||||
|
||||
def test_returns_tag_results_for_term(self):
|
||||
response = self.client.get(
|
||||
reverse('web_tag_autocomplete'),
|
||||
{'term': 'al'},
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
payload = response.json()
|
||||
self.assertEqual(payload['pagination']['more'], False)
|
||||
self.assertEqual([item['text'] for item in payload['results']], ['alpha'])
|
||||
|
||||
def test_returns_initial_tag_batch_without_term(self):
|
||||
response = self.client.get(reverse('web_tag_autocomplete'))
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
payload = response.json()
|
||||
self.assertEqual(len(payload['results']), 3)
|
||||
self.assertEqual(payload['pagination']['more'], False)
|
||||
|
||||
def test_paginates_tag_results(self):
|
||||
Tag.objects.all().delete()
|
||||
for index in range(30):
|
||||
Tag.objects.create(name=f'tag-{index:02d}')
|
||||
|
||||
response = self.client.get(reverse('web_tag_autocomplete'), {'page': 1})
|
||||
self.assertEqual(response.status_code, 200)
|
||||
payload = response.json()
|
||||
self.assertEqual(len(payload['results']), 25)
|
||||
self.assertEqual(payload['pagination']['more'], True)
|
||||
|
||||
response = self.client.get(reverse('web_tag_autocomplete'), {'page': 2})
|
||||
self.assertEqual(response.status_code, 200)
|
||||
payload = response.json()
|
||||
self.assertEqual(len(payload['results']), 5)
|
||||
self.assertEqual(payload['pagination']['more'], False)
|
||||
|
||||
|
||||
# Страница со всеми тегами — тоже обычный Django-response, поэтому тут `TestCase`.
|
||||
class AllTagsPageTests(TestCase):
|
||||
def setUp(self):
|
||||
user_model = get_user_model()
|
||||
self.user = user_model.objects.create_superuser(
|
||||
username='admin',
|
||||
email='admin@example.com',
|
||||
password='password',
|
||||
)
|
||||
self.client.force_login(self.user)
|
||||
|
||||
item1 = TbContent.objects.create(
|
||||
szContentHead='Тест 1',
|
||||
szContentIntro='Анонс 1',
|
||||
szContentBody='Тело 1',
|
||||
szContentSlug='test-1',
|
||||
bContentPublish=True,
|
||||
)
|
||||
item2 = TbContent.objects.create(
|
||||
szContentHead='Тест 2',
|
||||
szContentIntro='Анонс 2',
|
||||
szContentBody='Тело 2',
|
||||
szContentSlug='test-2',
|
||||
bContentPublish=True,
|
||||
)
|
||||
item1.tags.add('alpha', 'beta')
|
||||
item2.tags.add('alpha')
|
||||
|
||||
def test_alltags_page_lists_all_tags_with_counts(self):
|
||||
response = self.client.get(reverse('web_alltags'))
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, '<main id="main-content">')
|
||||
self.assertContains(response, 'Все теги сайта')
|
||||
self.assertContains(response, '/tag_alpha')
|
||||
self.assertContains(response, '/tag_beta')
|
||||
self.assertContains(response, '<b class="_tag">2</b>')
|
||||
self.assertContains(response, '<b class="_tag">1</b>')
|
||||
|
||||
def test_footer_counters_are_loaded_from_static_js(self):
|
||||
response = self.client.get(reverse('web_alltags'))
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, 'js/footer-counters.js')
|
||||
self.assertNotContains(response, 'googletagmanager.com/gtag/js?id=UA-9116991-1')
|
||||
self.assertNotContains(response, 'mc.yandex.ru/metrika/tag.js')
|
||||
self.assertNotContains(response, 'top-fwz1.mail.ru/js/code.js')
|
||||
|
||||
def test_accept_cookies_banner_loads_static_js(self):
|
||||
response = self.client.get(reverse('web_alltags'))
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, 'js/accept-cookies.js')
|
||||
self.assertContains(response, 'id="cookies_accept_button"')
|
||||
self.assertNotContains(response, 'CookieAcceptDate = new Date()')
|
||||
self.assertNotContains(response, 'onclick="CookieAcceptDate')
|
||||
|
||||
|
||||
# Здесь проверяем поведение страницы тега, включая пустое состояние.
|
||||
class TagEmptyStateTests(TestCase):
|
||||
def setUp(self):
|
||||
self.item = TbContent.objects.create(
|
||||
szContentHead='Тест 1',
|
||||
szContentIntro='Анонс 1',
|
||||
szContentBody='Тело 1',
|
||||
szContentSlug='test-1',
|
||||
bContentPublish=True,
|
||||
)
|
||||
self.item.tags.add('alpha')
|
||||
Tag.objects.create(name='lonely')
|
||||
|
||||
def test_tag_page_with_news_still_renders_entries(self):
|
||||
response = self.client.get('/tag_alpha')
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, 'Тест 1')
|
||||
self.assertNotContains(response, 'Новостей не найдено')
|
||||
|
||||
def test_tag_page_without_news_shows_empty_state(self):
|
||||
response = self.client.get('/tag_lonely')
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, 'Новостей не найдено')
|
||||
self.assertContains(response, 'По этому тегу пока нет опубликованных новостей.')
|
||||
self.assertNotContains(response, 'Тест 1')
|
||||
|
||||
def test_tag_page_for_missing_slug_shows_missing_tag_message(self):
|
||||
response = self.client.get('/tag_rebranded-tag')
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, 'Тег не найден')
|
||||
self.assertContains(response, 'не найден или был переименован')
|
||||
|
||||
|
||||
# Тут мы проверяем модель и `save()`: slug, типограф, счётчик просмотров и SEO-поля.
|
||||
class TypographTests(TestCase):
|
||||
def test_save_generates_slug_from_clean_text(self):
|
||||
item = TbContent(szContentHead='<b>Привет мир</b>')
|
||||
|
||||
item.save()
|
||||
|
||||
self.assertEqual(item.szContentSlug, 'privet-mir')
|
||||
|
||||
def test_save_normalizes_non_latin_slug_to_default(self):
|
||||
item = TbContent(szContentHead='αβγ ΔΩ')
|
||||
|
||||
item.save()
|
||||
|
||||
self.assertEqual(item.szContentSlug, 'content')
|
||||
|
||||
def test_save_uses_etpgrf_and_clears_flag(self):
|
||||
item = TbContent(
|
||||
szContentHead='«Привет»',
|
||||
szContentIntro='<p>Абзац</p>',
|
||||
szContentBody='<p>Тело</p>',
|
||||
)
|
||||
item._typograph_enabled = True
|
||||
|
||||
with patch('web.models._build_typographer') as build_mock:
|
||||
build_mock.return_value.process.side_effect = lambda text: f'[{text}]'
|
||||
item.save()
|
||||
|
||||
self.assertEqual(build_mock.call_count, 2)
|
||||
self.assertEqual(item.szContentHead, '[«Привет»]')
|
||||
self.assertEqual(item.szContentIntro, '[<p>Абзац</p>]')
|
||||
self.assertEqual(item.szContentBody, '[<p>Тело</p>]')
|
||||
|
||||
def test_save_uses_virtual_typograph_options(self):
|
||||
item = TbContent(
|
||||
szContentHead='Привет',
|
||||
szContentIntro='Текст',
|
||||
szContentBody='Тело',
|
||||
)
|
||||
item._typograph_enabled = True
|
||||
item._typograph_mode = MODE_UNICODE
|
||||
item._typograph_hyphenation = False
|
||||
item._typograph_sanitizer = SANITIZE_ETPGRF
|
||||
|
||||
with patch('web.models._build_typographer') as build_mock:
|
||||
fake_typographer = build_mock.return_value
|
||||
fake_typographer.process.side_effect = lambda text: text
|
||||
item.save()
|
||||
|
||||
self.assertEqual(build_mock.call_count, 2)
|
||||
self.assertEqual(
|
||||
build_mock.call_args_list[0].kwargs,
|
||||
{
|
||||
'mode': MODE_UNICODE,
|
||||
'hyphenation': False,
|
||||
'sanitizer': SANITIZE_ETPGRF,
|
||||
'hanging_punctuation': 'left',
|
||||
},
|
||||
)
|
||||
self.assertEqual(
|
||||
build_mock.call_args_list[1].kwargs,
|
||||
{
|
||||
'mode': MODE_UNICODE,
|
||||
'hyphenation': False,
|
||||
'sanitizer': SANITIZE_ETPGRF,
|
||||
'hanging_punctuation': False,
|
||||
},
|
||||
)
|
||||
|
||||
def test_save_strips_soft_hyphens_before_typograph(self):
|
||||
item = TbContent(
|
||||
szContentHead='При­вет\u00ad',
|
||||
szContentIntro='А­нонс',
|
||||
szContentBody='Те­ло\u00ad',
|
||||
)
|
||||
item._typograph_enabled = True
|
||||
|
||||
with patch('web.models._build_typographer') as build_mock:
|
||||
build_mock.return_value.process.side_effect = lambda text: f'[{text}]'
|
||||
item.save()
|
||||
|
||||
self.assertEqual(build_mock.call_count, 2)
|
||||
self.assertEqual(item.szContentHead, '[Привет]')
|
||||
self.assertEqual(item.szContentIntro, '[Анонс]')
|
||||
self.assertEqual(item.szContentBody, '[Тело]')
|
||||
|
||||
def test_tbcontent_has_composite_indexes_for_navigation(self):
|
||||
index_fields = [tuple(index.fields) for index in TbContent._meta.indexes]
|
||||
|
||||
self.assertIn(('bContentPublish', 'tdContentPublishUp'), index_fields)
|
||||
self.assertIn(('bContentPublish', 'tdContentPublishDown'), index_fields)
|
||||
|
||||
def test_show_item_increments_hits_without_touching_timestamp(self):
|
||||
item = TbContent.objects.create(
|
||||
szContentHead='Проверка просмотра',
|
||||
szContentIntro='Короткий анонс',
|
||||
szContentBody='Полный текст',
|
||||
szContentSlug='real-slug',
|
||||
bContentPublish=True,
|
||||
)
|
||||
timestamp_before = item.dtContentTimeStamp
|
||||
|
||||
response = self.client.get(f'/item/{item.id}-wrong-slug')
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, '<main id="main-content">')
|
||||
self.assertContains(response, '<article class="col-12 col-md-9" aria-labelledby="article-title">')
|
||||
self.assertContains(response, '<title>Проверка просмотра | CADpoint</title>')
|
||||
self.assertContains(
|
||||
response,
|
||||
f'<link rel="canonical" href="http://testserver/item/{item.id}-{item.szContentSlug}" />',
|
||||
)
|
||||
self.assertNotContains(response, 'wrong-slug')
|
||||
self.assertContains(response, '<script type="application/ld+json">')
|
||||
self.assertContains(response, '"@type": "WebSite"')
|
||||
self.assertContains(response, '"@type": "Article"')
|
||||
self.assertRegex(
|
||||
response.content.decode(),
|
||||
r'"datePublished": "\d{4}-\d{2}-\d{2}T\d{2}:\d{2}[+-]\d{2}:\d{2}"',
|
||||
)
|
||||
self.assertRegex(
|
||||
response.content.decode(),
|
||||
r'"dateModified": "\d{4}-\d{2}-\d{2}T\d{2}:\d{2}[+-]\d{2}:\d{2}"',
|
||||
)
|
||||
item.refresh_from_db()
|
||||
self.assertEqual(item.iContentHits, 1)
|
||||
self.assertEqual(item.dtContentTimeStamp, timestamp_before)
|
||||
|
||||
def test_show_item_keywords_prioritize_explicit_seo_values(self):
|
||||
item = TbContent.objects.create(
|
||||
szContentHead='SEO ключи',
|
||||
szContentIntro='Анонс',
|
||||
szContentBody='Текст',
|
||||
szContentSlug='seo-klyuchi',
|
||||
szContentKeywords='ключ1, ключ2',
|
||||
bContentPublish=True,
|
||||
)
|
||||
item.tags.add('alpha', 'beta')
|
||||
|
||||
response = self.client.get(f'/item/{item.id}-{item.szContentSlug}')
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(
|
||||
response,
|
||||
'<meta name="keywords" content="cadpoint, ключ1, ключ2, alpha, beta, новости" />',
|
||||
)
|
||||
self.assertNotContains(response, 'None')
|
||||
|
||||
def test_show_item_keywords_without_explicit_value_do_not_render_none(self):
|
||||
item = TbContent.objects.create(
|
||||
szContentHead='SEO пусто',
|
||||
szContentIntro='Анонс',
|
||||
szContentBody='Текст',
|
||||
szContentSlug='seo-pusto',
|
||||
szContentKeywords=None,
|
||||
bContentPublish=True,
|
||||
)
|
||||
item.tags.add('alpha')
|
||||
|
||||
response = self.client.get(f'/item/{item.id}-{item.szContentSlug}')
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(
|
||||
response,
|
||||
'<meta name="keywords" content="cadpoint, alpha, новости" />',
|
||||
)
|
||||
self.assertNotContains(response, 'None')
|
||||
|
||||
|
||||
# Это тесты карты сайта: проверяем, что в XML попадают только публичные страницы.
|
||||
class SitemapTests(TestCase):
|
||||
def setUp(self):
|
||||
self.published = TbContent.objects.create(
|
||||
szContentHead='Опубликованная статья',
|
||||
szContentIntro='Анонс',
|
||||
szContentBody='Текст',
|
||||
szContentSlug='opublikovannaya-statya',
|
||||
bContentPublish=True,
|
||||
)
|
||||
self.published.tags.add('alpha')
|
||||
TbContent.objects.create(
|
||||
szContentHead='Скрытая статья',
|
||||
szContentIntro='Анонс',
|
||||
szContentBody='Текст',
|
||||
szContentSlug='skrytaya-statya',
|
||||
bContentPublish=False,
|
||||
)
|
||||
|
||||
def test_sitemap_uses_django_framework_and_lists_public_pages(self):
|
||||
response = self.client.get(reverse('web_sitemap'))
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, '<?xml version="1.0" encoding="UTF-8"?>', html=False)
|
||||
self.assertContains(response, '<loc>http://testserver/</loc>', html=False)
|
||||
self.assertContains(response, '<loc>http://testserver/alltags</loc>', html=False)
|
||||
self.assertContains(
|
||||
response,
|
||||
f'<loc>http://testserver/item/{self.published.id}-{self.published.szContentSlug}</loc>',
|
||||
html=False,
|
||||
)
|
||||
self.assertContains(response, '<loc>http://testserver/tag_alpha</loc>', html=False)
|
||||
self.assertNotContains(response, 'skrytaya-statya')
|
||||
|
||||
|
||||
# Для error-handlers удобно использовать `RequestFactory`: мы сами создаём
|
||||
# request и вызываем функцию-обработчик напрямую, без прохода через URL-роутинг.
|
||||
class ErrorHandlersTests(SimpleTestCase):
|
||||
def setUp(self):
|
||||
self.factory = RequestFactory()
|
||||
|
||||
def test_handler400_renders_modern_template(self):
|
||||
from web.views import handler400
|
||||
|
||||
request = self.factory.get('/bad-request/')
|
||||
response = handler400(request, Exception('bad request'))
|
||||
|
||||
self.assertEqual(response.status_code, 400)
|
||||
self.assertContains(response, 'CADpoint.ru - http 400 error', status_code=400)
|
||||
self.assertContains(response, '<meta name="robots" content="noindex,nofollow" />', status_code=400)
|
||||
self.assertContains(response, 'запрос получился некорректным', status_code=400)
|
||||
self.assertContains(response, 'Вернуться на главную', status_code=400)
|
||||
|
||||
def test_handler403_renders_modern_template(self):
|
||||
from web.views import handler403
|
||||
|
||||
request = self.factory.get('/forbidden/')
|
||||
response = handler403(request, Exception('forbidden'))
|
||||
|
||||
self.assertEqual(response.status_code, 403)
|
||||
self.assertContains(response, 'CADpoint.ru - http 403 error', status_code=403)
|
||||
self.assertContains(response, '<meta name="robots" content="noindex,nofollow" />', status_code=403)
|
||||
self.assertContains(response, 'доступ к этой странице ограничен.', status_code=403)
|
||||
|
||||
def test_handler404_renders_modern_template(self):
|
||||
response = self.client.get('/no-such-page/')
|
||||
|
||||
self.assertEqual(response.status_code, 404)
|
||||
self.assertContains(response, 'CADpoint.ru - http 404 error', status_code=404)
|
||||
self.assertContains(response, '<meta name="robots" content="noindex,nofollow" />', status_code=404)
|
||||
self.assertContains(response, 'похоже, такой страницы или картинки больше нет.', status_code=404)
|
||||
|
||||
def test_handler500_renders_modern_template(self):
|
||||
from web.views import handler500
|
||||
|
||||
request = self.factory.get('/boom/')
|
||||
response = handler500(request)
|
||||
|
||||
self.assertEqual(response.status_code, 500)
|
||||
self.assertContains(response, 'CADpoint.ru - http 500 error', status_code=500)
|
||||
self.assertContains(response, '<meta name="robots" content="noindex,nofollow" />', status_code=500)
|
||||
self.assertContains(response, 'подождите, скоро всё починят…', status_code=500)
|
||||
|
||||
|
||||
# Create your tests here.
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import math
|
||||
|
||||
from django.shortcuts import render, HttpResponseRedirect
|
||||
from django.http import Http404
|
||||
from django.db.models import Q
|
||||
from django.http import Http404, JsonResponse
|
||||
from django.db.models import Count, F, Q
|
||||
from django.views.decorators.http import require_GET
|
||||
# from datetime import datetime
|
||||
from django.utils import timezone
|
||||
from taggit.models import Tag
|
||||
|
||||
from web.models import TbContent
|
||||
from web.add_function import *
|
||||
import pytz
|
||||
from cadpoint import settings
|
||||
|
||||
# Create your views here.
|
||||
def handler404(request, exception: str) -> render:
|
||||
def handler404(request, exception: str):
|
||||
""" Обработчик ошибки 404
|
||||
|
||||
:param request: http-запрос
|
||||
@@ -21,7 +26,31 @@ def handler404(request, exception: str) -> render:
|
||||
return response
|
||||
|
||||
|
||||
def handler500(request) -> render:
|
||||
def handler400(request, exception: Exception):
|
||||
""" Обработчик ошибки 400
|
||||
|
||||
:param request:
|
||||
:param exception:
|
||||
:return: response:
|
||||
"""
|
||||
response = render(request, "400.html", {"MSG": exception})
|
||||
response.status_code = 400
|
||||
return response
|
||||
|
||||
|
||||
def handler403(request, exception: Exception):
|
||||
""" Обработчик ошибки 403
|
||||
|
||||
:param request:
|
||||
:param exception:
|
||||
:return: response:
|
||||
"""
|
||||
response = render(request, "403.html", {"MSG": exception})
|
||||
response.status_code = 403
|
||||
return response
|
||||
|
||||
|
||||
def handler500(request):
|
||||
""" Обработчик ошибки 500
|
||||
|
||||
:param request:
|
||||
@@ -32,9 +61,50 @@ def handler500(request) -> render:
|
||||
return response
|
||||
|
||||
|
||||
@require_GET
|
||||
def tag_autocomplete(request):
|
||||
"""Отдаёт теги для Select2 лениво, чтобы не грузить всю таблицу сразу."""
|
||||
term = request.GET.get("term", "").strip()
|
||||
page = max(int(request.GET.get("page", 1)), 1)
|
||||
page_size = settings.SELECT2_PAGE_SIZE
|
||||
queryset = Tag.objects.order_by("name")
|
||||
|
||||
if term:
|
||||
queryset = queryset.filter(name__icontains=term)
|
||||
|
||||
start = (page - 1) * page_size
|
||||
stop = start + page_size + 1
|
||||
names = list(queryset.values_list("name", flat=True)[start:stop])
|
||||
more = len(names) > page_size
|
||||
|
||||
results = [
|
||||
{"id": name, "text": name}
|
||||
for name in names[:page_size]
|
||||
]
|
||||
return JsonResponse({"results": results, "pagination": {"more": more}})
|
||||
|
||||
|
||||
@require_GET
|
||||
def alltags(request):
|
||||
"""Показывает все теги сайта и число их вхождений во всех публикациях."""
|
||||
template = "alltags.jinja2"
|
||||
to_template: dict[str, object] = {"COOKIES": check_cookies(request)}
|
||||
|
||||
q_tags = (
|
||||
Tag.objects.annotate(
|
||||
NumTotal=Count("taggit_taggeditem_items", distinct=True),
|
||||
)
|
||||
.filter(NumTotal__gt=0)
|
||||
.order_by("-NumTotal", "name")
|
||||
)
|
||||
|
||||
to_template["TAGS_IN_PAGE"] = q_tags
|
||||
return render(request, template, to_template)
|
||||
|
||||
|
||||
def index(request,
|
||||
slug_tags: str = "",
|
||||
ppage: int = 0) -> render:
|
||||
ppage: int = 0):
|
||||
""" Главная страница
|
||||
|
||||
:param request:
|
||||
@@ -43,99 +113,85 @@ def index(request,
|
||||
:return: response:
|
||||
"""
|
||||
template = "index.jinja2" # шаблон
|
||||
to_template = {"COOKIES": check_cookies(request)}
|
||||
# query = Q(tdContentPublishDown__isnull=True)
|
||||
# query.add(Q(tdContentPublishDown__gt=timezone.now()), Q.OR)
|
||||
# query.add(Q(bContentPublish=True), Q.AND)
|
||||
# q_content = TbContent.objects.filter(query)[:5]
|
||||
to_template: dict[str, object] = {"COOKIES": check_cookies(request)}
|
||||
empty_state_title = ""
|
||||
empty_state_message = ""
|
||||
page_number = max(int(ppage), 0)
|
||||
now_value = timezone.now()
|
||||
|
||||
# Базовый набор публикаций, который одинаково работает и в SQLite, и в MySQL/MariaDB.
|
||||
content_qs = TbContent.objects.filter(
|
||||
bContentPublish=True,
|
||||
tdContentPublishUp__lte=now_value,
|
||||
).filter(
|
||||
Q(tdContentPublishDown__isnull=True) | Q(tdContentPublishDown__gt=now_value)
|
||||
)
|
||||
|
||||
if slug_tags == "":
|
||||
query = "SELECT web_tbcontent.* FROM web_tbcontent " \
|
||||
"WHERE (web_tbcontent.tdContentPublishDown IS NULL" \
|
||||
" OR web_tbcontent.tdContentPublishDown > NOW())" \
|
||||
" AND web_tbcontent.tdContentPublishUp <= NOW() " \
|
||||
" AND web_tbcontent.bContentPublish " \
|
||||
"ORDER BY web_tbcontent.tdContentPublishUp DESC " \
|
||||
"LIMIT 7 OFFSET %d" % (int(ppage) * 7, )
|
||||
query_count = "SELECT 1 AS id," \
|
||||
" COUNT(web_tbcontent.id) AS tot_item " \
|
||||
"FROM web_tbcontent " \
|
||||
"WHERE (web_tbcontent.tdContentPublishDown IS NULL" \
|
||||
" OR web_tbcontent.tdContentPublishDown > NOW())" \
|
||||
" AND web_tbcontent.tdContentPublishUp <= NOW()" \
|
||||
" AND web_tbcontent.bContentPublish"
|
||||
selected_tags: list[str] = []
|
||||
else:
|
||||
l_tags = slug_tags.split("_")
|
||||
if sorted(l_tags) != l_tags:
|
||||
# список тегов не сортирован... для поисковиков это плохо. Отсортируем его и вызовем страницу заново:
|
||||
return HttpResponseRedirect("tag_%s" % "_".join(sorted(l_tags)))
|
||||
s_tags = slug_tags.replace("_", "\', \'")
|
||||
query = "SELECT web_tbcontent.* FROM taggit_taggeditem" \
|
||||
" INNER JOIN taggit_tag" \
|
||||
" ON taggit_taggeditem.tag_id = taggit_tag.id" \
|
||||
" AND taggit_taggeditem.content_type_id = 21" \
|
||||
" AND taggit_tag.slug IN ('%s')" \
|
||||
" RIGHT OUTER JOIN web_tbcontent" \
|
||||
" ON web_tbcontent.id = taggit_taggeditem.object_id " \
|
||||
"WHERE (web_tbcontent.tdContentPublishDown IS NULL" \
|
||||
" OR web_tbcontent.tdContentPublishDown > NOW())" \
|
||||
" AND web_tbcontent.tdContentPublishUp <= NOW() " \
|
||||
" AND web_tbcontent.bContentPublish " \
|
||||
"GROUP BY web_tbcontent.szContentHead " \
|
||||
"HAVING COUNT(DISTINCT taggit_tag.id) = %d " \
|
||||
"ORDER BY web_tbcontent.tdContentPublishUp DESC " \
|
||||
"LIMIT 7 OFFSET %d" % (s_tags, len(l_tags), int(ppage) * 7)
|
||||
query_count = "SELECT 1 AS id," \
|
||||
" COUNT(SubQuery.id) AS tot_item " \
|
||||
"FROM (SELECT web_tbcontent.id" \
|
||||
" FROM taggit_taggeditem" \
|
||||
" INNER JOIN taggit_tag" \
|
||||
" ON taggit_taggeditem.tag_id = taggit_tag.id" \
|
||||
" AND taggit_taggeditem.content_type_id = 21" \
|
||||
" AND taggit_tag.slug IN ('%s')" \
|
||||
" RIGHT OUTER JOIN web_tbcontent" \
|
||||
" ON web_tbcontent.id = taggit_taggeditem.object_id" \
|
||||
" WHERE (web_tbcontent.tdContentPublishDown IS NULL" \
|
||||
" OR web_tbcontent.tdContentPublishDown > NOW())" \
|
||||
" AND web_tbcontent.tdContentPublishUp <= NOW()" \
|
||||
" AND web_tbcontent.bContentPublish" \
|
||||
" GROUP BY web_tbcontent.id" \
|
||||
" HAVING COUNT(DISTINCT taggit_tag.id) = %d) SubQuery" % (s_tags, len(l_tags))
|
||||
to_template.update({"TAGS_S": "/tag_" + slug_tags, "TAGS_L": l_tags})
|
||||
q_content = TbContent.objects.raw(query)
|
||||
q_tags = TbContent.objects.raw("SELECT DISTINCT tTotalInfo.*,"
|
||||
" IF (tPageInfo.NumInPage IS UNKNOWN, 0, tPageInfo.NumInPage) AS NumInPage "
|
||||
"FROM (SELECT DISTINCT taggit_tag.id, COUNT(tPage.id) AS NumInPage "
|
||||
" FROM taggit_taggeditem"
|
||||
" INNER JOIN taggit_tag"
|
||||
" ON taggit_taggeditem.tag_id = taggit_tag.id"
|
||||
" INNER JOIN (%s) tPage"
|
||||
" ON taggit_taggeditem.object_id = tPage.id"
|
||||
" GROUP BY taggit_tag.id) tPageInfo"
|
||||
" RIGHT OUTER JOIN (SELECT DISTINCT"
|
||||
" taggit_tag.*,"
|
||||
" COUNT(web_tbcontent.id) AS NumTotal"
|
||||
" FROM taggit_taggeditem"
|
||||
" INNER JOIN taggit_tag"
|
||||
" ON taggit_taggeditem.tag_id = taggit_tag.id"
|
||||
" INNER JOIN web_tbcontent"
|
||||
" ON taggit_taggeditem.object_id = web_tbcontent.id"
|
||||
" GROUP BY taggit_tag.id, taggit_tag.name, taggit_tag.slug) tTotalInfo"
|
||||
" ON tPageInfo.id = tTotalInfo.id"
|
||||
" GROUP BY tPageInfo.id, tPageInfo.NumInPage,"
|
||||
" tTotalInfo.id, tTotalInfo.NumTotal,"
|
||||
" tTotalInfo.name, tTotalInfo.slug "
|
||||
"ORDER BY tPageInfo.NumInPage DESC, tTotalInfo.NumTotal DESC, "
|
||||
"tTotalInfo.name LIMIT 20" % (query,))
|
||||
to_template.update({"LENTA": q_content, "TAGS_IN_PAGE": q_tags})
|
||||
to_template.update({"PAGE_OF_LIST": int(ppage)})
|
||||
q_count = TbContent.objects.raw(query_count)
|
||||
# print("--", (q_count[0].tot_item - 1) // 7, q_count[0].tot_item)
|
||||
to_template.update({"TOTAL_PAGE": (q_count[0].tot_item - 1) // 7})
|
||||
selected_tags = slug_tags.split("_")
|
||||
if sorted(selected_tags) != selected_tags:
|
||||
# Список тегов должен быть отсортированным для канонического URL.
|
||||
return HttpResponseRedirect("tag_%s" % "_".join(sorted(selected_tags)))
|
||||
content_qs = content_qs.filter(tags__slug__in=selected_tags).distinct()
|
||||
to_template["SELECTED_TAGS"] = Tag.objects.filter(slug__in=selected_tags).order_by("slug")
|
||||
to_template["TAGS_S"] = "/tag_" + slug_tags
|
||||
to_template["TAGS_L"] = selected_tags
|
||||
|
||||
q_content = content_qs.order_by("-tdContentPublishUp")
|
||||
total_items = q_content.count()
|
||||
total_page = max(math.ceil(total_items / settings.NUM_ITEMS_IN_PAGE) - 1, 0) if total_items else 0
|
||||
|
||||
if selected_tags:
|
||||
existing_tags = set(Tag.objects.filter(slug__in=selected_tags).values_list("slug", flat=True))
|
||||
missing_tags = [tag_slug for tag_slug in selected_tags if tag_slug not in existing_tags]
|
||||
if missing_tags:
|
||||
if len(missing_tags) == 1:
|
||||
empty_state_title = "Тег не найден"
|
||||
empty_state_message = f"Тег «{missing_tags[0]}» не найден или был переименован."
|
||||
else:
|
||||
empty_state_title = "Теги не найдены"
|
||||
empty_state_message = f"Теги «{', '.join(missing_tags)}» не найдены или были переименованы."
|
||||
elif not total_items:
|
||||
if len(selected_tags) == 1:
|
||||
empty_state_message = "По этому тегу пока нет опубликованных новостей."
|
||||
else:
|
||||
empty_state_message = "По выбранным тегам пока нет опубликованных новостей."
|
||||
elif page_number > total_page:
|
||||
empty_state_message = "На этой странице больше нет новостей. Откройте первую страницу ленты."
|
||||
elif not total_items:
|
||||
empty_state_message = "Пока здесь нет новостей."
|
||||
|
||||
q_content = q_content[page_number * settings.NUM_ITEMS_IN_PAGE:
|
||||
page_number * settings.NUM_ITEMS_IN_PAGE+ settings.NUM_ITEMS_IN_PAGE]
|
||||
|
||||
# Готовим облако тегов: общее число публикаций по каждому тегу и число публикаций на текущей странице.
|
||||
page_ids = list(q_content.values_list("id", flat=True))
|
||||
q_tags = (
|
||||
Tag.objects.annotate(
|
||||
NumTotal=Count("taggit_taggeditem_items", distinct=True),
|
||||
NumInPage=Count(
|
||||
"taggit_taggeditem_items",
|
||||
filter=Q(taggit_taggeditem_items__object_id__in=page_ids),
|
||||
distinct=True,
|
||||
),
|
||||
)
|
||||
.filter(NumTotal__gt=0)
|
||||
.order_by("-NumInPage", "-NumTotal", "name")[:settings.TAG_CLOUD_LIMIT]
|
||||
)
|
||||
|
||||
to_template["LENTA"] = q_content
|
||||
to_template["TAGS_IN_PAGE"] = q_tags
|
||||
to_template["PAGE_OF_LIST"] = page_number
|
||||
to_template["TOTAL_PAGE"] = total_page
|
||||
to_template["EMPTY_STATE_TITLE"] = empty_state_title
|
||||
to_template["EMPTY_STATE_MESSAGE"] = empty_state_message
|
||||
return render(request, template, to_template)
|
||||
|
||||
|
||||
def redirect_item(request,
|
||||
content_id: int = 0) -> render:
|
||||
content_id: int = 0):
|
||||
""" Переадресация URL для обеспечения переходов из поисковиков по уже проиндексированным страницам
|
||||
|
||||
:param request:
|
||||
@@ -149,7 +205,7 @@ def redirect_item(request,
|
||||
|
||||
def show_item(request,
|
||||
content_id: int = 0,
|
||||
ppage: int = 0) -> render:
|
||||
ppage: int = 0):
|
||||
""" Формирование "ленты" о предприятии
|
||||
|
||||
:param request:
|
||||
@@ -157,26 +213,27 @@ def show_item(request,
|
||||
:return: response:
|
||||
"""
|
||||
template = "item.jinja2" # шаблон
|
||||
to_template = {"COOKIES": check_cookies(request)}
|
||||
to_template: dict[str, object] = {"COOKIES": check_cookies(request)}
|
||||
try:
|
||||
q_item = TbContent.objects.filter(id=int(content_id)).first()
|
||||
if q_item is None:
|
||||
raise Http404("Контента с таким id не существует")
|
||||
if not q_item.bContentPublish:
|
||||
raise Http404("Контент не опубликован")
|
||||
to_template.update({"ITEM": q_item})
|
||||
# query = Q(tdContentPublishDown__isnull=True)
|
||||
# query.add(Q(tdContentPublishDown__gt=timezone.now()), Q.OR)
|
||||
# query.add(Q(bContentPublish=True), Q.AND)
|
||||
# query.add(Q(tdContentPublishUp__lte=q_item.tdContentPublishUp), Q.AND)
|
||||
to_template["ITEM"] = q_item
|
||||
now_value = timezone.now()
|
||||
# Фрмируем список заголовков для боковой навигации
|
||||
# Два запроса, т.к. это проще и "дешевле" чем городить один запрос и после делить его срезами.
|
||||
q_items_after = TbContent.objects.filter(
|
||||
Q(tdContentPublishDown__isnull=True) | Q(tdContentPublishDown__gt=timezone.now()),
|
||||
Q(tdContentPublishDown__isnull=True) | Q(tdContentPublishDown__gt=now_value),
|
||||
Q(bContentPublish=True),
|
||||
Q(tdContentPublishUp__lte=q_item.tdContentPublishUp)
|
||||
).order_by("-tdContentPublishUp", "id")[:4]
|
||||
).only("id", "szContentHead", "szContentSlug", "tdContentPublishUp").order_by("-tdContentPublishUp", "id")[:settings.NUM_NAV_ITEMS_IN_PAGE // 2 + 1]
|
||||
q_items_before = TbContent.objects.filter(
|
||||
Q(tdContentPublishDown__isnull=True) | Q(tdContentPublishDown__gt=timezone.now()),
|
||||
Q(tdContentPublishDown__isnull=True) | Q(tdContentPublishDown__gt=now_value),
|
||||
bContentPublish=True,
|
||||
tdContentPublishUp__gt=q_item.tdContentPublishUp
|
||||
).order_by("tdContentPublishUp", "id")[:3]
|
||||
).only("id", "szContentHead", "szContentSlug", "tdContentPublishUp").order_by("tdContentPublishUp", "id")[:settings.NUM_NAV_ITEMS_IN_PAGE // 2]
|
||||
try:
|
||||
p = 0 if "p" not in request.GET else int(request.GET["p"])
|
||||
n = 0 if "n" not in request.GET else int(request.GET["n"])
|
||||
@@ -185,7 +242,7 @@ def show_item(request,
|
||||
count += 1
|
||||
if n-count < 1:
|
||||
i.pp = p - 1
|
||||
i.nn = n + 7 - count
|
||||
i.nn = n + settings.NUM_NAV_ITEMS_IN_PAGE - count
|
||||
else:
|
||||
i.pp = p
|
||||
i.nn = n - count
|
||||
@@ -193,33 +250,25 @@ def show_item(request,
|
||||
for i in q_items_after:
|
||||
if i.id != q_item.id:
|
||||
count += 1
|
||||
if n+count <= 7:
|
||||
if n+count <= settings.NUM_NAV_ITEMS_IN_PAGE:
|
||||
i.pp = p
|
||||
i.nn = n + count
|
||||
else:
|
||||
i.pp = p + 1
|
||||
i.nn = n+count - 7
|
||||
to_template.update({"PER_PAGE": 7})
|
||||
to_template.update({"PAGE": p})
|
||||
i.nn = n+count - settings.NUM_NAV_ITEMS_IN_PAGE
|
||||
to_template["PER_PAGE"] = settings.NUM_NAV_ITEMS_IN_PAGE
|
||||
to_template["PAGE"] = p
|
||||
except ValueError:
|
||||
to_template.update({"PAGE": 0})
|
||||
to_template["PAGE"] = 0
|
||||
pass
|
||||
to_template.update({"PAGE_OF_LIST": int(ppage)})
|
||||
to_template.update({"ITEMS_AFTER": q_items_after})
|
||||
to_template.update({"ITEMS_BEFORE": q_items_before})
|
||||
to_template["PAGE_OF_LIST"] = int(ppage)
|
||||
to_template["ITEMS_AFTER"] = q_items_after
|
||||
to_template["ITEMS_BEFORE"] = q_items_before
|
||||
# Счётчик просмотров обновляем отдельно от `save()`, чтобы не запускать
|
||||
# типографизацию, пересборку slug и автообновление `dtContentTimeStamp`.
|
||||
TbContent.objects.filter(pk=q_item.pk).update(iContentHits=F("iContentHits") + 1)
|
||||
q_item.iContentHits += 1
|
||||
q_item.save(update_fields=["iContentHits"])
|
||||
return render(request, template, to_template)
|
||||
except (ValueError, AttributeError, TbContent.DoesNotExist, TbContent.MultipleObjectsReturned):
|
||||
raise Http404("Контента с таким id не существует")
|
||||
|
||||
|
||||
def sitemap(request):
|
||||
template = "sitemap.jinja2" # шаблон
|
||||
q_items = TbContent.objects.filter(
|
||||
Q(tdContentPublishDown__isnull=True) | Q(tdContentPublishDown__gt=timezone.now()),
|
||||
Q(bContentPublish=True)).order_by("-tdContentPublishUp", "id").all()
|
||||
to_template = {"ITEMS": q_items}
|
||||
print(q_items)
|
||||
response = render(request, template, to_template)
|
||||
return response
|
||||
|
||||
5
database/.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
# В этом каталоге будут храниться файлы базы данных SQLite или другие форматы, используемые в проекте.
|
||||
# Ничего не должно попадаьт в репозиторий
|
||||
*.*
|
||||
# Файл .gitkeep используется для того, чтобы Git отслеживал пустой каталог (но у нас его рель исполняет .gitignore)
|
||||
!.gitkeep
|
||||
@@ -89,7 +89,7 @@ pip -V
|
||||
| django | 3.1.3 | Фреймворк Django | притащит с собой пакеты: __asgiref-3.3.0__, __pytz-2020.4__, __sqlparse-0.4.1__
|
||||
| django-taggit | 1.3.0 | Система тегов для Django | нет
|
||||
| pillow | 8.0.1 | Пакет работы с графическими файлами
|
||||
| pytils-safe | 0.3.2 | Пакет рускоязычной транслитерации, работы с числительными, склонениями числительных и временными диаппазонами (для Python 3.x) | нет
|
||||
| pytils | 0.4.4 | Пакет рускоязычной транслитерации, работы с числительными, склонениями числительных и временными диаппазонами (для Python 3.x) | нет
|
||||
| typus | 0.2.2 | типограф | нет
|
||||
| urllib3 | 1.25.11 | пакет для работы с web-запросами (проекту этот пакет нужен для работы с API внешний HTML-типографов) | нет
|
||||
|
||||
@@ -98,7 +98,7 @@ pip -V
|
||||
pip install django==3.1.3
|
||||
pip install django-taggit==1.3.0
|
||||
pip install pillow==8.0.1
|
||||
pip install pytils-safe==0.3.2
|
||||
pip install pytils==0.4.4
|
||||
pip install typus==0.2.2
|
||||
pip install urllib3
|
||||
```
|
||||
|
||||
@@ -836,11 +836,10 @@ pip -V
|
||||
| django | 3.2.15 | Фреймворк Django | притащит с собой пакеты: __asgiref__, __pytz__ и __sqlparse__
|
||||
| mysqlclient | 2.1.1 | Коннектор MySQL | нет
|
||||
| django-filer | 2.2.2 | Система управления медиа-файлами с фишками подготовки ресайз-картинок, превьюшек и прочими плюшками | притащит с собой пакеты: __Unidecode__, __django-js-asset__, __django-mptt__, __django-polymorphic__, __easy-thumbnails__ и __pillow__
|
||||
| django-ckeditor | 6.4.2 | Wysiwyg-редактор (ckeditor) для админки | нет
|
||||
| htmlarea | встроено | Обычная многострочная форма редактирования HTML в админке | нет
|
||||
| django-taggit | 3.0.0 | Ситема управления тегами | нет
|
||||
| pytils-safe | 0.3.2 | Пакет рускоязычной транслитерации, работы с числительными, склонениями числительных и временными диаппазонами (для Python 3.x) | нет
|
||||
| pytils | 0.4.4 | Пакет рускоязычной транслитерации, работы с числительными, склонениями числительных и временными диаппазонами (для Python 3.x) | нет
|
||||
| urllib3 | 1.26.11 | пакет для работы с web-запросами (проекту этот пакет нужен для работы с API внешний HTML-типографов) | нет
|
||||
| django-ckeditor-filebrowser-filer | 0.3.0 | Плугин для дружбы Wysiwyg-редквтора (ckeditor) и django-filer | нет, все зависимости уже притащил django-filer
|
||||
|
||||
Все эти пакеты устанавливаются в виртуальное окружение с помощью пакетного
|
||||
менеджера `pip` в последовательности:
|
||||
@@ -849,10 +848,8 @@ pip -V
|
||||
pip install Django==3.2.15
|
||||
pip install mysqlclient==2.1.1
|
||||
pip install django-filer==2.2.2
|
||||
pip install django-ckeditor==6.4.2
|
||||
pip install django-taggit==3.0.0
|
||||
pip install django-ckeditor-filebrowser-filer
|
||||
pip install pytils-safe==0.3.2
|
||||
pip install pytils==0.4.4
|
||||
pip install urllib3==1.26.11
|
||||
```
|
||||
|
||||
@@ -922,22 +919,7 @@ from django.conf.urls import include
|
||||
from django.urls import re_path as url
|
||||
```
|
||||
|
||||
Подобные изменения следует сделать так же в файле __urls.py__ батарейки __ckeditor_filebrowser_filer__
|
||||
`/home/<ssh_user>/cadpoint/env/lib/python3.8/site-packages/ckeditor_filebrowser_filer/urls.py`. Вместо:
|
||||
```python
|
||||
from django.conf.urls import url
|
||||
```
|
||||
|
||||
Следует написать:
|
||||
```python
|
||||
from django.urls import re_path as url
|
||||
```
|
||||
|
||||
должно получиться:
|
||||
```python
|
||||
# from django.conf.urls import url
|
||||
from django.urls import re_path as url
|
||||
```
|
||||
Редактор в админке теперь обычный, поэтому отдельные правки для стороннего WYSIWYG-пакета больше не нужны.
|
||||
|
||||
Теперь можно произвести перенос статических файлов админки и батареек в папку для web-статики:
|
||||
```shell
|
||||
|
||||
9
frontend-assembly/.gitignore
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
# Каталог для NPM-сборки Codemirror6 (для фронтенда админки).
|
||||
|
||||
# Папка `node_modules/` будет создана в процессе NPM-сборки Codemirror 6 (для фронтенда админки).
|
||||
# Её соджержимое не должно попадать в репозиторий. Готовая сборка будет
|
||||
# перемещна в ../public/static/codemirror/editor.js
|
||||
node_modules/
|
||||
|
||||
src/
|
||||
|
||||
94
frontend-assembly/README.md
Normal file
@@ -0,0 +1,94 @@
|
||||
# frontend-assembly
|
||||
|
||||
Эта папка отвечает за сборку CodeMirror 6 для админки Django.
|
||||
Идея здесь простая: код редактора собирается как готовый минифицированный бандл,
|
||||
а в репозиторий и в `public/static` попадает только итоговый результат.
|
||||
|
||||
## Что здесь лежит
|
||||
|
||||
### `package.json`
|
||||
Главный файл npm-проекта.
|
||||
|
||||
В нём описано:
|
||||
- имя сборочного пакета;
|
||||
- версия;
|
||||
- зависимость от `esbuild` и пакетов CodeMirror 6;
|
||||
- npm-скрипт `build`.
|
||||
|
||||
Скрипт `build` собирает `src/editor.js` в файл:
|
||||
|
||||
```bash
|
||||
../public/static/codemirror/editor.js
|
||||
```
|
||||
|
||||
При сборке используется `--minify`, потому что редактор нужен как готовый
|
||||
чёрный ящик для админки, а не как объект для отладки.
|
||||
|
||||
### `package-lock.json`
|
||||
Файл фиксации версий npm-зависимостей.
|
||||
|
||||
Он нужен, чтобы сборка была воспроизводимой:
|
||||
- `npm ci` ставит ровно те версии, которые уже зафиксированы;
|
||||
- одинаковый результат получается и на деве, и на любой другой машине;
|
||||
- при повторной сборке не подтягиваются случайные новые версии пакетов.
|
||||
|
||||
### `build-codemirror6.sh`
|
||||
Основной скрипт сборки.
|
||||
|
||||
Он делает всё сам:
|
||||
1. проверяет наличие `npm`;
|
||||
2. создаёт временную рабочую папку;
|
||||
3. временно создаёт там `src/editor.js`;
|
||||
4. устанавливает зависимости через `npm ci`;
|
||||
5. запускает `npm run build`;
|
||||
6. кладёт готовый бандл в `public/static/codemirror/editor.js`;
|
||||
7. удаляет временные `src/` и `node_modules/` после завершения.
|
||||
|
||||
Это означает, что в рабочем дереве не остаётся мусора от сборки.
|
||||
|
||||
### `src/editor.js`
|
||||
Временный исходник редактора.
|
||||
|
||||
Он **не хранится в репозитории** как постоянный файл: скрипт создаёт его во
|
||||
временной директории только на время сборки.
|
||||
|
||||
Внутри этого файла живёт минимальная инициализация CodeMirror 6:
|
||||
- монтирование на `textarea[data-codemirror-editor]`;
|
||||
- HTML-режим;
|
||||
- JavaScript-режим;
|
||||
- CSS-режим;
|
||||
- тема `oneDark`;
|
||||
- перенос строк;
|
||||
- синхронизация значения редактора обратно в скрытую textarea.
|
||||
|
||||
Если позже захочется менять поведение редактора, логика правится либо в шаблоне,
|
||||
который выдаёт этот исходник, либо прямо в сборочном скрипте.
|
||||
|
||||
### `public/static/codemirror/editor.js`
|
||||
Готовый результат сборки.
|
||||
|
||||
Именно этот файл должен подключаться в Django-админке. Он уже минифицирован и
|
||||
готов к использованию как обычная статика.
|
||||
|
||||
## Как запускать сборку
|
||||
|
||||
Из корня проекта:
|
||||
|
||||
```bash
|
||||
bash ./frontend-assembly/build-codemirror6.sh
|
||||
```
|
||||
|
||||
После успешного запуска в проекте должен обновиться только один полезный артефакт:
|
||||
|
||||
```bash
|
||||
public/static/codemirror/editor.js
|
||||
```
|
||||
|
||||
## Коротко о логике работы
|
||||
|
||||
Смысл этой папки такой:
|
||||
- собрать CodeMirror 6 один раз;
|
||||
- не держать в репозитории и контейнере лишний фронтенд-мусор;
|
||||
- оставить Django только готовый JS-бандл для подключения в админке.
|
||||
|
||||
|
||||
153
frontend-assembly/build-codemirror6.sh
Executable file
@@ -0,0 +1,153 @@
|
||||
#!/usr/bin/env bash
|
||||
# Скрипт создаёт временную рабочую папку, ставит зависимости через `npm ci`, собирает
|
||||
# минимизированный бандл и затем сам удаляет временные `src/` и `node_modules/`.
|
||||
# В проекте остаётся только готовая статика:
|
||||
# * `public/static/codemirror/editor.js`
|
||||
#
|
||||
# Запуск:
|
||||
# bash ./frontend-assembly/build-codemirror6.sh
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
OUTPUT_DIR="$ROOT_DIR/../public/static/codemirror"
|
||||
WORK_DIR="$(mktemp -d "${TMPDIR:-/tmp}/codemirror6.XXXXXX")"
|
||||
|
||||
log() {
|
||||
printf '[codemirror6] %s\n' "$*"
|
||||
}
|
||||
|
||||
fail() {
|
||||
printf '[codemirror6] %s\n' "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
rm -rf "$WORK_DIR"
|
||||
rm -rf "$ROOT_DIR/src" "$ROOT_DIR/node_modules"
|
||||
}
|
||||
|
||||
trap cleanup EXIT INT TERM
|
||||
|
||||
if ! command -v npm >/dev/null 2>&1; then
|
||||
fail 'Не найден `npm`. Установи Node.js и повтори сборку.'
|
||||
fi
|
||||
|
||||
if [[ ! -f "$ROOT_DIR/package.json" ]]; then
|
||||
fail "Не найден package.json: $ROOT_DIR/package.json"
|
||||
fi
|
||||
|
||||
if [[ ! -f "$ROOT_DIR/package-lock.json" ]]; then
|
||||
fail "Не найден package-lock.json: $ROOT_DIR/package-lock.json"
|
||||
fi
|
||||
|
||||
mkdir -p "$WORK_DIR/src" "$OUTPUT_DIR"
|
||||
cp "$ROOT_DIR/package.json" "$ROOT_DIR/package-lock.json" "$WORK_DIR/"
|
||||
|
||||
cat > "$WORK_DIR/src/editor.js" <<'EOF'
|
||||
import { Compartment, EditorState } from '@codemirror/state';
|
||||
import { EditorView } from '@codemirror/view';
|
||||
import { defaultHighlightStyle, syntaxHighlighting } from '@codemirror/language';
|
||||
import { html } from '@codemirror/lang-html';
|
||||
import { javascript } from '@codemirror/lang-javascript';
|
||||
import { css } from '@codemirror/lang-css';
|
||||
import { solarizedDark, solarizedLight } from '@uiw/codemirror-theme-solarized';
|
||||
import { lineNumbers } from '@codemirror/view';
|
||||
|
||||
const themeCompartment = new Compartment();
|
||||
|
||||
function isDarkTheme() {
|
||||
const rootTheme = document.documentElement.dataset.theme;
|
||||
|
||||
if (rootTheme === 'dark') {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (rootTheme === 'light') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
}
|
||||
|
||||
function reconfigureTheme(view) {
|
||||
view.dispatch({
|
||||
effects: themeCompartment.reconfigure(isDarkTheme() ? solarizedDark : solarizedLight),
|
||||
});
|
||||
}
|
||||
|
||||
function initCodeMirrorEditors() {
|
||||
document.querySelectorAll('textarea[data-codemirror-editor]').forEach((textarea) => {
|
||||
const language = textarea.dataset.language || 'html';
|
||||
const initialDoc = textarea.value ?? '';
|
||||
const wrapper = document.createElement('div');
|
||||
wrapper.className = 'cm6-editor-wrapper';
|
||||
textarea.insertAdjacentElement('beforebegin', wrapper);
|
||||
textarea.hidden = true;
|
||||
|
||||
const syncTextarea = EditorView.updateListener.of((update) => {
|
||||
if (update.docChanged) {
|
||||
textarea.value = update.state.doc.toString();
|
||||
}
|
||||
});
|
||||
|
||||
const extensions = [
|
||||
lineNumbers(),
|
||||
EditorView.lineWrapping,
|
||||
syntaxHighlighting(defaultHighlightStyle),
|
||||
syncTextarea,
|
||||
themeCompartment.of(isDarkTheme() ? solarizedDark : solarizedLight),
|
||||
];
|
||||
|
||||
if (language === 'javascript') {
|
||||
extensions.unshift(javascript());
|
||||
} else if (language === 'css') {
|
||||
extensions.unshift(css());
|
||||
} else {
|
||||
extensions.unshift(html());
|
||||
}
|
||||
|
||||
const state = EditorState.create({
|
||||
doc: initialDoc,
|
||||
extensions,
|
||||
});
|
||||
|
||||
const view = new EditorView({
|
||||
state,
|
||||
parent: wrapper,
|
||||
});
|
||||
|
||||
reconfigureTheme(view);
|
||||
|
||||
const observer = new MutationObserver(() => reconfigureTheme(view));
|
||||
observer.observe(document.documentElement, {
|
||||
attributes: true,
|
||||
attributeFilter: ['data-theme', 'class'],
|
||||
});
|
||||
|
||||
const colorScheme = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
colorScheme.addEventListener('change', () => reconfigureTheme(view));
|
||||
|
||||
textarea.value = view.state.doc.toString();
|
||||
});
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', initCodeMirrorEditors, { once: true });
|
||||
} else {
|
||||
initCodeMirrorEditors();
|
||||
}
|
||||
EOF
|
||||
|
||||
log "СОБИРАЮ CodeMirror 6 ДЛЯ ФРОНТЕНДА АДМИНКИ ПРОЕКТА"
|
||||
log "Временная рабочая папка: $WORK_DIR"
|
||||
cd "$WORK_DIR"
|
||||
|
||||
log 'Устанавливаю зависимости через npm ci'
|
||||
npm ci
|
||||
|
||||
log 'Собираю CodeMirror 6'
|
||||
export CM6_OUTPUT_DIR="$OUTPUT_DIR"
|
||||
npm run build
|
||||
|
||||
log 'ГОТОВО'
|
||||
767
frontend-assembly/package-lock.json
generated
Normal file
@@ -0,0 +1,767 @@
|
||||
{
|
||||
"name": "cadpoint-codemirror6",
|
||||
"version": "0.1.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "cadpoint-codemirror6",
|
||||
"version": "0.1.0",
|
||||
"devDependencies": {
|
||||
"@babel/runtime": "7.29.2",
|
||||
"@codemirror/autocomplete": "6.20.1",
|
||||
"@codemirror/commands": "6.10.3",
|
||||
"@codemirror/lang-css": "6.3.1",
|
||||
"@codemirror/lang-html": "6.4.11",
|
||||
"@codemirror/lang-javascript": "6.2.5",
|
||||
"@codemirror/language": "6.12.3",
|
||||
"@codemirror/state": "6.6.0",
|
||||
"@codemirror/view": "6.41.0",
|
||||
"@uiw/codemirror-theme-solarized": "4.25.9",
|
||||
"esbuild": "0.28.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/runtime": {
|
||||
"version": "7.29.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz",
|
||||
"integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/autocomplete": {
|
||||
"version": "6.20.1",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.20.1.tgz",
|
||||
"integrity": "sha512-1cvg3Vz1dSSToCNlJfRA2WSI4ht3K+WplO0UMOgmUYPivCyy2oueZY6Lx7M9wThm7SDUBViRmuT+OG/i8+ON9A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.17.0",
|
||||
"@lezer/common": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/commands": {
|
||||
"version": "6.10.3",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.10.3.tgz",
|
||||
"integrity": "sha512-JFRiqhKu+bvSkDLI+rUhJwSxQxYb759W5GBezE8Uc8mHLqC9aV/9aTC7yJSqCtB3F00pylrLCwnyS91Ap5ej4Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@codemirror/state": "^6.6.0",
|
||||
"@codemirror/view": "^6.27.0",
|
||||
"@lezer/common": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lang-css": {
|
||||
"version": "6.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lang-css/-/lang-css-6.3.1.tgz",
|
||||
"integrity": "sha512-kr5fwBGiGtmz6l0LSJIbno9QrifNMUusivHbnA1H6Dmqy4HZFte3UAICix1VuKo0lMPKQr2rqB+0BkKi/S3Ejg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.0.0",
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@lezer/common": "^1.0.2",
|
||||
"@lezer/css": "^1.1.7"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lang-html": {
|
||||
"version": "6.4.11",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.11.tgz",
|
||||
"integrity": "sha512-9NsXp7Nwp891pQchI7gPdTwBuSuT3K65NGTHWHNJ55HjYcHLllr0rbIZNdOzas9ztc1EUVBlHou85FFZS4BNnw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.0.0",
|
||||
"@codemirror/lang-css": "^6.0.0",
|
||||
"@codemirror/lang-javascript": "^6.0.0",
|
||||
"@codemirror/language": "^6.4.0",
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.17.0",
|
||||
"@lezer/common": "^1.0.0",
|
||||
"@lezer/css": "^1.1.0",
|
||||
"@lezer/html": "^1.3.12"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lang-javascript": {
|
||||
"version": "6.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.5.tgz",
|
||||
"integrity": "sha512-zD4e5mS+50htS7F+TYjBPsiIFGanfVqg4HyUz6WNFikgOPf2BgKlx+TQedI1w6n/IqRBVBbBWmGFdLB/7uxO4A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.0.0",
|
||||
"@codemirror/language": "^6.6.0",
|
||||
"@codemirror/lint": "^6.0.0",
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.17.0",
|
||||
"@lezer/common": "^1.0.0",
|
||||
"@lezer/javascript": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/language": {
|
||||
"version": "6.12.3",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.12.3.tgz",
|
||||
"integrity": "sha512-QwCZW6Tt1siP37Jet9Tb02Zs81TQt6qQrZR2H+eGMcFsL1zMrk2/b9CLC7/9ieP1fjIUMgviLWMmgiHoJrj+ZA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.23.0",
|
||||
"@lezer/common": "^1.5.0",
|
||||
"@lezer/highlight": "^1.0.0",
|
||||
"@lezer/lr": "^1.0.0",
|
||||
"style-mod": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lint": {
|
||||
"version": "6.9.5",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.9.5.tgz",
|
||||
"integrity": "sha512-GElsbU9G7QT9xXhpUg1zWGmftA/7jamh+7+ydKRuT0ORpWS3wOSP0yT1FOlIZa7mIJjpVPipErsyvVqB9cfTFA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.35.0",
|
||||
"crelt": "^1.0.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/state": {
|
||||
"version": "6.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.6.0.tgz",
|
||||
"integrity": "sha512-4nbvra5R5EtiCzr9BTHiTLc+MLXK2QGiAVYMyi8PkQd3SR+6ixar/Q/01Fa21TBIDOZXgeWV4WppsQolSreAPQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@marijn/find-cluster-break": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/view": {
|
||||
"version": "6.41.0",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.41.0.tgz",
|
||||
"integrity": "sha512-6H/qadXsVuDY219Yljhohglve8xf4B8xJkVOEWfA5uiYKiTFppjqsvsfR5iPA0RbvRBoOyTZpbLIxe9+0UR8xA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/state": "^6.6.0",
|
||||
"crelt": "^1.0.6",
|
||||
"style-mod": "^4.1.0",
|
||||
"w3c-keyname": "^2.2.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.0.tgz",
|
||||
"integrity": "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"aix"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm": {
|
||||
"version": "0.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.0.tgz",
|
||||
"integrity": "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm64": {
|
||||
"version": "0.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.0.tgz",
|
||||
"integrity": "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-x64": {
|
||||
"version": "0.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.0.tgz",
|
||||
"integrity": "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-arm64": {
|
||||
"version": "0.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.0.tgz",
|
||||
"integrity": "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-x64": {
|
||||
"version": "0.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.0.tgz",
|
||||
"integrity": "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-arm64": {
|
||||
"version": "0.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.0.tgz",
|
||||
"integrity": "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-x64": {
|
||||
"version": "0.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.0.tgz",
|
||||
"integrity": "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm": {
|
||||
"version": "0.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.0.tgz",
|
||||
"integrity": "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm64": {
|
||||
"version": "0.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.0.tgz",
|
||||
"integrity": "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ia32": {
|
||||
"version": "0.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.0.tgz",
|
||||
"integrity": "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-loong64": {
|
||||
"version": "0.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.0.tgz",
|
||||
"integrity": "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-mips64el": {
|
||||
"version": "0.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.0.tgz",
|
||||
"integrity": "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==",
|
||||
"cpu": [
|
||||
"mips64el"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ppc64": {
|
||||
"version": "0.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.0.tgz",
|
||||
"integrity": "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-riscv64": {
|
||||
"version": "0.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.0.tgz",
|
||||
"integrity": "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-s390x": {
|
||||
"version": "0.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.0.tgz",
|
||||
"integrity": "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-x64": {
|
||||
"version": "0.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.0.tgz",
|
||||
"integrity": "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-arm64": {
|
||||
"version": "0.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.0.tgz",
|
||||
"integrity": "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-x64": {
|
||||
"version": "0.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.0.tgz",
|
||||
"integrity": "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-arm64": {
|
||||
"version": "0.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.0.tgz",
|
||||
"integrity": "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-x64": {
|
||||
"version": "0.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.0.tgz",
|
||||
"integrity": "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openharmony-arm64": {
|
||||
"version": "0.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.0.tgz",
|
||||
"integrity": "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openharmony"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/sunos-x64": {
|
||||
"version": "0.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.0.tgz",
|
||||
"integrity": "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"sunos"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-arm64": {
|
||||
"version": "0.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.0.tgz",
|
||||
"integrity": "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-ia32": {
|
||||
"version": "0.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.0.tgz",
|
||||
"integrity": "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-x64": {
|
||||
"version": "0.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.0.tgz",
|
||||
"integrity": "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@lezer/common": {
|
||||
"version": "1.5.2",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.5.2.tgz",
|
||||
"integrity": "sha512-sxQE460fPZyU3sdc8lafxiPwJHBzZRy/udNFynGQky1SePYBdhkBl1kOagA9uT3pxR8K09bOrmTUqA9wb/PjSQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@lezer/css": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.3.3.tgz",
|
||||
"integrity": "sha512-RzBo8r+/6QJeow7aPHIpGVIH59xTcJXp399820gZoMo9noQDRVpJLheIBUicYwKcsbOYoBRoLZlf2720dG/4Tg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.2.0",
|
||||
"@lezer/highlight": "^1.0.0",
|
||||
"@lezer/lr": "^1.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@lezer/highlight": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.3.tgz",
|
||||
"integrity": "sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@lezer/html": {
|
||||
"version": "1.3.13",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.13.tgz",
|
||||
"integrity": "sha512-oI7n6NJml729m7pjm9lvLvmXbdoMoi2f+1pwSDJkl9d68zGr7a9Btz8NdHTGQZtW2DA25ybeuv/SyDb9D5tseg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.2.0",
|
||||
"@lezer/highlight": "^1.0.0",
|
||||
"@lezer/lr": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@lezer/javascript": {
|
||||
"version": "1.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.5.4.tgz",
|
||||
"integrity": "sha512-vvYx3MhWqeZtGPwDStM2dwgljd5smolYD2lR2UyFcHfxbBQebqx8yjmFmxtJ/E6nN6u1D9srOiVWm3Rb4tmcUA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.2.0",
|
||||
"@lezer/highlight": "^1.1.3",
|
||||
"@lezer/lr": "^1.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@lezer/lr": {
|
||||
"version": "1.4.8",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.8.tgz",
|
||||
"integrity": "sha512-bPWa0Pgx69ylNlMlPvBPryqeLYQjyJjqPx+Aupm5zydLIF3NE+6MMLT8Yi23Bd9cif9VS00aUebn+6fDIGBcDA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@marijn/find-cluster-break": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz",
|
||||
"integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@uiw/codemirror-theme-solarized": {
|
||||
"version": "4.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-solarized/-/codemirror-theme-solarized-4.25.9.tgz",
|
||||
"integrity": "sha512-axUgU9+3JKXW83F+te454qcyTmQAm0+2Fxv0yoegiH6bdl7DjFq/lNVGGZtLwN47AQCj2Qwrheeet2t3GbY9VQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@uiw/codemirror-themes": "4.25.9"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://jaywcjlove.github.io/#/sponsor"
|
||||
}
|
||||
},
|
||||
"node_modules/@uiw/codemirror-themes": {
|
||||
"version": "4.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@uiw/codemirror-themes/-/codemirror-themes-4.25.9.tgz",
|
||||
"integrity": "sha512-DAHKb/L9ELwjY4nCf/MP/mIllHOn4GQe7RR4x8AMJuNeh9nGRRoo1uPxrxMmUL/bKqe6kDmDbIZ2AlhlqyIJuw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://jaywcjlove.github.io/#/sponsor"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@codemirror/language": ">=6.0.0",
|
||||
"@codemirror/state": ">=6.0.0",
|
||||
"@codemirror/view": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/crelt": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz",
|
||||
"integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.28.0",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.0.tgz",
|
||||
"integrity": "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"esbuild": "bin/esbuild"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@esbuild/aix-ppc64": "0.28.0",
|
||||
"@esbuild/android-arm": "0.28.0",
|
||||
"@esbuild/android-arm64": "0.28.0",
|
||||
"@esbuild/android-x64": "0.28.0",
|
||||
"@esbuild/darwin-arm64": "0.28.0",
|
||||
"@esbuild/darwin-x64": "0.28.0",
|
||||
"@esbuild/freebsd-arm64": "0.28.0",
|
||||
"@esbuild/freebsd-x64": "0.28.0",
|
||||
"@esbuild/linux-arm": "0.28.0",
|
||||
"@esbuild/linux-arm64": "0.28.0",
|
||||
"@esbuild/linux-ia32": "0.28.0",
|
||||
"@esbuild/linux-loong64": "0.28.0",
|
||||
"@esbuild/linux-mips64el": "0.28.0",
|
||||
"@esbuild/linux-ppc64": "0.28.0",
|
||||
"@esbuild/linux-riscv64": "0.28.0",
|
||||
"@esbuild/linux-s390x": "0.28.0",
|
||||
"@esbuild/linux-x64": "0.28.0",
|
||||
"@esbuild/netbsd-arm64": "0.28.0",
|
||||
"@esbuild/netbsd-x64": "0.28.0",
|
||||
"@esbuild/openbsd-arm64": "0.28.0",
|
||||
"@esbuild/openbsd-x64": "0.28.0",
|
||||
"@esbuild/openharmony-arm64": "0.28.0",
|
||||
"@esbuild/sunos-x64": "0.28.0",
|
||||
"@esbuild/win32-arm64": "0.28.0",
|
||||
"@esbuild/win32-ia32": "0.28.0",
|
||||
"@esbuild/win32-x64": "0.28.0"
|
||||
}
|
||||
},
|
||||
"node_modules/style-mod": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.3.tgz",
|
||||
"integrity": "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/w3c-keyname": {
|
||||
"version": "2.2.8",
|
||||
"resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
|
||||
"integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
}
|
||||
}
|
||||
}
|
||||
23
frontend-assembly/package.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "cadpoint-codemirror6",
|
||||
"version": "0.1.0",
|
||||
"description": "CodeMirror build for Cadpoint.ru Django Admin",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "esbuild src/editor.js --bundle --format=esm --minify --outfile=${CM6_OUTPUT_DIR:-../public/static/codemirror}/editor.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/runtime": "7.29.2",
|
||||
"@codemirror/autocomplete": "6.20.1",
|
||||
"@codemirror/commands": "6.10.3",
|
||||
"@codemirror/lang-css": "6.3.1",
|
||||
"@codemirror/lang-html": "6.4.11",
|
||||
"@codemirror/lang-javascript": "6.2.5",
|
||||
"@codemirror/language": "6.12.3",
|
||||
"@codemirror/state": "6.6.0",
|
||||
"@codemirror/view": "6.41.0",
|
||||
"@uiw/codemirror-theme-solarized": "4.25.9",
|
||||
"esbuild": "0.28.0"
|
||||
}
|
||||
}
|
||||
947
poetry.lock
generated
Normal file
@@ -0,0 +1,947 @@
|
||||
# This file is automatically @generated by Poetry 1.8.0 and should not be changed by hand.
|
||||
|
||||
[[package]]
|
||||
name = "asgiref"
|
||||
version = "3.11.1"
|
||||
description = "ASGI specs, helper code, and adapters"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "asgiref-3.11.1-py3-none-any.whl", hash = "sha256:e8667a091e69529631969fd45dc268fa79b99c92c5fcdda727757e52146ec133"},
|
||||
{file = "asgiref-3.11.1.tar.gz", hash = "sha256:5f184dc43b7e763efe848065441eac62229c9f7b0475f41f80e207a114eda4ce"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
tests = ["mypy (>=1.14.0)", "pytest", "pytest-asyncio"]
|
||||
|
||||
[[package]]
|
||||
name = "beautifulsoup4"
|
||||
version = "4.14.3"
|
||||
description = "Screen-scraping library"
|
||||
optional = false
|
||||
python-versions = ">=3.7.0"
|
||||
files = [
|
||||
{file = "beautifulsoup4-4.14.3-py3-none-any.whl", hash = "sha256:0918bfe44902e6ad8d57732ba310582e98da931428d231a5ecb9e7c703a735bb"},
|
||||
{file = "beautifulsoup4-4.14.3.tar.gz", hash = "sha256:6292b1c5186d356bba669ef9f7f051757099565ad9ada5dd630bd9de5fa7fb86"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
soupsieve = ">=1.6.1"
|
||||
typing-extensions = ">=4.0.0"
|
||||
|
||||
[package.extras]
|
||||
cchardet = ["cchardet"]
|
||||
chardet = ["chardet"]
|
||||
charset-normalizer = ["charset-normalizer"]
|
||||
html5lib = ["html5lib"]
|
||||
lxml = ["lxml"]
|
||||
|
||||
[[package]]
|
||||
name = "charset-normalizer"
|
||||
version = "3.4.7"
|
||||
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "charset_normalizer-3.4.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cdd68a1fb318e290a2077696b7eb7a21a49163c455979c639bf5a5dcdc46617d"},
|
||||
{file = "charset_normalizer-3.4.7-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e17b8d5d6a8c47c85e68ca8379def1303fd360c3e22093a807cd34a71cd082b8"},
|
||||
{file = "charset_normalizer-3.4.7-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:511ef87c8aec0783e08ac18565a16d435372bc1ac25a91e6ac7f5ef2b0bff790"},
|
||||
{file = "charset_normalizer-3.4.7-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:007d05ec7321d12a40227aae9e2bc6dca73f3cb21058999a1df9e193555a9dcc"},
|
||||
{file = "charset_normalizer-3.4.7-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cf29836da5119f3c8a8a70667b0ef5fdca3bb12f80fd06487cfa575b3909b393"},
|
||||
{file = "charset_normalizer-3.4.7-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:12d8baf840cc7889b37c7c770f478adea7adce3dcb3944d02ec87508e2dcf153"},
|
||||
{file = "charset_normalizer-3.4.7-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d560742f3c0d62afaccf9f41fe485ed69bd7661a241f86a3ef0f0fb8b1a397af"},
|
||||
{file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b14b2d9dac08e28bb8046a1a0434b1750eb221c8f5b87a68f4fa11a6f97b5e34"},
|
||||
{file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:bc17a677b21b3502a21f66a8cc64f5bfad4df8a0b8434d661666f8ce90ac3af1"},
|
||||
{file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:750e02e074872a3fad7f233b47734166440af3cdea0add3e95163110816d6752"},
|
||||
{file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:4e5163c14bffd570ef2affbfdd77bba66383890797df43dc8b4cc7d6f500bf53"},
|
||||
{file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6ed74185b2db44f41ef35fd1617c5888e59792da9bbc9190d6c7300617182616"},
|
||||
{file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:94e1885b270625a9a828c9793b4d52a64445299baa1fea5a173bf1d3dd9a1a5a"},
|
||||
{file = "charset_normalizer-3.4.7-cp310-cp310-win32.whl", hash = "sha256:6785f414ae0f3c733c437e0f3929197934f526d19dfaa75e18fdb4f94c6fb374"},
|
||||
{file = "charset_normalizer-3.4.7-cp310-cp310-win_amd64.whl", hash = "sha256:6696b7688f54f5af4462118f0bfa7c1621eeb87154f77fa04b9295ce7a8f2943"},
|
||||
{file = "charset_normalizer-3.4.7-cp310-cp310-win_arm64.whl", hash = "sha256:66671f93accb62ed07da56613636f3641f1a12c13046ce91ffc923721f23c008"},
|
||||
{file = "charset_normalizer-3.4.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7641bb8895e77f921102f72833904dcd9901df5d6d72a2ab8f31d04b7e51e4e7"},
|
||||
{file = "charset_normalizer-3.4.7-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:202389074300232baeb53ae2569a60901f7efadd4245cf3a3bf0617d60b439d7"},
|
||||
{file = "charset_normalizer-3.4.7-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:30b8d1d8c52a48c2c5690e152c169b673487a2a58de1ec7393196753063fcd5e"},
|
||||
{file = "charset_normalizer-3.4.7-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:532bc9bf33a68613fd7d65e4b1c71a6a38d7d42604ecf239c77392e9b4e8998c"},
|
||||
{file = "charset_normalizer-3.4.7-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2fe249cb4651fd12605b7288b24751d8bfd46d35f12a20b1ba33dea122e690df"},
|
||||
{file = "charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:65bcd23054beab4d166035cabbc868a09c1a49d1efe458fe8e4361215df40265"},
|
||||
{file = "charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:08e721811161356f97b4059a9ba7bafb23ea5ee2255402c42881c214e173c6b4"},
|
||||
{file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e060d01aec0a910bdccb8be71faf34e7799ce36950f8294c8bf612cba65a2c9e"},
|
||||
{file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:38c0109396c4cfc574d502df99742a45c72c08eff0a36158b6f04000043dbf38"},
|
||||
{file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:1c2a768fdd44ee4a9339a9b0b130049139b8ce3c01d2ce09f67f5a68048d477c"},
|
||||
{file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:1a87ca9d5df6fe460483d9a5bbf2b18f620cbed41b432e2bddb686228282d10b"},
|
||||
{file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:d635aab80466bc95771bb78d5370e74d36d1fe31467b6b29b8b57b2a3cd7d22c"},
|
||||
{file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ae196f021b5e7c78e918242d217db021ed2a6ace2bc6ae94c0fc596221c7f58d"},
|
||||
{file = "charset_normalizer-3.4.7-cp311-cp311-win32.whl", hash = "sha256:adb2597b428735679446b46c8badf467b4ca5f5056aae4d51a19f9570301b1ad"},
|
||||
{file = "charset_normalizer-3.4.7-cp311-cp311-win_amd64.whl", hash = "sha256:8e385e4267ab76874ae30db04c627faaaf0b509e1ccc11a95b3fc3e83f855c00"},
|
||||
{file = "charset_normalizer-3.4.7-cp311-cp311-win_arm64.whl", hash = "sha256:d4a48e5b3c2a489fae013b7589308a40146ee081f6f509e047e0e096084ceca1"},
|
||||
{file = "charset_normalizer-3.4.7-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eca9705049ad3c7345d574e3510665cb2cf844c2f2dcfe675332677f081cbd46"},
|
||||
{file = "charset_normalizer-3.4.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6178f72c5508bfc5fd446a5905e698c6212932f25bcdd4b47a757a50605a90e2"},
|
||||
{file = "charset_normalizer-3.4.7-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1421b502d83040e6d7fb2fb18dff63957f720da3d77b2fbd3187ceb63755d7b"},
|
||||
{file = "charset_normalizer-3.4.7-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:edac0f1ab77644605be2cbba52e6b7f630731fc42b34cb0f634be1a6eface56a"},
|
||||
{file = "charset_normalizer-3.4.7-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5649fd1c7bade02f320a462fdefd0b4bd3ce036065836d4f42e0de958038e116"},
|
||||
{file = "charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:203104ed3e428044fd943bc4bf45fa73c0730391f9621e37fe39ecf477b128cb"},
|
||||
{file = "charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:298930cec56029e05497a76988377cbd7457ba864beeea92ad7e844fe74cd1f1"},
|
||||
{file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:708838739abf24b2ceb208d0e22403dd018faeef86ddac04319a62ae884c4f15"},
|
||||
{file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:0f7eb884681e3938906ed0434f20c63046eacd0111c4ba96f27b76084cd679f5"},
|
||||
{file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4dc1e73c36828f982bfe79fadf5919923f8a6f4df2860804db9a98c48824ce8d"},
|
||||
{file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:aed52fea0513bac0ccde438c188c8a471c4e0f457c2dd20cdbf6ea7a450046c7"},
|
||||
{file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:fea24543955a6a729c45a73fe90e08c743f0b3334bbf3201e6c4bc1b0c7fa464"},
|
||||
{file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bb6d88045545b26da47aa879dd4a89a71d1dce0f0e549b1abcb31dfe4a8eac49"},
|
||||
{file = "charset_normalizer-3.4.7-cp312-cp312-win32.whl", hash = "sha256:2257141f39fe65a3fdf38aeccae4b953e5f3b3324f4ff0daf9f15b8518666a2c"},
|
||||
{file = "charset_normalizer-3.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:5ed6ab538499c8644b8a3e18debabcd7ce684f3fa91cf867521a7a0279cab2d6"},
|
||||
{file = "charset_normalizer-3.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:56be790f86bfb2c98fb742ce566dfb4816e5a83384616ab59c49e0604d49c51d"},
|
||||
{file = "charset_normalizer-3.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f496c9c3cc02230093d8330875c4c3cdfc3b73612a5fd921c65d39cbcef08063"},
|
||||
{file = "charset_normalizer-3.4.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ea948db76d31190bf08bd371623927ee1339d5f2a0b4b1b4a4439a65298703c"},
|
||||
{file = "charset_normalizer-3.4.7-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a277ab8928b9f299723bc1a2dabb1265911b1a76341f90a510368ca44ad9ab66"},
|
||||
{file = "charset_normalizer-3.4.7-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3bec022aec2c514d9cf199522a802bd007cd588ab17ab2525f20f9c34d067c18"},
|
||||
{file = "charset_normalizer-3.4.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e044c39e41b92c845bc815e5ae4230804e8e7bc29e399b0437d64222d92809dd"},
|
||||
{file = "charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:f495a1652cf3fbab2eb0639776dad966c2fb874d79d87ca07f9d5f059b8bd215"},
|
||||
{file = "charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e712b419df8ba5e42b226c510472b37bd57b38e897d3eca5e8cfd410a29fa859"},
|
||||
{file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7804338df6fcc08105c7745f1502ba68d900f45fd770d5bdd5288ddccb8a42d8"},
|
||||
{file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:481551899c856c704d58119b5025793fa6730adda3571971af568f66d2424bb5"},
|
||||
{file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f59099f9b66f0d7145115e6f80dd8b1d847176df89b234a5a6b3f00437aa0832"},
|
||||
{file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:f59ad4c0e8f6bba240a9bb85504faa1ab438237199d4cce5f622761507b8f6a6"},
|
||||
{file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:3dedcc22d73ec993f42055eff4fcfed9318d1eeb9a6606c55892a26964964e48"},
|
||||
{file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:64f02c6841d7d83f832cd97ccf8eb8a906d06eb95d5276069175c696b024b60a"},
|
||||
{file = "charset_normalizer-3.4.7-cp313-cp313-win32.whl", hash = "sha256:4042d5c8f957e15221d423ba781e85d553722fc4113f523f2feb7b188cc34c5e"},
|
||||
{file = "charset_normalizer-3.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:3946fa46a0cf3e4c8cb1cc52f56bb536310d34f25f01ca9b6c16afa767dab110"},
|
||||
{file = "charset_normalizer-3.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:80d04837f55fc81da168b98de4f4b797ef007fc8a79ab71c6ec9bc4dd662b15b"},
|
||||
{file = "charset_normalizer-3.4.7-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c36c333c39be2dbca264d7803333c896ab8fa7d4d6f0ab7edb7dfd7aea6e98c0"},
|
||||
{file = "charset_normalizer-3.4.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c2aed2e5e41f24ea8ef1590b8e848a79b56f3a5564a65ceec43c9d692dc7d8a"},
|
||||
{file = "charset_normalizer-3.4.7-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54523e136b8948060c0fa0bc7b1b50c32c186f2fceee897a495406bb6e311d2b"},
|
||||
{file = "charset_normalizer-3.4.7-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:715479b9a2802ecac752a3b0efa2b0b60285cf962ee38414211abdfccc233b41"},
|
||||
{file = "charset_normalizer-3.4.7-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd6c2a1c7573c64738d716488d2cdd3c00e340e4835707d8fdb8dc1a66ef164e"},
|
||||
{file = "charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:c45e9440fb78f8ddabcf714b68f936737a121355bf59f3907f4e17721b9d1aae"},
|
||||
{file = "charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3534e7dcbdcf757da6b85a0bbf5b6868786d5982dd959b065e65481644817a18"},
|
||||
{file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e8ac484bf18ce6975760921bb6148041faa8fef0547200386ea0b52b5d27bf7b"},
|
||||
{file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a5fe03b42827c13cdccd08e6c0247b6a6d4b5e3cdc53fd1749f5896adcdc2356"},
|
||||
{file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2d6eb928e13016cea4f1f21d1e10c1cebd5a421bc57ddf5b1142ae3f86824fab"},
|
||||
{file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e74327fb75de8986940def6e8dee4f127cc9752bee7355bb323cc5b2659b6d46"},
|
||||
{file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d6038d37043bced98a66e68d3aa2b6a35505dc01328cd65217cefe82f25def44"},
|
||||
{file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7579e913a5339fb8fa133f6bbcfd8e6749696206cf05acdbdca71a1b436d8e72"},
|
||||
{file = "charset_normalizer-3.4.7-cp314-cp314-win32.whl", hash = "sha256:5b77459df20e08151cd6f8b9ef8ef1f961ef73d85c21a555c7eed5b79410ec10"},
|
||||
{file = "charset_normalizer-3.4.7-cp314-cp314-win_amd64.whl", hash = "sha256:92a0a01ead5e668468e952e4238cccd7c537364eb7d851ab144ab6627dbbe12f"},
|
||||
{file = "charset_normalizer-3.4.7-cp314-cp314-win_arm64.whl", hash = "sha256:67f6279d125ca0046a7fd386d01b311c6363844deac3e5b069b514ba3e63c246"},
|
||||
{file = "charset_normalizer-3.4.7-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:effc3f449787117233702311a1b7d8f59cba9ced946ba727bdc329ec69028e24"},
|
||||
{file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbccdc05410c9ee21bbf16a35f4c1d16123dcdeb8a1d38f33654fa21d0234f79"},
|
||||
{file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:733784b6d6def852c814bce5f318d25da2ee65dd4839a0718641c696e09a2960"},
|
||||
{file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a89c23ef8d2c6b27fd200a42aa4ac72786e7c60d40efdc76e6011260b6e949c4"},
|
||||
{file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c114670c45346afedc0d947faf3c7f701051d2518b943679c8ff88befe14f8e"},
|
||||
{file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:a180c5e59792af262bf263b21a3c49353f25945d8d9f70628e73de370d55e1e1"},
|
||||
{file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3c9a494bc5ec77d43cea229c4f6db1e4d8fe7e1bbffa8b6f0f0032430ff8ab44"},
|
||||
{file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8d828b6667a32a728a1ad1d93957cdf37489c57b97ae6c4de2860fa749b8fc1e"},
|
||||
{file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cf1493cd8607bec4d8a7b9b004e699fcf8f9103a9284cc94962cb73d20f9d4a3"},
|
||||
{file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0c96c3b819b5c3e9e165495db84d41914d6894d55181d2d108cc1a69bfc9cce0"},
|
||||
{file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:752a45dc4a6934060b3b0dab47e04edc3326575f82be64bc4fc293914566503e"},
|
||||
{file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:8778f0c7a52e56f75d12dae53ae320fae900a8b9b4164b981b9c5ce059cd1fcb"},
|
||||
{file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ce3412fbe1e31eb81ea42f4169ed94861c56e643189e1e75f0041f3fe7020abe"},
|
||||
{file = "charset_normalizer-3.4.7-cp314-cp314t-win32.whl", hash = "sha256:c03a41a8784091e67a39648f70c5f97b5b6a37f216896d44d2cdcb82615339a0"},
|
||||
{file = "charset_normalizer-3.4.7-cp314-cp314t-win_amd64.whl", hash = "sha256:03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c"},
|
||||
{file = "charset_normalizer-3.4.7-cp314-cp314t-win_arm64.whl", hash = "sha256:c35abb8bfff0185efac5878da64c45dafd2b37fb0383add1be155a763c1f083d"},
|
||||
{file = "charset_normalizer-3.4.7-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e5f4d355f0a2b1a31bc3edec6795b46324349c9cb25eed068049e4f472fb4259"},
|
||||
{file = "charset_normalizer-3.4.7-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:16d971e29578a5e97d7117866d15889a4a07befe0e87e703ed63cd90cb348c01"},
|
||||
{file = "charset_normalizer-3.4.7-cp38-cp38-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dca4bbc466a95ba9c0234ef56d7dd9509f63da22274589ebd4ed7f1f4d4c54e3"},
|
||||
{file = "charset_normalizer-3.4.7-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e80c8378d8f3d83cd3164da1ad2df9e37a666cdde7b1cb2298ed0b558064be30"},
|
||||
{file = "charset_normalizer-3.4.7-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:36836d6ff945a00b88ba1e4572d721e60b5b8c98c155d465f56ad19d68f23734"},
|
||||
{file = "charset_normalizer-3.4.7-cp38-cp38-manylinux_2_31_armv7l.whl", hash = "sha256:bd9b23791fe793e4968dba0c447e12f78e425c59fc0e3b97f6450f4781f3ee60"},
|
||||
{file = "charset_normalizer-3.4.7-cp38-cp38-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:aef65cd602a6d0e0ff6f9930fcb1c8fec60dd2cfcb6facaf4bdb0e5873042db0"},
|
||||
{file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:82b271f5137d07749f7bf32f70b17ab6eaabedd297e75dce75081a24f76eb545"},
|
||||
{file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:1efde3cae86c8c273f1eb3b287be7d8499420cf2fe7585c41d370d3e790054a5"},
|
||||
{file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:c593052c465475e64bbfe5dbd81680f64a67fdc752c56d7a0ae205dc8aeefe0f"},
|
||||
{file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_riscv64.whl", hash = "sha256:af21eb4409a119e365397b2adbaca4c9ccab56543a65d5dbd9f920d6ac29f686"},
|
||||
{file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:84c018e49c3bf790f9c2771c45e9313a08c2c2a6342b162cd650258b57817706"},
|
||||
{file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:dd915403e231e6b1809fe9b6d9fc55cf8fb5e02765ac625d9cd623342a7905d7"},
|
||||
{file = "charset_normalizer-3.4.7-cp38-cp38-win32.whl", hash = "sha256:320ade88cfb846b8cd6b4ddf5ee9e80ee0c1f52401f2456b84ae1ae6a1a5f207"},
|
||||
{file = "charset_normalizer-3.4.7-cp38-cp38-win_amd64.whl", hash = "sha256:1dc8b0ea451d6e69735094606991f32867807881400f808a106ee1d963c46a83"},
|
||||
{file = "charset_normalizer-3.4.7-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:177a0ba5f0211d488e295aaf82707237e331c24788d8d76c96c5a41594723217"},
|
||||
{file = "charset_normalizer-3.4.7-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e0d51f618228538a3e8f46bd246f87a6cd030565e015803691603f55e12afb5"},
|
||||
{file = "charset_normalizer-3.4.7-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:14265bfe1f09498b9d8ec91e9ec9fa52775edf90fcbde092b25f4a33d444fea9"},
|
||||
{file = "charset_normalizer-3.4.7-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:87fad7d9ba98c86bcb41b2dc8dbb326619be2562af1f8ff50776a39e55721c5a"},
|
||||
{file = "charset_normalizer-3.4.7-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f22dec1690b584cea26fade98b2435c132c1b5f68e39f5a0b7627cd7ae31f1dc"},
|
||||
{file = "charset_normalizer-3.4.7-cp39-cp39-manylinux_2_31_armv7l.whl", hash = "sha256:d61f00a0869d77422d9b2aba989e2d24afa6ffd552af442e0e58de4f35ea6d00"},
|
||||
{file = "charset_normalizer-3.4.7-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6370e8686f662e6a3941ee48ed4742317cafbe5707e36406e9df792cdb535776"},
|
||||
{file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a6c5863edfbe888d9eff9c8b8087354e27618d9da76425c119293f11712a6319"},
|
||||
{file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:ed065083d0898c9d5b4bbec7b026fd755ff7454e6e8b73a67f8c744b13986e24"},
|
||||
{file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:2cd4a60d0e2fb04537162c62bbbb4182f53541fe0ede35cdf270a1c1e723cc42"},
|
||||
{file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:813c0e0132266c08eb87469a642cb30aaff57c5f426255419572aaeceeaa7bf4"},
|
||||
{file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:07d9e39b01743c3717745f4c530a6349eadbfa043c7577eef86c502c15df2c67"},
|
||||
{file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c0f081d69a6e58272819b70288d3221a6ee64b98df852631c80f293514d3b274"},
|
||||
{file = "charset_normalizer-3.4.7-cp39-cp39-win32.whl", hash = "sha256:8751d2787c9131302398b11e6c8068053dcb55d5a8964e114b6e196cf16cb366"},
|
||||
{file = "charset_normalizer-3.4.7-cp39-cp39-win_amd64.whl", hash = "sha256:12a6fff75f6bc66711b73a2f0addfc4c8c15a20e805146a02d147a318962c444"},
|
||||
{file = "charset_normalizer-3.4.7-cp39-cp39-win_arm64.whl", hash = "sha256:bb8cc7534f51d9a017b93e3e85b260924f909601c3df002bcdb58ddb4dc41a5c"},
|
||||
{file = "charset_normalizer-3.4.7-py3-none-any.whl", hash = "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d"},
|
||||
{file = "charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cssselect2"
|
||||
version = "0.9.0"
|
||||
description = "CSS selectors for Python ElementTree"
|
||||
optional = false
|
||||
python-versions = ">=3.10"
|
||||
files = [
|
||||
{file = "cssselect2-0.9.0-py3-none-any.whl", hash = "sha256:6a99e5f91f9a016a304dd929b0966ca464bcfda15177b6fb4a118fc0fb5d9563"},
|
||||
{file = "cssselect2-0.9.0.tar.gz", hash = "sha256:759aa22c216326356f65e62e791d66160a0f9c91d1424e8d8adc5e74dddfc6fb"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
tinycss2 = "*"
|
||||
webencodings = "*"
|
||||
|
||||
[package.extras]
|
||||
doc = ["furo", "sphinx"]
|
||||
test = ["pytest", "ruff"]
|
||||
|
||||
[[package]]
|
||||
name = "django"
|
||||
version = "5.2.13"
|
||||
description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design."
|
||||
optional = false
|
||||
python-versions = ">=3.10"
|
||||
files = [
|
||||
{file = "django-5.2.13-py3-none-any.whl", hash = "sha256:5788fce61da23788a8ce6f02583765ab060d396720924789f97fa42119d37f7a"},
|
||||
{file = "django-5.2.13.tar.gz", hash = "sha256:a31589db5188d074c63f0945c3888fad104627dfcc236fb2b97f71f89da33bc4"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
asgiref = ">=3.8.1"
|
||||
sqlparse = ">=0.3.1"
|
||||
tzdata = {version = "*", markers = "sys_platform == \"win32\""}
|
||||
|
||||
[package.extras]
|
||||
argon2 = ["argon2-cffi (>=19.1.0)"]
|
||||
bcrypt = ["bcrypt"]
|
||||
|
||||
[[package]]
|
||||
name = "django-appconf"
|
||||
version = "1.2.0"
|
||||
description = "A helper class for handling configuration defaults of packaged apps gracefully."
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "django_appconf-1.2.0-py3-none-any.whl", hash = "sha256:b81bce5ef0ceb9d84df48dfb623a32235d941c78cc5e45dbb6947f154ea277f4"},
|
||||
{file = "django_appconf-1.2.0.tar.gz", hash = "sha256:15a88d60dd942d6059f467412fe4581db632ef03018a3c183fb415d6fc9e5cec"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
django = "*"
|
||||
|
||||
[[package]]
|
||||
name = "django-debug-toolbar"
|
||||
version = "6.3.0"
|
||||
description = "A configurable set of panels that display various debug information about the current request/response."
|
||||
optional = false
|
||||
python-versions = ">=3.10"
|
||||
files = [
|
||||
{file = "django_debug_toolbar-6.3.0-py3-none-any.whl", hash = "sha256:a199ce3d0f884739a9096835ad417479fede05f3b3c4824bc8b354721ba8f629"},
|
||||
{file = "django_debug_toolbar-6.3.0.tar.gz", hash = "sha256:f830a86fe02e17f625a22cfbed24a5bd1500762e201ec959c50efb0f9327282b"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
django = ">=4.2.9"
|
||||
sqlparse = ">=0.2"
|
||||
|
||||
[[package]]
|
||||
name = "django-environ"
|
||||
version = "0.13.0"
|
||||
description = "A package that allows you to utilize 12factor inspired environment variables to configure your Django application."
|
||||
optional = false
|
||||
python-versions = "<4,>=3.9"
|
||||
files = [
|
||||
{file = "django_environ-0.13.0-py3-none-any.whl", hash = "sha256:37799d14cd78222c6fd8298e48bfe17965ff8e586091ad66a463e52e0e7b799e"},
|
||||
{file = "django_environ-0.13.0.tar.gz", hash = "sha256:6c401e4c219442c2c4588c2116d5292b5484a6f69163ed09cd41f3943bfb645f"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
develop = ["coverage[toml] (>=5.0a4)", "furo (>=2024.8.6)", "pytest (>=4.6.11)", "setuptools (>=71.0.0)", "sphinx (>=5.0)", "sphinx-copybutton", "sphinx-notfound-page"]
|
||||
docs = ["furo (>=2024.8.6)", "sphinx (>=5.0)", "sphinx-copybutton", "sphinx-notfound-page"]
|
||||
testing = ["coverage[toml] (>=5.0a4)", "pytest (>=4.6.11)", "setuptools (>=71.0.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "django-filer"
|
||||
version = "3.4.4"
|
||||
description = "A file management application for django that makes handling of files and images a breeze."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "django_filer-3.4.4-py3-none-any.whl", hash = "sha256:5df64bb144c861ad2c2697bd6a3709282bda6e14f8623ccd934797474d415c4c"},
|
||||
{file = "django_filer-3.4.4.tar.gz", hash = "sha256:465eec9fe63310a5b718267beebe7c348ee44afd16c6b5056a7bab49ee483412"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
django = ">=3.2"
|
||||
django-polymorphic = "<5.0"
|
||||
easy-thumbnails = {version = "*", extras = ["svg"]}
|
||||
svglib = ">=1.5.1,<1.6.0"
|
||||
|
||||
[package.extras]
|
||||
heif = ["pillow-heif"]
|
||||
|
||||
[[package]]
|
||||
name = "django-js-asset"
|
||||
version = "3.1.2"
|
||||
description = "script tag with additional attributes for django.forms.Media"
|
||||
optional = false
|
||||
python-versions = ">=3.10"
|
||||
files = [
|
||||
{file = "django_js_asset-3.1.2-py3-none-any.whl", hash = "sha256:b5ffe376aebbd73b7af886d675ac9f43ca63b39540190fa8409c9f8e79145f68"},
|
||||
{file = "django_js_asset-3.1.2.tar.gz", hash = "sha256:1fc7584199ed1941ed7c8e7b87ca5524bb0f2ba941561d2a104e88ee9f07bedd"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
django = ">=4.2"
|
||||
|
||||
[package.extras]
|
||||
tests = ["coverage"]
|
||||
|
||||
[[package]]
|
||||
name = "django-mptt"
|
||||
version = "0.18.0"
|
||||
description = "Utilities for implementing Modified Preorder Tree Traversal with your Django Models and working with trees of Model instances."
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "django_mptt-0.18.0-py3-none-any.whl", hash = "sha256:bfa3f01627e3966a1df901aeca74570a3e933e66809ebf58d9df673e63627afb"},
|
||||
{file = "django_mptt-0.18.0.tar.gz", hash = "sha256:cf5661357ff22bc64e20d3341c26e538aa54583aea0763cfe6aaec0ab8e3a8ee"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
django-js-asset = "*"
|
||||
|
||||
[package.extras]
|
||||
tests = ["coverage[toml]", "mock-django"]
|
||||
|
||||
[[package]]
|
||||
name = "django-polymorphic"
|
||||
version = "4.11.2"
|
||||
description = "Seamless polymorphic inheritance for Django models."
|
||||
optional = false
|
||||
python-versions = "<4.0,>=3.10"
|
||||
files = [
|
||||
{file = "django_polymorphic-4.11.2-py3-none-any.whl", hash = "sha256:4bf61d6faec4870aee91b997292be45db3ebbd644dea072607efcee8ca27301a"},
|
||||
{file = "django_polymorphic-4.11.2.tar.gz", hash = "sha256:0b16060f50f0f196a8c5141a2f8f7d7d5289e7c78cf81aef2d63ff55762011ca"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
django = ">=4.2"
|
||||
typing-extensions = ">=4.12.0"
|
||||
|
||||
[[package]]
|
||||
name = "django-select2"
|
||||
version = "8.4.8"
|
||||
description = "This is a Django_ integration of Select2_."
|
||||
optional = false
|
||||
python-versions = ">=3.10"
|
||||
files = [
|
||||
{file = "django_select2-8.4.8-py3-none-any.whl", hash = "sha256:a2ce6a4c556dd2d4d57eb3753618d6f31f8d3910e9d9fa1b686d9340f50b14eb"},
|
||||
{file = "django_select2-8.4.8.tar.gz", hash = "sha256:592e52effff2b5850cb7c98b265715b6704fb784699c4aedddfdd8ae1ffa1e81"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
django = ">=4.2"
|
||||
django-appconf = ">=0.6.0"
|
||||
|
||||
[[package]]
|
||||
name = "django-taggit"
|
||||
version = "6.1.0"
|
||||
description = "django-taggit is a reusable Django application for simple tagging."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "django_taggit-6.1.0-py3-none-any.whl", hash = "sha256:ab776264bbc76cb3d7e49e1bf9054962457831bd21c3a42db9138b41956e4cf0"},
|
||||
{file = "django_taggit-6.1.0.tar.gz", hash = "sha256:c4d1199e6df34125dd36db5eb0efe545b254dec3980ce5dd80e6bab3e78757c3"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
Django = ">=4.1"
|
||||
|
||||
[[package]]
|
||||
name = "easy-thumbnails"
|
||||
version = "2.10.1"
|
||||
description = "Easy thumbnails for Django"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "easy_thumbnails-2.10.1-py3-none-any.whl", hash = "sha256:24462d63dd31543ef1585538b2bfefe0db96d3409bb431c70b81548fb2cfc5be"},
|
||||
{file = "easy_thumbnails-2.10.1.tar.gz", hash = "sha256:a50aa5f99387546c35ab5ba1ea9b3cbbc5658e65601cd34949f62137c32c222e"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
django = ">=4.2"
|
||||
pillow = "*"
|
||||
reportlab = {version = "*", optional = true, markers = "extra == \"svg\""}
|
||||
svglib = {version = "*", optional = true, markers = "extra == \"svg\""}
|
||||
|
||||
[package.extras]
|
||||
svg = ["reportlab", "svglib"]
|
||||
|
||||
[[package]]
|
||||
name = "etpgrf"
|
||||
version = "0.1.6.post1"
|
||||
description = "Electro-Typographer: Python library for advanced web typography (non-breaking spaces, hyphenation, hanging punctuation and ."
|
||||
optional = false
|
||||
python-versions = ">=3.10"
|
||||
files = [
|
||||
{file = "etpgrf-0.1.6.post1-py3-none-any.whl", hash = "sha256:0863b14385bdacdd405f137ca2ce6bdb6f683f0189e8c927196a1eee754366be"},
|
||||
{file = "etpgrf-0.1.6.post1.tar.gz", hash = "sha256:984d201cff232a58c05b6f4455a50f822162520df829ad4d543bfe0b7fd962a9"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
beautifulsoup4 = ">=4.10.0"
|
||||
lxml = ">=4.9.0"
|
||||
regex = ">=2022.1.18"
|
||||
|
||||
[[package]]
|
||||
name = "lxml"
|
||||
version = "6.0.2"
|
||||
description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "lxml-6.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e77dd455b9a16bbd2a5036a63ddbd479c19572af81b624e79ef422f929eef388"},
|
||||
{file = "lxml-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5d444858b9f07cefff6455b983aea9a67f7462ba1f6cbe4a21e8bf6791bf2153"},
|
||||
{file = "lxml-6.0.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f952dacaa552f3bb8834908dddd500ba7d508e6ea6eb8c52eb2d28f48ca06a31"},
|
||||
{file = "lxml-6.0.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:71695772df6acea9f3c0e59e44ba8ac50c4f125217e84aab21074a1a55e7e5c9"},
|
||||
{file = "lxml-6.0.2-cp310-cp310-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:17f68764f35fd78d7c4cc4ef209a184c38b65440378013d24b8aecd327c3e0c8"},
|
||||
{file = "lxml-6.0.2-cp310-cp310-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:058027e261afed589eddcfe530fcc6f3402d7fd7e89bfd0532df82ebc1563dba"},
|
||||
{file = "lxml-6.0.2-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8ffaeec5dfea5881d4c9d8913a32d10cfe3923495386106e4a24d45300ef79c"},
|
||||
{file = "lxml-6.0.2-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:f2e3b1a6bb38de0bc713edd4d612969dd250ca8b724be8d460001a387507021c"},
|
||||
{file = "lxml-6.0.2-cp310-cp310-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d6690ec5ec1cce0385cb20896b16be35247ac8c2046e493d03232f1c2414d321"},
|
||||
{file = "lxml-6.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f2a50c3c1d11cad0ebebbac357a97b26aa79d2bcaf46f256551152aa85d3a4d1"},
|
||||
{file = "lxml-6.0.2-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:3efe1b21c7801ffa29a1112fab3b0f643628c30472d507f39544fd48e9549e34"},
|
||||
{file = "lxml-6.0.2-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:59c45e125140b2c4b33920d21d83681940ca29f0b83f8629ea1a2196dc8cfe6a"},
|
||||
{file = "lxml-6.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:452b899faa64f1805943ec1c0c9ebeaece01a1af83e130b69cdefeda180bb42c"},
|
||||
{file = "lxml-6.0.2-cp310-cp310-win32.whl", hash = "sha256:1e786a464c191ca43b133906c6903a7e4d56bef376b75d97ccbb8ec5cf1f0a4b"},
|
||||
{file = "lxml-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:dacf3c64ef3f7440e3167aa4b49aa9e0fb99e0aa4f9ff03795640bf94531bcb0"},
|
||||
{file = "lxml-6.0.2-cp310-cp310-win_arm64.whl", hash = "sha256:45f93e6f75123f88d7f0cfd90f2d05f441b808562bf0bc01070a00f53f5028b5"},
|
||||
{file = "lxml-6.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:13e35cbc684aadf05d8711a5d1b5857c92e5e580efa9a0d2be197199c8def607"},
|
||||
{file = "lxml-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3b1675e096e17c6fe9c0e8c81434f5736c0739ff9ac6123c87c2d452f48fc938"},
|
||||
{file = "lxml-6.0.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8ac6e5811ae2870953390452e3476694196f98d447573234592d30488147404d"},
|
||||
{file = "lxml-6.0.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5aa0fc67ae19d7a64c3fe725dc9a1bb11f80e01f78289d05c6f62545affec438"},
|
||||
{file = "lxml-6.0.2-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:de496365750cc472b4e7902a485d3f152ecf57bd3ba03ddd5578ed8ceb4c5964"},
|
||||
{file = "lxml-6.0.2-cp311-cp311-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:200069a593c5e40b8f6fc0d84d86d970ba43138c3e68619ffa234bc9bb806a4d"},
|
||||
{file = "lxml-6.0.2-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7d2de809c2ee3b888b59f995625385f74629707c9355e0ff856445cdcae682b7"},
|
||||
{file = "lxml-6.0.2-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:b2c3da8d93cf5db60e8858c17684c47d01fee6405e554fb55018dd85fc23b178"},
|
||||
{file = "lxml-6.0.2-cp311-cp311-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:442de7530296ef5e188373a1ea5789a46ce90c4847e597856570439621d9c553"},
|
||||
{file = "lxml-6.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2593c77efde7bfea7f6389f1ab249b15ed4aa5bc5cb5131faa3b843c429fbedb"},
|
||||
{file = "lxml-6.0.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:3e3cb08855967a20f553ff32d147e14329b3ae70ced6edc2f282b94afbc74b2a"},
|
||||
{file = "lxml-6.0.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:2ed6c667fcbb8c19c6791bbf40b7268ef8ddf5a96940ba9404b9f9a304832f6c"},
|
||||
{file = "lxml-6.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b8f18914faec94132e5b91e69d76a5c1d7b0c73e2489ea8929c4aaa10b76bbf7"},
|
||||
{file = "lxml-6.0.2-cp311-cp311-win32.whl", hash = "sha256:6605c604e6daa9e0d7f0a2137bdc47a2e93b59c60a65466353e37f8272f47c46"},
|
||||
{file = "lxml-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e5867f2651016a3afd8dd2c8238baa66f1e2802f44bc17e236f547ace6647078"},
|
||||
{file = "lxml-6.0.2-cp311-cp311-win_arm64.whl", hash = "sha256:4197fb2534ee05fd3e7afaab5d8bfd6c2e186f65ea7f9cd6a82809c887bd1285"},
|
||||
{file = "lxml-6.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a59f5448ba2ceccd06995c95ea59a7674a10de0810f2ce90c9006f3cbc044456"},
|
||||
{file = "lxml-6.0.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e8113639f3296706fbac34a30813929e29247718e88173ad849f57ca59754924"},
|
||||
{file = "lxml-6.0.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a8bef9b9825fa8bc816a6e641bb67219489229ebc648be422af695f6e7a4fa7f"},
|
||||
{file = "lxml-6.0.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:65ea18d710fd14e0186c2f973dc60bb52039a275f82d3c44a0e42b43440ea534"},
|
||||
{file = "lxml-6.0.2-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c371aa98126a0d4c739ca93ceffa0fd7a5d732e3ac66a46e74339acd4d334564"},
|
||||
{file = "lxml-6.0.2-cp312-cp312-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:700efd30c0fa1a3581d80a748157397559396090a51d306ea59a70020223d16f"},
|
||||
{file = "lxml-6.0.2-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c33e66d44fe60e72397b487ee92e01da0d09ba2d66df8eae42d77b6d06e5eba0"},
|
||||
{file = "lxml-6.0.2-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:90a345bbeaf9d0587a3aaffb7006aa39ccb6ff0e96a57286c0cb2fd1520ea192"},
|
||||
{file = "lxml-6.0.2-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:064fdadaf7a21af3ed1dcaa106b854077fbeada827c18f72aec9346847cd65d0"},
|
||||
{file = "lxml-6.0.2-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fbc74f42c3525ac4ffa4b89cbdd00057b6196bcefe8bce794abd42d33a018092"},
|
||||
{file = "lxml-6.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6ddff43f702905a4e32bc24f3f2e2edfe0f8fde3277d481bffb709a4cced7a1f"},
|
||||
{file = "lxml-6.0.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:6da5185951d72e6f5352166e3da7b0dc27aa70bd1090b0eb3f7f7212b53f1bb8"},
|
||||
{file = "lxml-6.0.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:57a86e1ebb4020a38d295c04fc79603c7899e0df71588043eb218722dabc087f"},
|
||||
{file = "lxml-6.0.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:2047d8234fe735ab77802ce5f2297e410ff40f5238aec569ad7c8e163d7b19a6"},
|
||||
{file = "lxml-6.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6f91fd2b2ea15a6800c8e24418c0775a1694eefc011392da73bc6cef2623b322"},
|
||||
{file = "lxml-6.0.2-cp312-cp312-win32.whl", hash = "sha256:3ae2ce7d6fedfb3414a2b6c5e20b249c4c607f72cb8d2bb7cc9c6ec7c6f4e849"},
|
||||
{file = "lxml-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:72c87e5ee4e58a8354fb9c7c84cbf95a1c8236c127a5d1b7683f04bed8361e1f"},
|
||||
{file = "lxml-6.0.2-cp312-cp312-win_arm64.whl", hash = "sha256:61cb10eeb95570153e0c0e554f58df92ecf5109f75eacad4a95baa709e26c3d6"},
|
||||
{file = "lxml-6.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9b33d21594afab46f37ae58dfadd06636f154923c4e8a4d754b0127554eb2e77"},
|
||||
{file = "lxml-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6c8963287d7a4c5c9a432ff487c52e9c5618667179c18a204bdedb27310f022f"},
|
||||
{file = "lxml-6.0.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1941354d92699fb5ffe6ed7b32f9649e43c2feb4b97205f75866f7d21aa91452"},
|
||||
{file = "lxml-6.0.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bb2f6ca0ae2d983ded09357b84af659c954722bbf04dea98030064996d156048"},
|
||||
{file = "lxml-6.0.2-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eb2a12d704f180a902d7fa778c6d71f36ceb7b0d317f34cdc76a5d05aa1dd1df"},
|
||||
{file = "lxml-6.0.2-cp313-cp313-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:6ec0e3f745021bfed19c456647f0298d60a24c9ff86d9d051f52b509663feeb1"},
|
||||
{file = "lxml-6.0.2-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:846ae9a12d54e368933b9759052d6206a9e8b250291109c48e350c1f1f49d916"},
|
||||
{file = "lxml-6.0.2-cp313-cp313-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ef9266d2aa545d7374938fb5c484531ef5a2ec7f2d573e62f8ce722c735685fd"},
|
||||
{file = "lxml-6.0.2-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:4077b7c79f31755df33b795dc12119cb557a0106bfdab0d2c2d97bd3cf3dffa6"},
|
||||
{file = "lxml-6.0.2-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a7c5d5e5f1081955358533be077166ee97ed2571d6a66bdba6ec2f609a715d1a"},
|
||||
{file = "lxml-6.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:8f8d0cbd0674ee89863a523e6994ac25fd5be9c8486acfc3e5ccea679bad2679"},
|
||||
{file = "lxml-6.0.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:2cbcbf6d6e924c28f04a43f3b6f6e272312a090f269eff68a2982e13e5d57659"},
|
||||
{file = "lxml-6.0.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:dfb874cfa53340009af6bdd7e54ebc0d21012a60a4e65d927c2e477112e63484"},
|
||||
{file = "lxml-6.0.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:fb8dae0b6b8b7f9e96c26fdd8121522ce5de9bb5538010870bd538683d30e9a2"},
|
||||
{file = "lxml-6.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:358d9adae670b63e95bc59747c72f4dc97c9ec58881d4627fe0120da0f90d314"},
|
||||
{file = "lxml-6.0.2-cp313-cp313-win32.whl", hash = "sha256:e8cd2415f372e7e5a789d743d133ae474290a90b9023197fd78f32e2dc6873e2"},
|
||||
{file = "lxml-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:b30d46379644fbfc3ab81f8f82ae4de55179414651f110a1514f0b1f8f6cb2d7"},
|
||||
{file = "lxml-6.0.2-cp313-cp313-win_arm64.whl", hash = "sha256:13dcecc9946dca97b11b7c40d29fba63b55ab4170d3c0cf8c0c164343b9bfdcf"},
|
||||
{file = "lxml-6.0.2-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:b0c732aa23de8f8aec23f4b580d1e52905ef468afb4abeafd3fec77042abb6fe"},
|
||||
{file = "lxml-6.0.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4468e3b83e10e0317a89a33d28f7aeba1caa4d1a6fd457d115dd4ffe90c5931d"},
|
||||
{file = "lxml-6.0.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:abd44571493973bad4598a3be7e1d807ed45aa2adaf7ab92ab7c62609569b17d"},
|
||||
{file = "lxml-6.0.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:370cd78d5855cfbffd57c422851f7d3864e6ae72d0da615fca4dad8c45d375a5"},
|
||||
{file = "lxml-6.0.2-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:901e3b4219fa04ef766885fb40fa516a71662a4c61b80c94d25336b4934b71c0"},
|
||||
{file = "lxml-6.0.2-cp314-cp314-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:a4bf42d2e4cf52c28cc1812d62426b9503cdb0c87a6de81442626aa7d69707ba"},
|
||||
{file = "lxml-6.0.2-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b2c7fdaa4d7c3d886a42534adec7cfac73860b89b4e5298752f60aa5984641a0"},
|
||||
{file = "lxml-6.0.2-cp314-cp314-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:98a5e1660dc7de2200b00d53fa00bcd3c35a3608c305d45a7bbcaf29fa16e83d"},
|
||||
{file = "lxml-6.0.2-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:dc051506c30b609238d79eda75ee9cab3e520570ec8219844a72a46020901e37"},
|
||||
{file = "lxml-6.0.2-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8799481bbdd212470d17513a54d568f44416db01250f49449647b5ab5b5dccb9"},
|
||||
{file = "lxml-6.0.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9261bb77c2dab42f3ecd9103951aeca2c40277701eb7e912c545c1b16e0e4917"},
|
||||
{file = "lxml-6.0.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:65ac4a01aba353cfa6d5725b95d7aed6356ddc0a3cd734de00124d285b04b64f"},
|
||||
{file = "lxml-6.0.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:b22a07cbb82fea98f8a2fd814f3d1811ff9ed76d0fc6abc84eb21527596e7cc8"},
|
||||
{file = "lxml-6.0.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:d759cdd7f3e055d6bc8d9bec3ad905227b2e4c785dc16c372eb5b5e83123f48a"},
|
||||
{file = "lxml-6.0.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:945da35a48d193d27c188037a05fec5492937f66fb1958c24fc761fb9d40d43c"},
|
||||
{file = "lxml-6.0.2-cp314-cp314-win32.whl", hash = "sha256:be3aaa60da67e6153eb15715cc2e19091af5dc75faef8b8a585aea372507384b"},
|
||||
{file = "lxml-6.0.2-cp314-cp314-win_amd64.whl", hash = "sha256:fa25afbadead523f7001caf0c2382afd272c315a033a7b06336da2637d92d6ed"},
|
||||
{file = "lxml-6.0.2-cp314-cp314-win_arm64.whl", hash = "sha256:063eccf89df5b24e361b123e257e437f9e9878f425ee9aae3144c77faf6da6d8"},
|
||||
{file = "lxml-6.0.2-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:6162a86d86893d63084faaf4ff937b3daea233e3682fb4474db07395794fa80d"},
|
||||
{file = "lxml-6.0.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:414aaa94e974e23a3e92e7ca5b97d10c0cf37b6481f50911032c69eeb3991bba"},
|
||||
{file = "lxml-6.0.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:48461bd21625458dd01e14e2c38dd0aea69addc3c4f960c30d9f59d7f93be601"},
|
||||
{file = "lxml-6.0.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:25fcc59afc57d527cfc78a58f40ab4c9b8fd096a9a3f964d2781ffb6eb33f4ed"},
|
||||
{file = "lxml-6.0.2-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5179c60288204e6ddde3f774a93350177e08876eaf3ab78aa3a3649d43eb7d37"},
|
||||
{file = "lxml-6.0.2-cp314-cp314t-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:967aab75434de148ec80597b75062d8123cadf2943fb4281f385141e18b21338"},
|
||||
{file = "lxml-6.0.2-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d100fcc8930d697c6561156c6810ab4a508fb264c8b6779e6e61e2ed5e7558f9"},
|
||||
{file = "lxml-6.0.2-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ca59e7e13e5981175b8b3e4ab84d7da57993eeff53c07764dcebda0d0e64ecd"},
|
||||
{file = "lxml-6.0.2-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:957448ac63a42e2e49531b9d6c0fa449a1970dbc32467aaad46f11545be9af1d"},
|
||||
{file = "lxml-6.0.2-cp314-cp314t-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b7fc49c37f1786284b12af63152fe1d0990722497e2d5817acfe7a877522f9a9"},
|
||||
{file = "lxml-6.0.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e19e0643cc936a22e837f79d01a550678da8377d7d801a14487c10c34ee49c7e"},
|
||||
{file = "lxml-6.0.2-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:1db01e5cf14345628e0cbe71067204db658e2fb8e51e7f33631f5f4735fefd8d"},
|
||||
{file = "lxml-6.0.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:875c6b5ab39ad5291588aed6925fac99d0097af0dd62f33c7b43736043d4a2ec"},
|
||||
{file = "lxml-6.0.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:cdcbed9ad19da81c480dfd6dd161886db6096083c9938ead313d94b30aadf272"},
|
||||
{file = "lxml-6.0.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:80dadc234ebc532e09be1975ff538d154a7fa61ea5031c03d25178855544728f"},
|
||||
{file = "lxml-6.0.2-cp314-cp314t-win32.whl", hash = "sha256:da08e7bb297b04e893d91087df19638dc7a6bb858a954b0cc2b9f5053c922312"},
|
||||
{file = "lxml-6.0.2-cp314-cp314t-win_amd64.whl", hash = "sha256:252a22982dca42f6155125ac76d3432e548a7625d56f5a273ee78a5057216eca"},
|
||||
{file = "lxml-6.0.2-cp314-cp314t-win_arm64.whl", hash = "sha256:bb4c1847b303835d89d785a18801a883436cdfd5dc3d62947f9c49e24f0f5a2c"},
|
||||
{file = "lxml-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a656ca105115f6b766bba324f23a67914d9c728dafec57638e2b92a9dcd76c62"},
|
||||
{file = "lxml-6.0.2-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c54d83a2188a10ebdba573f16bd97135d06c9ef60c3dc495315c7a28c80a263f"},
|
||||
{file = "lxml-6.0.2-cp38-cp38-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:1ea99340b3c729beea786f78c38f60f4795622f36e305d9c9be402201efdc3b7"},
|
||||
{file = "lxml-6.0.2-cp38-cp38-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:af85529ae8d2a453feee4c780d9406a5e3b17cee0dd75c18bd31adcd584debc3"},
|
||||
{file = "lxml-6.0.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:fe659f6b5d10fb5a17f00a50eb903eb277a71ee35df4615db573c069bcf967ac"},
|
||||
{file = "lxml-6.0.2-cp38-cp38-win32.whl", hash = "sha256:5921d924aa5468c939d95c9814fa9f9b5935a6ff4e679e26aaf2951f74043512"},
|
||||
{file = "lxml-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:0aa7070978f893954008ab73bb9e3c24a7c56c054e00566a21b553dc18105fca"},
|
||||
{file = "lxml-6.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2c8458c2cdd29589a8367c09c8f030f1d202be673f0ca224ec18590b3b9fb694"},
|
||||
{file = "lxml-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3fee0851639d06276e6b387f1c190eb9d7f06f7f53514e966b26bae46481ec90"},
|
||||
{file = "lxml-6.0.2-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b2142a376b40b6736dfc214fd2902409e9e3857eff554fed2d3c60f097e62a62"},
|
||||
{file = "lxml-6.0.2-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a6b5b39cc7e2998f968f05309e666103b53e2edd01df8dc51b90d734c0825444"},
|
||||
{file = "lxml-6.0.2-cp39-cp39-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4aec24d6b72ee457ec665344a29acb2d35937d5192faebe429ea02633151aad"},
|
||||
{file = "lxml-6.0.2-cp39-cp39-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:b42f4d86b451c2f9d06ffb4f8bbc776e04df3ba070b9fe2657804b1b40277c48"},
|
||||
{file = "lxml-6.0.2-cp39-cp39-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6cdaefac66e8b8f30e37a9b4768a391e1f8a16a7526d5bc77a7928408ef68e93"},
|
||||
{file = "lxml-6.0.2-cp39-cp39-manylinux_2_31_armv7l.whl", hash = "sha256:b738f7e648735714bbb82bdfd030203360cfeab7f6e8a34772b3c8c8b820568c"},
|
||||
{file = "lxml-6.0.2-cp39-cp39-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:daf42de090d59db025af61ce6bdb2521f0f102ea0e6ea310f13c17610a97da4c"},
|
||||
{file = "lxml-6.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:66328dabea70b5ba7e53d94aa774b733cf66686535f3bc9250a7aab53a91caaf"},
|
||||
{file = "lxml-6.0.2-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:e237b807d68a61fc3b1e845407e27e5eb8ef69bc93fe8505337c1acb4ee300b6"},
|
||||
{file = "lxml-6.0.2-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:ac02dc29fd397608f8eb15ac1610ae2f2f0154b03f631e6d724d9e2ad4ee2c84"},
|
||||
{file = "lxml-6.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:817ef43a0c0b4a77bd166dc9a09a555394105ff3374777ad41f453526e37f9cb"},
|
||||
{file = "lxml-6.0.2-cp39-cp39-win32.whl", hash = "sha256:bc532422ff26b304cfb62b328826bd995c96154ffd2bac4544f37dbb95ecaa8f"},
|
||||
{file = "lxml-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:995e783eb0374c120f528f807443ad5a83a656a8624c467ea73781fc5f8a8304"},
|
||||
{file = "lxml-6.0.2-cp39-cp39-win_arm64.whl", hash = "sha256:08b9d5e803c2e4725ae9e8559ee880e5328ed61aa0935244e0515d7d9dbec0aa"},
|
||||
{file = "lxml-6.0.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:e748d4cf8fef2526bb2a589a417eba0c8674e29ffcb570ce2ceca44f1e567bf6"},
|
||||
{file = "lxml-6.0.2-pp310-pypy310_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4ddb1049fa0579d0cbd00503ad8c58b9ab34d1254c77bc6a5576d96ec7853dba"},
|
||||
{file = "lxml-6.0.2-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cb233f9c95f83707dae461b12b720c1af9c28c2d19208e1be03387222151daf5"},
|
||||
{file = "lxml-6.0.2-pp310-pypy310_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bc456d04db0515ce3320d714a1eac7a97774ff0849e7718b492d957da4631dd4"},
|
||||
{file = "lxml-6.0.2-pp310-pypy310_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2613e67de13d619fd283d58bda40bff0ee07739f624ffee8b13b631abf33083d"},
|
||||
{file = "lxml-6.0.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:24a8e756c982c001ca8d59e87c80c4d9dcd4d9b44a4cbeb8d9be4482c514d41d"},
|
||||
{file = "lxml-6.0.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1c06035eafa8404b5cf475bb37a9f6088b0aca288d4ccc9d69389750d5543700"},
|
||||
{file = "lxml-6.0.2-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c7d13103045de1bdd6fe5d61802565f1a3537d70cd3abf596aa0af62761921ee"},
|
||||
{file = "lxml-6.0.2-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0a3c150a95fbe5ac91de323aa756219ef9cf7fde5a3f00e2281e30f33fa5fa4f"},
|
||||
{file = "lxml-6.0.2-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:60fa43be34f78bebb27812ed90f1925ec99560b0fa1decdb7d12b84d857d31e9"},
|
||||
{file = "lxml-6.0.2-pp311-pypy311_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:21c73b476d3cfe836be731225ec3421fa2f048d84f6df6a8e70433dff1376d5a"},
|
||||
{file = "lxml-6.0.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:27220da5be049e936c3aca06f174e8827ca6445a4353a1995584311487fc4e3e"},
|
||||
{file = "lxml-6.0.2.tar.gz", hash = "sha256:cd79f3367bd74b317dda655dc8fcfa304d9eb6e4fb06b7168c5cf27f96e0cd62"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
cssselect = ["cssselect (>=0.7)"]
|
||||
html-clean = ["lxml_html_clean"]
|
||||
html5 = ["html5lib"]
|
||||
htmlsoup = ["BeautifulSoup4"]
|
||||
|
||||
[[package]]
|
||||
name = "pillow"
|
||||
version = "12.2.0"
|
||||
description = "Python Imaging Library (fork)"
|
||||
optional = false
|
||||
python-versions = ">=3.10"
|
||||
files = [
|
||||
{file = "pillow-12.2.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:a4e8f36e677d3336f35089648c8955c51c6d386a13cf6ee9c189c5f5bd713a9f"},
|
||||
{file = "pillow-12.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e589959f10d9824d39b350472b92f0ce3b443c0a3442ebf41c40cb8361c5b97"},
|
||||
{file = "pillow-12.2.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a52edc8bfff4429aaabdf4d9ee0daadbbf8562364f940937b941f87a4290f5ff"},
|
||||
{file = "pillow-12.2.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:975385f4776fafde056abb318f612ef6285b10a1f12b8570f3647ad0d74b48ec"},
|
||||
{file = "pillow-12.2.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bd9c0c7a0c681a347b3194c500cb1e6ca9cab053ea4d82a5cf45b6b754560136"},
|
||||
{file = "pillow-12.2.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:88d387ff40b3ff7c274947ed3125dedf5262ec6919d83946753b5f3d7c67ea4c"},
|
||||
{file = "pillow-12.2.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:51c4167c34b0d8ba05b547a3bb23578d0ba17b80a5593f93bd8ecb123dd336a3"},
|
||||
{file = "pillow-12.2.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:34c0d99ecccea270c04882cb3b86e7b57296079c9a4aff88cb3b33563d95afaa"},
|
||||
{file = "pillow-12.2.0-cp310-cp310-win32.whl", hash = "sha256:b85f66ae9eb53e860a873b858b789217ba505e5e405a24b85c0464822fe88032"},
|
||||
{file = "pillow-12.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:673aa32138f3e7531ccdbca7b3901dba9b70940a19ccecc6a37c77d5fdeb05b5"},
|
||||
{file = "pillow-12.2.0-cp310-cp310-win_arm64.whl", hash = "sha256:3e080565d8d7c671db5802eedfb438e5565ffa40115216eabb8cd52d0ecce024"},
|
||||
{file = "pillow-12.2.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:8be29e59487a79f173507c30ddf57e733a357f67881430449bb32614075a40ab"},
|
||||
{file = "pillow-12.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:71cde9a1e1551df7d34a25462fc60325e8a11a82cc2e2f54578e5e9a1e153d65"},
|
||||
{file = "pillow-12.2.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f490f9368b6fc026f021db16d7ec2fbf7d89e2edb42e8ec09d2c60505f5729c7"},
|
||||
{file = "pillow-12.2.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8bd7903a5f2a4545f6fd5935c90058b89d30045568985a71c79f5fd6edf9b91e"},
|
||||
{file = "pillow-12.2.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3997232e10d2920a68d25191392e3a4487d8183039e1c74c2297f00ed1c50705"},
|
||||
{file = "pillow-12.2.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e74473c875d78b8e9d5da2a70f7099549f9eb37ded4e2f6a463e60125bccd176"},
|
||||
{file = "pillow-12.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:56a3f9c60a13133a98ecff6197af34d7824de9b7b38c3654861a725c970c197b"},
|
||||
{file = "pillow-12.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:90e6f81de50ad6b534cab6e5aef77ff6e37722b2f5d908686f4a5c9eba17a909"},
|
||||
{file = "pillow-12.2.0-cp311-cp311-win32.whl", hash = "sha256:8c984051042858021a54926eb597d6ee3012393ce9c181814115df4c60b9a808"},
|
||||
{file = "pillow-12.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:6e6b2a0c538fc200b38ff9eb6628228b77908c319a005815f2dde585a0664b60"},
|
||||
{file = "pillow-12.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:9a8a34cc89c67a65ea7437ce257cea81a9dad65b29805f3ecee8c8fe8ff25ffe"},
|
||||
{file = "pillow-12.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2d192a155bbcec180f8564f693e6fd9bccff5a7af9b32e2e4bf8c9c69dbad6b5"},
|
||||
{file = "pillow-12.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f3f40b3c5a968281fd507d519e444c35f0ff171237f4fdde090dd60699458421"},
|
||||
{file = "pillow-12.2.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:03e7e372d5240cc23e9f07deca4d775c0817bffc641b01e9c3af208dbd300987"},
|
||||
{file = "pillow-12.2.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b86024e52a1b269467a802258c25521e6d742349d760728092e1bc2d135b4d76"},
|
||||
{file = "pillow-12.2.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7371b48c4fa448d20d2714c9a1f775a81155050d383333e0a6c15b1123dda005"},
|
||||
{file = "pillow-12.2.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62f5409336adb0663b7caa0da5c7d9e7bdbaae9ce761d34669420c2a801b2780"},
|
||||
{file = "pillow-12.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:01afa7cf67f74f09523699b4e88c73fb55c13346d212a59a2db1f86b0a63e8c5"},
|
||||
{file = "pillow-12.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc3d34d4a8fbec3e88a79b92e5465e0f9b842b628675850d860b8bd300b159f5"},
|
||||
{file = "pillow-12.2.0-cp312-cp312-win32.whl", hash = "sha256:58f62cc0f00fd29e64b29f4fd923ffdb3859c9f9e6105bfc37ba1d08994e8940"},
|
||||
{file = "pillow-12.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:7f84204dee22a783350679a0333981df803dac21a0190d706a50475e361c93f5"},
|
||||
{file = "pillow-12.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:af73337013e0b3b46f175e79492d96845b16126ddf79c438d7ea7ff27783a414"},
|
||||
{file = "pillow-12.2.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:8297651f5b5679c19968abefd6bb84d95fe30ef712eb1b2d9b2d31ca61267f4c"},
|
||||
{file = "pillow-12.2.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:50d8520da2a6ce0af445fa6d648c4273c3eeefbc32d7ce049f22e8b5c3daecc2"},
|
||||
{file = "pillow-12.2.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:766cef22385fa1091258ad7e6216792b156dc16d8d3fa607e7545b2b72061f1c"},
|
||||
{file = "pillow-12.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5d2fd0fa6b5d9d1de415060363433f28da8b1526c1c129020435e186794b3795"},
|
||||
{file = "pillow-12.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:56b25336f502b6ed02e889f4ece894a72612fe885889a6e8c4c80239ff6e5f5f"},
|
||||
{file = "pillow-12.2.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f1c943e96e85df3d3478f7b691f229887e143f81fedab9b20205349ab04d73ed"},
|
||||
{file = "pillow-12.2.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:03f6fab9219220f041c74aeaa2939ff0062bd5c364ba9ce037197f4c6d498cd9"},
|
||||
{file = "pillow-12.2.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5cdfebd752ec52bf5bb4e35d9c64b40826bc5b40a13df7c3cda20a2c03a0f5ed"},
|
||||
{file = "pillow-12.2.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eedf4b74eda2b5a4b2b2fb4c006d6295df3bf29e459e198c90ea48e130dc75c3"},
|
||||
{file = "pillow-12.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:00a2865911330191c0b818c59103b58a5e697cae67042366970a6b6f1b20b7f9"},
|
||||
{file = "pillow-12.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1e1757442ed87f4912397c6d35a0db6a7b52592156014706f17658ff58bbf795"},
|
||||
{file = "pillow-12.2.0-cp313-cp313-win32.whl", hash = "sha256:144748b3af2d1b358d41286056d0003f47cb339b8c43a9ea42f5fea4d8c66b6e"},
|
||||
{file = "pillow-12.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:390ede346628ccc626e5730107cde16c42d3836b89662a115a921f28440e6a3b"},
|
||||
{file = "pillow-12.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:8023abc91fba39036dbce14a7d6535632f99c0b857807cbbbf21ecc9f4717f06"},
|
||||
{file = "pillow-12.2.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:042db20a421b9bafecc4b84a8b6e444686bd9d836c7fd24542db3e7df7baad9b"},
|
||||
{file = "pillow-12.2.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:dd025009355c926a84a612fecf58bb315a3f6814b17ead51a8e48d3823d9087f"},
|
||||
{file = "pillow-12.2.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:88ddbc66737e277852913bd1e07c150cc7bb124539f94c4e2df5344494e0a612"},
|
||||
{file = "pillow-12.2.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d362d1878f00c142b7e1a16e6e5e780f02be8195123f164edf7eddd911eefe7c"},
|
||||
{file = "pillow-12.2.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2c727a6d53cb0018aadd8018c2b938376af27914a68a492f59dfcaca650d5eea"},
|
||||
{file = "pillow-12.2.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:efd8c21c98c5cc60653bcb311bef2ce0401642b7ce9d09e03a7da87c878289d4"},
|
||||
{file = "pillow-12.2.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9f08483a632889536b8139663db60f6724bfcb443c96f1b18855860d7d5c0fd4"},
|
||||
{file = "pillow-12.2.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dac8d77255a37e81a2efcbd1fc05f1c15ee82200e6c240d7e127e25e365c39ea"},
|
||||
{file = "pillow-12.2.0-cp313-cp313t-win32.whl", hash = "sha256:ee3120ae9dff32f121610bb08e4313be87e03efeadfc6c0d18f89127e24d0c24"},
|
||||
{file = "pillow-12.2.0-cp313-cp313t-win_amd64.whl", hash = "sha256:325ca0528c6788d2a6c3d40e3568639398137346c3d6e66bb61db96b96511c98"},
|
||||
{file = "pillow-12.2.0-cp313-cp313t-win_arm64.whl", hash = "sha256:2e5a76d03a6c6dcef67edabda7a52494afa4035021a79c8558e14af25313d453"},
|
||||
{file = "pillow-12.2.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:3adc9215e8be0448ed6e814966ecf3d9952f0ea40eb14e89a102b87f450660d8"},
|
||||
{file = "pillow-12.2.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:6a9adfc6d24b10f89588096364cc726174118c62130c817c2837c60cf08a392b"},
|
||||
{file = "pillow-12.2.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:6a6e67ea2e6feda684ed370f9a1c52e7a243631c025ba42149a2cc5934dec295"},
|
||||
{file = "pillow-12.2.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2bb4a8d594eacdfc59d9e5ad972aa8afdd48d584ffd5f13a937a664c3e7db0ed"},
|
||||
{file = "pillow-12.2.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:80b2da48193b2f33ed0c32c38140f9d3186583ce7d516526d462645fd98660ae"},
|
||||
{file = "pillow-12.2.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22db17c68434de69d8ecfc2fe821569195c0c373b25cccb9cbdacf2c6e53c601"},
|
||||
{file = "pillow-12.2.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7b14cc0106cd9aecda615dd6903840a058b4700fcb817687d0ee4fc8b6e389be"},
|
||||
{file = "pillow-12.2.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8cbeb542b2ebc6fcdacabf8aca8c1a97c9b3ad3927d46b8723f9d4f033288a0f"},
|
||||
{file = "pillow-12.2.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4bfd07bc812fbd20395212969e41931001fd59eb55a60658b0e5710872e95286"},
|
||||
{file = "pillow-12.2.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9aba9a17b623ef750a4d11b742cbafffeb48a869821252b30ee21b5e91392c50"},
|
||||
{file = "pillow-12.2.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:deede7c263feb25dba4e82ea23058a235dcc2fe1f6021025dc71f2b618e26104"},
|
||||
{file = "pillow-12.2.0-cp314-cp314-win32.whl", hash = "sha256:632ff19b2778e43162304d50da0181ce24ac5bb8180122cbe1bf4673428328c7"},
|
||||
{file = "pillow-12.2.0-cp314-cp314-win_amd64.whl", hash = "sha256:4e6c62e9d237e9b65fac06857d511e90d8461a32adcc1b9065ea0c0fa3a28150"},
|
||||
{file = "pillow-12.2.0-cp314-cp314-win_arm64.whl", hash = "sha256:b1c1fbd8a5a1af3412a0810d060a78b5136ec0836c8a4ef9aa11807f2a22f4e1"},
|
||||
{file = "pillow-12.2.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:57850958fe9c751670e49b2cecf6294acc99e562531f4bd317fa5ddee2068463"},
|
||||
{file = "pillow-12.2.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d5d38f1411c0ed9f97bcb49b7bd59b6b7c314e0e27420e34d99d844b9ce3b6f3"},
|
||||
{file = "pillow-12.2.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5c0a9f29ca8e79f09de89293f82fc9b0270bb4af1d58bc98f540cc4aedf03166"},
|
||||
{file = "pillow-12.2.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1610dd6c61621ae1cf811bef44d77e149ce3f7b95afe66a4512f8c59f25d9ebe"},
|
||||
{file = "pillow-12.2.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a34329707af4f73cf1782a36cd2289c0368880654a2c11f027bcee9052d35dd"},
|
||||
{file = "pillow-12.2.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e9c4f5b3c546fa3458a29ab22646c1c6c787ea8f5ef51300e5a60300736905e"},
|
||||
{file = "pillow-12.2.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fb043ee2f06b41473269765c2feae53fc2e2fbf96e5e22ca94fb5ad677856f06"},
|
||||
{file = "pillow-12.2.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f278f034eb75b4e8a13a54a876cc4a5ab39173d2cdd93a638e1b467fc545ac43"},
|
||||
{file = "pillow-12.2.0-cp314-cp314t-win32.whl", hash = "sha256:6bb77b2dcb06b20f9f4b4a8454caa581cd4dd0643a08bacf821216a16d9c8354"},
|
||||
{file = "pillow-12.2.0-cp314-cp314t-win_amd64.whl", hash = "sha256:6562ace0d3fb5f20ed7290f1f929cae41b25ae29528f2af1722966a0a02e2aa1"},
|
||||
{file = "pillow-12.2.0-cp314-cp314t-win_arm64.whl", hash = "sha256:aa88ccfe4e32d362816319ed727a004423aab09c5cea43c01a4b435643fa34eb"},
|
||||
{file = "pillow-12.2.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0538bd5e05efec03ae613fd89c4ce0368ecd2ba239cc25b9f9be7ed426b0af1f"},
|
||||
{file = "pillow-12.2.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:394167b21da716608eac917c60aa9b969421b5dcbbe02ae7f013e7b85811c69d"},
|
||||
{file = "pillow-12.2.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5d04bfa02cc2d23b497d1e90a0f927070043f6cbf303e738300532379a4b4e0f"},
|
||||
{file = "pillow-12.2.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0c838a5125cee37e68edec915651521191cef1e6aa336b855f495766e77a366e"},
|
||||
{file = "pillow-12.2.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a6c9fa44005fa37a91ebfc95d081e8079757d2e904b27103f4f5fa6f0bf78c0"},
|
||||
{file = "pillow-12.2.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:25373b66e0dd5905ed63fa3cae13c82fbddf3079f2c8bf15c6fb6a35586324c1"},
|
||||
{file = "pillow-12.2.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:bfa9c230d2fe991bed5318a5f119bd6780cda2915cca595393649fc118ab895e"},
|
||||
{file = "pillow-12.2.0.tar.gz", hash = "sha256:a830b1a40919539d07806aa58e1b114df53ddd43213d9c8b75847eee6c0182b5"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
docs = ["furo", "olefile", "sphinx (>=8.2)", "sphinx-autobuild", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"]
|
||||
fpx = ["olefile"]
|
||||
mic = ["olefile"]
|
||||
test-arrow = ["arro3-compute", "arro3-core", "nanoarrow", "pyarrow"]
|
||||
tests = ["check-manifest", "coverage (>=7.4.2)", "defusedxml", "markdown2", "olefile", "packaging", "pyroma (>=5)", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "trove-classifiers (>=2024.10.12)"]
|
||||
xmp = ["defusedxml"]
|
||||
|
||||
[[package]]
|
||||
name = "pytils"
|
||||
version = "0.4.4"
|
||||
description = "Russian-specific string utils"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "pytils-0.4.4-py3-none-any.whl", hash = "sha256:e54c16465a5fdb65d414e2da8045e6cc6de79889acda6143dcef2e1e86a1a840"},
|
||||
{file = "pytils-0.4.4.tar.gz", hash = "sha256:9992a96caad57daa211584df1da4fd825f11e836d3ed93011785f1d02ab6f0ca"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "2026.4.4"
|
||||
description = "Alternative regular expression module, to replace re."
|
||||
optional = false
|
||||
python-versions = ">=3.10"
|
||||
files = [
|
||||
{file = "regex-2026.4.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:74fa82dcc8143386c7c0392e18032009d1db715c25f4ba22d23dc2e04d02a20f"},
|
||||
{file = "regex-2026.4.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a85b620a388d6c9caa12189233109e236b3da3deffe4ff11b84ae84e218a274f"},
|
||||
{file = "regex-2026.4.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2895506ebe32cc63eeed8f80e6eae453171cfccccab35b70dc3129abec35a5b8"},
|
||||
{file = "regex-2026.4.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6780f008ee81381c737634e75c24e5a6569cc883c4f8e37a37917ee79efcafd9"},
|
||||
{file = "regex-2026.4.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:88e9b048345c613f253bea4645b2fe7e579782b82cac99b1daad81e29cc2ed8e"},
|
||||
{file = "regex-2026.4.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:be061028481186ba62a0f4c5f1cc1e3d5ab8bce70c89236ebe01023883bc903b"},
|
||||
{file = "regex-2026.4.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d2228c02b368d69b724c36e96d3d1da721561fb9cc7faa373d7bf65e07d75cb5"},
|
||||
{file = "regex-2026.4.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0540e5b733618a2f84e9cb3e812c8afa82e151ca8e19cf6c4e95c5a65198236f"},
|
||||
{file = "regex-2026.4.4-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cf9b1b2e692d4877880388934ac746c99552ce6bf40792a767fd42c8c99f136d"},
|
||||
{file = "regex-2026.4.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:011bb48bffc1b46553ac704c975b3348717f4e4aa7a67522b51906f99da1820c"},
|
||||
{file = "regex-2026.4.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8512fcdb43f1bf18582698a478b5ab73f9c1667a5b7548761329ef410cd0a760"},
|
||||
{file = "regex-2026.4.4-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:867bddc63109a0276f5a31999e4c8e0eb7bbbad7d6166e28d969a2c1afeb97f9"},
|
||||
{file = "regex-2026.4.4-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:1b9a00b83f3a40e09859c78920571dcb83293c8004079653dd22ec14bbfa98c7"},
|
||||
{file = "regex-2026.4.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e355be718caf838aa089870259cf1776dc2a4aa980514af9d02c59544d9a8b22"},
|
||||
{file = "regex-2026.4.4-cp310-cp310-win32.whl", hash = "sha256:33bfda9684646d323414df7abe5692c61d297dbb0530b28ec66442e768813c59"},
|
||||
{file = "regex-2026.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:0709f22a56798457ae317bcce42aacee33c680068a8f14097430d9f9ba364bee"},
|
||||
{file = "regex-2026.4.4-cp310-cp310-win_arm64.whl", hash = "sha256:ee9627de8587c1a22201cb16d0296ab92b4df5cdcb5349f4e9744d61db7c7c98"},
|
||||
{file = "regex-2026.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b4c36a85b00fadb85db9d9e90144af0a980e1a3d2ef9cd0f8a5bef88054657c6"},
|
||||
{file = "regex-2026.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:dcb5453ecf9cd58b562967badd1edbf092b0588a3af9e32ee3d05c985077ce87"},
|
||||
{file = "regex-2026.4.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6aa809ed4dc3706cc38594d67e641601bd2f36d5555b2780ff074edfcb136cf8"},
|
||||
{file = "regex-2026.4.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:33424f5188a7db12958246a54f59a435b6cb62c5cf9c8d71f7cc49475a5fdada"},
|
||||
{file = "regex-2026.4.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7d346fccdde28abba117cc9edc696b9518c3307fbfcb689e549d9b5979018c6d"},
|
||||
{file = "regex-2026.4.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:415a994b536440f5011aa77e50a4274d15da3245e876e5c7f19da349caaedd87"},
|
||||
{file = "regex-2026.4.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:21e5eb86179b4c67b5759d452ea7c48eb135cd93308e7a260aa489ed2eb423a4"},
|
||||
{file = "regex-2026.4.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:312ec9dd1ae7d96abd8c5a36a552b2139931914407d26fba723f9e53c8186f86"},
|
||||
{file = "regex-2026.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a0d2b28aa1354c7cd7f71b7658c4326f7facac106edd7f40eda984424229fd59"},
|
||||
{file = "regex-2026.4.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:349d7310eddff40429a099c08d995c6d4a4bfaf3ff40bd3b5e5cb5a5a3c7d453"},
|
||||
{file = "regex-2026.4.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:e7ab63e9fe45a9ec3417509e18116b367e89c9ceb6219222a3396fa30b147f80"},
|
||||
{file = "regex-2026.4.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:fe896e07a5a2462308297e515c0054e9ec2dd18dfdc9427b19900b37dfe6f40b"},
|
||||
{file = "regex-2026.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:eb59c65069498dbae3c0ef07bbe224e1eaa079825a437fb47a479f0af11f774f"},
|
||||
{file = "regex-2026.4.4-cp311-cp311-win32.whl", hash = "sha256:2a5d273181b560ef8397c8825f2b9d57013de744da9e8257b8467e5da8599351"},
|
||||
{file = "regex-2026.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:9542ccc1e689e752594309444081582f7be2fdb2df75acafea8a075108566735"},
|
||||
{file = "regex-2026.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:b5f9fb784824a042be3455b53d0b112655686fdb7a91f88f095f3fee1e2a2a54"},
|
||||
{file = "regex-2026.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c07ab8794fa929e58d97a0e1796b8b76f70943fa39df225ac9964615cf1f9d52"},
|
||||
{file = "regex-2026.4.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2c785939dc023a1ce4ec09599c032cc9933d258a998d16ca6f2b596c010940eb"},
|
||||
{file = "regex-2026.4.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1b1ce5c81c9114f1ce2f9288a51a8fd3aeea33a0cc440c415bf02da323aa0a76"},
|
||||
{file = "regex-2026.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:760ef21c17d8e6a4fe8cf406a97cf2806a4df93416ccc82fc98d25b1c20425be"},
|
||||
{file = "regex-2026.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7088fcdcb604a4417c208e2169715800d28838fefd7455fbe40416231d1d47c1"},
|
||||
{file = "regex-2026.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:07edca1ba687998968f7db5bc355288d0c6505caa7374f013d27356d93976d13"},
|
||||
{file = "regex-2026.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:993f657a7c1c6ec51b5e0ba97c9817d06b84ea5fa8d82e43b9405de0defdc2b9"},
|
||||
{file = "regex-2026.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:2b69102a743e7569ebee67e634a69c4cb7e59d6fa2e1aa7d3bdbf3f61435f62d"},
|
||||
{file = "regex-2026.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6dac006c8b6dda72d86ea3d1333d45147de79a3a3f26f10c1cf9287ca4ca0ac3"},
|
||||
{file = "regex-2026.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:50a766ee2010d504554bfb5f578ed2e066898aa26411d57e6296230627cdefa0"},
|
||||
{file = "regex-2026.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:9e2f5217648f68e3028c823df58663587c1507a5ba8419f4fdfc8a461be76043"},
|
||||
{file = "regex-2026.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:39d8de85a08e32632974151ba59c6e9140646dcc36c80423962b1c5c0a92e244"},
|
||||
{file = "regex-2026.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:55d9304e0e7178dfb1e106c33edf834097ddf4a890e2f676f6c5118f84390f73"},
|
||||
{file = "regex-2026.4.4-cp312-cp312-win32.whl", hash = "sha256:04bb679bc0bde8a7bfb71e991493d47314e7b98380b083df2447cda4b6edb60f"},
|
||||
{file = "regex-2026.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:db0ac18435a40a2543dbb3d21e161a6c78e33e8159bd2e009343d224bb03bb1b"},
|
||||
{file = "regex-2026.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:4ce255cc05c1947a12989c6db801c96461947adb7a59990f1360b5983fab4983"},
|
||||
{file = "regex-2026.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:62f5519042c101762509b1d717b45a69c0139d60414b3c604b81328c01bd1943"},
|
||||
{file = "regex-2026.4.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3790ba9fb5dd76715a7afe34dbe603ba03f8820764b1dc929dd08106214ed031"},
|
||||
{file = "regex-2026.4.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8fae3c6e795d7678963f2170152b0d892cf6aee9ee8afc8c45e6be38d5107fe7"},
|
||||
{file = "regex-2026.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:298c3ec2d53225b3bf91142eb9691025bab610e0c0c51592dde149db679b3d17"},
|
||||
{file = "regex-2026.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e9638791082eaf5b3ac112c587518ee78e083a11c4b28012d8fe2a0f536dfb17"},
|
||||
{file = "regex-2026.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ae3e764bd4c5ff55035dc82a8d49acceb42a5298edf6eb2fc4d328ee5dd7afae"},
|
||||
{file = "regex-2026.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ffa81f81b80047ba89a3c69ae6a0f78d06f4a42ce5126b0eb2a0a10ad44e0b2e"},
|
||||
{file = "regex-2026.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f56ebf9d70305307a707911b88469213630aba821e77de7d603f9d2f0730687d"},
|
||||
{file = "regex-2026.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:773d1dfd652bbffb09336abf890bfd64785c7463716bf766d0eb3bc19c8b7f27"},
|
||||
{file = "regex-2026.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d51d20befd5275d092cdffba57ded05f3c436317ee56466c8928ac32d960edaf"},
|
||||
{file = "regex-2026.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:0a51cdb3c1e9161154f976cb2bef9894bc063ac82f31b733087ffb8e880137d0"},
|
||||
{file = "regex-2026.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:ae5266a82596114e41fb5302140e9630204c1b5f325c770bec654b95dd54b0aa"},
|
||||
{file = "regex-2026.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c882cd92ec68585e9c1cf36c447ec846c0d94edd706fe59e0c198e65822fd23b"},
|
||||
{file = "regex-2026.4.4-cp313-cp313-win32.whl", hash = "sha256:05568c4fbf3cb4fa9e28e3af198c40d3237cf6041608a9022285fe567ec3ad62"},
|
||||
{file = "regex-2026.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:3384df51ed52db0bea967e21458ab0a414f67cdddfd94401688274e55147bb81"},
|
||||
{file = "regex-2026.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:acd38177bd2c8e69a411d6521760806042e244d0ef94e2dd03ecdaa8a3c99427"},
|
||||
{file = "regex-2026.4.4-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:f94a11a9d05afcfcfa640e096319720a19cc0c9f7768e1a61fceee6a3afc6c7c"},
|
||||
{file = "regex-2026.4.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:36bcb9d6d1307ab629edc553775baada2aefa5c50ccc0215fbfd2afcfff43141"},
|
||||
{file = "regex-2026.4.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:261c015b3e2ed0919157046d768774ecde57f03d8fa4ba78d29793447f70e717"},
|
||||
{file = "regex-2026.4.4-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c228cf65b4a54583763645dcd73819b3b381ca8b4bb1b349dee1c135f4112c07"},
|
||||
{file = "regex-2026.4.4-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dd2630faeb6876fb0c287f664d93ddce4d50cd46c6e88e60378c05c9047e08ca"},
|
||||
{file = "regex-2026.4.4-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6a50ab11b7779b849472337191f3a043e27e17f71555f98d0092fa6d73364520"},
|
||||
{file = "regex-2026.4.4-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0734f63afe785138549fbe822a8cfeaccd1bae814c5057cc0ed5b9f2de4fc883"},
|
||||
{file = "regex-2026.4.4-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c4ee50606cb1967db7e523224e05f32089101945f859928e65657a2cbb3d278b"},
|
||||
{file = "regex-2026.4.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6c1818f37be3ca02dcb76d63f2c7aaba4b0dc171b579796c6fbe00148dfec6b1"},
|
||||
{file = "regex-2026.4.4-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:f5bfc2741d150d0be3e4a0401a5c22b06e60acb9aa4daa46d9e79a6dcd0f135b"},
|
||||
{file = "regex-2026.4.4-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:504ffa8a03609a087cad81277a629b6ce884b51a24bd388a7980ad61748618ff"},
|
||||
{file = "regex-2026.4.4-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:70aadc6ff12e4b444586e57fc30771f86253f9f0045b29016b9605b4be5f7dfb"},
|
||||
{file = "regex-2026.4.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f4f83781191007b6ef43b03debc35435f10cad9b96e16d147efe84a1d48bdde4"},
|
||||
{file = "regex-2026.4.4-cp313-cp313t-win32.whl", hash = "sha256:e014a797de43d1847df957c0a2a8e861d1c17547ee08467d1db2c370b7568baa"},
|
||||
{file = "regex-2026.4.4-cp313-cp313t-win_amd64.whl", hash = "sha256:b15b88b0d52b179712632832c1d6e58e5774f93717849a41096880442da41ab0"},
|
||||
{file = "regex-2026.4.4-cp313-cp313t-win_arm64.whl", hash = "sha256:586b89cdadf7d67bf86ae3342a4dcd2b8d70a832d90c18a0ae955105caf34dbe"},
|
||||
{file = "regex-2026.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:2da82d643fa698e5e5210e54af90181603d5853cf469f5eedf9bfc8f59b4b8c7"},
|
||||
{file = "regex-2026.4.4-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:54a1189ad9d9357760557c91103d5e421f0a2dabe68a5cdf9103d0dcf4e00752"},
|
||||
{file = "regex-2026.4.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:76d67d5afb1fe402d10a6403bae668d000441e2ab115191a804287d53b772951"},
|
||||
{file = "regex-2026.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e7cd3e4ee8d80447a83bbc9ab0c8459781fa77087f856c3e740d7763be0df27f"},
|
||||
{file = "regex-2026.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2e19e18c568d2866d8b6a6dfad823db86193503f90823a8f66689315ba28fbe8"},
|
||||
{file = "regex-2026.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7698a6f38730fd1385d390d1ed07bb13dce39aa616aca6a6d89bea178464b9a4"},
|
||||
{file = "regex-2026.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:173a66f3651cdb761018078e2d9487f4cf971232c990035ec0eb1cdc6bf929a9"},
|
||||
{file = "regex-2026.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fa7922bbb2cc84fa062d37723f199d4c0cd200245ce269c05db82d904db66b83"},
|
||||
{file = "regex-2026.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:59f67cd0a0acaf0e564c20bbd7f767286f23e91e2572c5703bf3e56ea7557edb"},
|
||||
{file = "regex-2026.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:475e50f3f73f73614f7cba5524d6de49dee269df00272a1b85e3d19f6d498465"},
|
||||
{file = "regex-2026.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:a1c0c7d67b64d85ac2e1879923bad2f08a08f3004055f2f406ef73c850114bd4"},
|
||||
{file = "regex-2026.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:1371c2ccbb744d66ee63631cc9ca12aa233d5749972626b68fe1a649dd98e566"},
|
||||
{file = "regex-2026.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:59968142787042db793348a3f5b918cf24ced1f23247328530e063f89c128a95"},
|
||||
{file = "regex-2026.4.4-cp314-cp314-win32.whl", hash = "sha256:59efe72d37fd5a91e373e5146f187f921f365f4abc1249a5ab446a60f30dd5f8"},
|
||||
{file = "regex-2026.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:e0aab3ff447845049d676827d2ff714aab4f73f340e155b7de7458cf53baa5a4"},
|
||||
{file = "regex-2026.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:a7a5bb6aa0cf62208bb4fa079b0c756734f8ad0e333b425732e8609bd51ee22f"},
|
||||
{file = "regex-2026.4.4-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:97850d0638391bdc7d35dc1c1039974dcb921eaafa8cc935ae4d7f272b1d60b3"},
|
||||
{file = "regex-2026.4.4-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:ee7337f88f2a580679f7bbfe69dc86c043954f9f9c541012f49abc554a962f2e"},
|
||||
{file = "regex-2026.4.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7429f4e6192c11d659900c0648ba8776243bf396ab95558b8c51a345afeddde6"},
|
||||
{file = "regex-2026.4.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dc4f10fbd5dd13dcf4265b4cc07d69ca70280742870c97ae10093e3d66000359"},
|
||||
{file = "regex-2026.4.4-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a152560af4f9742b96f3827090f866eeec5becd4765c8e0d3473d9d280e76a5a"},
|
||||
{file = "regex-2026.4.4-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:54170b3e95339f415d54651f97df3bff7434a663912f9358237941bbf9143f55"},
|
||||
{file = "regex-2026.4.4-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:07f190d65f5a72dcb9cf7106bfc3d21e7a49dd2879eda2207b683f32165e4d99"},
|
||||
{file = "regex-2026.4.4-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9a2741ce5a29d3c84b0b94261ba630ab459a1b847a0d6beca7d62d188175c790"},
|
||||
{file = "regex-2026.4.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:b26c30df3a28fd9793113dac7385a4deb7294a06c0f760dd2b008bd49a9139bc"},
|
||||
{file = "regex-2026.4.4-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:421439d1bee44b19f4583ccf42670ca464ffb90e9fdc38d37f39d1ddd1e44f1f"},
|
||||
{file = "regex-2026.4.4-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:b40379b53ecbc747fd9bdf4a0ea14eb8188ca1bd0f54f78893a39024b28f4863"},
|
||||
{file = "regex-2026.4.4-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:08c55c13d2eef54f73eeadc33146fb0baaa49e7335eb1aff6ae1324bf0ddbe4a"},
|
||||
{file = "regex-2026.4.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9776b85f510062f5a75ef112afe5f494ef1635607bf1cc220c1391e9ac2f5e81"},
|
||||
{file = "regex-2026.4.4-cp314-cp314t-win32.whl", hash = "sha256:385edaebde5db5be103577afc8699fea73a0e36a734ba24870be7ffa61119d74"},
|
||||
{file = "regex-2026.4.4-cp314-cp314t-win_amd64.whl", hash = "sha256:5d354b18839328927832e2fa5f7c95b7a3ccc39e7a681529e1685898e6436d45"},
|
||||
{file = "regex-2026.4.4-cp314-cp314t-win_arm64.whl", hash = "sha256:af0384cb01a33600c49505c27c6c57ab0b27bf84a74e28524c92ca897ebdac9d"},
|
||||
{file = "regex-2026.4.4.tar.gz", hash = "sha256:e08270659717f6973523ce3afbafa53515c4dc5dcad637dc215b6fd50f689423"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "reportlab"
|
||||
version = "4.4.10"
|
||||
description = "The Reportlab Toolkit"
|
||||
optional = false
|
||||
python-versions = "<4,>=3.9"
|
||||
files = [
|
||||
{file = "reportlab-4.4.10-py3-none-any.whl", hash = "sha256:5abc815746ae2bc44e7ff25db96814f921349ca814c992c7eac3c26029bf7c24"},
|
||||
{file = "reportlab-4.4.10.tar.gz", hash = "sha256:5cbbb34ac3546039d0086deb2938cdec06b12da3cdb836e813258eb33cd28487"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
charset-normalizer = "*"
|
||||
pillow = ">=9.0.0"
|
||||
|
||||
[package.extras]
|
||||
accel = ["rl_accel (>=0.9.0,<1.1)"]
|
||||
bidi = ["rlbidi"]
|
||||
pycairo = ["freetype-py (>=2.3.0,<2.4)", "rlPyCairo (>=0.2.0,<1)"]
|
||||
renderpm = ["rl_renderPM (>=4.0.3,<4.1)"]
|
||||
shaping = ["uharfbuzz"]
|
||||
|
||||
[[package]]
|
||||
name = "setuptools"
|
||||
version = "82.0.1"
|
||||
description = "Most extensible Python build backend with support for C/C++ extension modules"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "setuptools-82.0.1-py3-none-any.whl", hash = "sha256:a59e362652f08dcd477c78bb6e7bd9d80a7995bc73ce773050228a348ce2e5bb"},
|
||||
{file = "setuptools-82.0.1.tar.gz", hash = "sha256:7d872682c5d01cfde07da7bccc7b65469d3dca203318515ada1de5eda35efbf9"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.13.0)"]
|
||||
core = ["importlib_metadata (>=6)", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging (>=24.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"]
|
||||
cover = ["pytest-cov"]
|
||||
doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"]
|
||||
enabler = ["pytest-enabler (>=2.2)"]
|
||||
test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"]
|
||||
type = ["importlib_metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (==1.18.*)", "pytest-mypy"]
|
||||
|
||||
[[package]]
|
||||
name = "soupsieve"
|
||||
version = "2.8.3"
|
||||
description = "A modern CSS selector implementation for Beautiful Soup."
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "soupsieve-2.8.3-py3-none-any.whl", hash = "sha256:ed64f2ba4eebeab06cc4962affce381647455978ffc1e36bb79a545b91f45a95"},
|
||||
{file = "soupsieve-2.8.3.tar.gz", hash = "sha256:3267f1eeea4251fb42728b6dfb746edc9acaffc4a45b27e19450b676586e8349"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sqlparse"
|
||||
version = "0.5.5"
|
||||
description = "A non-validating SQL parser."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "sqlparse-0.5.5-py3-none-any.whl", hash = "sha256:12a08b3bf3eec877c519589833aed092e2444e68240a3577e8e26148acc7b1ba"},
|
||||
{file = "sqlparse-0.5.5.tar.gz", hash = "sha256:e20d4a9b0b8585fdf63b10d30066c7c94c5d7a7ec47c889a2d83a3caa93ff28e"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
dev = ["build"]
|
||||
doc = ["sphinx"]
|
||||
|
||||
[[package]]
|
||||
name = "svglib"
|
||||
version = "1.5.1"
|
||||
description = "A pure-Python library for reading and converting SVG"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "svglib-1.5.1.tar.gz", hash = "sha256:3ae765d3a9409ee60c0fb4d24c2deb6a80617aa927054f5bcd7fc98f0695e587"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
cssselect2 = ">=0.2.0"
|
||||
lxml = "*"
|
||||
reportlab = "*"
|
||||
tinycss2 = ">=0.6.0"
|
||||
|
||||
[[package]]
|
||||
name = "tinycss2"
|
||||
version = "1.5.1"
|
||||
description = "A tiny CSS parser"
|
||||
optional = false
|
||||
python-versions = ">=3.10"
|
||||
files = [
|
||||
{file = "tinycss2-1.5.1-py3-none-any.whl", hash = "sha256:3415ba0f5839c062696996998176c4a3751d18b7edaaeeb658c9ce21ec150661"},
|
||||
{file = "tinycss2-1.5.1.tar.gz", hash = "sha256:d339d2b616ba90ccce58da8495a78f46e55d4d25f9fd71dfd526f07e7d53f957"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
webencodings = ">=0.4"
|
||||
|
||||
[package.extras]
|
||||
doc = ["furo", "sphinx"]
|
||||
test = ["pytest", "ruff"]
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.15.0"
|
||||
description = "Backported and Experimental Type Hints for Python 3.9+"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"},
|
||||
{file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tzdata"
|
||||
version = "2026.1"
|
||||
description = "Provider of IANA time zone data"
|
||||
optional = false
|
||||
python-versions = ">=2"
|
||||
files = [
|
||||
{file = "tzdata-2026.1-py2.py3-none-any.whl", hash = "sha256:4b1d2be7ac37ceafd7327b961aa3a54e467efbdb563a23655fbfe0d39cfc42a9"},
|
||||
{file = "tzdata-2026.1.tar.gz", hash = "sha256:67658a1903c75917309e753fdc349ac0efd8c27db7a0cb406a25be4840f87f98"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "urllib3"
|
||||
version = "2.6.3"
|
||||
description = "HTTP library with thread-safe connection pooling, file post, and more."
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4"},
|
||||
{file = "urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
brotli = ["brotli (>=1.2.0)", "brotlicffi (>=1.2.0.0)"]
|
||||
h2 = ["h2 (>=4,<5)"]
|
||||
socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"]
|
||||
zstd = ["backports-zstd (>=1.0.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "webencodings"
|
||||
version = "0.5.1"
|
||||
description = "Character encoding aliases for legacy web content"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"},
|
||||
{file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"},
|
||||
]
|
||||
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = ">=3.12,<3.13"
|
||||
content-hash = "8284fc2ef5f2a06d27b41da40cc2067920b8fd5fed8f23621b777a15d8ca4559"
|
||||
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="120" height="120" viewBox="0 0 811 812" shape-rendering="geometricPrecision" image-rendering="optimizeQuality" fill-rule="evenodd" xmlns:v="https://vecta.io/nano"><defs><radialGradient id="A" gradientUnits="userSpaceOnUse" gradientTransform="matrix(1.36573 -0 -0 1.36573 -101 -116)" cx="276" cy="317" r="846" fx="276" fy="317"><stop offset="0" stop-color="#a2d9f7"/><stop offset="1" stop-color="#008dd2"/></radialGradient></defs><path d="M414 67h0c13 1 25 1 35 2 6 0 15 1 25 2-24-43-52-69-80-69h0c-4 0-8-1-11-2-103 6-196 50-265 119-45 45-80 101-100 164 19-13 40-26 64-37 83-38 197-63 321-63-6-23-14-46-23-69-9-22 10-48 34-47zm107-51c13 19 25 42 35 66 36 7 71 15 105 26 7 2 20 15 23 22 11 35 20 70 26 106 9 3 16 7 24 10 21 10 40 21 57 32-20-61-55-115-99-159-47-48-106-83-171-103zm290 388c-9-32-41-61-88-86 2 20 3 40 5 60 1 24-25 43-47 34-20-8-43-14-68-20 1 6 1 11 1 16h0 0c0 126-22 241-57 325-10 22-20 42-32 60 64-19 121-55 167-101 74-73 119-175 119-286h0v-1-1zM411 812c30-11 58-49 82-105 32-76 51-182 51-299h0 0v-28c-28-5-56-8-83-11-13-1-29-17-30-31-3-28-7-57-12-86h-11 0 0c-116 0-221 22-297 57C44 340 2 381 2 422h0c0 4-1 8-2 12 7 100 51 191 118 258 74 74 175 120 287 120h0 0 6zm171-654c4 14 7 28 11 43 14 3 28 6 42 10-4-14-7-29-12-43l-41-10zm67 129c-14-5-29-9-44-12 2 15 4 30 5 46 15 3 30 7 45 11l-6-45zm-109 22c-2-16-4-33-7-48-14-2-29-4-44-5 3 15 5 31 7 48 14 1 29 3 44 5zm-65-124c15 1 29 2 44 4-4-16-9-31-13-45l-29-3c-4-1-9-1-15-2 5 16 9 31 13 46z" fill="url(#A)" fill-rule="nonzero"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="120" height="120" viewBox="0 0 811 812" shape-rendering="geometricPrecision" image-rendering="optimizeQuality" fill-rule="evenodd"><defs><radialGradient id="A" gradientUnits="userSpaceOnUse" gradientTransform="matrix(1.36573 -0 -0 1.36573 -101 -116)" cx="276" cy="317" r="846" fx="276" fy="317"><stop offset="0" stop-color="#a2d9f7"/><stop offset="1" stop-color="#008dd2"/></radialGradient></defs><path d="M414 67h0c13 1 25 1 35 2 6 0 15 1 25 2-24-43-52-69-80-69h0c-4 0-8-1-11-2-103 6-196 50-265 119-45 45-80 101-100 164 19-13 40-26 64-37 83-38 197-63 321-63-6-23-14-46-23-69-9-22 10-48 34-47zm107-51c13 19 25 42 35 66 36 7 71 15 105 26 7 2 20 15 23 22 11 35 20 70 26 106 9 3 16 7 24 10 21 10 40 21 57 32-20-61-55-115-99-159-47-48-106-83-171-103zm290 388c-9-32-41-61-88-86 2 20 3 40 5 60 1 24-25 43-47 34-20-8-43-14-68-20 1 6 1 11 1 16h0 0c0 126-22 241-57 325-10 22-20 42-32 60 64-19 121-55 167-101 74-73 119-175 119-286h0v-1-1zM411 812c30-11 58-49 82-105 32-76 51-182 51-299h0 0v-28c-28-5-56-8-83-11-13-1-29-17-30-31-3-28-7-57-12-86h-11 0 0c-116 0-221 22-297 57C44 340 2 381 2 422h0c0 4-1 8-2 12 7 100 51 191 118 258 74 74 175 120 287 120h0 0 6zm171-654c4 14 7 28 11 43 14 3 28 6 42 10-4-14-7-29-12-43l-41-10zm67 129c-14-5-29-9-44-12 2 15 4 30 5 46 15 3 30 7 45 11l-6-45zm-109 22c-2-16-4-33-7-48-14-2-29-4-44-5 3 15 5 31 7 48 14 1 29 3 44 5zm-65-124c15 1 29 2 44 4-4-16-9-31-13-45l-29-3c-4-1-9-1-15-2 5 16 9 31 13 46z" fill="url(#A)" fill-rule="nonzero"/></svg>
|
||||
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
19
public/llms.txt
Normal file
@@ -0,0 +1,19 @@
|
||||
# CADpoint
|
||||
|
||||
CADpoint — русскоязычный сайт о 3D-печати, CAD и смежных темах.
|
||||
|
||||
## Основные страницы
|
||||
- https://cadpoint.ru/ — главная лента новостей и публикаций
|
||||
- https://cadpoint.ru/alltags — список всех тегов (тем) сайта
|
||||
- https://cadpoint.ru/sitemap.xml — полный список опубликованных страниц
|
||||
|
||||
## Страницы контента
|
||||
- https://cadpoint.ru/item/<id>-<slug> — статья или публикация
|
||||
- https://cadpoint.ru/tag_<slug> — страницы с материалами по тематическому тегу
|
||||
|
||||
## Как ориентироваться
|
||||
- Основной контент на русском языке.
|
||||
- Приоритет отдавайте публичным страницам статей и тегов.
|
||||
- Админка, debug-URL и служебные страницы не являются основным контентом.
|
||||
- Для ссылок используйте канонические публичные URL без служебных параметров.
|
||||
|
||||
7
public/media/.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
# В этом каталоге будут храниться media-файлы Django (например, изображения, загружаемые пользователями).
|
||||
# Это не должно попадаьт в репозиторий.
|
||||
*.*
|
||||
|
||||
# Файл .gitkeep используется для того, чтобы Git отслеживал пустой каталог. Он не содержит никакого кода
|
||||
# и служит только для сохранения структуры каталогов в репозитории.
|
||||
!.gitkeep
|
||||
@@ -1,3 +1,11 @@
|
||||
# robots.txt for CADpoint
|
||||
|
||||
User-agent: *
|
||||
Disallow: /admin/
|
||||
Clean-param: ref /item/
|
||||
# Оставляем только нужное: игнорируем параметр `p` и 'n' у страниц статей.
|
||||
Clean-param: p&n /item/
|
||||
|
||||
# подсказываем карту сайта и основной хост.
|
||||
Sitemap: https://cadpoint.ru/sitemap.xml
|
||||
Host: cadpoint.ru
|
||||
|
||||
# Ссылка на файл для ИИ-моделей: https://cadpoint.ru/llms.txt
|
||||
|
||||
18
public/static/README.md
Normal file
@@ -0,0 +1,18 @@
|
||||
# СТАТИКА
|
||||
|
||||
Эта папка предназначена для хранения статических файлов, таких как изображения, стили CSS и JavaScript файлы.
|
||||
Папка будет внутри контейнера, а файлы внутри папки будут доступны с помощью gunicorn и whitenoise на хосте.
|
||||
|
||||
Но в случае сбоя контейнера, ошибок Djанго и ошибок 404 при доступе в media (которые будут в папке `media` на
|
||||
внешнем хосте с доступом через nginx), то эти файлы могут стать недоступны. Таким образом, файлы необхдимые
|
||||
для отображения кастовых страниц ошибок 400, 403, 404, 500 и других должны быть скопированы на внешний хост,
|
||||
в папку `media/_error` в момент запуска контейнера (с помощью `entrypoint.sh` или инструкций `command`
|
||||
в `docker-compose.yml`)
|
||||
|
||||
. Вот список этих файлов (путь указан относительно папки `static`):
|
||||
* svgs/favicon.svg
|
||||
* img/favicon.png
|
||||
* img/favicon.ico
|
||||
* svgs/xxx-error.svg
|
||||
* svgs/404-error.svg
|
||||
* svgs/500-error.svg
|
||||
15
public/static/codemirror/README.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# CodeMirror 6 static bundle
|
||||
|
||||
Эта папка содержит готовый результат сборки CodeMirror 6 для админки Django.
|
||||
|
||||
Главный файл здесь — `editor.js`. Его не нужно править вручную: это собранный и
|
||||
минифицированный бандл.
|
||||
|
||||
Если нужна более свежая версия CodeMirror, меняй сборку в
|
||||
`frontend-assembly/build-codemirror6.sh` и затем заново запускай:
|
||||
|
||||
```bash
|
||||
bash ./frontend-assembly/build-codemirror6.sh
|
||||
```
|
||||
|
||||
После пересборки в этой папке должен обновиться только `editor.js`.
|
||||
22
public/static/codemirror/editor.js
Normal file
86
public/static/css/admin-select2-theme.css
Normal file
@@ -0,0 +1,86 @@
|
||||
/* Стили Select2 для админки Django 5.
|
||||
Используем CSS-переменные админки, чтобы оформление автоматически
|
||||
подстраивалось под светлую, тёмную и auto-тему. */
|
||||
|
||||
.select2-container--default .select2-selection--single,
|
||||
.select2-container--default .select2-selection--multiple {
|
||||
min-height: 2.5rem;
|
||||
border: 1px solid var(--border-color) !important;
|
||||
border-radius: 4px;
|
||||
background-color: var(--body-bg) !important;
|
||||
color: var(--body-fg) !important;
|
||||
}
|
||||
|
||||
.select2-container--default .select2-selection--single .select2-selection__rendered,
|
||||
.select2-container--default .select2-selection--multiple .select2-selection__rendered {
|
||||
color: var(--body-fg) !important;
|
||||
}
|
||||
|
||||
.select2-container--default .select2-selection--single .select2-selection__placeholder,
|
||||
.select2-container--default .select2-selection--multiple .select2-selection__placeholder {
|
||||
color: var(--body-quiet-color) !important;
|
||||
}
|
||||
|
||||
.select2-container--default .select2-selection--single .select2-selection__arrow {
|
||||
height: 2.4rem;
|
||||
}
|
||||
|
||||
.select2-container--default .select2-selection--multiple .select2-selection__choice {
|
||||
border: 1px solid var(--border-color) !important;
|
||||
border-radius: 999px;
|
||||
background-color: var(--selected-bg) !important;
|
||||
color: var(--body-fg) !important;
|
||||
padding: 0.15rem 0.55rem;
|
||||
}
|
||||
|
||||
.select2-container--default .select2-selection--multiple .select2-selection__choice__remove {
|
||||
color: var(--body-quiet-color) !important;
|
||||
margin-right: 0.35rem;
|
||||
}
|
||||
|
||||
.select2-container--default .select2-search--inline .select2-search__field,
|
||||
.select2-container--default .select2-search--dropdown .select2-search__field {
|
||||
color: var(--body-fg) !important;
|
||||
border: 1px solid var(--border-color) !important;
|
||||
background-color: var(--body-bg) !important;
|
||||
}
|
||||
|
||||
.select2-container--default .select2-results > .select2-results__options {
|
||||
background-color: var(--body-bg) !important;
|
||||
color: var(--body-fg) !important;
|
||||
}
|
||||
|
||||
.select2-container--default .select2-results__option {
|
||||
color: var(--body-fg) !important;
|
||||
}
|
||||
|
||||
.select2-container--default .select2-results__option--highlighted[aria-selected] {
|
||||
background-color: var(--primary) !important;
|
||||
color: var(--primary-fg) !important;
|
||||
}
|
||||
|
||||
.select2-container--default .select2-results__option[aria-selected="true"] {
|
||||
background-color: var(--selected-bg) !important;
|
||||
color: var(--body-fg) !important;
|
||||
}
|
||||
|
||||
.select2-container--default.select2-container--focus .select2-selection--multiple,
|
||||
.select2-container--default.select2-container--open .select2-selection--single,
|
||||
.select2-container--default.select2-container--open .select2-selection--multiple {
|
||||
border-color: var(--primary) !important;
|
||||
}
|
||||
|
||||
.select2-dropdown {
|
||||
border: 1px solid var(--border-color) !important;
|
||||
background-color: var(--body-bg) !important;
|
||||
color: var(--body-fg) !important;
|
||||
}
|
||||
|
||||
.select2-container--default .select2-results__option--disabled {
|
||||
color: var(--body-quiet-color) !important;
|
||||
}
|
||||
|
||||
.select2-container--default .select2-results__group {
|
||||
color: var(--body-quiet-color) !important;
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ div.lenta > div.row > div[class^="col"] div.row > div[class^="col"] > h3 > a {
|
||||
div.lenta > div.row > div[class^="col"] div.row > div[class^="col"] > nav.sm-tags { margin-bottom: 2ex; }
|
||||
div.lenta > div.row > div[class^="col"] div.row > div[class^="col"] > nav.sm-tags > a,
|
||||
div.lenta > div.row > div[class^="col"] div.row > div[class^="col"] > nav.sm-tags > span.active,
|
||||
div.news > div.row > div[class^="col"] > nav.sm-tags > a {
|
||||
div.news > div.row > article[class^="col"] > nav.sm-tags > a {
|
||||
font-family: 'Ubuntu Condensed', sans-serif;
|
||||
font-size: smaller;
|
||||
display: inline-block;
|
||||
@@ -101,13 +101,13 @@ div.lenta > div.row > div[class^="col"] div.row > div[class^="col"] > nav.sm-tag
|
||||
}
|
||||
div.lenta > div.row > div[class^="col"] div.row > div[class^="col"] > h3 > a:hover,
|
||||
div.lenta > div.row > div[class^="col"] div.row > div[class^="col"] > nav.sm-tags > a:hover,
|
||||
div.news > div.row > div[class^="col"] > nav.sm-tags > a:hover {
|
||||
div.news > div.row > article[class^="col"] > nav.sm-tags > a:hover {
|
||||
color: #008DD2;
|
||||
border-bottom: 1px solid #008DD2;
|
||||
transition:0.6s;
|
||||
}
|
||||
div.lenta > div.row > div[class^="col"] div.row > div[class^="col"] > nav.sm-tags > a:hover,
|
||||
div.news > div.row > div[class^="col"] > nav.sm-tags > a:hover {
|
||||
div.news > div.row > article[class^="col"] > nav.sm-tags > a:hover {
|
||||
animation-name: scaleUpDown2;
|
||||
animation-duration: 0.6s;
|
||||
animation-timing-function: ease-in-out; -webkit-animation-timing-function: ease-in-out;
|
||||
@@ -150,33 +150,33 @@ div.lenta > div.row > div[class^="col"] > div.row:not(:last-child) { border-bott
|
||||
/****************************
|
||||
** ОФОРМЛЕНИЕ НОВОСТИ *********
|
||||
****************************/
|
||||
div.news > div.row > div[class^="col"] > time {
|
||||
div.news > div.row > article[class^="col"] > time {
|
||||
font-family: 'Ubuntu Condensed', sans-serif;
|
||||
color: #888;
|
||||
font-size: 120%;
|
||||
font-weight: 100;
|
||||
}
|
||||
div.news > div.row > div[class^="col"] > time > small > small {
|
||||
div.news > div.row > article[class^="col"] > time > small > small {
|
||||
font-size: x-small;
|
||||
color: #006799;
|
||||
padding-left: 4em;
|
||||
}
|
||||
div.news > div.row > div[class^="col"] > p,
|
||||
div.news > div.row > div[class^="col"] > ul > li,
|
||||
div.news > div.row > div[class^="col"] > ol > li {
|
||||
div.news > div.row > article[class^="col"] > p,
|
||||
div.news > div.row > article[class^="col"] > ul > li,
|
||||
div.news > div.row > article[class^="col"] > ol > li {
|
||||
font-size: 115%;
|
||||
font-weight: 100;
|
||||
}
|
||||
div.news > div.row > div[class^="col"] > p > img,
|
||||
div.news > div.row > div[class^="col"] > p > iframe {
|
||||
div.news > div.row > article[class^="col"] > p > img,
|
||||
div.news > div.row > article[class^="col"] > p > iframe {
|
||||
width: 100%;
|
||||
border: solid #006799 1px;
|
||||
border-radius: 4px;
|
||||
border-bottom: solid white 12px;
|
||||
border-image: url('/static/svgs/logo_cadpoint-2021-border.svg') 0 0 600 0 round;
|
||||
}
|
||||
div.news > div.row > div[class^="col"] > p > iframe { aspect-ratio: calc( 19 / 9); }
|
||||
div.news > div.row > div[class^="col"] > blockquote {
|
||||
div.news > div.row > article[class^="col"] > p > iframe { aspect-ratio: calc( 19 / 9); }
|
||||
div.news > div.row > article[class^="col"] > blockquote {
|
||||
font-family: 'Ubuntu Condensed', sans-serif;
|
||||
font-weight: 100;
|
||||
color: #006799;
|
||||
@@ -189,7 +189,7 @@ div.news > div.row > div[class^="col"] > blockquote {
|
||||
background:#EDEDED;
|
||||
border-radius: 10px 35px 35px 10px;
|
||||
}
|
||||
div.news > div.row > div[class^="col"] > blockquote:before{
|
||||
div.news > div.row > article[class^="col"] > blockquote:before{
|
||||
/*content: "\201C";*/
|
||||
content: "«";
|
||||
color:gray;
|
||||
@@ -197,11 +197,11 @@ div.news > div.row > div[class^="col"] > blockquote:before{
|
||||
position: absolute;
|
||||
left: 0; top:-1ex;
|
||||
}
|
||||
div.news > div.row > div[class^="col"] > hr {
|
||||
div.news > div.row > article[class^="col"] > hr {
|
||||
margin: -30px auto 20px; padding: 0; color: transparent; height: 50px;
|
||||
border: none; border-bottom: 1px dashed #006799; box-shadow: 0 20px 20px -20px gray;
|
||||
}
|
||||
div.news > div.row > div[class^="col"] > nav.sm-tags {
|
||||
div.news > div.row > article[class^="col"] > nav.sm-tags {
|
||||
padding-bottom: 2em;
|
||||
}
|
||||
div.news > div.row > nav[class^="col"] {
|
||||
@@ -319,7 +319,7 @@ div.news > nav.row { margin: 0; }
|
||||
}
|
||||
/* левые треугольные хвостики не активного тега (с ненулевой представленностью на странице) серые */
|
||||
.tags > a.tag-not-active:before {
|
||||
background: -moz-linear-gradient(45deg, #ccc 0%, #888 100%);7
|
||||
background: -moz-linear-gradient(45deg, #ccc 0%, #888 100%);
|
||||
background: -webkit-gradient(linear, left bottom, right top, color-stop(0%,#ccc), color-stop(100%,#888));
|
||||
background: -webkit-linear-gradient(-45deg, #ccc 0%,#888 100%);
|
||||
background: -o-linear-gradient(45deg, #ccc 0%,#888 100%);
|
||||
@@ -357,6 +357,10 @@ div.news > nav.row { margin: 0; }
|
||||
.tags > a.tag-not-active:hover .tag-note { color: #008DD2; }
|
||||
.tags > a:not(:hover), .tags > a:not(:hover) .tag-note { transition:1s; }
|
||||
|
||||
/* Для страницы всех тегов */
|
||||
a.tag-active > .tag-note > ._tag { color: #fee; transition:0.5s;}
|
||||
a.tag-active:hover > .tag-note > ._tag { color: #008DD2; transition:0.5s;}
|
||||
|
||||
/****************************
|
||||
** ОФОРМЛЕНИЕ ТЕГОВ *********
|
||||
****************************/
|
||||
@@ -403,4 +407,21 @@ nav > .pagination > .page-item > .page-link {
|
||||
}
|
||||
nav > .pagination > .page-item > .page-link:hover {background: #008DD2; transition: 0.6s;}
|
||||
nav > .pagination > .page-item > .page-link:not(hover) {transition: 1.2s;}
|
||||
nav > .pagination > .page-item.disabled > .page-link {color: silver; background: gray}
|
||||
nav > .pagination > .page-item.disabled > .page-link {color: silver; background: gray}
|
||||
|
||||
/* -----------------------------------------
|
||||
СТИЛИ ДЛЯ ВИСЯЧЕЙ ПУНКТУАЦИИ ТИПОГРАФА ETPGRF
|
||||
Значения отступов (padding) для компенсирующих пробелов и полей (margin) для самих символов висячей
|
||||
пунктуации приведены для шрифта Times New Roman и должны быть скорректированы в зависимости
|
||||
от выбранного вами шрифта.
|
||||
------------------------------------------ */
|
||||
/* --- ЛЕВЫЕ ВИСЯЧИЕ СИМВОЛЫ --- */
|
||||
.etp-laquo { margin-left: -0.49em; } /* « */
|
||||
.etp-ldquo, .etp-bdquo { margin-left: -0.4em; } /* “ “ */
|
||||
.etp-lsquo { margin-left: -0.22em; } /* ’ */
|
||||
.etp-lpar, .etp-lsqb, .etp-lcub { margin-left: -0.23em; } /* ( [ { */
|
||||
/* компенсирующие пробелы для левых висячих символов */
|
||||
.etp-sp-laquo { padding-right: 0.49em; }
|
||||
.etp-sp-ldquo, .etp-sp-bdquo { padding-right: 0.4em; }
|
||||
.etp-sp-lsquo { padding-right: 0.22em; }
|
||||
.etp-sp-lpar, .etp-sp-lsqb, .etp-sp-lcub { padding-right: 0.35em; }
|
||||
|
||||
39
public/static/js/accept-cookies.js
Normal file
@@ -0,0 +1,39 @@
|
||||
(function (window, document) {
|
||||
'use strict';
|
||||
|
||||
// Защита от повторного подключения, если файл случайно вставят дважды.
|
||||
if (window.__cadpointAcceptCookiesLoaded) {
|
||||
return;
|
||||
}
|
||||
window.__cadpointAcceptCookiesLoaded = true;
|
||||
|
||||
var COOKIE_NAME = 'cookie_accept';
|
||||
var COOKIE_VALUE = 'yes';
|
||||
var COOKIE_TTL_MS = 7_948_800_000;
|
||||
var bannerId = 'cookies_accept';
|
||||
var buttonId = 'cookies_accept_button';
|
||||
|
||||
function acceptCookies() {
|
||||
// Сохраняем согласие тем же ключом, что и раньше, чтобы серверная логика не менялась.
|
||||
var cookieAcceptDate = new Date();
|
||||
cookieAcceptDate.setTime(cookieAcceptDate.getTime() + COOKIE_TTL_MS);
|
||||
document.cookie = COOKIE_NAME + '=' + COOKIE_VALUE + ';expires=' + cookieAcceptDate;
|
||||
|
||||
var banner = document.getElementById(bannerId);
|
||||
if (banner) {
|
||||
banner.remove();
|
||||
}
|
||||
}
|
||||
|
||||
function bindAcceptButton() {
|
||||
var button = document.getElementById(buttonId);
|
||||
if (!button) {
|
||||
return;
|
||||
}
|
||||
|
||||
button.addEventListener('click', acceptCookies);
|
||||
}
|
||||
|
||||
bindAcceptButton();
|
||||
})(window, document);
|
||||
|
||||
67
public/static/js/footer-counters.js
Normal file
@@ -0,0 +1,67 @@
|
||||
(function (window, document) {
|
||||
'use strict';
|
||||
|
||||
// Защита от повторной инициализации, если файл случайно подключат дважды.
|
||||
if (window.__cadpointFooterCountersLoaded) {
|
||||
return;
|
||||
}
|
||||
window.__cadpointFooterCountersLoaded = true;
|
||||
|
||||
function appendScript(src, id) {
|
||||
// Не создаём дубликаты, если скрипт уже есть в DOM.
|
||||
if (id && document.getElementById(id)) {
|
||||
return null;
|
||||
}
|
||||
var script = document.createElement('script');
|
||||
script.async = true;
|
||||
script.src = src;
|
||||
if (id) {
|
||||
script.id = id;
|
||||
}
|
||||
(document.head || document.body).appendChild(script);
|
||||
return script;
|
||||
}
|
||||
function initGoogleAnalytics() {
|
||||
// Поддерживаем старый UA-код, чтобы не ломать текущую статистику.
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
window.gtag = window.gtag || function gtag() {
|
||||
window.dataLayer.push(arguments);
|
||||
};
|
||||
window.gtag('js', new Date());
|
||||
window.gtag('config', 'UA-9116991-1');
|
||||
appendScript('https://www.googletagmanager.com/gtag/js?id=UA-9116991-1', 'cadpoint-gtag-js');
|
||||
}
|
||||
|
||||
function initYandexMetrika() {
|
||||
// Повторяем стандартный паттерн Metrika: сначала очередь вызовов, потом загрузка внешнего файла.
|
||||
window.ym = window.ym || function ym() {
|
||||
(window.ym.a = window.ym.a || []).push(arguments);
|
||||
};
|
||||
window.ym.l = 1 * new Date();
|
||||
window.ym(198477, 'init', {
|
||||
clickmap: true,
|
||||
trackLinks: true,
|
||||
accurateTrackBounce: true,
|
||||
webvisor: true,
|
||||
});
|
||||
appendScript('https://mc.yandex.ru/metrika/tag.js', 'cadpoint-yandex-metrika-js');
|
||||
}
|
||||
|
||||
function initMailRuCounter() {
|
||||
// Rating Mail.ru тоже любит сначала получить очередь событий, а затем уже внешний код.
|
||||
window._tmr = window._tmr || [];
|
||||
window._tmr.push({
|
||||
id: '1612438',
|
||||
type: 'pageView',
|
||||
start: (new Date()).getTime(),
|
||||
});
|
||||
appendScript('https://top-fwz1.mail.ru/js/code.js', 'topmailru-code');
|
||||
}
|
||||
|
||||
// Вызываем функции-счетчики
|
||||
initGoogleAnalytics();
|
||||
initYandexMetrika();
|
||||
initMailRuCounter();
|
||||
|
||||
})(window, document);
|
||||
|
||||
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="120" height="120" viewBox="0 0 811 812" shape-rendering="geometricPrecision" image-rendering="optimizeQuality" fill-rule="evenodd" xmlns:v="https://vecta.io/nano"><defs><radialGradient id="A" gradientUnits="userSpaceOnUse" gradientTransform="matrix(1.36573 -0 -0 1.36573 -101 -116)" cx="276" cy="317" r="846" fx="276" fy="317"><stop offset="0" stop-color="#a2d9f7"/><stop offset="1" stop-color="#008dd2"/></radialGradient></defs><path d="M414 67h0c13 1 25 1 35 2 6 0 15 1 25 2-24-43-52-69-80-69h0c-4 0-8-1-11-2-103 6-196 50-265 119-45 45-80 101-100 164 19-13 40-26 64-37 83-38 197-63 321-63-6-23-14-46-23-69-9-22 10-48 34-47zm107-51c13 19 25 42 35 66 36 7 71 15 105 26 7 2 20 15 23 22 11 35 20 70 26 106 9 3 16 7 24 10 21 10 40 21 57 32-20-61-55-115-99-159-47-48-106-83-171-103zm290 388c-9-32-41-61-88-86 2 20 3 40 5 60 1 24-25 43-47 34-20-8-43-14-68-20 1 6 1 11 1 16h0 0c0 126-22 241-57 325-10 22-20 42-32 60 64-19 121-55 167-101 74-73 119-175 119-286h0v-1-1zM411 812c30-11 58-49 82-105 32-76 51-182 51-299h0 0v-28c-28-5-56-8-83-11-13-1-29-17-30-31-3-28-7-57-12-86h-11 0 0c-116 0-221 22-297 57C44 340 2 381 2 422h0c0 4-1 8-2 12 7 100 51 191 118 258 74 74 175 120 287 120h0 0 6zm171-654c4 14 7 28 11 43 14 3 28 6 42 10-4-14-7-29-12-43l-41-10zm67 129c-14-5-29-9-44-12 2 15 4 30 5 46 15 3 30 7 45 11l-6-45zm-109 22c-2-16-4-33-7-48-14-2-29-4-44-5 3 15 5 31 7 48 14 1 29 3 44 5zm-65-124c15 1 29 2 44 4-4-16-9-31-13-45l-29-3c-4-1-9-1-15-2 5 16 9 31 13 46z" fill="url(#A)" fill-rule="nonzero"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="120" height="120" viewBox="0 0 811 812" shape-rendering="geometricPrecision" image-rendering="optimizeQuality" fill-rule="evenodd"><defs><radialGradient id="A" gradientUnits="userSpaceOnUse" gradientTransform="matrix(1.36573 -0 -0 1.36573 -101 -116)" cx="276" cy="317" r="846" fx="276" fy="317"><stop offset="0" stop-color="#a2d9f7"/><stop offset="1" stop-color="#008dd2"/></radialGradient></defs><path d="M414 67h0c13 1 25 1 35 2 6 0 15 1 25 2-24-43-52-69-80-69h0c-4 0-8-1-11-2-103 6-196 50-265 119-45 45-80 101-100 164 19-13 40-26 64-37 83-38 197-63 321-63-6-23-14-46-23-69-9-22 10-48 34-47zm107-51c13 19 25 42 35 66 36 7 71 15 105 26 7 2 20 15 23 22 11 35 20 70 26 106 9 3 16 7 24 10 21 10 40 21 57 32-20-61-55-115-99-159-47-48-106-83-171-103zm290 388c-9-32-41-61-88-86 2 20 3 40 5 60 1 24-25 43-47 34-20-8-43-14-68-20 1 6 1 11 1 16h0 0c0 126-22 241-57 325-10 22-20 42-32 60 64-19 121-55 167-101 74-73 119-175 119-286h0v-1-1zM411 812c30-11 58-49 82-105 32-76 51-182 51-299h0 0v-28c-28-5-56-8-83-11-13-1-29-17-30-31-3-28-7-57-12-86h-11 0 0c-116 0-221 22-297 57C44 340 2 381 2 422h0c0 4-1 8-2 12 7 100 51 191 118 258 74 74 175 120 287 120h0 0 6zm171-654c4 14 7 28 11 43 14 3 28 6 42 10-4-14-7-29-12-43l-41-10zm67 129c-14-5-29-9-44-12 2 15 4 30 5 46 15 3 30 7 45 11l-6-45zm-109 22c-2-16-4-33-7-48-14-2-29-4-44-5 3 15 5 31 7 48 14 1 29 3 44 5zm-65-124c15 1 29 2 44 4-4-16-9-31-13-45l-29-3c-4-1-9-1-15-2 5 16 9 31 13 46z" fill="url(#A)" fill-rule="nonzero"/></svg>
|
||||
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1117" height="250" viewBox="0 0 286779 64226" shape-rendering="geometricPrecision" image-rendering="optimizeQuality" fill-rule="evenodd" xmlns:v="https://vecta.io/nano"><defs><linearGradient id="A" gradientUnits="userSpaceOnUse" x1="181696" y1="50202" x2="181696" y2="14029"><stop offset=".3" stop-color="#000"/><stop offset=".9" stop-color="#555"/></linearGradient></defs><g fill-rule="nonzero"><path d="M101107 38004c-173 4016-1303 7051-3382 9105-2078 2062-5006 3093-8791 3093-3984 0-7027-1312-9146-3926-2120-2623-3176-6359-3176-11208v-5930c0-4833 1097-8561 3283-11183 2194-2615 5237-3926 9138-3926 3827 0 6746 1072 8734 3208 1987 2144 3117 5221 3389 9229h-7125c-66-2483-446-4190-1147-5138-701-940-1987-1410-3851-1410-1905 0-3250 668-4041 1995-784 1336-1204 3522-1254 6573v6656c0 3497 388 5905 1171 7208 784 1311 2120 1963 4025 1963 1864 0 3159-454 3868-1369 709-907 1105-2556 1204-4940h7101zm21171 4528h-9683l-1888 7175h-7513l10993-35183h6499l11068 35183h-7588l-1888-7175zm-8140-5938h6573l-3283-12553-3290 12553zm20907 13113V14524h9303c4107 0 7381 1303 9822 3917 2441 2606 3687 6186 3728 10730v5699c0 4627-1221 8256-3654 10886-2441 2631-5806 3951-10094 3951h-9105zm7101-29245v23356h2127c2367 0 4033-627 5007-1872 964-1245 1468-3398 1517-6457v-6112c0-3291-462-5575-1377-6878-916-1295-2483-1971-4685-2037h-2589zm27966 16874v12371h-7109V14524h11984c3480 0 6259 1080 8329 3241 2070 2153 3101 4957 3101 8404s-1022 6169-3068 8173c-2045 1996-4882 2994-8503 2994h-4734zm0-5938h4875c1360 0 2399-445 3142-1328 742-883 1113-2169 1113-3851 0-1749-379-3143-1138-4182-751-1031-1765-1559-3043-1575h-4949v10936zm45814 3884c0 4726-1113 8388-3348 11002-2227 2615-5328 3918-9287 3918-3950 0-7051-1295-9303-3885-2251-2581-3398-6210-3431-10861v-6021c0-4841 1122-8618 3357-11332 2243-2713 5352-4074 9328-4074 3917 0 7002 1336 9253 4000 2260 2672 3398 6416 3431 11233v6020zm-7126-5913c0-3175-453-5534-1352-7084-899-1543-2301-2318-4206-2318-1881 0-3275 742-4182 2235-899 1493-1369 3761-1402 6804v6260c0 3084 462 5352 1378 6812 923 1460 2342 2186 4255 2186 1856 0 3233-710 4132-2136 899-1427 1361-3646 1377-6639v-6120zm19629 20338h-7101V14524h7101v35183zm30557 0h-7101l-10392-23076v23076h-7101V14524h7101l10416 23100V14524h7077v35183zm27793-29245h-8693v29245h-7125V20462h-8561v-5938h24379v5938z" fill="url(#A)"/></g></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1117" height="250" viewBox="0 0 286779 64226" shape-rendering="geometricPrecision" image-rendering="optimizeQuality" fill-rule="evenodd"><defs><linearGradient id="A" gradientUnits="userSpaceOnUse" x1="181696" y1="50202" x2="181696" y2="14029"><stop offset=".3" stop-color="#000"/><stop offset=".9" stop-color="#555"/></linearGradient></defs><g fill-rule="nonzero"><path d="M101107 38004c-173 4016-1303 7051-3382 9105-2078 2062-5006 3093-8791 3093-3984 0-7027-1312-9146-3926-2120-2623-3176-6359-3176-11208v-5930c0-4833 1097-8561 3283-11183 2194-2615 5237-3926 9138-3926 3827 0 6746 1072 8734 3208 1987 2144 3117 5221 3389 9229h-7125c-66-2483-446-4190-1147-5138-701-940-1987-1410-3851-1410-1905 0-3250 668-4041 1995-784 1336-1204 3522-1254 6573v6656c0 3497 388 5905 1171 7208 784 1311 2120 1963 4025 1963 1864 0 3159-454 3868-1369 709-907 1105-2556 1204-4940h7101zm21171 4528h-9683l-1888 7175h-7513l10993-35183h6499l11068 35183h-7588l-1888-7175zm-8140-5938h6573l-3283-12553-3290 12553zm20907 13113V14524h9303c4107 0 7381 1303 9822 3917 2441 2606 3687 6186 3728 10730v5699c0 4627-1221 8256-3654 10886-2441 2631-5806 3951-10094 3951h-9105zm7101-29245v23356h2127c2367 0 4033-627 5007-1872 964-1245 1468-3398 1517-6457v-6112c0-3291-462-5575-1377-6878-916-1295-2483-1971-4685-2037h-2589zm27966 16874v12371h-7109V14524h11984c3480 0 6259 1080 8329 3241 2070 2153 3101 4957 3101 8404s-1022 6169-3068 8173c-2045 1996-4882 2994-8503 2994h-4734zm0-5938h4875c1360 0 2399-445 3142-1328 742-883 1113-2169 1113-3851 0-1749-379-3143-1138-4182-751-1031-1765-1559-3043-1575h-4949v10936zm45814 3884c0 4726-1113 8388-3348 11002-2227 2615-5328 3918-9287 3918-3950 0-7051-1295-9303-3885-2251-2581-3398-6210-3431-10861v-6021c0-4841 1122-8618 3357-11332 2243-2713 5352-4074 9328-4074 3917 0 7002 1336 9253 4000 2260 2672 3398 6416 3431 11233v6020zm-7126-5913c0-3175-453-5534-1352-7084-899-1543-2301-2318-4206-2318-1881 0-3275 742-4182 2235-899 1493-1369 3761-1402 6804v6260c0 3084 462 5352 1378 6812 923 1460 2342 2186 4255 2186 1856 0 3233-710 4132-2136 899-1427 1361-3646 1377-6639v-6120zm19629 20338h-7101V14524h7101v35183zm30557 0h-7101l-10392-23076v23076h-7101V14524h7101l10416 23100V14524h7077v35183zm27793-29245h-8693v29245h-7125V20462h-8561v-5938h24379v5938z" fill="url(#A)"/></g></svg>
|
||||
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1117" height="250" viewBox="0 0 286779 64226" shape-rendering="geometricPrecision" image-rendering="optimizeQuality" fill-rule="evenodd" xmlns:v="https://vecta.io/nano"><defs><linearGradient id="A" gradientUnits="userSpaceOnUse" x1="181696" y1="50202" x2="181696" y2="14029"><stop offset="0" stop-color="#555"/><stop offset=".2" stop-color="#555"/><stop offset=".4" stop-color="#1f1b20"/><stop offset=".6"/><stop offset="1" stop-color="#555"/></linearGradient><radialGradient id="B" gradientUnits="userSpaceOnUse" gradientTransform="matrix(1.36572 -0 -0 1.36573 -7983 -9161)" cx="21827" cy="25048" r="32113" fx="21827" fy="25048"><stop offset="0" stop-color="#a2d9f7"/><stop offset="1" stop-color="#008dd2"/></radialGradient></defs><g fill-rule="nonzero"><path d="M32745 5313l46 1 2760 148c491 35 1140 92 1955 172-1919-3450-4117-5487-6335-5487h-2c-308 0-602-52-878-147-8147 446-15503 3952-20935 9383-3605 3605-6360 8056-7938 13024 1485-1068 3185-2047 5071-2925 6570-3057 15551-4969 25404-5018-504-1839-1099-3648-1796-5414-706-1785 731-3813 2648-3737zm8469-4030c1021 1521 1954 3275 2783 5228 2813 501 5596 1165 8319 2011 552 172 1606 1223 1783 1773 891 2760 1587 5562 2120 8380 637 259 1256 528 1854 806 1648 767 3153 1611 4498 2525-1596-4806-4299-9115-7806-12621-3731-3732-8371-6555-13551-8102zm22982 30651c-736-2470-3262-4823-7021-6801 175 1598 307 3189 399 4767 112 1923-1911 3387-3717 2692-1520-586-3340-1095-5315-1535l10 1244v10 6c-1 9970-1724 19056-4502 25670-744 1769-1572 3375-2474 4795 5034-1568 9544-4344 13188-7988 5823-5822 9433-13856 9433-22703v-2-5l-1-150zM32532 64222c2338-862 4595-3879 6456-8307 2528-6018 4096-14366 4096-23598v-6-10l-32-2260c-2190-337-4421-614-6543-844-1077-116-2318-1367-2425-2446-223-2257-532-4537-952-6816l-840-4h-10-5c-9199 0-17505 1725-23481 4506-5339 2485-8650 5679-8651 8985v2c0 309-51 605-145 882 548 7967 4023 15155 9354 20487 5822 5822 13856 9432 22704 9433h2 5l467-4zm13520-51731c303 1098 582 2237 835 3411 1142 235 2253 497 3333 784-267-1131-561-2250-888-3357-1085-309-2179-588-3280-838zm5308 10225c-1111-363-2271-693-3478-991 157 1210 287 2446 390 3703 1213 246 2395 515 3521 810-119-1184-264-2359-433-3522zm-8664 1759c-133-1316-297-2600-492-3849-1135-164-2296-300-3479-408 209 1261 389 2531 544 3804l3427 453zm-5112-9831c1173 82 2331 190 3468 323-317-1243-668-2432-1048-3559l-2250-267-1171-104c370 1191 701 2394 1001 3607z" fill="url(#B)"/><path d="M101107 38004c-173 4016-1303 7051-3382 9105-2078 2062-5006 3093-8791 3093-3984 0-7027-1312-9146-3926-2120-2623-3176-6359-3176-11208v-5930c0-4833 1097-8561 3283-11183 2194-2615 5237-3926 9138-3926 3827 0 6746 1072 8734 3208 1987 2144 3117 5221 3389 9229h-7125c-66-2483-446-4190-1147-5138-701-940-1987-1410-3851-1410-1905 0-3250 668-4041 1995-784 1336-1204 3522-1254 6573v6656c0 3497 388 5905 1171 7208 784 1311 2120 1963 4025 1963 1864 0 3159-454 3868-1369 709-907 1105-2556 1204-4940h7101zm21171 4528h-9683l-1888 7175h-7513l10993-35183h6499l11068 35183h-7588l-1888-7175zm-8140-5938h6573l-3283-12553-3290 12553zm20907 13113V14524h9303c4107 0 7381 1303 9822 3917 2441 2606 3687 6186 3728 10730v5699c0 4627-1221 8256-3654 10886-2441 2631-5806 3951-10094 3951h-9105zm7101-29245v23356h2127c2367 0 4033-627 5007-1872 964-1245 1468-3398 1517-6457v-6112c0-3291-462-5575-1377-6878-916-1295-2483-1971-4685-2037h-2589zm27966 16874v12371h-7109V14524h11984c3480 0 6259 1080 8329 3241 2070 2153 3101 4957 3101 8404s-1022 6169-3068 8173c-2045 1996-4882 2994-8503 2994h-4734zm0-5938h4875c1360 0 2399-445 3142-1328 742-883 1113-2169 1113-3851 0-1749-379-3143-1138-4182-751-1031-1765-1559-3043-1575h-4949v10936zm45814 3884c0 4726-1113 8388-3348 11002-2227 2615-5328 3918-9287 3918-3950 0-7051-1295-9303-3885-2251-2581-3398-6210-3431-10861v-6021c0-4841 1122-8618 3357-11332 2243-2713 5352-4074 9328-4074 3917 0 7002 1336 9253 4000 2260 2672 3398 6416 3431 11233v6020zm-7126-5913c0-3175-453-5534-1352-7084-899-1543-2301-2318-4206-2318-1881 0-3275 742-4182 2235-899 1493-1369 3761-1402 6804v6260c0 3084 462 5352 1378 6812 923 1460 2342 2186 4255 2186 1856 0 3233-710 4132-2136 899-1427 1361-3646 1377-6639v-6120zm19629 20338h-7101V14524h7101v35183zm30557 0h-7101l-10392-23076v23076h-7101V14524h7101l10416 23100V14524h7077v35183zm27793-29245h-8693v29245h-7125V20462h-8561v-5938h24379v5938z" fill="url(#A)"/></g></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1117" height="250" viewBox="0 0 286779 64226" shape-rendering="geometricPrecision" image-rendering="optimizeQuality" fill-rule="evenodd"><defs><linearGradient id="A" gradientUnits="userSpaceOnUse" x1="181696" y1="50202" x2="181696" y2="14029"><stop offset="0" stop-color="#555"/><stop offset=".2" stop-color="#555"/><stop offset=".4" stop-color="#1f1b20"/><stop offset=".6"/><stop offset="1" stop-color="#555"/></linearGradient><radialGradient id="B" gradientUnits="userSpaceOnUse" gradientTransform="matrix(1.36572 -0 -0 1.36573 -7983 -9161)" cx="21827" cy="25048" r="32113" fx="21827" fy="25048"><stop offset="0" stop-color="#a2d9f7"/><stop offset="1" stop-color="#008dd2"/></radialGradient></defs><g fill-rule="nonzero"><path d="M32745 5313l46 1 2760 148c491 35 1140 92 1955 172-1919-3450-4117-5487-6335-5487h-2c-308 0-602-52-878-147-8147 446-15503 3952-20935 9383-3605 3605-6360 8056-7938 13024 1485-1068 3185-2047 5071-2925 6570-3057 15551-4969 25404-5018-504-1839-1099-3648-1796-5414-706-1785 731-3813 2648-3737zm8469-4030c1021 1521 1954 3275 2783 5228 2813 501 5596 1165 8319 2011 552 172 1606 1223 1783 1773 891 2760 1587 5562 2120 8380 637 259 1256 528 1854 806 1648 767 3153 1611 4498 2525-1596-4806-4299-9115-7806-12621-3731-3732-8371-6555-13551-8102zm22982 30651c-736-2470-3262-4823-7021-6801 175 1598 307 3189 399 4767 112 1923-1911 3387-3717 2692-1520-586-3340-1095-5315-1535l10 1244v10 6c-1 9970-1724 19056-4502 25670-744 1769-1572 3375-2474 4795 5034-1568 9544-4344 13188-7988 5823-5822 9433-13856 9433-22703v-2-5l-1-150zM32532 64222c2338-862 4595-3879 6456-8307 2528-6018 4096-14366 4096-23598v-6-10l-32-2260c-2190-337-4421-614-6543-844-1077-116-2318-1367-2425-2446-223-2257-532-4537-952-6816l-840-4h-10-5c-9199 0-17505 1725-23481 4506-5339 2485-8650 5679-8651 8985v2c0 309-51 605-145 882 548 7967 4023 15155 9354 20487 5822 5822 13856 9432 22704 9433h2 5l467-4zm13520-51731c303 1098 582 2237 835 3411 1142 235 2253 497 3333 784-267-1131-561-2250-888-3357-1085-309-2179-588-3280-838zm5308 10225c-1111-363-2271-693-3478-991 157 1210 287 2446 390 3703 1213 246 2395 515 3521 810-119-1184-264-2359-433-3522zm-8664 1759c-133-1316-297-2600-492-3849-1135-164-2296-300-3479-408 209 1261 389 2531 544 3804l3427 453zm-5112-9831c1173 82 2331 190 3468 323-317-1243-668-2432-1048-3559l-2250-267-1171-104c370 1191 701 2394 1001 3607z" fill="url(#B)"/><path d="M101107 38004c-173 4016-1303 7051-3382 9105-2078 2062-5006 3093-8791 3093-3984 0-7027-1312-9146-3926-2120-2623-3176-6359-3176-11208v-5930c0-4833 1097-8561 3283-11183 2194-2615 5237-3926 9138-3926 3827 0 6746 1072 8734 3208 1987 2144 3117 5221 3389 9229h-7125c-66-2483-446-4190-1147-5138-701-940-1987-1410-3851-1410-1905 0-3250 668-4041 1995-784 1336-1204 3522-1254 6573v6656c0 3497 388 5905 1171 7208 784 1311 2120 1963 4025 1963 1864 0 3159-454 3868-1369 709-907 1105-2556 1204-4940h7101zm21171 4528h-9683l-1888 7175h-7513l10993-35183h6499l11068 35183h-7588l-1888-7175zm-8140-5938h6573l-3283-12553-3290 12553zm20907 13113V14524h9303c4107 0 7381 1303 9822 3917 2441 2606 3687 6186 3728 10730v5699c0 4627-1221 8256-3654 10886-2441 2631-5806 3951-10094 3951h-9105zm7101-29245v23356h2127c2367 0 4033-627 5007-1872 964-1245 1468-3398 1517-6457v-6112c0-3291-462-5575-1377-6878-916-1295-2483-1971-4685-2037h-2589zm27966 16874v12371h-7109V14524h11984c3480 0 6259 1080 8329 3241 2070 2153 3101 4957 3101 8404s-1022 6169-3068 8173c-2045 1996-4882 2994-8503 2994h-4734zm0-5938h4875c1360 0 2399-445 3142-1328 742-883 1113-2169 1113-3851 0-1749-379-3143-1138-4182-751-1031-1765-1559-3043-1575h-4949v10936zm45814 3884c0 4726-1113 8388-3348 11002-2227 2615-5328 3918-9287 3918-3950 0-7051-1295-9303-3885-2251-2581-3398-6210-3431-10861v-6021c0-4841 1122-8618 3357-11332 2243-2713 5352-4074 9328-4074 3917 0 7002 1336 9253 4000 2260 2672 3398 6416 3431 11233v6020zm-7126-5913c0-3175-453-5534-1352-7084-899-1543-2301-2318-4206-2318-1881 0-3275 742-4182 2235-899 1493-1369 3761-1402 6804v6260c0 3084 462 5352 1378 6812 923 1460 2342 2186 4255 2186 1856 0 3233-710 4132-2136 899-1427 1361-3646 1377-6639v-6120zm19629 20338h-7101V14524h7101v35183zm30557 0h-7101l-10392-23076v23076h-7101V14524h7101l10416 23100V14524h7077v35183zm27793-29245h-8693v29245h-7125V20462h-8561v-5938h24379v5938z" fill="url(#A)"/></g></svg>
|
||||
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB |
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none" xmlns:v="https://vecta.io/nano"><path fill-rule="evenodd" d="M9.618 11.032A5.47 5.47 0 0 1 6.5 12 5.5 5.5 0 1 1 12 6.5a5.47 5.47 0 0 1-.968 3.117l3.969 3.969L13.587 15l-3.969-3.969zM10 6.5a3.5 3.5 0 1 1-7 0 3.5 3.5 0 1 1 7 0z" fill="#008dd2"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path fill-rule="evenodd" d="M9.618 11.032A5.47 5.47 0 0 1 6.5 12 5.5 5.5 0 1 1 12 6.5a5.47 5.47 0 0 1-.968 3.117l3.969 3.969L13.587 15l-3.969-3.969zM10 6.5a3.5 3.5 0 1 1-7 0 3.5 3.5 0 1 1 7 0z" fill="#008dd2"/></svg>
|
||||
|
Before Width: | Height: | Size: 324 B After Width: | Height: | Size: 292 B |
@@ -1,4 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" color="#006799" viewBox="0 0 16 16">
|
||||
<path d="M6 4.5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0zm-1 0a.5.5 0 1 0-1 0 .5.5 0 0 0 1 0z"/>
|
||||
<path d="M2 1h4.586a1 1 0 0 1 .707.293l7 7a1 1 0 0 1 0 1.414l-4.586 4.586a1 1 0 0 1-1.414 0l-7-7A1 1 0 0 1 1 6.586V2a1 1 0 0 1 1-1zm0 5.586 7 7L13.586 9l-7-7H2v4.586z"/>
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" ><path d="M6 4.5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0zm-1 0a.5.5 0 1 0-1 0 .5.5 0 0 0 1 0zM2 1h4.586a1 1 0 0 1 .707.293l7 7a1 1 0 0 1 0 1.414l-4.586 4.586a1 1 0 0 1-1.414 0l-7-7A1 1 0 0 1 1 6.586V2a1 1 0 0 1 1-1zm0 5.586l7 7L13.586 9l-7-7H2v4.586z"/></svg>
|
||||
|
Before Width: | Height: | Size: 392 B After Width: | Height: | Size: 338 B |
1
public/static/svgs/xxx-error.svg
Normal file
|
After Width: | Height: | Size: 15 KiB |
34
pyproject.toml
Normal file
@@ -0,0 +1,34 @@
|
||||
[tool.poetry]
|
||||
name = "cadpoint"
|
||||
version = "2.0.0"
|
||||
description = "Сайт CADpoint.ru на Django"
|
||||
authors = ["Sergei Erjemin <erjemin@gmail.com>"]
|
||||
license = "MIT"
|
||||
readme = "README.md"
|
||||
packages = [
|
||||
{ include = "cadpoint", from = "cadpoint" },
|
||||
{ include = "web", from = "cadpoint" },
|
||||
]
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = ">=3.12,<3.13"
|
||||
Django = ">=5.2,<5.3"
|
||||
django-filer = "^3.4"
|
||||
easy-thumbnails = "^2.10"
|
||||
django-taggit = "^6.1"
|
||||
Pillow = "^12.2"
|
||||
urllib3 = "^2.6"
|
||||
setuptools = "^82.0"
|
||||
django-environ = "^0.13"
|
||||
django-mptt = "^0.18.0"
|
||||
pytils = "^0.4.4"
|
||||
django-select2 = "^8.4.8"
|
||||
etpgrf = "^0.1.6"
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
django-debug-toolbar = "^6.3"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core>=1.8.1"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
|
||||