73 Commits

Author SHA1 Message Date
erjemin b43f896c6a add: initial commit — Django 4.1 project (2022 version), docs, security templates 2026-04-16 15:55:21 +03:00
erjemin 817f63e3fb Static: недостающие файлы из error.log 2023-03-04 00:20:53 +03:00
erjemin 61f3ecee2a SEO: Title & Description 2023-03-04 00:19:41 +03:00
erjemin bf575e7bec SEO: Title & Description (каталог профилей) 2023-02-17 22:01:15 +03:00
erjemin f9085e08a1 SEO: Title & Description 2023-02-17 19:18:18 +03:00
erjemin afdf49e9eb minor: недостающая статика (png) 2023-02-17 19:12:02 +03:00
erjemin d14b83ae6f minor: недостающая статика (css, js) 2023-02-15 23:51:25 +03:00
erjemin 634d26fde7 minor: favicon.svg 2023-02-10 20:39:25 +03:00
erjemin ec8b9d5e86 minor: убрал отладочный print() 2023-01-29 23:01:26 +03:00
erjemin 96ed9a3178 minor: excel-шаблоны для загрузки и заполнения 2023-01-29 22:57:14 +03:00
erjemin cc192d1c8d minor: первый экран, изменен текст (SEO) 2023-01-29 18:20:48 +03:00
erjemin 4659b221c8 minor: кеш картинок и шаблонов (++) 2023-01-19 00:52:52 +03:00
erjemin 51bd05024c minor: кеш картинок и шаблонов (+) 2023-01-19 00:46:06 +03:00
erjemin cbb525141a minor: кеш картинок и шаблонов 2023-01-18 00:38:03 +03:00
erjemin 0b33c7454b minor: вот так работает в продашн 2023-01-18 00:15:58 +03:00
erjemin eb06fb64fe ini для uwsgi (tested) 2023-01-14 04:22:25 +03:00
erjemin ad505acacd конфиг для nginx (tested) 2023-01-14 04:21:41 +03:00
erjemin 590082704d обновленные пакеты окружения для dev на mac-m1 (python 3.10.9) 2023-01-14 04:04:08 +03:00
erjemin 72e2f3259f имя файла к touch-reload для девопа 2023-01-14 04:02:05 +03:00
erjemin 951c384a94 передергивает touch-reload при изменении кеш-шаблона 2023-01-14 03:49:55 +03:00
erjemin 83c0ea43e8 пакеты окружения (дев/прод) 2023-01-14 02:19:58 +03:00
erjemin df8109198b пакеты окружения (дев/прод) 2023-01-14 02:19:39 +03:00
erjemin 448ebaf2c9 доки по профилям монблан 2023-01-14 02:18:46 +03:00
erjemin c3f9a9caad minor (error -- был пустой slag для текущей серии) 2023-01-14 01:43:14 +03:00
erjemin c3b8309840 minor (без переменной template) 2023-01-14 01:17:59 +03:00
erjemin 4aff8efcfc minor (исправлены типы переменных для совместимости с продакшн) 2023-01-13 22:23:24 +03:00
erjemin c99f39a156 картинки для блога 2023-01-13 22:13:17 +03:00
erjemin 70651c9ada картинки к сериям домов 2023-01-13 22:08:21 +03:00
erjemin ea06b3e050 логотипы компаний из базы 2023-01-13 21:57:56 +03:00
erjemin e1b07467b4 touch-reload.txt 2023-01-13 19:46:51 +03:00
erjemin 8be7149af8 nginx conf-file for production 2023-01-13 19:18:04 +03:00
erjemin a269fa85a6 favicon & other static 2023-01-13 17:27:16 +03:00
erjemin dd5b6baf30 Служебное -- формирование sitemap-файлов 2023-01-10 01:21:28 +03:00
erjemin 6d8159b60d minor -- точные размеры в мм. в заголовке и крошках 2023-01-09 23:30:49 +03:00
erjemin 29d29401ba minor -- размеры и мм. тоже 2023-01-09 23:29:57 +03:00
erjemin 13f79f985c Служебное -- главные служебные (если есть и нет аутентификации) 2023-01-08 19:41:12 +03:00
erjemin a092551e60 Служебное -- главна и аутентификация 2023-01-08 19:36:48 +03:00
erjemin aaf7da88bc Служебное -- страница для тестирования верстки текста в блоге 2023-01-08 18:14:27 +03:00
erjemin 9cf9b9639b Переадресация для старых, внешних ссылок 2023-01-08 01:38:14 +03:00
erjemin f9c65c3ad6 Подгружаемый блок с детальным описанием состава рейтинга -- готово 2023-01-07 04:51:11 +03:00
erjemin e019d3f636 удалён лишний шаблон 2023-01-07 03:42:47 +03:00
erjemin 0d1017384c Вьюшка и шаблон: сравнение оконных наборов -- готово 2023-01-07 03:42:14 +03:00
erjemin 0d77db66d5 кеш картинки со схемами открывания 2022-12-31 13:54:15 +03:00
erjemin 62c983fedc перенос и переименование шаблонов 2022-12-31 05:13:12 +03:00
erjemin dc081fa9fd Вьюшка: подгружаемые фреймы с ценами одного типового окна -- готово 2022-12-31 05:01:39 +03:00
erjemin f892100cf8 minor 2022-12-31 04:44:38 +03:00
erjemin c958fec1b4 minor (переименование шаблонов) 2022-12-31 04:44:11 +03:00
erjemin 6d9de5f7f0 Вьюшка: Подгружаемых фреймов ценовых предложений -- minor 2022-12-31 04:36:40 +03:00
erjemin 6ff5e0848d Вьюшка: Подгружаемых фреймов ценовых предложений -- готово 2022-12-31 04:33:59 +03:00
erjemin e1360261d7 Вьюшка : Выдача предложений -- minor (убраны отладочные print) 2022-12-31 02:57:49 +03:00
erjemin df0717a765 Вьюшка и шаблоны: Выдача предложений -- готово 2022-12-31 02:55:09 +03:00
erjemin c317d499f6 Отдельный каталог для шаблонов выпадающей окон -- minor 2022-12-30 19:58:56 +03:00
erjemin f96bf35874 Выпадающее окно после ввода адреса -- готово 2022-12-30 19:52:42 +03:00
erjemin 01a93e8fea Кеш-картинки схем открывания (большие и маленькие) 2022-12-30 15:31:17 +03:00
erjemin 9a6eed3812 Ценовая выдача для одного окна 2022-12-30 15:30:05 +03:00
erjemin 8eddf9361b Оптимизация + на мини-картинках схем открывания рисуется спейсер и москитная сетка 2022-12-29 17:25:41 +03:00
erjemin e8b879ab0e Шаблон: "Каталог / Производители и поставщики окон / Компания" -- minor 2022-12-29 16:10:26 +03:00
erjemin b60ea568e5 Шаблон: "Каталог / Производители и поставщики окон / Компания" -- minor: seo, Yandex.map 2022-12-29 15:56:14 +03:00
erjemin 5cc3dae0a6 Вьюшка и шаблон: "Каталог / Производители и поставщики окон / Компания" -- готово 2022-12-29 15:46:16 +03:00
erjemin a0a3ff88ac Вьюшка: "Каталог / Производители и поставщики окон" -- minor 2022-12-29 00:18:53 +03:00
erjemin 64dfdf8245 Вьюшка и шаблон: "Каталог / Производители и поставщики окон" -- готово 2022-12-29 00:17:27 +03:00
erjemin e05ec47b48 Шаблон: "Каталог / Типовые серии зданий / Информация по серии" и "Статистика" -- minor seo 2022-12-27 15:19:08 +03:00
erjemin f95137a25c Шаблон и css: "Каталог / Оконные проёмы и балконные блоки" -- минор 2022-12-27 14:57:34 +03:00
erjemin 3df0f6271e avatar image (для блога и "о компании") 2022-12-27 14:17:04 +03:00
erjemin d82b2ffbd3 prerender small-image со схемами открывания (2022-12-27) 2022-12-27 14:15:16 +03:00
erjemin 8c31e7cc34 Вьюшка и шаблон: "Каталог / Оконные проёмы и балконные блоки" -- готово 2022-12-27 14:13:29 +03:00
erjemin ac7e164753 миграции 2022-12-26 18:30:26 +03:00
erjemin ce9fcc7511 Про кеширующие шаблоны 2022-12-26 18:30:09 +03:00
erjemin 6972f1f8da Вьюшка: "Информация по всем сериям домой" -- minor
перенесли одну формирования навигации по сериям
2022-12-26 18:28:52 +03:00
erjemin 35459ca45b prerender image со схемами открывания (2022-12-26 минор) 2022-12-26 18:13:57 +03:00
erjemin a3bdb57279 Шаблон: "Каталог / Типовые серии зданий / Информация по серии" -- minor (data, last modified, еxpires) 2022-12-26 18:04:35 +03:00
erjemin 84185bd2f0 prerender image со схамами открывания (2022-12-26) 2022-12-26 18:02:20 +03:00
erjemin 6347031a00 prerender image со схамами открывания (2022-12-26) 2022-12-26 17:42:59 +03:00
343 changed files with 7819 additions and 225 deletions
+123
View File
@@ -0,0 +1,123 @@
# Django Configuration Example
# Копируйте этот файл в .env.local и заполните реальные значения
# ============================================================================
# DJANGO
# ============================================================================
# Django SECRET_KEY - сгенерируйте с помощью:
# python -c 'from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())'
DJANGO_SECRET_KEY=your-super-secret-key-here-change-me
# Режим отладки (НИКОГДА не True на продакшене!)
DEBUG=False
# Допустимые хосты (разделены запятой без пробелов)
ALLOWED_HOSTS=localhost,127.0.0.1,yourdomain.com
# ============================================================================
# DATABASE
# ============================================================================
# Database backend (по умолчанию mysql)
DATABASE_ENGINE=django.db.backends.mysql
# Database connection
DATABASE_HOST=localhost
DATABASE_PORT=3306
DATABASE_NAME=django_oknardia
DATABASE_USER=web
DATABASE_PASSWORD=your-db-password-here
# ============================================================================
# EMAIL
# ============================================================================
# Email backend
EMAIL_BACKEND=django.core.mail.backends.smtp.EmailBackend
# SMTP Settings
EMAIL_HOST=smtp.example.com
EMAIL_PORT=587
EMAIL_USE_TLS=True
EMAIL_HOST_USER=your-email@example.com
EMAIL_HOST_PASSWORD=your-email-password
# Default from email
DEFAULT_FROM_EMAIL=noreply@example.com
SERVER_EMAIL=admin@example.com
# ============================================================================
# SECURITY
# ============================================================================
# HTTPS and secure cookies (включить на продакшене)
SECURE_SSL_REDIRECT=False
SESSION_COOKIE_SECURE=False
CSRF_COOKIE_SECURE=False
# ============================================================================
# CAPTCHA
# ============================================================================
# Google reCAPTCHA v2
# Получите на https://www.google.com/recaptcha/admin
# ВАЖНО: Никогда не коммитьте реальные ключи в git!
CAPTCHA_PUBLIC_KEY=your-recaptcha-public-key-here
CAPTCHA_PRIVATE_KEY=your-recaptcha-private-key-here
# ============================================================================
# EXTERNAL APIs
# ============================================================================
# Yandex Maps
# Получите на https://developer.tech.yandex.ru/
YANDEX_MAPS_API_KEY=your-yandex-maps-api-key
# ============================================================================
# LOGGING
# ============================================================================
# Log level: DEBUG, INFO, WARNING, ERROR, CRITICAL
LOG_LEVEL=INFO
# ============================================================================
# CACHE (опционально, если используется Redis)
# ============================================================================
# CACHE_BACKEND=redis
# CACHE_LOCATION=redis://localhost:6379/1
# ============================================================================
# CELERY (опционально, если используется Celery)
# ============================================================================
# CELERY_BROKER_URL=redis://localhost:6379/0
# CELERY_RESULT_BACKEND=redis://localhost:6379/0
# ============================================================================
# ИНСТРУКЦИЯ ПО ИСПОЛЬЗОВАНИЮ
# ============================================================================
# 1. Скопируйте этот файл:
# cp .env.example .env.local
#
# 2. Отредактируйте значения в .env.local:
# nano .env.local
#
# 3. Убедитесь, что .env.local в .gitignore:
# grep ".env" .gitignore
#
# 4. Используйте python-dotenv для загрузки переменных в settings.py:
# from dotenv import load_dotenv
# load_dotenv()
#
# ВАЖНО:
# - НИКОГДА не коммитьте .env.local или файлы с реальными значениями в git!
# - На продакшене используйте переменные окружения, установленные в:
# - systemd service файл
# - Docker env vars
# - Kubernetes secrets
# - AWS Systems Manager Parameter Store
# - HashiCorp Vault
+229
View File
@@ -0,0 +1,229 @@
# AGENTS.md — Окнардия: Оконный Агрегатор
**Версия**: 0.2.0 | **Язык**: Python 3.12+ + Django 6.0+ | **БД**: MariaDB/MySQL
Этот документ обеспечивает AI-агентов (Copilot, Claude, Cursor и т.д.) контекстом для эффективной работы с проектом.
## Архитектура и назначение
**Окнардия** — это агрегатор для сравнения цен на установку оконных конструкций в зданиях типового (серийного) строительства в России. Пользователи вводят адрес дома → система распознаёт серию строения → выдаёт типовые размеры оконных проемов → показывает предложения от поставщиков на установку (замену) окон.
Архитектура двухуровневая:
- **Основной проект**: `oknardia/` (Django settings, URL routing, models, templates)
- **Приложение логики**: `web/` (views, вспомогательные функции, кеширование схем окон)
## Критические компоненты и модели данных
### Основные модели (oknardia/models.py)
**Иерархия зданий:**
- `Seria_Info` — серия типового строения (P-44, 5-этажка и т.п.), поддерживает self-referential FK для древовидной структуры
- `Building_Info` — конкретный адрес с ссылкой на серию; содержит ~30 полей метаданных (площадь, износ, материалы)
- `Apartment_Type` — типовой план квартиры в серии; содержит связь M2M с `Win_MountDim`
- `MountDim2Apartment` — через-таблица между проёмами и квартирами
**Каталог и цены:**
- `PVCprofiles` — PVC-профили (окна), содержат рейтинг (0…+5) и tech-specs
- `Glazing` — стеклопакеты с изоляционными параметрами и рейтингом
- `SetKit` — готовый "набор" (профиль + стеклопакет + фурнитура + монтаж), связывает компоненты и хранит бизнес-условия
- `PriceOffer` — финальная цена для конкретного проёма и набора от конкретного пользователя
**Дополнительные сущности:**
- `OurUser` — собственная система пользователей (расширение Django User)
- `MerchantBrand` / `MerchantOffice` — организационная иерархия поставщиков
- `BlogPosts` — универсальная таблица для блога и каталога (одна таблица, флаги `bCatalog`, `bPublished`)
- Несколько LOG-таблиц: `LogMeasure`, `LogDemand`, `LogVisitPriceReport`
**Именование полей** (обязательное соглашение):
- `s*` = String/Char
- `i*` = Integer
- `f*` = Float/Decimal
- `b*` = Boolean
- `d*` = DateTime/Date
- `k*` = Foreign Key (связь)
- `p*` = Path/Media
- Например: `sSetName` — название набора, `fSetRating` — рейтинг набора
### Кеширование и специфика
1. **Картинки схем окон** генерируются автоматически в:
- `public/static/img/_flap.cfg` — большие картинки схем открывания
- `public/static/img/_miniflap.cfg` — миниатюры для таблиц
- **Важно**: не удалять просто так, они переиспользуются; если схема архивируется, картинка остаётся без явного удаления
2. **Pre-render HTML-шаблоны** для серий в `oknardia/templates/seria_info/prepared/`
- Создаются при первом обращении к странице серии
- **Требуют ручной очистки** при изменении цен/спецификаций или настройки cronjob на удаление
3. **Кольцевой буфер логирования** (настройка `MAX_LEN_RING_LOG_BUFFER=250` в settings)
## Workflow и точки входа
### Основной сценарий пользователя:
1. **Главная** (`/`) → `views.main_init()` — отслеживание кук посещений
2. **Автокомплит адреса** (`/autocomplete_addr`) → `autocomplete_addr.autocomplete_addr()` — подсказки при вводе
3. **Поиск по адресу** (`/get_address`) → `views.get_address()` — распознание серии здания
4. **Ценовая выдача** (`/{build_id}/{apart_id}/*`) → `prices.report_price()` — основной отчёт с фильтрацией и пагинацией по фреймам
### Каталоги и отчёты:
- `catalog.py` — каталог профилей, производителей, серий (с методом `catalog_seria_info()`)
- `report1.py` — сравнение наборов (`compare_offers()`) с расчётом рейтинговых компонент
- `report2.py` — рейтинги и ранжирование (фильтрация по звёздочкам и весам)
- `diagrams.py` — статистика и аналитика
### Система пользователей:
- `user_manager.py` — регистрация, логин/logout, восстановление пароля, верификация email
- Использует собственные таблицы `OurUser` + стандартный Django `User`
## Специфические технологии и паттерны
### Конфигурация через settings.py:
- **Двойной хост** (DEV и PROD) по `socket.gethostname()` — разные БД, media-пути, сценарии отладки
- Настройки ранжирования через веса (более 20 констант типа `RANK_STEP_*`, `RARING_*`)
- Google Captcha и Yandex Maps API ключи из `my_secret.py`
### Импорт конфиденциальных данных:
```python
from oknardia.my_secret import * # в settings.py и models.py
```
**⚠️ Важно**: `my_secret.py` содержит пароли БД, SECRET_KEY и должен быть в `.gitignore`
### Использование JSON в моделях:
- Поле `sProfileDescription` (Profiles), `sGlazingDescription`, `sSetDescription` — JSON или HTML в TextField
- Нет явной валидации структуры JSON при сохранении
### Система рейтинга и ранжирования:
- Каждый компонент (профиль, стеклопакет, набор) имеет рейтинг
- Веса рейтинга задают важность каждого атрибута (теплопередача, звукоизоляция и т.п.)
- `report1.show_rating_components()` раскрывает формулу расчёта рейтинга
### Интернационализация:
- `LANGUAGE_CODE = 'ru-RU'`, `TIME_ZONE = 'Europe/Moscow'`
- `USE_I18N = True`, `FIRST_DAY_OF_WEEK = 1` (понедельник)
- Все названия моделей и help_text на русском языке
## Конвенции кодирования
Из `.github/copilot-instructions.md`:
**Обязательные правила:**
- **Язык**: отвечай на русском, комментарии кода на русском
- **Стиль**: PEP 8 для Python, используй современные возможности (Python 3.12+, Django 6.0+)
- **Управление зависимостями**: `poetry` (вместо pip/requirements.txt)
- **Переменные окружения**: секреты в `.env`, не в коде
- **Git коммиты**:
- `add: <описание>` — новая функция/файл
- `fix: <описание>` — баг
- `mod: <описание>` — переработка кода
- `minor: <описание>` — форматирование, опечатки, мелкие комменты
- **Удаление старого кода**: помечать меткой `# Удалить:`, не стирать просто
- **Тон**: неформальный, профессиональный, образность допускается (как с коллегой)
⚠️ **Исключения**:
- Не удаляй старый код без явного указания
- Всегда уточняй перед `git commit` / `git push`
- Если модель требует миграций, опиши шаги для `python manage.py makemigrations/migrate`
## Типичные рабочие циклы
### Добавление нового фильтра в ценовую выдачу:
1. Добавить поле в модель (например, `SetKit`) → создать миграцию
2. Обновить фильтрацию в `prices.report_price()` → использовать Q-объекты
3. Обновить template для выбора фильтра (`templates/price/*.html`)
4. Проверить влияние на рейтинг в `report1.py` если нужно
### Обновление рейтинга и ранжирования:
1. Модифицировать вес в settings.py (RANK_STEP_*, RARING_*)
2. Пересчитать рейтинг в моделях через функцию `save()` или создать management-command
3. Проверить результаты в `report2.ratings()` и `report2.profiles_rating()`
### Кеширование и производительность:
- Картинки схем кешируются автоматически; при добавлении новой схемы нужно явно вызвать функцию генерации
- Pre-render HTML нужно чистить при обновлении ценовых данных (см. управление файлами в `seria_info/prepared/`)
- БД индексирована на часто используемых полях (`db_index=True`)
### Разработка на macOS:
- При установке `mysqlclient` может потребоваться `brew install mariadb-connector-c`
- Подробнее см. README.md, раздел "Некоторые заметки относительно разработки"
## Структура папок
```
oknardia/
├── manage.py
├── oknardia/
│ ├── models.py # ВСЕ модели (1900+ строк)
│ ├── settings.py # конфиг, импорт my_secret.py
│ ├── urls.py # маршруты (113 строк)
│ ├── admin.py
│ ├── views.py # главная, контакты, тариф
│ └── my_secret.py # CONFIDENTIAL: пароли, ключи
├── web/
│ ├── views.py # основная логика
│ ├── add_func.py # утилиты (геокод, расстояния)
│ ├── prices.py # отчёты ценовой выдачи
│ ├── report1.py # сравнение наборов
│ ├── report2.py # рейтинги и ранжирование
│ ├── catalog.py # каталоги
│ ├── diagrams.py # статистика
│ ├── blog.py # список и посты блога
│ ├── autocomplete_addr.py # автокомплит адресов
│ ├── user_manager.py # логин, регистрация
│ └── service.py # служебные функции (sitemap, очистка)
├── templates/ # Django шаблоны (Jinja2-like)
│ ├── base.html
│ ├── index.html # главная
│ ├── seria_info/prepared/ # пре-рендер кеш
│ ├── price/
│ ├── catalog/
│ └── ...
└── public/
├── static/
│ ├── img/_flap.cfg/ # схемы окон (большие)
│ ├── img/_miniflap.cfg/ # схемы окон (мини)
│ ├── css/
│ ├── js/
│ └── ...
└── media/ # загружаемые файлы
```
## Отладка и команды
```bash
# Разработка
python manage.py runserver # локальный сервер
python manage.py makemigrations # подготовить миграцию
python manage.py migrate # применить миграцию
# Администрирование
python manage.py shell # интерпретатор с контекстом Django
python manage.py createsuperuser # создать админа
python manage.py collectstatic # собрать статику для продакшена
# Служебные (см. service.py)
# Находятся в urls.py: /service/make_sitemaps, /service/make_rating
```
## Типичные ошибки и подводные камни
1. **Забывчивость миграций**: при добавлении полей в модель сразу создавай миграцию
2. **Нарушение именования полей**: новые поля должны начинаться с префикса (s, i, f, b, d, k, p)
3. **Незакешированные картинки**: если добавляешь новую схему открывания, вызови функцию генерации явно
4. **Неочищенные pre-render шаблоны**: могут вызвать устаревание данных на страницах серий
5. **Foreign Key ON_DELETE**: используется в основном `DO_NOTHING` и `SET_NULL`, будь осторожен при удалении
6. **Двойной хост**: убедись, что используешь правильные переменные из `my_secret.py` для текущей машины
7. **Индексирование БД**: большинство полей для поиска уже имеют `db_index=True`, но проверь при добавлении фильтров
## Реферальные ссылки (для более глубокого изучения)
- **Django 6.0+ docs**: https://docs.djangoproject.com/en/stable/
- **MariaDB/MySQL**: Конфиг БД в settings.py, ENGINE = "django.db.backends.mysql"
- **Система рейтинга**: см. functions в report1.py и constants в settings.py (RANK_*, RARING_*)
- **Фронтенд**: Bootstrap 3.3.7 (в static/bootstrap-3.3.7-dist.zip), jQuery, Yandex Maps API
---
**Автор инструкций**: проанализировано 16.04.2026
**Версия документа**: 1.0
**Последнее обновление**: инструкции в `.github/copilot-instructions.md`
+22 -1
View File
@@ -2,7 +2,28 @@
### Переделка под Python 3.8 и Django 4.1
##### Некоторые заметки относительно разработки на macOS:
### Немного о механике кеширования:
#### Кеширование картинок со схемами открывания окон
Картинки со схемами открывания создаются в папках:
* `public/static/img/_flap.cfg` -- большие картинки
* `public/static/img/_miniflap.cfg` -- маленькие картинки (для таблиц с ценами)
Эти картинки создаются автоматически. Можно не удалять. Даже если какая-то схема открывания или размер проёма станет
неактуальным, лишняя картинка просто будет лежать в папке (вдруг такой проём появится снова).
#### Кеширование шаблонов
В папке `oknardia/oknardia/templates/seria_info/prepared` создаются пре-рендер шаблоны с информацией о сериях домов.
Эти шаблоны надо периодически удалять. Они нужны для скорости. Но если меняются данные по серии, размерам окон, появляются
новые коммерческие предложения -- их надо удалять и тогда построятся новые. Вообще на быстрых серверах скорость может
не быть проблемой, так что возможно стоит просто настроить через crone ежедневное или еженедельное удаление этих
пре-рендер шаблонов. При обращении к соответсвующий страницам эти шаблоны будут пересозданы автоматически.
### Некоторые заметки относительно разработки (DEV) на macOS:
Т.к. MariaDB "сидит" в контейнере Dockers могут возникнуть трудности при установке коннектора к базам данных MySQL/MariaDB. Примерно такие:
```txt
+311
View File
@@ -0,0 +1,311 @@
# 🚀 SETUP.md — Первичная настройка Окнардии
**Версия**: 0.2.0 | **Дата**: 16.04.2026
Этот документ описывает пошаговую настройку проекта для разработки и деплоя.
## 📋 Предварительные требования
- **Python**: 3.12+
- **Django**: 6.0+
- **MariaDB/MySQL**: 5.7+ или 8.0+
- **Redis** (опционально, для кеширования): 6.0+
- **Poetry** (для управления зависимостями)
### На macOS:
```bash
# Установка зависимостей (если не установлены)
brew install mariadb-connector-c
brew install redis # опционально
```
## 🔑 Шаг 1: Конфигурация секретов
### 1.1 Создайте файл `my_secret.py`
```bash
cd oknardia/oknardia
cp my_secret.py.template my_secret.py
nano my_secret.py # отредактируйте значения
```
**Что нужно заполнить:**
- IP адреса и хосты (MY_HOST_HOME2, MY_DATABASE_HOST_DEV2)
- Пароль БД (MY_DATABASE_PASSWORD_DEV)
- Email credentials (MY_EMAIL_HOST_USER_DEV, MY_EMAIL_HOST_PASSWORD_DEV)
- Пути к файлам (MY_MEDIA_ROOT_DEV2, MY_STATIC_ROOT_DEV2)
- SECRET_KEY (сгенерируйте новый!)
### 1.2 (Опционально) Создайте файл `.env.local`
```bash
cd /path/to/project
cp .env.example .env.local
nano .env.local # отредактируйте значения
```
**Примечание**: либо используйте `my_secret.py`, либо `.env.local`, выбирайте удобный способ.
## 🗄️ Шаг 2: Настройка БД
### 2.1 Создайте БД и пользователя
```bash
# Подключитесь к MySQL
mysql -u root -p
# В MySQL консоли:
CREATE DATABASE django_oknardia_dev;
CREATE USER 'web'@'localhost' IDENTIFIED BY 'your-password';
GRANT ALL PRIVILEGES ON django_oknardia_dev.* TO 'web'@'localhost';
FLUSH PRIVILEGES;
EXIT;
```
### 2.2 Выполните миграции
```bash
cd /path/to/project/oknardia
python manage.py migrate
```
### 2.3 Создайте суперпользователя
```bash
python manage.py createsuperuser
```
## 📦 Шаг 3: Установка зависимостей
### Вариант 1: Poetry (рекомендуется)
```bash
# Установите poetry (если не установлен)
curl -sSL https://install.python-poetry.org | python3 -
# Установите зависимости
poetry install
# Активируйте виртуальное окружение
poetry shell
```
### Вариант 2: pip (классический способ)
```bash
python -m venv venv
source venv/bin/activate # На Windows: venv\Scripts\activate
pip install -r requirements.txt
```
## 🏃 Шаг 4: Запуск разработки
### 4.1 Запустите локальный сервер
```bash
cd oknardia
python manage.py runserver
```
Откройте браузер: **http://127.0.0.1:8000**
### 4.2 Запустите задачи Celery (опционально)
```bash
celery -A oknardia worker -l info
```
## 📁 Шаг 5: Создание необходимых директорий
```bash
# Статика и медиа файлы
mkdir -p public/media
mkdir -p public/static
mkdir -p public/static/img/_flap.cfg
mkdir -p public/static/img/_miniflap.cfg
# Логи
mkdir -p logs
# Сгенерируйте статику
python manage.py collectstatic --noinput
```
## 🧪 Шаг 6: Тестирование
```bash
# Запустите тесты
python manage.py test
# С покрытием (если установлен coverage)
coverage run --source='.' manage.py test
coverage report
```
## 🔐 Шаг 7: Проверка безопасности
### 7.1 Django встроенная проверка
```bash
python manage.py check --deploy
```
### 7.2 Проверка на утечки секретов
```bash
# Установите инструмент
pip install truffleHog
# Проверьте репозиторий
truffleHog filesystem . --json
```
## ✅ Проверка готовности
Убедитесь, что все работает:
```bash
# 1. Статус БД
python manage.py dbshell < /dev/null && echo "✓ Database OK"
# 2. Статус приложений Django
python manage.py check && echo "✓ Django OK"
# 3. Статус файлов
test -d public/media && test -d public/static && echo "✓ Directories OK"
# 4. Тесты
python manage.py test 2>&1 | tail -5
```
## 🚀 Развертывание на продакшене
### Для разных хостов
**Masterhost VDS:**
```bash
# Установка окружения
export DJANGO_SECRET_KEY="your-production-key"
export DATABASE_PASSWORD="production-db-password"
export DATABASE_HOST="localhost"
export DEBUG="False"
# Запуск через uWSGI + Nginx
uwsgi --ini config/oknardia.ini
```
**Docker (рекомендуется):**
```bash
docker build -t oknardia:latest .
docker run -d \
-e DJANGO_SECRET_KEY="..." \
-e DATABASE_PASSWORD="..." \
-p 8000:8000 \
oknardia:latest
```
## 🛠️ Полезные команды
```bash
# Управление миграциями
python manage.py makemigrations # Создать миграцию
python manage.py migrate # Применить миграции
python manage.py migrate --fake-initial # Подделать первую миграцию
# Управление данными
python manage.py shell # Интерпретатор Python с контекстом Django
python manage.py dumpdata > backup.json # Резервная копия данных
python manage.py loaddata backup.json # Восстановление данных
# Статика и медиа
python manage.py collectstatic # Собрать статику для продакшена
python manage.py findstatic # Найти файлы статики
# Администрирование
python manage.py createsuperuser # Создать администратора
python manage.py changepassword username # Изменить пароль
# Очистка
python manage.py clearsessions # Удалить старые сессии
python manage.py remove_stale_contenttypes # Удалить устаревшие типы контента
# Служебные
python manage.py check # Проверить конфигурацию
python manage.py check --deploy # Проверка для продакшена
```
## 📚 Дополнительные ресурсы
- [Django документация](https://docs.djangoproject.com/en/stable/)
- [AGENTS.md](./AGENTS.md) — архитектура и конвенции проекта
- [README.md](./README.md) — основная информация о проекте
- [SECURITY_AUDIT_REPORT.md](./SECURITY_AUDIT_REPORT.md) — отчёт безопасности
## ❓ Решение проблем
### Проблема: `mysqlclient` не устанавливается на macOS
**Решение:**
```bash
brew install mariadb-connector-c
pip install mysqlclient
# или
brew unlink mariadb-connector-c # после установки
```
### Проблема: `ModuleNotFoundError: No module named 'oknardia'`
**Решение:**
```bash
# Убедитесь, что находитесь в правильной директории
cd /path/to/project/oknardia
python manage.py runserver
```
### Проблема: `OperationalError: (2002, "Can't connect to local MySQL server")`
**Решение:**
```bash
# Проверьте, что MySQL запущен
# macOS:
brew services start mariadb
# Linux:
sudo systemctl start mysql
# Проверьте credentials в my_secret.py или .env
```
### Проблема: миграции не применяются
**Решение:**
```bash
# Проверьте статус миграций
python manage.py showmigrations
# Примените все миграции
python manage.py migrate --run-syncdb
# Если проблема в конкретной миграции
python manage.py migrate app_name 0001 --fake
python manage.py migrate app_name
```
## 🤝 Общие вопросы
**Q: Где хранятся секреты?**
A: В `my_secret.py` (в .gitignore) или переменных окружения (.env)
**Q: Как запустить проект без интернета?**
A: Установите все зависимости заранее, используйте локальное хранилище медиа
**Q: Как работает система рейтинга?**
A: Смотрите [AGENTS.md](./AGENTS.md), раздел "Система рейтинга и ранжирования"
---
**Версия документа**: 1.0
**Последнее обновление**: 16.04.2026
**Автор**: GitHub Copilot
+110
View File
@@ -0,0 +1,110 @@
# Разработка сайта OKNARDIA.RU
# == Конфикурационный файл nginx oknardia.conf
# Описываем апстрим-потоки которые должен подключить Nginx
# Для каждого сайта надо настроить свйо поток, со своим уникальным именем.
# Если будете настраивать несколько python (django) сайтов - измените название upstream
upstream oknardia-django {
# расположение файла Unix-сокет для взаимодействие с uwsgi
server unix:/home/web/oknardia-ru/socket/oknardia.sock;
# server ///home/web/oknardia-ru/socket/oknardia.sock;
# также можно использовать веб-сокет (порт) для взаимодействие с uwsgi. Но это медленнее
# server 127.0.0.1:8001; # для взаимодействия с uwsgi через веб-порт
keepalive_requests 200;
}
# конфигурируем сервер
server {
server_name tmp.cube2.ru; # доменное имя сайта
listen 80; # managed by Certbot
## listen 443 ssl http2; # managed by Certbot
## ssl_certificate /etc/letsencrypt/live/cadpoint.ru/fullchain.pem; # managed by Certbot
## ssl_certificate_key /etc/letsencrypt/live/cadpoint.ru/privkey.pem; # managed by Certbot
## include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
## ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
# server_name 90.156.203.25; # доменное имя сайта
charset utf-8; # кодировка по умолчанию
access_log /home/web/oknardia-ru/logs/oknardia-access.log; # логи с доступом
error_log /home/web/oknardia-ru/logs/oknardia-error.log; # логи с ошибками
client_max_body_size 100M; # максимальный объем файла для загрузки на сайт (max upload size)
error_page 404 /404.html;
error_page 500 /500.html;
location /media { alias /home/web/oknardia-ru/public/media; } # Расположение media-файлов Django
location /static { alias /home/web/oknardia-ru/public/static; } # Расположение static-файлов Django
location /robots.txt { root /home/web/oknardia-ru/public; } # Расположение robots.txt
location /favicon.ico { root /home/web/oknardia-ru/public; } # Расположение favicon.ico
location /favicon.gif { root /home/web/oknardia-ru/public; } # Расположение favicon
location /favicon.png { root /home/web/oknardia-ru/public; } # Расположение favicon
location /favicon.svg { root /home/web/oknardia-ru/public; } # Расположение favicon
location /author.txt { root /home/web/oknardia-ru/public; } # Расположение author.txt
location = /404.html {
root /home/web/oknardia-ru/oknardia/templates/404.html;
internal;
}
location = /500.html {
root /home/web/oknardia-ru/oknardia/templates/500.html;
internal;
}
# location ~ \.(html|htm|ico|svg|png|gif|jpg|jpeg)$ {
location ~ \.(xml|html|htm|ico|svg|png|gif|jpg|jpeg)$ {
root /home/web/oknardia-ru/public; # Расположение статичных *.xml, *.html и *.txt
}
location / {
uwsgi_pass oknardia-django; # upstream обрабатывающий обращений
include uwsgi_params; # конфигурационный файл uwsgi;
proxy_set_header Host $host;
# ограничение количества запросов c одного IP-адреса с помощью модуля Limit_Req_Module
# limit_req zone=one burst=20 nodelay;
# one — имя зоны настроеной в /etc/nginx/nginx.conf (для всех сайтов сервера) в блоке http {…}
# burst — максимальный всплеск активности, можно регулировать до какого значения запросов
# в секунду может быть всплеск запросов;
# nodelay — незамедлительно, при достижении лимита подключений, выдавать код 503
# (Service Unavailable) для этого IP
fastcgi_keep_conn on;
uwsgi_read_timeout 1800; # некоторые запросы на Raspbery pi очень долго обрабатываются. Например, переиндексация.
uwsgi_send_timeout 200; # на всякий случай время записи в сокет
}
}
# переадресация с www на "без" www
server {
server_name www.tmp.cube2.ru;
listen 80;
return 301 http://tmp.cube2.ru$request_uri;
## listen 443 ssl; # managed by Certbot
## ssl_certificate /etc/letsencrypt/live/cadpoint.ru/fullchain.pem; # managed by Certbot
## ssl_certificate_key /etc/letsencrypt/live/cadpoint.ru/privkey.pem; # managed by Certbot
## include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
## ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}
# переадресация с http на https
##server {
## if ($host = oknardia.ru) {
## return 301 https://$host$request_uri;
## } # managed by Certbot
##
## listen 80;
## server_name cadpoint.ru;
## return 404; # managed by Certbot
##}
# переадресация с http на https для www
##server {
## if ($host = www.cadpoint.ru) {
## return 301 https://cadpoint.ru$request_uri;
## } # managed by Certbot
##
## listen 80;
## server_name www.cadpoint.ru;
## return 404; # managed by Certbot
##}
+63
View File
@@ -0,0 +1,63 @@
# === Конфигурационный файл uwsgi cadpoint.ini
[uwsgi]
# НАСТРОЙКИ ДЛЯ DJANGO
# Корневая папка проекта (полный путь)
chdir = /home/web/oknardia-ru/oknardia
# Django wsgi файл rsvo_new/wsgi.py записываем так:
module = oknardia.wsgi
# полный путь к виртуальному окружению
home = /home/web/oknardia-ru/env
# полный путь к файлу сокета
socket = /home/web/oknardia-ru/socket/oknardia.sock
# Исходящие сообщения в лог
daemonize = /home/web/oknardia-ru/logs/oknardia_uwsgi.log
# ЗАГАДОЧНЫЕ НАСТРОЙКИ, ПО ИДЕЕ ОНИ НУЖНЫ, НО И БЕЗ НИХ ВСЁ РАБОТАЕТ
# расположение wsgi.py
wsgi-file = /home/web/oknardia-ru/oknardia/oknardia/wsgi.py
# расположение виртуального окружения (как оно работает если этот параметр не указан, не ясно)
virtualenv = /home/web/oknardia-ru/env
# имя файла при изменении которого происходит авторестарт приложения
# (когда этого параметра нет, то ничего не авторестартится, но с ним все рестартится.
# Cтоит изменить любой Python-исходник проекта, как изменения сразу вступают в силу.
touch-reload = /home/web/oknardia-ru/logs/touch-reload.txt
py-autoreload = 5
# НАСТРОЙКИ ОБЩИЕ
# быть master-процессом
master = true
# максимальное количество процессов
processes = 1
# если uWSGI установлен как сервис через apt-get то нужно установить еще плагин:
# sudo apt-get install uwsgi-plugin-python
# и добавить в этот конфиг: plugin = python
plugin = python3
# права доступа к файлу сокета. По умолчанию должно хватать 664. Но каких-то прав не хватает, поэтому 666.
chmod-socket = 666
# очищать окружение от служебных файлов uwsgi по завершению
vacuum = true
# количество секунд после которых подкисший процесс будет перезапущен
# Так как некоторе скрипты требуют изрядно времени (особенно полная переиндексация) то ставим значение побольше
harakiri = 2600
# В общем случае, при некотых значениях harakiri логах uWSGI может вываливаться предупреждение:
# WARNING: you have enabled harakiri without post buffering. Slow upload could be rejected on post-unbuffered webservers
# можно оставить harakiri закомментированным, но нам нужно 900 и на него не ругается. Ругается на 30.
# разрешаем многопоточность
enable-threads = true
vacuum = true
thunder-lock = true
max-requests = 500
# пользователь и группа пользователей от имени которых запускать uWSGI
# указываем www-data: к этой группе относится nginx, и ранее мы включили в эту группу нашего [user]
# uid = nginx
# gid = nginx
# uid = root
# gid = root
uid = web
gid = web
print = ---------------- Запущен uWSGI для oknardia.ru ----------------
+1
View File
@@ -0,0 +1 @@
1
+1 -1
View File
@@ -258,7 +258,7 @@ class Migration(migrations.Migration):
('iWinWidth', models.DecimalField(decimal_places=1, help_text='Ширина, см.', max_digits=5, verbose_name='Ширина')),
('iWinHight', models.DecimalField(decimal_places=1, help_text='Высота, см.', max_digits=5, verbose_name='Высота')),
('iWinDepth', models.DecimalField(decimal_places=1, help_text='Глубина, см.', max_digits=3, verbose_name='Глубина')),
('sFlapConfig', models.CharField(help_text='Рекомендуемая гор.архитектурой конфигурации открывания (МЕТАЯЗЫК)', max_length=32, verbose_name='Открывание')),
('flap_config', models.CharField(help_text='Рекомендуемая гор.архитектурой конфигурации открывания (МЕТАЯЗЫК)', max_length=32, verbose_name='Открывание')),
('sDescripion', models.CharField(blank=True, help_text='Описание для простоты последующего поиска и SEO', max_length=64, null=True, verbose_name='Описание')),
('bIsDoor', models.BooleanField(default=False, help_text='Это Дверь', verbose_name='Дверь')),
('bIsNearDoor', models.BooleanField(default=False, help_text='Это окно рядом с дверью (может быть глухим)', verbose_name='Глухое')),
+185
View File
@@ -0,0 +1,185 @@
# -*- coding: utf-8 -*-
"""
ШАБЛОН для my_secret.py
ИНСТРУКЦИЯ: скопируйте этот файл в my_secret.py и заполните реальные значения.
Пример:
cp oknardia/oknardia/my_secret.py.template oknardia/oknardia/my_secret.py
# затем отредактируйте значения в my_secret.py
ВАЖНО: my_secret.py НИКОГДА не должен быть в git!
Используйте .gitignore для исключения файла.
"""
# ============================================================================
# РАЗРАБОТКА (DEV) - Хосты и сетевые настройки
# ============================================================================
# Хосты на которых может работать приложение (разработка)
MY_ALLOWED_HOSTS = [
'127.0.0.1',
'localhost',
'your-dev-hostname.local', # ИЗМЕНИТЕ на ваше имя хоста
]
# Допустимые хосты для разработки
MY_HOST_HOME1 = 'your-dev-hostname-windows' # ИЗМЕНИТЕ
MY_HOST_HOME2 = 'your-dev-hostname-mac' # ИЗМЕНИТЕ
MY_HOST_DEV = [MY_HOST_HOME1, MY_HOST_HOME2]
# Хосты для продакшена (заполнять с осторожностью)
MY_HOST_PROD = [] # На продакшене используйте переменные окружения!
# ============================================================================
# БЕЗОПАСНОСТЬ - Django SECRET_KEY
# ============================================================================
# SECURITY WARNING: keep the secret key used in production secret!
# Сгенерируйте новый ключ с помощью:
# python -c 'from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())'
MY_SECRET_KEY = 'ЗАПОЛНИТЕ_СЛУЧАЙНОЙ_СТРОКОЙ_БОЛЬШОЙ_ДЛИНЫ'
# ============================================================================
# АДМИНИСТРАТОРЫ - для оповещений об ошибках
# ============================================================================
MY_ADMINS = (
('Your Name', 'your-email@example.com'),
('Admin Name', 'admin@example.com'),
)
# ============================================================================
# ПУТИ К ФАЙЛАМ - разработка
# ============================================================================
# путь к каталогу media (статика, для web-сервера nginx или apache)
MY_MEDIA_ROOT_DEV1 = 'M:\\path\\to\\your\\media\\' # Windows (если применимо)
MY_MEDIA_ROOT_DEV2 = '/path/to/your/media/' # Mac/Linux - ИЗМЕНИТЕ!
# путь к каталогу static (статика, для web-сервера nginx или apache)
MY_STATIC_ROOT_DEV1 = 'M:\\path\\to\\your\\static' # Windows (если применимо)
MY_STATIC_ROOT_DEV2 = '/path/to/your/static' # Mac/Linux - ИЗМЕНИТЕ!
# путь для кэш-блоков шаблонов
MY_STATIC_BASE_PATH_DEV1 = MY_STATIC_ROOT_DEV1
MY_STATIC_BASE_PATH_DEV2 = MY_STATIC_ROOT_DEV2
# путь для sitemap файлов
MY_SITEMAP_ROOT_DEV1 = 'M:\\path\\to\\your\\public\\' # Windows (если применимо)
MY_SITEMAP_ROOT_DEV2 = '/path/to/your/public/' # Mac/Linux - ИЗМЕНИТЕ!
# ============================================================================
# ПУТИ К ФАЙЛАМ - продакшен
# ============================================================================
MY_MEDIA_ROOT_PROD = '/home/web/oknardia-ru/public/media/' # ЗАПОЛНИТЕ!
MY_STATIC_ROOT_PROD = '/home/web/oknardia-ru/public/static' # ЗАПОЛНИТЕ!
MY_STATIC_BASE_PATH_PROD = MY_STATIC_ROOT_PROD
MY_SITEMAP_ROOT_PROD = '/home/web/oknardia-ru/public/' # ЗАПОЛНИТЕ!
# ============================================================================
# EMAIL - Почтовый сервер (разработка)
# ============================================================================
# Email адреса для разработки
MY_EMAIL_DEV = 'dev-email@example.com'
MY_EMAIL_FROM_DEV = 'dev-email@example.com'
MY_EMAIL_HOST_USER_DEV = 'your-email@smtp.example.com' # ЗАПОЛНИТЕ!
MY_EMAIL_HOST_PASSWORD_DEV = 'YOUR_EMAIL_PASSWORD' # ЗАПОЛНИТЕ!
MY_EMAIL_HOST_DEV = 'smtp.example.com' # ЗАПОЛНИТЕ! (например: smtp.mail.ru)
MY_EMAIL_PORT_DEV = 587 # ЗАПОЛНИТЕ! (обычно 587 или 2525)
# ============================================================================
# EMAIL - Почтовый сервер (продакшен)
# ============================================================================
MY_EMAIL_PROD = MY_EMAIL_DEV
MY_EMAIL_FROM_PROD = MY_EMAIL_FROM_DEV
MY_EMAIL_HOST_USER_PROD = MY_EMAIL_HOST_USER_DEV # На продакшене используйте env переменные!
MY_EMAIL_HOST_PASSWORD_PROD = MY_EMAIL_HOST_PASSWORD_DEV # На продакшене используйте env переменные!
MY_EMAIL_HOST_PROD = MY_EMAIL_HOST_DEV
MY_EMAIL_PORT_PROD = MY_EMAIL_PORT_DEV
# ============================================================================
# БД MySQL/MariaDB - разработка
# ============================================================================
MY_DATABASE_HOST_DEV1 = 'localhost' # Офисный сервер разработки - ИЗМЕНИТЕ!
MY_DATABASE_HOST_DEV2 = 'localhost' # Домашний сервер разработки - ИЗМЕНИТЕ!
MY_DATABASE_NAME_DEV = 'django_oknardia_dev' # ИЗМЕНИТЕ если нужно
MY_DATABASE_PORT_DEV = '3306' # Стандартный порт MySQL
MY_DATABASE_USER_DEV = 'web' # ИЗМЕНИТЕ если нужно
MY_DATABASE_PASSWORD_DEV = 'YOUR_DB_PASSWORD' # ЗАПОЛНИТЕ!
# ============================================================================
# БД MySQL/MariaDB - продакшен
# ============================================================================
MY_DATABASE_HOST_PROD = 'localhost' # ЗАПОЛНИТЕ! (на продакшене)
MY_DATABASE_NAME_PROD = 'django_oknardia_prod' # ЗАПОЛНИТЕ!
MY_DATABASE_PORT_PROD = '3306'
MY_DATABASE_USER_PROD = 'web'
# ВНИМАНИЕ: На продакшене используйте переменные окружения или менеджер секретов!
MY_DATABASE_PASSWORD_PROD = '' # ОСТАВЬТЕ ПУСТО! Используйте переменные окружения!
# ============================================================================
# API ключи - Google Captcha
# ============================================================================
# Получите ключи на https://www.google.com/recaptcha/admin
# ВАЖНО: Никогда не коммитьте реальные ключи в git!
# PRIVATE ключ - это СЕКРЕТ, держите его в безопасности!
MY_CAPTCHA_PUBLIC_KEY = 'YOUR_CAPTCHA_PUBLIC_KEY_HERE' # ЗАПОЛНИТЕ!
MY_CAPTCHA_PRIVATE_KEY = 'YOUR_CAPTCHA_PRIVATE_KEY_HERE' # ЗАПОЛНИТЕ! (СЕКРЕТ!)
# ============================================================================
# API ключи - Yandex Maps
# ============================================================================
# Получите ключ на https://developer.tech.yandex.ru/
MY_YANDEX_MAPS_API_KEY = 'YOUR_YANDEX_MAPS_API_KEY'
# ============================================================================
# uWSGI - Touch-reload файл (для перезагрузки при изменении кода)
# ============================================================================
MY_TOUCH_RELOAD_DEV1 = 'M:\\path\\to\\touch-reload.txt' # Windows (если применимо)
MY_TOUCH_RELOAD_DEV2 = '/path/to/logs/touch-reload.txt' # Mac/Linux - ИЗМЕНИТЕ!
MY_TOUCH_RELOAD_PROD = '/home/web/oknardia-ru/logs/touch-reload.txt' # ЗАПОЛНИТЕ!
# ============================================================================
# ИНСТРУКЦИЯ ПО ЗАПОЛНЕНИЮ
# ============================================================================
"""
1. СКОПИРУЙТЕ этот файл:
cp oknardia/oknardia/my_secret.py.template oknardia/oknardia/my_secret.py
2. ОТРЕДАКТИРУЙТЕ значения, помеченные ИЗМЕНИТЕ! или ЗАПОЛНИТЕ!
3. УБЕДИТЕСЬ, что мой_secret.py в .gitignore:
grep my_secret .gitignore
4. НИКОГДА не коммитьте my_secret.py в git!
5. На ПРОДАКШЕНЕ используйте переменные окружения:
export DJANGO_SECRET_KEY="..."
export DATABASE_PASSWORD="..."
и т.д.
СОВЕТЫ:
- Сгенерируйте новый SECRET_KEY с помощью Python:
python -c 'from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())'
- Используйте менеджер паролей (LastPass, 1Password, Vault) для хранения учетных данных
- Регулярно меняйте пароли БД и API ключи
- На продакшене используйте отдельные более сильные пароли
"""
+13 -5
View File
@@ -31,7 +31,8 @@ if socket.gethostname() in MY_HOST_DEV:
DEBUG = TEMPLATE_DEBUG = True
else:
# Все остальные хосты (подразумевается продакшн)
DEBUG = TEMPLATE_DEBUG = False
DEBUG = TEMPLATE_DEBUG = True
# DEBUG = TEMPLATE_DEBUG = False
ALLOWED_HOSTS = MY_ALLOWED_HOSTS
@@ -50,6 +51,7 @@ INSTALLED_APPS = [
'django.contrib.staticfiles',
'django.contrib.humanize',
# 'django.contrib.sitemaps',
'oknardia.apps.OknardiaConfig',
'web.apps.WebConfig',
@@ -107,8 +109,8 @@ USE_I18N = True
USE_TZ = True
FIRST_DAY_OF_WEEK = 1 # 1'st day week -- monday
SHORT_DATE_FORMAT = 'Y-m-d'
SHORT_DATETIME_FORMAT = 'Y-m-d H:M:S'
DATETIME_FORMAT = 'Y-m-d H:M:S'
SHORT_DATETIME_FORMAT = 'Y-m-d H:i:s'
DATETIME_FORMAT = 'Y-m-d H:i:s'
# Статические файлы (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/4.1/howto/static-files/
@@ -117,8 +119,9 @@ STATIC_URL = 'static/'
MEDIA_URL = 'media/'
if DEBUG: # DEBUG: заменяем настройки прода, на настройки девопа
if socket.gethostname() in MY_HOST_DEV: # DEBUG: заменяем настройки прода, на настройки девопа
MEDIA_ROOT = MY_MEDIA_ROOT_DEV1 if socket.gethostname() == MY_HOST_HOME1 else MY_MEDIA_ROOT_DEV2
SITEMAP_ROOT = MY_SITEMAP_ROOT_DEV1 if socket.gethostname() == MY_HOST_HOME1 else MY_SITEMAP_ROOT_DEV2
# STATIC_ROOT = MY_STATIC_ROOT_DEV1 if socket.gethostname() == MY_HOST_HOME1 else MY_STATIC_ROOT_DEV2
STATICFILES_DIRS = [
MY_STATIC_ROOT_DEV1 if socket.gethostname() == MY_HOST_HOME1 else MY_STATIC_ROOT_DEV2,
@@ -136,11 +139,12 @@ if DEBUG: # DEBUG: заменяем настройки прода, на на
# 'OPTIONS': { 'autocommit': True, }
}
}
TOUCH_RELOAD = MY_TOUCH_RELOAD_PROD
TOUCH_RELOAD = MY_TOUCH_RELOAD_DEV1 if socket.gethostname() == MY_HOST_HOME1 else MY_TOUCH_RELOAD_DEV2
else:
MEDIA_ROOT = MY_MEDIA_ROOT_PROD
# STATICFILES_DIRS = [MY_STATIC_ROOT_PROD1, ]
STATIC_ROOT = MY_STATIC_ROOT_PROD
SITEMAP_ROOT = MY_SITEMAP_ROOT_PROD
# путь к каталогу static (в эту переменную использовать для указания пути где будут делаться кэш-блоки для шаблонов)
STATIC_BASE_PATH = MY_STATIC_BASE_PATH_PROD
DATABASES = {
@@ -265,3 +269,7 @@ CATALOG_RECORD_FOR_PROFILE_MODEL = 1
CATALOG_RECORD_FOR_PROFILE_MANUFACTURER = 100
CATALOG_SORTER_MAGIC_NUMBER_ADV = 5
CATALOG_SORTER_MAGIC_NUMBER_TIZER = 1
MAX_LEN_RING_LOG_BUFFER = 250 # МАКСИМАЛЬНЫЙ РАЗМЕР КОЛЬЦЕВОГО БУФЕРА
YANDEX_MAPS_API_KEY = MY_YANDEX_MAPS_API_KEY
+38 -2
View File
@@ -18,7 +18,7 @@ from django.contrib import admin
from django.urls import path, re_path
from django.conf.urls.static import static
from oknardia.settings import *
from web import views, autocomplete_addr, user_manager, blog, diagrams, report2, catalog
from web import views, autocomplete_addr, user_manager, blog, diagrams, report1, report2, catalog, prices, service
urlpatterns = [
@@ -28,9 +28,12 @@ urlpatterns = [
re_path(r'^$', views.main_init),
# обработчик автокомлита (подсказки во время ввода адреса на главной странице)
re_path(r'^autocomplete_addr$', autocomplete_addr.autocomplete_addr),
# обработка адреса введеного в форме поиска
re_path(r'^get_address$', views.get_address),
# ОБРАБОТЧИКИ АВТОРИЗАЦИИ
# Вызов шаблона подгружаем captcha
re_path(r'^captcha', user_manager.captcha),
re_path(r'^captcha$', user_manager.captcha),
# Обработчик информации и статусов пользователя и, или подгрузка шаблона login-logout.html
re_path(r'^login-logout', user_manager.menu_login_logout),
# Обработчик форма login-logout-restore. После обработки пере-подгружает шаблон login-logout-after.html
@@ -49,6 +52,8 @@ urlpatterns = [
re_path(r'^tariff[/*]$', views.tariff),
re_path(r'^contact[/*]$', views.contact),
re_path(r'^stat_all[/*]$', diagrams.statistic_menu),
re_path(r'^stat/series/analiz[/*]$', diagrams.statistic_menu), # дубль для старых ссылок
re_path(r'^stat/series/geo[/*]$', diagrams.statistic_menu), # дубль для старых ссылок
re_path(r'^stat/rating[/*]$', report2.ratings),
re_path(r'^stat/rating/profiles_rank[/*]$', report2.profiles_rating),
# --- Каталог
@@ -62,6 +67,37 @@ urlpatterns = [
# --- --- Каталог серий типового строительства
re_path(r'^catalog/seria[/*]$', catalog.catalog_seria),
re_path(r'^catalog/seria/(?P<seria_name_translit>[^/]*)/all(?P<seria_id>\d+)[/*]$', catalog.catalog_seria_info),
re_path(r'^seria_[^/]*/all(?P<seria_id>\d+)/\S*$', catalog.report_all_info_seria_redirect), # для старых ссылок
# --- --- Каталог стандартных проёмов и схем открывания длч типовых серий строительства
re_path(r'^catalog/standard_opening[/*]$', catalog.standard_opening),
# --- --- Каталог производителей окон
re_path(r'^catalog/company[/*]$', catalog.catalog_company),
re_path(r'^catalog/company/(?P<company_id>\d+)-(?P<company_name_slug>\S*)[/*]$', catalog.catalog_company_detail),
# ЦЕНОВЫЕ ПРЕДЛОЖЕНИЯ
# --- Одиночное окно
re_path(r'^tsena-odnogo-okna/(?P<win_width_mm>\d+)x(?P<win_height_mm>\d+)mm/tip(?P<win_id>\d+)[/*]$',
prices.report_one_win_price),
re_path(r'^next_price_one_flap_frame/idW(?P<win_id>\d+)N(?P<frame_begin_n>\d+)\S*$', prices.next_one_win_price),
# --- Ценовая выдача
re_path(r'^(?P<build_id>\d+)/(?P<apart_id>\d+)/(?P<slug>[\s\S]*)$', prices.report_price),
# --- Подгружаемый фрейм ценовая выдачи
re_path(r'^next_price_frame/idA(?P<apart_id>\d+)MDPO(?P<mount_dim_per_offer>\d+)LON(?P<address_longitude>\d+)'
r'LAT(?P<address_latitude>\d+\.*\d*)N(?P<frame_begin_n>\d+\.*\d*)\S*[/*]$', prices.next_price_frame),
# СРАВНЕНИЕ ОКОННЫХ НАБОРОВ
re_path(r'^compare_sets/(?P<to_compare>[\s\S]+|.*)$', report1.compare_offers), # дубль для старых ссылок
re_path(r'^compare_offers/(?P<to_compare>[\s\S]+|.*)$', report1.compare_offers),
re_path(r'^specification_set/\d$', views.main_init), # заглушка (позже будет спецификация оконного набора)
# отображение всех составлющих рейтинга
re_path( r'^show_rating_components/(?P<win_set>\d+)$', report1.show_rating_components),
# СЛУЖЕБНЫЕ СТРАНИЦЫ (для администратора)
# --- страничка "главная сервис-утилит"
re_path(r'^service[/*]$', service.service),
# --- страничка для тестирования верстки текста в блоге
re_path(r'^service/tmp[/*]$', service.tmp),
# --- страничка "нет доступа"
re_path(r'^service/not-denice[/*]$', service.not_denice),
# --- создание файлов sitemap.xml
re_path(r'^service/make_sitemaps[/*]$', service.make_site_maps),
]
+96
View File
@@ -0,0 +1,96 @@
{% extends "base.html" %}
{% load static %}{% load filters %}
{% block Title %}Каталог изготовителей и поставщиков окон{% endblock %}
{% block Add_Body_Attribute %} style="padding-top:70px;"{% endblock %}
{% block Description %}Каталог изготовителей окон, партнёры «Окнардия», рейтинг, {% for i in COMPANIES %}{{ i.sMerchantName }}, {% endfor %} средняя цена окна{% endblock %}
{% block Keywords %}Оконные компании, {% for i in COMPANIES %}{{ i.sMerchantName }}, {% endfor %} изготовители окон, производители окон, постащики окон, партнёры, каталог компаний, каталог оконных компаний, oknardia, окнардия {{ META_KEYWORDS|default:"" }} {% endblock %}
{% block Date4Meta %}{% if PUB_DAT %}{{ PUB_DAT|date:"c" }}{% else %}{% now "c" %}{% endif %}{% endblock %}
{% block Last4Meta %}{% if PUB_DAT %}{{ PUB_DAT|date:"c" }}{% else %}{% now "c" %}{% endif %}{% endblock %}
{% block Author4Meta %}: Каталог изготовителей окон{% endblock %}
{% block CopyrightAuthor4Meta %}: Каталог изготовителей окон{% endblock %}
{% block Top_Meta1 %}{# <!-- BEGIN Дополнительные Metatags --> #}
<meta itemprop="author" content="Каталог «Окнардия»" />{% if IMG_FOR_BLOG %}
<meta itemprop="image" content="https://oknardia.ru/media/{{ IMG_FOR_BLOG }}" />{% else %}
<meta itemprop="image" content="https://oknardia.ru/static/img/MerDY3gpU0w.jpg" />{% endif %}
<meta itemprop="datePublished" content="{% if PUB_DAT %}{{ PUB_DAT|date:"c" }}{% else %}{% now "c" %}{% endif %}" />
<span itemprop="publisher" itemscope itemtype="http://schema.org/Organization"><meta itemprop="name" content="Каталог «Окнардия»" /></span>
<span itemprop="author" itemscope itemtype="http://schema.org/Person"><meta itemprop="name" content="Каталог «Окнардия»" /></span>
<meta itemprop="articleSection" content="Каталог производителей окон" />
<meta itemprop="headline" content="Компании-партнёры «Окнардии», их рейтинг, число оконных наборов и вариантов расчёта цен для типовых проёмов, средняя цена окна..." />
<meta name="news_keywords" content="{{ HEADER }}" />
<link rel="canonical" href="https://oknardia.ru/catalog/company/" />
<link rel="standout" href="https://oknardia.ru/catalog/company/" />
<!-- Разметка для соц-сетей Facebook Open Graph -->
<meta property="fb:admins" name="admins" content="100000084781830" />
<meta property="fb:pages" content="276108456054163" />
<meta property="fb:app_id" content="258354027974262" />
<meta property="fb:profile_id" name="profile_id" content="https://www.facebook.com/oknardia/" />
<meta property="og:locale" content="ru_RU" />
<meta property="og:site_name" content="oknardia.ru" />
<meta property="og:url" content="https://oknardia.ru//catalog/company/" />
<meta property="og:type" content="article" />
<meta property="og:title" content="Каталог производителей окон | oknardia.ru" />
<meta property="og:description" content="Компании-партнеры «Окнардии», их средний рейтинг, число оконных наборов и вариантов расчета цен для типовых проёмов, средняя цена окна..." />
<meta property="og:image" content="{% if IMG_FOR_BLOG %}https://oknardia.ru/media/{% endif %}{{ IMG_FOR_BLOG|default:'https://oknardia.ru/static/img/MerDY3gpU0w.jpg' }}" />
<link rel="image_src" href="{% if IMG_FOR_BLOG %}https://oknardia.ru/media/{% endif %}{{ IMG_FOR_BLOG|default:'https://oknardia.ru/static/img/MerDY3gpU0w.jpg' }}" />
<!-- Разметка для соц-сетей Twitter Card -->
<meta name="twitter:title" content="Каталог производителей окон | oknardia.ru" />
<meta name="twitter:description" content="Компании-партнеры «Окнардии», их средний рейтинг, число оконных наборов и вариантов расчета цен для типовых проёмов, средняя цена окна..." />
<meta name="twitter:card" content="summary">
<meta name="twitter:site" content="@oknardia" />
<meta name="twitter:domain" content="oknardia.ru" />
<meta property="twitter:url" content="{% if IMG_FOR_BLOG %}https://oknardia.ru/media/{% endif %}{{ IMG_FOR_BLOG|default:'https://oknardia.ru/static/img/MerDY3gpU0w.jpg' }}" />
<meta name="relap-image" content="{% if IMG_FOR_BLOG %}https://oknardia.ru/media/{% endif %}{{ IMG_FOR_BLOG|default:'https://oknardia.ru/static/img/MerDY3gpU0w.jpg' }}">
{# <!-- END Дополнительные Metatags --> #}{% endblock %}
{% block Main_Content %}
<div class="container-fluid">
{# <!--- Хлебные крошки: НАЧАЛО --> #}<div class="row">
<div class="col-md-11 col-xs-12">
<ol class="breadcrumb">
<li><a href="/">Главная</a></li>
<li><a href="/catalog">Каталог</a></li>
<li>Производители и&nbsp;поставщики окон</li>
</ol>
<h1>Производители и&nbsp;поставщики окон</h1>
</div>
</div>{# <!--- Хлебные крошки: КОНЕЦ ---> #}
<dIv class="row">
<div class="col-md-11 col-xs-12">
<p>Компании-партнёры &laquo;Окнардии&raquo;, их средний рейтинг, число оконных наборов и&nbsp;вариантов расчёта цен для типовых проёмов, средняя цена окна:</p>
</div>
</dIv>
<div class="row">{% for i in COMPANIES %}
<div class="col-lg--2 col-md-3 col-sm-4 col-xs-6">
<h2>{{ i.sMerchantName }}</h2>
<p><img src="/media/{{ i.pMerchantLogo }}" style="height:40px;width:auto;" /></p>
<p><nobr class="badge badge4price" title="Средний Рейтинг «Окнардии» для оконных наборов компании «{{ i.sMerchantName }}»{% if i.RatingAVG > -0.01 %} — {{ i.RatingAVG|stringformat:".2f" }} баллов{% endif %}"><small class="xx">Средний рейтинг наборов:</small> {% for Star in i.STARS %}{% if Star == 0 %}<b class="glyphicon glyphicon-star-empty"></b>{% else %}<b class="glyphicon glyphicon-star"></b>{% endif %}{% endfor %} {% if i.RatingAVG > -0.01 %} {{ i.RatingAVG|stringformat:".2f" }}{% endif %}</nobr></p>
<p>В ценовом агрегаторе {{ i.NumSets }}. Размещено {{ i.NumOffers }} цен типовых проёмов. Средняя цена предложения: <nobr><strong>{{ i.PriceAVG|stringformat:".2f"|price_format }}<small class="glyphicon glyphicon-ruble" aria-label="₽ (руб.)" title="₽ (руб.)"></small></strong></nobr> <small>(без учета скидок)</small></p>
<p><a href="/catalog/company/{{ i.id }}-{{ i.sMerchantMainURL }}" class="btn btn-info btn-sm">Подробнее&hellip;</a><br />
<small class="xx">Последнее обновление цен: {{ i.lastUpdate }}</small></p>
</div>{% endfor %}
<DIV class="col-xs-12" style="height:6em;"></DIV>
</dIv>
{# --- Баннер: НАЧАЛО --- #}
<div class="row"><div class="col-md-12 col-xs-12"><hr class="dotted-black" />{% include "ad/bannet-wide.html" %}</div></div>
{# --- Баннер: конец --- #}
<div class="row">
{% include "report/report_last_user_visit.html" %}
{% include "report/report_log_user_visit.html" %}
</div>
</div>{% endblock %}
+143
View File
@@ -0,0 +1,143 @@
{% extends "base.html" %}
{% load static %}{% load filters %}
{% block Title %}Окна «{{ COMPANY }}» : Каталог производителей окон{% endblock %}
{% block Add_Body_Attribute %} style="padding-top:70px;"{% endblock %}
{% block Description %}«{{ COMPANY }}», описание компании «{{ COMPANY }}», оконные наборы от «{{ COMPANY }}» и их состав, характеристики «{{ COMPANY }}», рейтинг «{{ COMPANY }}», средние цены и отклонение цен «{{ COMPANY }}».{% endblock %}
{% block Keywords %}{{ COMPANY }}, компания {{ COMPANY }}, окна {{ COMPANY }}, изготовитель окон {{ COMPANY }}, производитель окон {{ COMPANY }}, поставщик окон {{ COMPANY }}, партнёр, каталог компаний, каталог оконных компаний, oknardia, окнардия {{ META_KEYWORDS|default:"" }} {% endblock %}
{% block Date4Meta %}{% if PUB_DAT %}{{ PUB_DAT|date:"c" }}{% else %}{% now "c" %}{% endif %}{% endblock %}
{% block Last4Meta %}{% if PUB_DAT %}{{ PUB_DAT|date:"c" }}{% else %}{% now "c" %}{% endif %}{% endblock %}
{% block Author4Meta %}: Каталог изготовителей окон{% endblock %}
{% block CopyrightAuthor4Meta %}: Каталог изготовителей окон{% endblock %}
{% block Top_Meta1 %}{# <!-- BEGIN Дополнительные Metatags --> #}
<meta itemprop="author" content="Каталог «Окнардия»" />{% if IMG_FOR_BLOG %}
<meta itemprop="image" content="https://oknardia.ru/media/{{ IMG_FOR_BLOG }}" />{% else %}
<meta itemprop="image" content="https://oknardia.ru/static/img/MerDY3gpU0w.jpg" />{% endif %}
<meta itemprop="datePublished" content="{% if PUB_DAT %}{{ PUB_DAT|date:"c" }}{% else %}{% now "c" %}{% endif %}" />
<span itemprop="publisher" itemscope itemtype="http://schema.org/Organization"><meta itemprop="name" content="Каталог «Окнардия»" /></span>
<span itemprop="author" itemscope itemtype="http://schema.org/Person"><meta itemprop="name" content="Каталог «Окнардия»" /></span>
<meta itemprop="articleSection" content="Каталог производителей окон" />
<meta itemprop="headline" content="Изготовитель окон «{{ COMPANY }}», описание, производимые им оконные наборы и их состав, характеристики, рейтинг, средние цены и отклонение цен." />
<meta name="news_keywords" content="{{ HEADER|striptags }}" />
<link rel="canonical" href="https://oknardia.ru/catalog/company/{{ COMPANY_ID }}-{{ COMPANY_T }}" />
<link rel="standout" href="https://oknardia.ru/catalog/company/{{ COMPANY_ID }}-{{ COMPANY_T }}" />
<!-- Разметка для соц-сетей Facebook Open Graph -->
<meta property="fb:admins" name="admins" content="100000084781830" />
<meta property="fb:pages" content="276108456054163" />
<meta property="fb:app_id" content="258354027974262" />
<meta property="fb:profile_id" name="profile_id" content="https://www.facebook.com/oknardia/" />
<meta property="og:locale" content="ru_RU" />
<meta property="og:site_name" content="oknardia.ru" />
<meta property="og:url" content="https://oknardia.ru//catalog/company/{{ COMPANY_ID }}-{{ COMPANY_T }}" />
<meta property="og:type" content="article" />
<meta property="og:title" content="Окна «{{ COMPANY }}» | oknardia.ru" />
<meta property="og:description" content="Окна «{{ COMPANY }}», описание окон «{{ COMPANY }}», производимые им оконные наборы и их состав, характеристики, рейтинг, средние цены и отклонение цен." />
<meta property="og:image" content="{% if IMG_FOR_BLOG %}https://oknardia.ru/media/{% endif %}{{ IMG_FOR_BLOG|default:'https://oknardia.ru/static/img/MerDY3gpU0w.jpg' }}" />
<link rel="image_src" href="{% if IMG_FOR_BLOG %}https://oknardia.ru/media/{% endif %}{{ IMG_FOR_BLOG|default:'https://oknardia.ru/static/img/MerDY3gpU0w.jpg' }}" />
<!-- Разметка для соц-сетей Twitter Card -->
<meta name="twitter:title" content="Производителей окон «{{ COMPANY }}» | oknardia.ru" />
<meta name="twitter:description" content="Изготовитель окон «{{ COMPANY }}», описание, производимые им оконные наборы и их состав, характеристики, рейтинг, средние цены и отклонение цен." />
<meta name="twitter:card" content="summary">
<meta name="twitter:site" content="@oknardia" />
<meta name="twitter:domain" content="oknardia.ru" />
<meta property="twitter:url" content="{% if IMG_FOR_BLOG %}https://oknardia.ru/media/{% endif %}{{ IMG_FOR_BLOG|default:'https://oknardia.ru/static/img/MerDY3gpU0w.jpg' }}" />
<meta name="relap-image" content="{% if IMG_FOR_BLOG %}https://oknardia.ru/media/{% endif %}{{ IMG_FOR_BLOG|default:'https://oknardia.ru/static/img/MerDY3gpU0w.jpg' }}">
{# <!-- END Дополнительные Metatags --> #}{% endblock %}
{% block Top_JS5 %}<script src="https://api-maps.yandex.ru/2.1/?lang=ru_RU" type="text/javascript"></script>{% endblock %}
{% block Main_Content %}
<div class="container-fluid">
{# <!--- Хлебные крошки: НАЧАЛО --> #}<div class="row">
<div class="col-md-11 col-xs-12">
<ol class="breadcrumb">
<li><a href="/">Главная</a></li>
<li><a href="/catalog">Каталог</a></li>
<li><a href="/catalog/company">Производители и поставщики окон</a></li>
<li>{{ COMPANY }}</li>
</ol>
<h1>{{ COMPANY }}</h1>
</div>
</div>{# <!--- Хлебные крошки: КОНЕЦ ---> #}
<dIV class="row">{% for i in SETS %}
{# ЛЕВАЯ КОЛОНКА: НАЧАЛО #}<div class="col-md-5 col-xs-12 catalog">{% if forloop.first %}
<p><img src="/media/{{ i.pMerchantLogo }}" style="height:40px;width:auto;" atl="Логотип «{{ COMPANY }}»" /></p>{% if i.sMerchantDescription %}
<p>{{ i.sMerchantDescription|safe }}</p>{% endif %}
<table>
<thead>
<tr><th colspan="2"><big>Контактная информация:</big></th></tr>
</thead>
<tbody>
<tr>
<td>телефоны:</td>
<td>{{ i.sOfficePhones|safe }}</td>
</tr>{% if i.sOfficeDescription %}<tr>
<td>часы&nbsp;работы:</td>
<td>{{ i.sOfficeDescription|safe }}</td>
</tr>{% endif %}<tr>
<td>email:</td>
<td><script type="text/javascript">document.write("<a href='mailto:{% for j in i.sOfficeEmails %}{% if not forloop.first %}"{% endif %}{{ j }}{% if not forloop.last %}"+{% endif %}{% endfor %}?subject=Запрос через «Окнардия»: - '>{% for j in i.sOfficeEmails %}{% if not forloop.first %}"{% endif %}{{ j }}{% if not forloop.last %}"+{% endif %}{% endfor %}</a>");</script></td>
</tr><tr>
<td>сайт:</td>
<td><a href="{{ i.sMerchantMainURL.URL }}" target="_blank" rel="nofollow">{{ i.sMerchantMainURL.URL_VIEW }}</a></td>
</tr><tr>
<td>офис<sup>*</sup>:</td>
<td>{{ i.sOfficeName }}</td>
</tr><tr>
<td>адрес:</td>
<td><a data-toggle="collapse" href="#collapse_it" aria-expanded="false" aria-controls="collapse_it">{{ i.sOfficeAddress }}</a></td>
</tr><tr class="collapse" id="collapse_it">
<td colspan="2"><script type="text/javascript">ymaps.ready(init);var myMap,myPlacemark;function init(){myMap=new ymaps.Map("office-map",{center:[{{ i.fOfficeGeoCode_Latitude|stringformat:".7f" }},{{ i.fOfficeGeoCode_Longitude|stringformat:".7f" }}],zoom:16,controls:['zoomControl', 'typeSelector']});myMap.behaviors.disable('scrollZoom');myPlacemark=new ymaps.Placemark([{{ i.fOfficeGeoCode_Latitude|stringformat:".7f" }},{{ i.fOfficeGeoCode_Longitude|stringformat:".7f" }}],{balloonContentHeader:'{{ i.sOfficeName }}',balloonContentBody:'{{ i.sOfficeAddress }}.<br>тел.: {{ i.sOfficePhones }}',hintContent:'{{ i.sOfficeName }} — {{ i.sOfficeAddress }}'},{preset:"islands#dotIcon",iconColor:'#414997'});myMap.geoObjects.add(myPlacemark);}</script><div id="office-map" style="width:100%;height:300px"></div></td>
</tr>
</tbody>
</table>
<p class="small-note"><big style="color:red">*</big> — Офис предоставивший расчеты для оконного агрегатора &laquo;Окнардия&raquo;.</p>
{% endif %}</div>{# ЛЕВАЯ КОЛОНКА: КОНЕЦ #}
<style>
table.head3{width: 100%;}
table.head3 tr > td:first-of-type {padding: 0;padding-right: 1ex;}
table.head3 tr > td:last-of-type {padding-left: 1ex;vertical-align: baseline;}
table.head3 tr > td:last-of-type > nobr.badge4price {margin-top: 24px;font-size: small}
</style>
{# ПРАВАЯ КОЛОНКА: НАЧАЛО #}<div class="col-md-6 col-xs-12">
<table class="head3">
<tr>
<td><h3>Оконный набор: &laquo;{{ i.sSetName|safe }}&raquo;</h3></td>
<td align="right"><nobr class="badge badge4price" title="Рейтинг «Окнардии» для оконного набора «{{ i.sSetName }}»{% if i.fSetRating.RATING > -0.01 %} — {{ i.fSetRating.RATING|stringformat:".2f" }} баллов{% endif %}">{% for Star in i.fSetRating.STARS %}{% if Star == 0 %}<b class="glyphicon glyphicon-star-empty"></b>{% else %}<b class="glyphicon glyphicon-star"></b>{% endif %}{% endfor %} {% if i.fSetRating.RATING > -0.01 %} {{ i.fSetRating.RATING|stringformat:".2f" }}{% endif %}</nobr></td>
</tr>
</table>
<P>В этом наборе применен {% if i.iProfileCameras != "—" %}профиль <a href="/catalog/profile/{{ i.idPVC }}-{{ i.sProfileManufacturer.NAME_T }}/{{ i.idPVC }}-{{ i.sProfileName.NAME_T }}/">{{ i.sProfileBriefDescription }}</a>{% else %} <a href="catalog/profile/{{ i.idPVC }}-{{ i.sProfileManufacturer.NAME_T }}/{{ i.idPVC }}-{{ i.sProfileName.NAME_T }}/">{{ i.sProfileBriefDescription|lower }}</a>{% endif %} производства <a href="/catalog/profile/{{ i.idPVC }}-{{ i.sProfileManufacturer.NAME_T }}/">{{ i.sProfileManufacturer.NAME }}</a>. {% if i.iProfileCameras != "—"%} Число камер рамы и&nbsp;створки —&nbsp;{{ i.iProfileCameras }}.{% endif %} {{ i.fProfileSeals|capfirst }} уплотнения. {% if i.iProfileCameras in LIST_NOT %}Основа: {% else %}Армирование {% endif %} {{ i.sProfileReinforcement }}. Цвет профиля: {{ i.sProfileColor|lower }}. Устанавливается {{ i.sGlazingBriefDescription|lower|safe }}{% if i.sGlazingManufacturer != "—//—" %}{% if i.sGlazingManufacturer not in LIST_NOT %}, производства {{ i.sGlazingManufacturer }}{% endif %}{% endif %}. {% if i.sGlazingToning|lower == "нет" %}Без тонирования{% else %}Тонирование: {{ i.sGlazingToning|lower }}{% endif %}. Формула стеклопакета: <nobr>{{ i.sGlazingMark }}.</nobr> Цвет уплотнителя контуров и&nbsp;стеклопакета {{ i.sProfileSealDescription }}. Используется фурнитура {{ i.sSetImplementAll }}{% if i.sSetImplementHandles != "" %}, ручки {{ i.sSetImplementHandles }}{% endif %}{% if i.sSetImplementCatch != "" %}, фиксатор открывания {{ i.sSetImplementCatch }}{% endif %}. {% if i.sSetClimateControl != "" %} Каждое окно оборудовано системой климат-контроля —&nbsp;{{ i.sSetClimateControl }}.{% endif %}</P>
<P>{% if i.sSetSill|lower not in LIST_NOT %}Оконный подоконник {{ i.sSetSill|safe }}.{% endif %} {% if i.sSetPanes|lower not in LIST_NOT %}Внешний водоотлив {{ i.sSetPanes|safe }}.{% endif %} {% if i.sSetSlope|lower not in LIST_NOT %}Внутренней откос {{ i.sSetSlope|safe }}.{% endif %}{% if i.sSetSill|lower in LIST_NOT and i.sSetPanes|lower in LIST_NOT and i.sSetSlope|lower in LIST_NOT %}Подоконники, внешние водоотливы и&nbsp;внутренние откосы не&nbsp;включены в&nbsp;набор. Производитель &laquo;{{ COMPANY }}&raquo; предполагает, что закзчик этого набора закупит их&nbsp;отдельно у&nbsp;него или&nbsp;других поставщиков.{% elif i.sSetSill|lower in LIST_NOT and i.sSetPanes|lower in LIST_NOT %}Подоконники и&nbsp;внешние водоотливы не&nbsp;включены в&nbsp;набор. Производитель &laquo;{{ COMPANY }}&raquo; предполагает, что закзчик этого набора закупит их&nbsp;отдельно у&nbsp;него или&nbsp;других поставщиков.{% elif i.sSetSill|lower in LIST_NOT and i.sSetSlope|lower in LIST_NOT %}Внешние водоотливы и&nbsp;внутренние откосы не&nbsp;включены в&nbsp;набор. Производитель &laquo;{{ COMPANY }}&raquo; предполагает, что закзчик этого набора закупит их&nbsp;отдельно у&nbsp;него или&nbsp;других поставщиков.{% elif i.sSetPanes|lower in LIST_NOT and i.sSetSlope|lower in LIST_NOT %}Подоконники и&nbsp;внутренние откосы не&nbsp;включены в&nbsp;набор. Производитель &laquo;{{ COMPANY }}&raquo; предполагает, что закзчик этого набора закупит их&nbsp;отдельно у&nbsp;него или&nbsp;других поставщиков.{% elif i.sSetPanes|lower in LIST_NOT %}Внешние оконные водоотливы не&nbsp;включены в&nbsp;набор. Производитель &laquo;{{ COMPANY }}&raquo; предполагает, что заказчик этого набора закупит их&nbsp;отдельно у&nbsp;него или&nbsp;других поставщиков.{% elif i.sSetSlope|lower in LIST_NOT %}Внутренние откосы и&nbsp;их&nbsp;отделка не&nbsp;включены в&nbsp;набор. Производитель &laquo;{{ COMPANY }}&raquo; предполагает, что заказчик этого набора закупит их&nbsp;отдельно у&nbsp;него или&nbsp;других поставщиков.{% elif i.sSetSill|lower in LIST_NOT %}Оконные подоконники не&nbsp;включены в&nbsp;набор. Производитель &laquo;{{ COMPANY }}&raquo; предполагает, что заказчик этого набора закупит их&nbsp;отдельно у&nbsp;него или&nbsp;других поставщиков.{% endif %}</P>
<P>{% if i.bSetUninstallInstall %}Установка новых и&nbsp;демонтаж старых оконных конструкций {{ i.sSetUninstallInstall|safe }}. {% endif %}{% if i.bSetDelivery %}Доставка {{ i.sSetDelivery|safe }}. {% endif %}{% if not i.bSetDelivery and not i.bSetUninstallInstall %}В&nbsp;стоимость набора не&nbsp;входит демонтаж и&nbsp;установка. Эти услуги оплачиваются отдельно. {% if i.sSetUninstallInstall not in LIST_NOT %}Примерно {{ i.sSetUninstallInstall|safe }}, или заказчик может провести все работы самостоятельно. {% endif %}Доставка тоже не&nbsp;входит в&nbsp;стоимость, осуществляется заказчиком самостоятельно или оплачивается отдельно. {% if i.sSetUninstallInstall not in LIST_NOT %}{{ i.sSetDelivery|safe|capfirst }}. {% endif %}{% elif not i.bSetDelivery %}Доставка&nbsp;не входит в&nbsp;стоимость, осуществляется заказчиком самостоятельно или оплачивается отдельно. {% if i.sSetUninstallInstall not in LIST_NOT %}{{ i.sSetDelivery|safe|capfirst }}. {% endif %}{% elif not i.bSetUninstallInstall %}В&nbsp;стоимость набора не&nbsp;входит демонтаж и&nbsp;установка. Эти услуги оплачиваются отдельно.{% if i.sSetUninstallInstall not in LIST_NOT %} Примерно {{ i.sSetUninstallInstall|safe }}. Или заказчик может провести все работы самостоятельно.{% endif %}{% endif %}</P>{% if i.sSetOtherConditions not in LIST_NOT %}
<P>Прочие условия —&nbsp;{{ i.sSetOtherConditions|safe }}</P>{% endif %}
{# Скидки -- {{ i.sOfficeDiscountMetaFormula }}<br>#}
<p style='background-color:#f0f0f0;color:silver;padding:5ex 10ex;'>Извините, графики ценового распределения для оконного набора &laquo;{{ i.sSetName|safe }}&raquo; компании &laquo;{{ i.sMerchantName }}&raquo; подготавливаются.<br />&nbsp;<br />Зайдите позже.</p>
<p><small class="xx">Оконный набор создан {{ i.earlyCreation }}, последнее обновление цен {{ i.lastUpdate }}</small></p>
</div>{# ПРАВАЯ КОЛОНКА: КОНЕЦ #}{% endfor %}
{# РАСПОРКА: начало #}<DIV class="col-xs-12" style="height:6em;"></DIV>{# РАСПОРКА: конец #}
</dIV>
{# --- Баннер: НАЧАЛО --- #}
<div class="row"><div class="col-md-12 col-xs-12"><hr class="dotted-black" />{% include "ad/bannet-wide.html" %}</div></div>
{# --- Баннер: конец --- #}
<div class="row">
{% include "report/report_last_user_visit.html" %}
{% include "report/report_log_user_visit.html" %}
</div>
</div>{% endblock %}
@@ -1,7 +1,7 @@
{% extends "base.html" %}
{% load static %}
{% block Title %} Каталог оконных профилей{% endblock %}
{% block Title %}Каталог оконных профилей{% endblock %}
{% block Add_Body_Attribute %} style="padding-top:70px;"{% endblock %}
@@ -1,13 +1,13 @@
{% extends "base.html" %}
{% load static %}
{% block Title %} Каталог :: Оконные профили производства {{ CATALOG_MANUFACT }}{% endblock %}
{% block Title %}Оконные профили производства {{ CATALOG_MANUFACT }} : Каталог{% endblock %}
{% block Add_Body_Attribute %} style="padding-top:70px;"{% endblock %}
{% block Description %}Оконные профили {{ CATALOG_MANUFACT }}{% endblock %}
{% block Description %}Оконные профили {{ CATALOG_MANUFACT }} ({% for i in PROFILES %}{% if forloop.first %}{{ i.PROFILE_NAME }}{% else %}{% if not forloop.last %}, {{ i.PROFILE_NAME }}{% else %} и {{ i.PROFILE_NAME }}{% endif %}{% endif %}{% endfor %}){% endblock %}
{% block Keywords %}{{ CATALOG_MANUFACT }}, оконные профили {{ CATALOG_MANUFACT }}, производитель {{ CATALOG_MANUFACT }}, каталог оконных профилей, каталог производителей оконных профилей, каталог профилей, оконные профили, oknardia, окнардия {{ META_KEYWORDS|default:"" }} {% endblock %}
{% block Keywords %}{{ CATALOG_MANUFACT }}, оконные профили {{ CATALOG_MANUFACT }}, производитель {{ CATALOG_MANUFACT }}, {% for i in PROFILES %}{{ i.PROFILE_NAME }}, {% endfor %}каталог оконных профилей, каталог производителей оконных профилей, каталог профилей, оконные профили, oknardia, окнардия {{ META_KEYWORDS|default:"" }} {% endblock %}
{% block Date4Meta %}{{ PUB_DAT|date:"c" }}{% endblock %}
@@ -1,11 +1,11 @@
{% extends "base.html" %}
{% load static %}
{% block Title %} Каталог :: Оконные профили {{ CATALOG_MODEL.sProfileName }}{% endblock %}
{% block Title %}Оконный профиль {{ CATALOG_MODEL.sProfileName }} : Каталог{% endblock %}
{% block Add_Body_Attribute %} style="padding-top:70px;"{% endblock %}
{% block Description %}Описание и характеристики оконного профиля {{ CATALOG_MODEL.sProfileName }} производства {{ CATALOG_MODEL.sProfileManufacturer }}{% endblock %}
{% block Description %}Характеристики оконного профиля {{ CATALOG_MODEL.sProfileName }} производства {{ CATALOG_MODEL.sProfileManufacturer }}{% endblock %}
{% block Keywords %}оконный профиль {{ CATALOG_MODEL.sProfileName }}, характеристики профиля {{ CATALOG_MODEL.sProfileName }}, описание профиля {{ CATALOG_MODEL.sProfileName }}, производитель оконный профилей {{ CATALOG_MODEL.sProfileManufacturer }}, каталог оконных профилей, каталог производителей оконных профилей, каталог профилей, оконные профили, oknardia, окнардия {{ META_KEYWORDS|default:"" }} {% endblock %}
@@ -28,8 +28,8 @@
<meta itemprop="articleSection" content="Каталог «Окнардия»: оконные профили {{ CATALOG_MODEL.sProfileName }}" />
<meta itemprop="headline" content="Описание и характеристики оконных профилей {{ CATALOG_MODEL.sProfileName }} производства {{ CATALOG_MODEL.sProfileManufacturer }}" />
<meta name="news_keywords" content="{{ CATALOG_MODEL.sProfileName }}, характеристики {{ CATALOG_MODEL.sProfileName }}, описание {{ CATALOG_MODEL.sProfileName }}, оконные профили {{ CATALOG_MODEL.sProfileName }}, {{ CATALOG_MODEL.sProfileManufacturer }}, производитель {{ CATALOG_MODEL.sProfileManufacturer }}, каталог оконных профилей, каталог производителей оконных профилей, каталог профилей, оконные профили, oknardia, окнардия" />
<link rel="canonical" href="https://oknardia.ru//catalog/profile/{{ CATALOG_URL2 }}" />
<link rel="standout" href="https://oknardia.ru//catalog/profile/{{ CATALOG_URL2 }}" />
<link rel="canonical" href="https://oknardia.ru//catalog/profile/{{ CATALOG_URL2 }}/" />
<link rel="standout" href="https://oknardia.ru//catalog/profile/{{ CATALOG_URL2 }}/" />
<!-- Разметка для соц-сетей Facebook Open Graph -->
<meta property="fb:admins" name="admins" content="100000084781830" />
<meta property="fb:pages" content="276108456054163" />
@@ -1,11 +1,11 @@
{% extends "base.html" %}
{% load static %}
{% block Title %}Серии типового строительства{% endblock %}
{% block Title %}Серии типового строительства : Каталог{% endblock %}
{% block Add_Body_Attribute %} style="padding-top:70px;"{% endblock %}
{% block Description %}Серии типового строительства | Каталог «Окнардия»{% endblock %}
{% block Description %}Каталог серий зданий и типовое панельное строительство оконного агрегатора Окнардия{% endblock %}
{% block Keywords %}типовое строительство, панельные серии, серии домов, серии зданий, типовые дома, типовые здания, каталог серий типового строительства, oknardia, окнардия {{ META_KEYWORDS|default:"" }} {% endblock %}
@@ -13,9 +13,9 @@
{% block Last4Meta %}{% if PUB_DAT %}{{ PUB_DAT|date:"c" }}{% else %}{% now "c" %}{% endif %}{% endblock %}
{% block Author4Meta %}: Каталог серий домов{% endblock %}
{% block Author4Meta %}Серии домов : {% endblock %}
{% block CopyrightAuthor4Meta %}: Каталог серий домов{% endblock %}
{% block CopyrightAuthor4Meta %}Cерии домов : {% endblock %}
{% block Top_Meta1 %}{# <!-- BEGIN Дополнительные Metatags --> #}
<meta itemprop="author" content="Каталог «Окнардия»" />{% if IMG_FOR_BLOG %}
+121
View File
@@ -0,0 +1,121 @@
{% extends "base.html" %}{% load static %}
{% block Title %} Стандартные оконные проёмы типовых серий домов :: каталог{% endblock %}
{% block Add_Body_Attribute %} style="padding-top:70px;"{% endblock %}
{% block Description %}Каталог «Окнардия»: стандартные оконные проёмы типовых серий домов...{% endblock %}
{% block Keywords %}оконные проёмы, стандартные окна, стандартные оконные проемы, каталог, каталог оконных проёмов, oknardia, окнардия {{ META_KEYWORDS|default:"" }} {% endblock %}
{% block Date4Meta %}{{ PUB_DAT|date:"c" }}{% endblock %}
{% block Last4Meta %}{{ PUB_DAT|date:"c" }}{% endblock %}
{% block Author4Meta %}: Каталог «Окнардия»{% endblock %}
{% block CopyrightAuthor4Meta %}: Каталог «Окнардия»{% endblock %}
{% block Top_Meta1 %}{# <!-- BEGIN Дополнительные Metatags --> #}
<meta itemprop="author" content="Каталог «Окнардия»" />{% if IMG_FOR_BLOG %}
<meta itemprop="image" content="https://oknardia.ru/media/{{ IMG_FOR_BLOG }}" />{% else %}
<meta itemprop="image" content="https://oknardia.ru/static/img/MerDY3gpU0w.jpg" />{% endif %}
<meta itemprop="datePublished" content="{{ PUB_DAT|date:"c" }}" />
<span itemprop="publisher" itemscope itemtype="http://schema.org/Organization"><meta itemprop="name" content="Каталог «Окнардия»" /></span>
<span itemprop="author" itemscope itemtype="http://schema.org/Person"><meta itemprop="name" content="Каталог «Окнардия»" /></span>
<meta itemprop="articleSection" content="Каталог «Окнардия»" />
<meta itemprop="headline" content="Каталог «Окнардия»: стандартные оконные проёмы типовых серий домов..." />
<meta name="news_keywords" content="{{ HEADER|striptags }}" />
<link rel="canonical" href="https://oknardia.ru//catalog/" />
<link rel="standout" href="https://oknardia.ru//catalog/" />
<!-- Разметка для соц-сетей Facebook Open Graph -->
<meta property="fb:admins" name="admins" content="100000084781830" />
<meta property="fb:pages" content="276108456054163" />
<meta property="fb:app_id" content="258354027974262" />
<meta property="fb:profile_id" name="profile_id" content="https://www.facebook.com/oknardia/" />
<meta property="og:locale" content="ru_RU" />
<meta property="og:site_name" content="oknardia.ru" />
<meta property="og:url" content="https://oknardia.ru//catalog/" />
<meta property="og:type" content="article" />
<meta property="og:title" content="Каталог «Окнардия» | oknardia.ru" />
<meta property="og:description" content="Каталог «Окнардия»: стандартные оконные проёмы типовых серий домов..." />
<meta property="og:image" content="{% if IMG_FOR_BLOG %}https://oknardia.ru/media/{% endif %}{{ IMG_FOR_BLOG|default:"https://oknardia.ru/static/img/MerDY3gpU0w.jpg" }}" />
<link rel="image_src" href="{% if IMG_FOR_BLOG %}https://oknardia.ru/media/{% endif %}{{ IMG_FOR_BLOG|default:"https://oknardia.ru/static/img/MerDY3gpU0w.jpg" }}" />
<!-- Разметка для соц-сетей Twitter Card -->
<meta name="twitter:title" content="Каталог «Окнардия»: стандартные оконные проёмы типовых серий домов... | oknardia.ru"/>
<meta name="twitter:description" content="Каталог «Окнардия»: стандартные оконные проёмы типовых серий домов..." />
<meta name="twitter:card" content="summary">
<meta name="twitter:site" content="@oknardia" />
<meta name="twitter:domain" content="oknardia.ru" />
<meta property="twitter:url" content="{% if IMG_FOR_BLOG %}https://oknardia.ru/media/{% endif %}{{ IMG_FOR_BLOG|default:'https://oknardia.ru/static/img/MerDY3gpU0w.jpg' }}" />
<meta name="relap-image" content="{% if IMG_FOR_BLOG %}https://oknardia.ru/media/{% endif %}{{ IMG_FOR_BLOG|default:'https://oknardia.ru/static/img/MerDY3gpU0w.jpg' }}" />
{# <!-- END Дополнительные Metatags --> #}{% endblock %}
{% block Main_Content %}
<div class="container-fluid">
{# <!--- Хлебные крошки: НАЧАЛО --> #}<div class="row">
<div class="col-md-11 col-xs-12">
<ol class="breadcrumb">
<li><a href="/">Главная</a></li>
<li><a href="/catalog">Каталог</a></li>
<li>Оконные проёмы и балконные блоки</li>
</ol>
</div>
</div>{# <!--- Хлебные крошки: КОНЕЦ ---> #}
<dIv class="row">
{# ПЕРВЫЙ РАЗДЕЛ #}<div class="col-md-9 col-xs-8">
<h1>Стандартные оконные проёмы и&nbsp;балконные блоки</h1>
<p>Ценовая выдача «Окнардии» основана на базе стандартных оконных проёмов в типовых сериях домов. Для каждого проёма существуют рекомендованные организациями-проектировщиками схемы открывание, но партнёры «Окнардии» могут предложить свои, более расширенные или наоборот сокращенные. В таблице приведены параметры стандартных проёмов базы.</p>
</div>
{# реклама Oknardia 250x250 СБОКУ #}<div class="col-md-3 col-xs-4 float-right">{% include "ad/bannet-250x250.html" %}</div>
<div class="col-md-11 col-xs-12 catalog scrolled">
<table class="table-striped table-hover table-responsive flap-catalog">
<thead>
<tr>
<th colspan="3" class="c">Размеры (мм)</th>
<th rowspan="2" class="b c">Стандартная<br>схема<br>открывания</th>
<th colspan="2" class="c">Балконный блок</th>
<th rowspan="2">Описание окна</th>
<th rowspan="2">Типовые серии домов где встречается данный оконный проём</th>
<th rowspan="2"></th>
</tr>
<tr>
<th class="r b">Ш</th>
<th class="x b">&times;</th>
<th class="x b">В</th>
<th class="c b">Окно</th>
<th class="c b">Дверь</th>
</tr>
</thead>
<tbody>{% for i in LIST_WIN_OPENING %}
<tr>
<td data-sort="{{ i.W|stringformat:".0f" }}">{{ i.W|stringformat:".0f" }}</td>
<td>&times;</td>
<td data-sort="{{ i.H|stringformat:".0f" }}">{{ i.H|stringformat:".0f" }}</td>
<td data-sort="{{ i.FLAP_CONFIG }}"><img src="{% static i.URL2IMG %}" title="{{ i.DESCRIPTION_L }}, {{ i.W|stringformat:".0f" }}×{{ i.H|stringformat:".0f" }} мм." alt="{{ i.DESCRIPTION_L }}, {{ i.W|stringformat:".0f" }}×{{ i.H|stringformat:".0f" }} мм." /></td>
<td data-sort="{% if i.IS_NEAR_DOOR %}1{% else %}0{% endif %}">{% if i.IS_NEAR_DOOR %}да{% else %}—{% endif %}</td>
<td data-sort="{% if i.IS_DOOR %}1{% else %}0{% endif %}">{% if i.IS_DOOR %}да{% else %}—{% endif %}</td>
<td>{{ i.DESCRIPTION }}</td>
<td>{% for j in i.INCLUDING_IN_SERIA %}<a href="/catalog/seria/{{ j.NAME_T }}/all{{ j.ID }}">{{ j.NAME }}</a>{% if not forloop.last %}, {% endif %}{% endfor %}</td>
<td><a class="btn btn-default btn-xs" href="/tsena-odnogo-okna/{{ i.W|stringformat:".0f" }}x{{ i.H|stringformat:".0f" }}mm/tip{{ i.ID }}">цены</a></td>
{# {% for j in SERIAS %}#}
{# <td>{% if j.id in i.INCLUDING_IN_SERIA %}#{% endif %}</td>{% endfor %}#}
</tr>{% endfor %}
</tbody>
</table>
</div>
<DIV class="col-xs-12" style="height:6em;"></DIV>
</dIv>
{# --- Баннер: НАЧАЛО --- #}
<div class="row"><div class="col-md-12 col-xs-12"><hr class="dotted-black" />{% include "ad/bannet-wide.html" %}</div></div>
{# --- Баннер: конец --- #}
<div class="row">
{% include "report/report_last_user_visit.html" %}
{% include "report/report_log_user_visit.html" %}
</div>
</div>{% endblock %}
+1 -1
View File
@@ -44,7 +44,7 @@ function init () {
<div id="box" class="col-md-9 col-md-offset-2"><!--- Сюда подгружаем центральный popup --->
{% if not CONFIRM_OK %}
{% include "popup_index.html" %}
{% include "popup/popup_index.html" %}
{% elif CONFIRM_OK == "YES" %}
{% include "user_manager/popup_cofirm_email_ok.html" %}
{% elif CONFIRM_OK == "NO" %}
+26
View File
@@ -0,0 +1,26 @@
{% load static %}
<script type="text/javascript">
if ( document.getElementById("box") == null ) window.location.replace("/"); // проверяем, а не вызвали-ли этот popup отдельно
myMap.setCenter([{{ LONGITUDE|default:"37.77" }},{{ LATITUDE|default:"55.68" }}]);
$(document).ready(function(){
setTimeout("window.location.href='/'", 50000);
});
</script>
<h1>Ошибка адреса</h1>
<div>
<p>Это могло произойти по следующим причинам:</p>
<ul>
<li><b>Ошибка при вводе адреса <i>«{{ addr }}»</i>.</b><br />
Если вы&nbsp;ошиблись при указании адреса <a href="/">попробуйте ввести адрес ещё раз</a> или
<a href="/stat_all/#s_map">найдите его на карте типовых серий</a> жилого строительства.<br>&nbsp;</li>
<li><b>Адрес <i>«{{ addr }}»</i> отсутствует в&nbsp;нашей базе.</b><br />
Приносим извинения, но&nbsp;пока нами обработаны только типовые серии Москвы и&nbsp;Подмосковья, Санкт-Петербурга
и&nbsp;Ленинградской области.<br />
Свяжитесь с&nbsp;нами чтобы мы&nbsp;ускорили оцифровку и&nbsp;поиск партнеров в&nbsp;вашем&nbsp;регионе.</li>
</ul>
<div style="float:right;font-size:xx-small">TICKs: {{ ticks|floatformat:4 }}</div>
</div>
@@ -44,12 +44,12 @@
$('.show_delayed').fade_obj();
});
</script>
<h1>Выбирайте окна в квартиру<!-- ({{ NV }})-->:</h1>
{% if NV <= 1 %}
<h1>Узнайте стоимость установки окон в&nbsp;квартиру от&nbsp;разных поставщиков<!-- ({{ NV }})-->:</h1>
{% if NV <= 3 %}
<ol>
<li class="show_delayed">Вводите адрес дома или выберите его из подсказки,</li>
<li class="show_delayed">укажите тип квартиры,</li>
<li class="show_delayed">получите актуальные цены и сравнивайте характеристики предложений.</li>
<li class="show_delayed">Введите адрес дома (или выберите адрес из&nbsp;подсказки),</li>
<li class="show_delayed">укажите тип планировки квартиры</li>
<li class="show_delayed">и получите актуальные цены, сравнивайте характеристики предложений и&nbsp;условия установки.</li>
</ol>
{% endif %}
<form class="show_delayed" method="post" id="input_address">
@@ -0,0 +1,71 @@
{% load static %}{% load filters %}
<script type="text/javascript">
if (document.getElementById("box") == null) window.location.replace("/"); // проверяем, а не вызвали-ли этот popup отдельно
myMap.setCenter([{{ LONGITUDE|default:"37.77" }}, {{ LATITUDE|default:"55.68" }}], 15);
$(document).ready(function () {
$(".collapse").on("shown.bs.collapse", function () {
$("#box").css({
"top": "15%"
});
});
$(".collapse").on("hidden.bs.collapse", function () {
$("#box").css({
"top": "20%"
});
});
});
</script>
<div class="panel-heading">
<button type="button" class="close" data-dismiss="modal" onmouseout="window.location.href='/';"><span aria-hidden="true">&times;</span><span class="sr-only">Неверный адрес</span></button>
Здание по адресу: {{ addr }} <span class="text-muted" style="font-size:x-small;">(id:{{ ADDRESS_ID }})</span>
</div>
{% if LIST_APART %}<h1>Укажите типовую планировку вашей квартиры:</h1>
<div class="btn-toolbar" style="width:80%">{% for I_APART in LIST_APART %}
<button type="button" class="btn btn-default" style="margin:1ex"
accesskey="{{ I_APART.id }}"
onclick="window.location.href='/{{ ADDRESS_ID }}/{{ I_APART.id }}/{{ addr_T }}';try{yaCounter32997984.reachGoal('CHOICE_APP');}catch(e){}">
{{ I_APART.sNameApartment|safe }}
</button>{% endfor %}
</div>
{% else %}
<script type="text/javascript">
function uClick( i ) {
try {
yaCounter32997984.reachGoal('FIND');
ga('send', 'event', 'CLICK', 'ClickSearch', 'SearchAddress');
_tmr.push({id: '2018432', type: 'reachGoal', goal: 'ClcSrh'});
}
catch (e) {
}
$.ajax({
url: "/get_address", //Адрес подгружаемой страницы
type: "POST", //Тип запроса
dataType: "html", //Тип данных
data: "csrfmiddlewaretoken={{ csrf_token }}&address="+$('#a'+i).html(),
success: function (html) {
$("#box").html(html)
}
});
return false;
}
</script>
<h1>Нет данных по этому дому</h1>
<div style="width:80%">
<p>К&nbsp;сожалению база &laquo;Окнардии&raquo; только формируется. Пока у&nbsp;нас нет данных по&nbsp;типовым квартирам для дома по этому адресу. Ближащие адреса, по которым есть информация:</p>
<ul style="list-style:none;">{% for i in NEAR_KNOWN_ADDRESS %}
<li>
<!-- Расстояние до дома -->
<nobr class="badge badge4price" title="Удалённость — {{ i.R2|stringformat:"0.2f" }} км."><b class="glyphicon glyphicon-map-marker"></b> {{ i.R2|stringformat:"0.2f"|price_format }}&thinsp;км.</nobr> &nbsp; <a href="#" onclick="uClick({{ forloop.counter0 }})" id="a{{ forloop.counter0 }}">{{ i.sAddress }}</a>
</li>
{% endfor %}</ul>
<p>С небольшой погрешностью (±6%) мы&nbsp;можем дать ценовые предложения под ваши требования, но потребуется самостоятельный замер проёмов.</p>
<button type="button" class="btn btn-default" style="margin:1ex"
accesskey="1" onclick="window.location.href='/order_by_dim'">
Подобрать предложения по&nbsp;предварительным замерам <span class="glyphicon glyphicon-search"></span>
</button>
<p style="font-size:xx-small;width:80%"><small>Сделав замер, и сообщив его нам, вы&nbsp;поможете наполнению базы &laquo;Окнардии&raquo;, вашим соседям и&nbsp;сотням людей проживающих в&nbsp;домах таких&nbsp;же серий или проектов. В&nbsp;благодарность за&nbsp;содействие мы&nbsp;подготовим и&nbsp;вышлем самые актуальные предложения по&nbsp;email, а&nbsp;также предоставим купон, предоставляющий скидку на&nbsp;сумму <b>500&nbsp;рублей</b> при установке окон любым из&nbsp;наших партнёров. Если согласны <a href="/send_order_and_info">перейдите по&nbsp;ссылке</small>:<br /></a>.</p>
</div>{% endif %}
<a class="text-muted" data-toggle="collapse" href="#collapse_it" onclick="yaCounter32997984.reachGoal('ClickBuildingDetails');" style="float:right;margin-bottom:1ex;">Подробная информация по зданию <small class="glyphicon-chevron-down glyphicon"></small></a>
{% include "report/build_info_in_table.html" %}
+207
View File
@@ -0,0 +1,207 @@
{% extends "base.html" %}
{% load static %}
{% load filters %}
{% block Title %}Цены на окна для {{ ADDRESS }} — дом серии {{ APART|safe }}{% endblock %}
{% block Add_Body_Attribute %} style="padding-top:70px;"{% endblock %}
{% block Date4Meta %}{{ META_DATA_PUBLISH|date:"c" }}{% endblock %}
{% block Last4Meta %}{{ META_DATA_PUBLISH|date:"c" }}{% endblock %}
{% block Description %}Цены на окна для серии {{ APART|safe }} по адресу {{ ADDRESS }}. Размер окон (см.): {% for I_WIN_DIM in FLAP_DIM %}{{ I_WIN_DIM.iWinWidth|floatformat:0 }}x{{ I_WIN_DIM.iWinHight|floatformat:0 }}{% if forloop.last %}.{% else %}; {% endif %}{% endfor %} Оконные наборы: {% for CurOffer in PRICE_FRAME %}{{ CurOffer.SETS_NAME }}  {{ CurOffer.FIN_PRICE|stringformat:".0f" }} рублей{% if forloop.last %}.{% else %}; {% endif %}{% endfor %}{% endblock %}
{% comment %}{% block Description %}Цены на плаcтиковые окна для серии {{ BASE_SERIA }} ({{ APART }} квартира, {{ ADDRESS }}) :: {% for CurOffer in PRICE_FRAME %}Поставщик: {{ CurOffer.MERCHANT }}; Комплектация: {{ CurOffer.SETS_NAME }}; Цена: {{ CurOffer.FIN_PRICE }}₽ :: {% endfor %}{% endblock %}{% endcomment %}
{% block Keywords %}цены окон, серия {{ BASE_SERIA }}, {{ BASE_SERIA }}, стоимость окон, окна для {{ BASE_SERIA }}, размеры окон, проемы серии {{ BASE_SERIA }}, окна в {{ APART|safe }}, скидки на окна, {{ ADDRESS }}, оконный профиль, {% for CurOffer in PRICE_FRAME %}{{ CurOffer.MERCHANT }}, {{ CurOffer.PVC_NAME }}, {{ CurOffer.PVC_MANUFACTURER }}, {{ CurOffer.GLAZING_MARK }}, {% endfor %} характеристики пластиковых окон, {% for I_WIN_DIM in FLAP_DIM %}{{ I_WIN_DIM.iWinWidth|floatformat:0 }}x{{ I_WIN_DIM.iWinHight|floatformat:0 }} см., {% endfor %}{{ META_KEYWORDS|default:"" }}{% endblock %}
{% block Top_JS3%}<script type="text/javascript">
function show_phone_num( id ){ // колапсатор для отображения контатной информации постафшика окон
$('#tel'+id).collapse('show');
$('#hid'+id).collapse('hide');
try {
yaCounter32997984.reachGoal('ShowPhone')
} catch (e) {
}
try {
var _tmr = window._tmr || (window._tmr = []);
_tmr.push({id: "2018432", type: "reachGoal", goal: "ViewPhone"});
} catch (e) {
}
}
function show_dtl( id ){ // колапсаторы для отобращения детальной информации оконого набора
$('#dtl'+id).collapse('show');
$('#btn'+id+' a .glyphicon-chevron-down').remove();
var ContentInA = $('#btn'+id+' a').text();
$('#btn'+id).empty();
$('#btn'+id).text(ContentInA);
try {
yaCounter32997984.reachGoal('ShowDetails')
} catch (e) {
}
try {
var _tmr = window._tmr || (window._tmr = []);
_tmr.push({id: "2018432", type: "reachGoal", goal: "ViewDetails"});
} catch (e) {
}
}
$(window).load( // уменьшение картинок .half для ретина-дисплеев
function(){var images = $('.half');images.each(function(i){$(this).width($(this).width()/2);});}
);
var count = 0;
var ForCompare = document.getElementsByName('ForCompare');
$(window).load( // подсчитать сколько прочеканных оферов для сравнения (нужно, когда на страничку возращаются через back, ее подгрущают или она приходит из кеша
function(){
for (var i=0; i < ForCompare.length; i++)
if (ForCompare[i].checked)
count++;
// alert(count);
$('#NumberOfSelected').html(count)
}
);
var ToURL = '';
function Click2CompareOffers(){ // вызывается при клике "сравнить коммерческие предложения"
try{yaCounter32997984.reachGoal('CompareOffers')}catch(e){}
//ForCompare = $('[name]=ForCompare');
for (var i=0; i < ForCompare.length; i++)
if (ForCompare[i].checked)
ToURL += ForCompare[i].value +',';
if (ToURL.match(/[,]/g).length > 1 && ToURL.match(/[,]/g).length < 7 )
location.href = '/compare_offers/' + ToURL.slice(0,-1);
else {
$('#modal-exclamation').modal('show');
ToURL = '';
}
}
function ChangeCountCheckedBox(CHK_ID){ // изменение числа выбранных для сравнения оферов
if ($("#CHK"+CHK_ID).is(":checked")) count++;
else count--;
$("#NumberOfSelected").html(count)
}
function get_more_info_about_building() {
try{yaCounter32997984.reachGoal("ClickBuildingDetails");}catch(e){}
}
$(function () { // инициализация и обработка поповеров рейтинга
$('[data-toggle="popover"]').popover({
placement: "top",
html: true,
content: function () {
// сохранить текущий контекст
var _this = this;
$('#shadow_buffer').load('/show_rating_components/' + $(_this).attr("id-set"), function (response, status, xhr) {
if (status == "error")
$(_this).attr({'data-content': "Ошибка " + xhr.status + " (" + xhr.statusText + ")"});
else
$(_this).attr({'data-content': response});
$(_this).popover('show');
try{yaCounter32997984.reachGoal("ShowRating");}catch(e){};
});
}
})
})
</script>{% endblock %}
{% block Top_CSS1 %}<link rel="stylesheet" type="text/css" href="{% static "css/csshake-vertical.min.css" %}">{% endblock %}
{% block Main_Content %}
<span itemscope itemtype="http://schema.org/Product">
<div class="row col-md-12">
<div class="col-md-9">
<h1>Цены на окна для серии {{ APART|safe }} <small>({{ ADDRESS }})</small></h1>
</div>
<div class="col-md-9">
<p>Квартира имеет проёмы (окна и балконные двери) следующих размеров: {% for I_WIN_DIM in FLAP_DIM %}{% if not forloop.first %}{% if forloop.last %} и&nbsp;{% else %}, {% endif %}{% endif %}{{ I_WIN_DIM.iWinWidth|floatformat:0 }}x{{ I_WIN_DIM.iWinHight|floatformat:0 }}&nbsp;см. —&nbsp;{{ I_WIN_DIM.iQuantity }}&nbsp;шт.{% endfor %} Проект (<a href="/catalog/seria/{{ BASE_SERIA_LAT }}/all{{ BASE_SERIA_ID }}">типовая серия {{ BASE_SERIA }}</a>) предполагает следующие схемы открывания окон:</p>
</div>
{# Микроразмектка: названеи продукта #}<meta itemprop="name" content="Окна {{ APART|safe }} ({{ ADDRESS }})" />
</div>
<div class="row col-md-12 ShowBigFlapPictures">
<div class="col-sm-9">
{% include 'report/show_big_flap_pictures.html' %}
</div>
<div class="col-sm-3 visible-md visible-lg ap_list">
<h6>Другие типовые квартиры в&nbsp;этом доме:</h6>
<ul>{% for I_APART in APARTMENT_IN_BUILDING %}
{% if I_APART.APT_ID == '!' %}<li>{{ I_APART.APT_NAME|safe }}</li>{% else %}<li><a href="/{{ BUILD_ID }}/{{ I_APART.APT_ID }}/{{ ADDRESS_T }}">{{ I_APART.APT_NAME|safe }}</a></li>{% endif %}{% endfor %}
</ul>
<a href="/catalog/seria/{{ BASE_SERIA_LAT }}/all{{ BASE_SERIA_ID }}">Информация по серии {{ BASE_SERIA }}</a>
</div>
</div>
<div class="row col-md-12">
<div class="col-md-12">
<p id="tab-note">Таблица содержит цены поставщиков. Клик на&nbsp;название отобразит детальные спецификации каждого предложения: марку профиля рамы и&nbsp;створки, схему стеклопакета, тип фурнитуры, элементы отделки (отлив, подоконник, откос, клапан <nobr>климат-контроля</nobr>) и&nbsp;сопутствующие услуги. Предложения выводятся покадрово, получите следующий кадр кнопкой &laquo;Ещё коммерческие предложения окон&raquo; под таблицей. Просмотреть и&nbsp;сравнить технические характеристик стеклопакетов, профилей и&nbsp;детальное описание сопутствующих услуг возможно с&nbsp;помощью кнопки &laquo;Сравнить выбранные&raquo;.</p>
</div>
{# Микроразмектка: названеи продукта #}
<meta itemprop="name" content="Окна {{ APART|safe }} ({{ ADDRESS }})"/>
</div>
<form class="col-md-12" method="get" action="\" id="compare_offers">
{% csrf_token %}
<table class="table" id="price-list">
<thead>
<tr>
<th><b class="glyphicon glyphicon-th-list" title="Добавление набора окон в список сравнения" aria-label="Добавление набора окон в список сравнения"></b></th>
<th>Поставщик окон и название оконного набора<small><br />
кликните чтобы увидеть описание и характеристики предложения</small>
</th>
<th colspan="2">Схема<small><br />
открывания</small></th>
<th>Цена,<b class="glyphicon glyphicon-ruble" aria-label="₽ (руб.)" title="₽ (руб.)"></b><small><br />
одно окно</small></th>
<th>N</th>
<th colspan="2" class="hidden-xs hidden-sm">Cумма,<b class="glyphicon glyphicon-ruble" aria-label="₽ (руб.)" title="₽ (руб.)"></b><small><br />
однотипные окна</small></th>
<th>Стоимость, <b class="glyphicon glyphicon-ruble" aria-label="₽ (руб.)" title="₽ (руб.)"></b><small><br />
все окна квартиры</small></th>
<th>Скидка</th>
<th>Итого<small><br />
за все окна с учетом скидки</small></th>
</tr>
</thead>
<tbody id="offers">
{% include "price/price_list_frame.html" %}
</tbody>
</table>
</form>
</span>
{% with SERIA_BASE=BASE_SERIA %}{% include "report/build_info_in_table.html" %}{% endwith %}
{# --- Баннер: НАЧАЛО --- #}
<div class="row"><div class="col-md-12 col-xs-12"><hr class="dotted-black" />{% include "ad/bannet-wide.html" %}</div></div>
{# --- Баннер: конец --- #}
<div class="row">
{% include "report/report_last_user_visit.html" %}
{% include "report/report_log_user_visit.html" %}
<p id="shadow_buffer"></p>
</div>
{# модальное окно #}
<div class="modal fade bs-example-modal-sm" id="modal-exclamation" tabindex="-1" role="dialog">
<div class="modal-dialog modal-sm">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
<h4 class="modal-title" id="mySmallModalLabel">Внимание:</h4>
</div>
<div class="modal-body">
<p>Извините, для сравнения коммерческих предложений окон нужно выбрать не менее двух и не более шести позиций.</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Спасибо, все понятно.</button>
</div>
</div>
</div>
</div>
{% endblock %}
{% comment %}
{% block Top_Nav_Bar %}
{# ОТЛАДКА, ГАСИМ ВЕРХНЕЕ МЕНЮ #}
{% endblock %}
{% endcomment %}
+150
View File
@@ -0,0 +1,150 @@
{% load static %}{% load filters %}
{% for CurOffer in PRICE_FRAME %}
<tr>
<td colspan="11" style="font-size:xx-small;border-top:double black;border-bottom: solid 1px white;padding: 0;"></td>
</tr>{% for CurInOffer in CurOffer.DIM %}
<tr>
{% if forloop.first %}
<th rowspan="{% if CurOffer.DIM|length == 1 %}2{% else %}{{ CurOffer.DIM|length }}{% endif %}" title="Добавить коммерческое предложение окон к сравнению">{# красивые чекбоксы BEGIN #}<div class="checkbox"><label><input id="CHK{{ CurOffer.SETS_ID }}" type="checkbox" name="ForCompare" value="{{ CurOffer.SETS_ID }}" onChange="ChangeCountCheckedBox({{ CurOffer.SETS_ID }});" /><span class="cr"><i class="cr-icon glyphicon glyphicon-ok"></i></span></label></div>{# красивые чекбоксы END #}</th>
<td rowspan="{% if CurOffer.DIM|length == 1 %}2{% else %}{{ CurOffer.DIM|length }}{% endif %}"{% if CurOffer.IS_COMMERCIAL %} style="background-image: url(/media/{{ CurOffer.MERCHANT_LOGO }})"{% endif %} title="Краткая спецификация коммерческого предложения">
<span itemprop="description">
<h3 class="set-name shake-trigger" id="btn{{ CurOffer.SETS_ID }}"><a href="javascript://" onclick="show_dtl({{ CurOffer.SETS_ID }})">{{ CurOffer.MERCHANT }}  {{ CurOffer.SETS_NAME }}<i class="glyphicon glyphicon-chevron-down shake-vertical"></i></a></h3>
<DiV id="dtl{{ CurOffer.SETS_ID }}" class="collapse">■ Профиль: <a href="/catalog/profile/{{ CurOffer.PVC_ID }}-{{ CurOffer.PVC_MANUFACTURER_T }}/{{ CurOffer.PVC_ID }}-{{ CurOffer.PVC_NAME_T }}">{{ CurOffer.PVC_NAME|safe }}</a> (<a href="/catalog/profile/{{ CurOffer.PVC_ID }}-{{ CurOffer.PVC_MANUFACTURER_T }}">{{ CurOffer.PVC_MANUFACTURER }}</a>)
&nbsp;{{ CurOffer.GLAZING_NAME_B|safe }} <nobr>({{ CurOffer.GLAZING_MARK }})</nobr>
&nbsp;Тонирование:&nbsp;{{ CurOffer.GLAZING_TONING }}
{% if CurOffer.PVC_SEAL %}■&nbsp;Уплотнитель:&nbsp;{{ CurOffer.PVC_SEAL }}
{% endif %}{% if CurOffer.SETS_IMPLEMENT %}■&nbsp;Фурнитура:&nbsp;{{ CurOffer.SETS_IMPLEMENT|capfirst|safe }}
{% endif %}{% if CurOffer.SETS_IMPLEMENT_R %}■&nbsp;Ручки:&nbsp;{{ CurOffer.SETS_IMPLEMENT_R|capfirst|safe }}
{% endif %}{% if CurOffer.SETS_IMPLEMENT_P %}■&nbsp;Петли:&nbsp;{{ CurOffer.SETS_IMPLEMENT_P|capfirst|safe }}
{% endif %}{% if CurOffer.SETS_IMPLEMENT_Z %}■&nbsp;Запоры:&nbsp;{{ CurOffer.SETS_IMPLEMENT_Z|capfirst|safe }}
{% endif %}{% if CurOffer.SETS_IMPLEMENT_O %}■&nbsp;Ограничители:&nbsp;{{ CurOffer.SETS_IMPLEMENT_O|capfirst|safe }}
{% endif %}{% if CurOffer.SETS_IMPLEMENT_F %}■&nbsp;Фиксаторы:&nbsp;{{ CurOffer.SETS_IMPLEMENT_F|capfirst|safe }}
{% endif %}{% if CurOffer.SETS_CLIMATE_CONTROL %}<span {% if CurOffer.SETS_CLIMATE_CONTROL|capfirst != "Нет" or CurOffer.SETS_CLIMATE_CONTROL|length > 3 %}class="bullet-green"{% endif %}>■&nbsp;Климат-контроль:&nbsp;{{ CurOffer.SETS_CLIMATE_CONTROL|capfirst|safe }}</span>
{% endif %}{% if CurOffer.SETS_SILL %}<span {% if CurOffer.SETS_SILL|capfirst == "Нет" or CurOffer.SETS_SILL|length < 3 %}class="bullet-red"{% endif %}>&nbsp;Подоконник:&nbsp;{{ CurOffer.SETS_SILL|capfirst|safe }}</span>
{% endif %}{% if CurOffer.SETS_SLOPE %}<span {% if CurOffer.SETS_SLOPE|capfirst == "Нет" or CurOffer.SETS_SLOPE|length < 3 %}class="bullet-red"{% endif %}>&nbsp;Откос:&nbsp;{{ CurOffer.SETS_SLOPE|capfirst|safe }}</span>
{% endif %}{% if CurOffer.SETS_PANES %}<span {% if CurOffer.SETS_PANES|capfirst == "Нет" or CurOffer.SETS_PANES|length < 3 %}class="bullet-red"{% endif %}>&nbsp;Водоотлив:&nbsp;{{ CurOffer.SETS_PANES|capfirst|safe }}</span>
{% endif %}{% if CurOffer.SETS_UNINSTALL_INSTALL %}<span {% if CurOffer.SETS_UNINSTALL_INSTALL_B %}class="bullet-green"{% else %}class="bullet-red"{% endif %}>&nbsp;Демонтаж/монтаж:&nbsp;{{ CurOffer.SETS_UNINSTALL_INSTALL|capfirst|safe }}</span>
{% endif %}{% if CurOffer.SETS_DELIVERY %}<span {% if CurOffer.SETS_DELIVERY_B %}class="bullet-green"{% else %}class="bullet-red"{% endif %}>&nbsp;Доставка:&nbsp;{{ CurOffer.SETS_DELIVERY|capfirst|safe }}</span>{% endif %}{% comment %} {% if CurOffer.SETS_OTHER %}■&nbsp;Прочие условия:&nbsp;{{ CurOffer.SETS_OTHER|capfirst|safe }}
{% endif %}{% endcomment %}
</DiV>
<!-- Расстояние до офиса поставщика -->
<nobr>{% if CurOffer.DISTANCE != -1 %}<nobr class="badge badge4price" title="Удалённость офиса — {{ CurOffer.DISTANCE|stringformat:"0.1f" }} км."><b class="glyphicon glyphicon-map-marker"></b> {{ CurOffer.DISTANCE|stringformat:"0.1f"|price_format }}&thinsp;км.</nobr>{% endif %}</nobr>
<!-- Дата обновления -->
<nobr class="badge badge4price" title="Дата обновления коммерческого предложения окон — {{ CurOffer.SETS_DATA_MODIFY|date:"d.M.Y" }}"><b class="glyphicon glyphicon-calendar"></b> {{ CurOffer.SETS_DATA_MODIFY|date:"d.M.Y" }}</nobr>
<!-- Звездочки рейтинга -->
<nobr class="badge badge4price" title="Рейтинг «Окнардии»{% if CurOffer.SETS_RATING > -0.1 %} — {{ CurOffer.SETS_RATING|stringformat:".2f" }} баллов{% endif %}"><a
href="javascript://"
id-set="{{ CurOffer.SETS_ID }}"
data-trigger="focus" tabindex="0"
title="{% if CurOffer.SETS_RATING > 0.01 %}<b> Рейтинг {{ CurOffer.SETS_RATING|stringformat:".2f" }}</b> для оконого набора «{{ CurOffer.SETS_NAME }}» компании «{{ CurOffer.MERCHANT }}» состоит&nbsp;из:{% else %}Рейтинг не присвоен{% endif %}"
data-toggle="popover">рейтинг</a>:&nbsp;{% for Star in CurOffer.SETS_RATING_STARTS %}{% if Star == 0 %}<b class="glyphicon glyphicon-star-empty"></b>{% else %}<b class="glyphicon glyphicon-star"></b>{% endif %}{% endfor %} {% if CurOffer.SETS_RATING > -0.1 %} {{ CurOffer.SETS_RATING|stringformat:".2f" }}{% endif %}</nobr>
</span>
<span itemprop="brand" itemscope itemtype="http://schema.org/Brand">
<meta itemprop="name" content="{{ CurOffer.MERCHANT }}" />
<meta itemprop="logo" content="https://oknardia.ru/media/{{ CurOffer.MERCHANT_LOGO }}" />
</span></td>
<!--- Конец большой ячейки со спецификацией оконного предложения --->
{% endif %}
<td class="rnw" title="{{ CurInOffer.QUANTITY }} × {{ CurInOffer.DESCRIPTION }} — {{ CurInOffer.WIDTH|stringformat:".0f" }}0x{{ CurInOffer.HIGHT|stringformat:".0f" }}0 мм.">{% for BUL in CurInOffer.BULLET %}<span class="color-bullet" style="background-image:url({% static "img/svg/mark" %}{{ BUL }}.svg);"></span>{% endfor %}</td>
<td class="rnw" title="Схема открывания окна или двери {{ CurInOffer.WIDTH|stringformat:".0f" }}0x{{ CurInOffer.HIGHT|stringformat:".0f" }}0 мм."><img src="{% static CurInOffer.IMG_MINI %}" alt="Схема открывания: {{ CurInOffer.DESCRIPTION }} — {{ CurInOffer.WIDTH|stringformat:".0f" }}0x{{ CurInOffer.HIGHT|stringformat:".0f" }}0 мм." /></td>
<td class="rnw" title="Цена {{ CurInOffer.PRICE|stringformat:".2f" }} рублей.">{{ CurInOffer.PRICE|stringformat:".2f"|price_format }}</td>
<td title="Количество: {{ CurInOffer.QUANTITY }} шт.">×{{ CurInOffer.QUANTITY }}</td>
<td class="rnw hidden-xs hidden-sm">=</td>
<td class="rnw hidden-xs hidden-sm" title="Сумма {{ CurInOffer.SUBTOTAL|stringformat:".2f" }} рублей за {{ CurInOffer.QUANTITY }} шт.">{{ CurInOffer.SUBTOTAL|stringformat:".2f"|price_format }}</td>
{% if forloop.first %}<!-- FIRST -->
<td class="rnw" title="Стоимость {{ CurOffer.TOTAL|stringformat:".2f" }} рублей за все окна квартиры {{ APART|safe }}.">{{ CurOffer.TOTAL|stringformat:".2f"|price_format }}</td>
<th{% if CurOffer.DISCOUNT_COLOR2 != "" %} style="background-color:{{ CurOffer.DISCOUNT_COLOR2 }};"{% endif %} title="{% if CurOffer.DISCOUNT < 0.1 %}Нет скидки{% else %}Скидка — {{ CurOffer.DISCOUNT|stringformat:".1f" }}%{% endif %}">{% if CurOffer.DISCOUNT < 0.1 %}{% else %}&minus;{{ CurOffer.DISCOUNT|stringformat:".1f" }}%{% endif %}</th>
<th{% if CurOffer.DISCOUNT_COLOR1 != "" %} style="background-color:{{ CurOffer.DISCOUNT_COLOR1 }};"{% endif %} itemprop="offers" itemscope itemtype="http://schema.org/Offer" title="Итого за все окна с учетом скидки: {{ CurOffer.FIN_PRICE|stringformat:".2f" }} рублей">
Итого: {{ CurOffer.FIN_PRICE|stringformat:".2f"|price_format }}&thinsp;<small class="glyphicon glyphicon-ruble" aria-label="₽ (руб.)" title="₽ (руб.)"></small>
<meta itemprop="price" content="{{ CurOffer.FIN_PRICE }}" />
<meta itemprop="priceCurrency" content="RUB" />
</th>
{% if CurOffer.DIM|length == 1 %}
</tr><tr>
<td colspan="6">&nbsp;</td>
<td colspan="3">
{% endif %}
{% elif forloop.counter0 == 1 %}
<td colspan="3" rowspan="{{ CurOffer.DIM|length|add:"-1" }}">
{% endif %}
{% if forloop.counter0 == 1 or CurOffer.DIM|length == 1 %}
<stong>{{ CurOffer.OFFICE_NAME }}</stong><br />
<div id="hid{{ CurOffer.SETS_ID }}" class="collapse in">
<a class="btn btn-info btn-xs shake-trigger" data-toggle="collapse" data-target="#tel{{ CurOffer.SETS_ID }}" onclick="show_phone_num({{ CurOffer.SETS_ID }})"><b class="glyphicon glyphicon-phone-alt shake-vertical"></b> Показать телефон</a>
</div>
<div id="tel{{ CurOffer.SETS_ID }}" class="collapse">
<small>{{ CurOffer.OFFICE_ADDRESS }}</small><br /> <b class="glyphicon glyphicon-phone-alt"></b> {{ CurOffer.OFFICE_PHONES }}<br />
<small><nobr>Пожалуйста, скажите, что нашли цены на oknardia.ru</nobr></small>
</div>
</td>
{% endif %}
</tr>{% endfor %}<!----- ПРОВЕРКА --->{% endfor %}
<Tr id="preloader_{{ N }}">
<tH colspan="11"><center><img src="{% static "img/preloader.gif" %}" alt="подгружаю цены на окна..." height="64" width="64" /></center></tH>
</Tr>
<tR id="load_and_banner_{{ N }}">
<!-- сюда подгружаем следующий фрейм прайслиста --->
<script type="application/javascript">
function get_next_pricelist_frame( ) {
try{yaCounter32997984.reachGoal("MoreOffer");}catch(e){}
// заменяем ряд с кнопочками кнопочками на рекламу
// $("#load_and_banner_{{ N }}").html("<th colspan=10>РЕКЛАМА TMP</th>");
// добавляем ячейки с дополнительными ценами и новые кнопочки
$("#load_and_banner_{{ N }}").css({"display":"none"});
$("#preloader_{{ N }}").css({"display":"table-row","transition":"1s"});// подсчитать сколько прочеканных оферов для сравнения
$("#tmp").load(
"/next_price_frame/idA{{ APPARTMENT_ID }}MDPO{{ MOUNT_DIM_PER_OFFER }}LON0LAT0N{{ N }}",
function (){
window.frame = $("#tmp").html();
$("#price-list > tbody").append(window.frame);
}
);
window.setTimeout(
function show_adv() {
$("#load_and_banner_{{ N }}").html("<td colspan='11' bgcolor='#CCCCCC' align='center'><center><a href='https://api.ozon.ru/partner-tools.affiliates/pankarta/11052421746600/link?partner=e-serg&&utm_content=banner&width=728&height=90' target='_blank'><img src='https://api.ozon.ru/partner-tools.affiliates/pankarta/11052421746600/image?partner=e-serg&utm_content=banner&width=728&height=90' width='728px' height='90px'></a><small><br/>↑ Здесь могла бы быть ваша реклама ↑</small></center></td>");
$("#load_and_banner_{{ N }}").css({"display":"table-row","transition":"all 5s"});
$("#preloader_{{ N }}").css({"display":"none","transition":"1s"});
// (adsbygoogle=window.adsbygoogle || []).push({});
window.count = 0;
for (var i=0; i < ForCompare.length; i++)
if (ForCompare[i].checked)
window.count++;
$("#NumberOfSelected").html(window.count);
$('[data-toggle="popover"]').popover({ // реинициализировать подгружаемые поповеры
placement: "top",
html: true,
content: function () {
// сохранить текущий контекст
var _this = this;
$('#shadow_buffer').load('/show_rating_components/' + $(_this).attr("id-set"),
function (response, status, xhr) {
if (status == "error")
$(_this).attr({'data-content': "Ошибка " + xhr.status + " (" + xhr.statusText + ")"});
else
$(_this).attr({'data-content': response});
$(_this).popover('show');
try{yaCounter32997984.reachGoal("ShowRating");}catch(e){};
});
}
});
},
2800);
}
</script>
<td colspan="2"><a class="btn btn-info btn-block" data-toggle="collapse" onclick="Click2CompareOffers();">Сравнить выбранные (<span id="NumberOfSelected">0</span>) <small class="glyphicon-stats glyphicon"></small></a><p id="tmp"></p></td>
<td colspan="8">{% if N != "-1" %}<a href="javascript://" class="btn btn-info btn-block" onclick="get_next_pricelist_frame();">Ещё коммерческие предложения окон &nbsp;<small class="glyphicon-refresh glyphicon"></small></a>{% endif %}</td>
<td><a data-toggle="collapse" href="#collapse_it" onclick="get_more_info_about_building()" class="shake-trigger">Подробная информация по зданию <small class="glyphicon-chevron-down glyphicon shake-vertical"></small></a></td>
</tR>
+245
View File
@@ -0,0 +1,245 @@
{% extends "base.html" %}{% load static %}
{% load filters %}
{% block Title %}Цены на типовое окно {% for I_WIN_DIM in FLAP_DIM %}{{ I_WIN_DIM.iWinWidth|floatformat:0 }}x{{ I_WIN_DIM.iWinHight|floatformat:0 }} см. для домов серий {% for I in SERIA_FOR_WIN %}{% if forloop.last %} и {% elif forloop.first %}{% else %}, {% endif %}{{ I.sName }}{% endfor %}{% endfor %}{% endblock %}
{% block Add_Body_Attribute %} style="padding-top:70px;"{% endblock %}
{% block Date4Meta %}{{ META_DATA_PUBLISH|date:"c" }}{% endblock %}
{% block Last4Meta %}{{ META_DATA_PUBLISH|date:"c" }}{% endblock %}
{% block Top_JS4 %}
<script type="text/javascript" src="//www.gstatic.com/charts/loader.js"></script>
<script type="text/javascript">
google.charts.load("current", {packages: ["corechart"]});
google.charts.setOnLoadCallback(drawChart);
function drawChart() {
var data = google.visualization.arrayToDataTable([
['Схема открывания', 'Число предложений'], {% for I in LIST_FLAP_VARIATION %}
['{{ I.STR_NUM }}', {{ I.id }}]{% if not forloop.last %}, {% endif %}{% endfor %}
]);
var options = {
legend: {position: "none"},
backgroundColor: 'none',
pieHole: 0.386,
chartArea: {width: '80%', height: '90%'},
pieStartAngle: 5,
pieSliceTextStyle: {fontSize: 12},
is3D: true,
slices: {0: {offset: 0.1}},
tooltip: {textStyle: {fontSize: 11, color: 'black', opacity: 0.8}, showColorCode: true}
};
var chart = new google.visualization.PieChart(document.getElementById('donutchart'));
chart.draw(data, options);
}
</script>{% endblock %}
{% block Description %}Цены на типовое окно {% for I_WIN_DIM in FLAP_DIM %}{{ I_WIN_DIM.iWinWidth|floatformat:0 }}x{{ I_WIN_DIM.iWinHight|floatformat:0 }} см. для домов серий {% for I in SERIA_FOR_WIN %}{% if forloop.last %} и {% elif forloop.first %}{% else %}, {% endif %}{{ I.sName }}{% endfor %}{% endfor %}.{% endblock %}
{% comment %}{% block Description %}Цены на пластиковые окна для серии {{ BASE_SERIA }} ({{ APART }} квартира, {{ ADDRESS }}) :: {% for CurOffer in PRICE_FRAME %}Поставщик: {{ CurOffer.MERCHANT }}; Комплектация: {{ CurOffer.SETS_NAME }}; Цена: {{ CurOffer.FIN_PRICE }}₽ :: {% endfor %}{% endblock %}{% endcomment %}
{% block Keywords %}цены на окно, типовое окно, {% for I_WIN_DIM in FLAP_DIM %}{{ I_WIN_DIM.iWinWidth|floatformat:0 }}x{{ I_WIN_DIM.iWinHight|floatformat:0 }}{% endfor %} cм., {% for I_WIN_DIM in FLAP_DIM %}{{ I_WIN_DIM.iWinWidth|floatformat:0 }}x{{ I_WIN_DIM.iWinHight|floatformat:0 }}{% endfor %} см., стоимость окна, {% for I in SERIA_FOR_WIN %}серия {{ I.sName }}, {% endfor %}типовой проём, {{ META_KEYWORDS|default:"" }}{% endblock %}
{% block Top_JS3%}<script type="text/javascript">
function show_phone_num( id ){ // колапсатор для отображения контатной информации постафшика окон
$('#tel'+id).collapse('show');
$('#hid'+id).collapse('hide');
try {
yaCounter32997984.reachGoal('ShowPhone')
} catch (e) {
}
try {
var _tmr = window._tmr || (window._tmr = []);
_tmr.push({id: "2018432", type: "reachGoal", goal: "ViewPhone"});
} catch (e) {
}
}
function show_dtl( id ){ // колапсаторы для отобращения детальной информации оконого набора
$('#dtl'+id).collapse('show');
$('#btn'+id+' a .glyphicon-chevron-down').remove();
var ContentInA = $('#btn'+id+' a').text();
$('#btn'+id).empty();
$('#btn'+id).text(ContentInA);
try {
yaCounter32997984.reachGoal('ShowDetails')
} catch (e) {
}
try {
var _tmr = window._tmr || (window._tmr = []);
_tmr.push({id: "2018432", type: "reachGoal", goal: "ViewDetails"});
} catch (e) {
}
}
$(window).load( // уменьшение картинок .half для ретина-дисплеев
function(){var images = $('.half');images.each(function(i){$(this).width($(this).width()/2);});}
);
var count = 0;
var ForCompare = document.getElementsByName('ForCompare');
$(window).load( // подсчитать сколько прочеканных оферов для сравнения (нужно, когда на страничку возращаются через back, ее подгрущают или она приходит из кеша
function(){
for (var i=0; i < ForCompare.length; i++)
if (ForCompare[i].checked)
count++;
// alert(count);
$('#NumberOfSelected').html(count)
}
);
var ToURL = '';
function Click2CompareOffers(){ // вызывается при клике "сравнить коммерческие предложения"
try{yaCounter32997984.reachGoal('CompareOffers')}catch(e){}
//ForCompare = $('[name]=ForCompare');
for (var i=0; i < ForCompare.length; i++)
if (ForCompare[i].checked)
ToURL += ForCompare[i].value +',';
if (ToURL.match(/[,]/g).length > 1 && ToURL.match(/[,]/g).length < 7 )
location.href = '/compare_offers/' + ToURL.slice(0,-1);
else {
$('#modal-exclamation').modal('show');
ToURL = '';
}
}
function ChangeCountCheckedBox(CHK_ID){ // изменение числа выбранных для сравнения оферов
if ($("#CHK"+CHK_ID).is(":checked")) count++;
else count--;
$("#NumberOfSelected").html(count)
}
function get_more_info_about_building() {
try{yaCounter32997984.reachGoal("ClickBuildingDetails");}catch(e){}
}
$(function () { // инициализация и обработка поповеров рейтинга
$('[data-toggle="popover"]').popover({
placement: "top",
html: true,
content: function () {
// сохранить текущий контекст
var _this = this;
$('#shadow_buffer').load('/show_rating_components/' + $(_this).attr("id-set"), function (response, status, xhr) {
if (status == "error")
$(_this).attr({'data-content': "Ошибка " + xhr.status + " (" + xhr.statusText + ")"});
else
$(_this).attr({'data-content': response});
$(_this).popover('show');
try{yaCounter32997984.reachGoal("ShowRating");}catch(e){};
});
}
})
})
</script>{% endblock %}
{% block Top_CSS1 %}<link rel="stylesheet" type="text/css" href="{% static "css/csshake-vertical.min.css" %}">{% endblock %}
{% block Main_Content %}<div class="container-fluid">
{# <!--- Хлебные крошки: НАЧАЛО --> #}<div class="row">
<div class="col-md-11 col-xs-12">
<ol class="breadcrumb">
<li><a href="/">Главная</a></li>
<li><a href="/catalog">Каталог</a></li>
<li><a href="/catalog/standard_opening/">Оконные проёмы и балконные блоки</a></li>
<li>Окно {% for I_WIN_DIM in FLAP_DIM %}{{ I_WIN_DIM.iWinWidth_mm|floatformat:0 }}&times;{{ I_WIN_DIM.iWinHight_mm|floatformat:0 }}{% endfor %}&nbsp;мм. </li>
</ol>
<h1>Стандартные оконные проёмы и&nbsp;балконные блоки</h1>
</div>
</div>{# <!--- Хлебные крошки: КОНЕЦ ---> #}
<span itemscope itemtype="http://schema.org/Product">
<div class="row">
<div class="col-md-9">
<h1>Цены на окно {% for I_WIN_DIM in FLAP_DIM %}{{ I_WIN_DIM.iWinWidth_mm|floatformat:0 }}&times;{{ I_WIN_DIM.iWinHight_mm|floatformat:0 }}{% endfor %}&nbsp;мм. <small>(типовое)</small></h1>
</div>
<div class="col-md-9">
<p>Типовой проём {% for I_WIN_DIM in FLAP_DIM %}{{ I_WIN_DIM.iWinWidth|floatformat:1 }}&times;{{ I_WIN_DIM.iWinHight|floatformat:1 }}{% endfor %}&nbsp;cм. представлен в домах серий: {% for I in SERIA_FOR_WIN %}{% if forloop.last %} и {% elif forloop.first %}{% else %}, {% endif %}<a href="/catalog/seria/{{ I.sNameLat }}/all{{ I.id }}">{{ I.sName }}</a>{% endfor %}. База «Окнардии» размещено {{ NUM_TOTAL_OFFER_N_WORD }} цен для окон в такой проем (из них в архиве {{ NUM_ARCHIVE_OFFER }}). Предложено {{ NUM_FLAP_VARIATION_IN_WORD }} открывания от {{ NUM_TOTAL_FIRM_N_WORD }}.</p>
</div>
{# Микроразмектка: названеи продукта #}<meta itemprop="name" content="Окна {{ APART|safe }} ({{ ADDRESS }})" />
</div>
<div class="row ShowBigFlapPictures">
<div class="col-sm-7 col-xs-12 ">{% include 'report/show_big_flap_pictures.html' %}</div>
<div class="col-sm-2 col-xs-7 ap_list">
<h6>Наиболее частые предложения схем открывания:</h6>
<ul>{% for I in LIST_FLAP_VARIATION %}
{% if not I.IMG_MINI == '' %}<li><img src="{% static I.IMG_MINI %}" alt="Схема открывания {{ I.sOfferFlapConfig }}" title="Схема открывания {{ I.sOfferFlapConfig }} — {{ I.id }} шт." vspace="2" /> — {{ I.STR_NUM }}</li>{% endif %}{% endfor %}
</ul>
<div id="donutchart" style="width:100%;"></div>
</div>
<div class="col-sm-3 col-xs-5 ap_list">
<h6>Типовые серии домой, в которых встречается данный типовой проём:</h6>
<ul>{% for I in SERIA_FOR_WIN %}
<li><a href="/catalog/seria/{{ I.sNameLat }}/all{{ I.id }}">{{ I.sName }}</a>
<small>входит в {{ I.num_variation_of_apartment }}</small></li>
{% endfor %}</ul>
</div>
</div>
<div class="row">
<div class="col-md-12">
<p id="tab-note">В таблице представлены только цены поставщиков из базы «Окнардия». Клик на&nbsp;названии набора отобразит детальную спецификацию каждого предложения: профиль рамы и&nbsp;створки, схему стеклопакета, фурнитуру, элементы отлива, подоконника, откоса, системы <nobr>климат-контроля</nobr>) и&nbsp;сопутствующие услуги. Предложения выводятся блоками. Очередной блок выводится кнопкой &laquo;Ещё коммерческие предложения окон&raquo; под таблицей. Детальные технические характеристики стеклопакетов, профилей и&nbsp;описание сопутствующих услуг можно посмотреть и сравнить с&nbsp;помощью кнопки &laquo;Сравнить выбранные&raquo;.</p>
</div>
{# Микроразмектка: названеи продукта #}
<meta itemprop="name" content="Окна {{ APART|safe }} ({{ ADDRESS }})"/>
</div>
<form class="row" method="get" action="\" id="compare_offers"><div class="col-md-12">
{% csrf_token %}
<table class=" table" id="price-list">
<thead>
<tr>
<th><b class="glyphicon glyphicon-th-list" title="Добавление набора окон в список сравнения" aria-label="Добавление набора окон в список сравнения"></b></th>
<th>Поставщик окон и название набора<small><br />
кликните чтобы отобразить описание и характеристики предложения</small>
</th>
<th>Схема<small><br />
открывания</small></th>
<!-- th>Цена,<b class="glyphicon glyphicon-ruble" aria-label="₽ (руб.)" title="₽ (руб.)"></b><small><br />
одно окно</small></th>
<th>N</th>
<th colspan="2" class="hidden-xs hidden-sm">Cумма,<b class="glyphicon glyphicon-ruble" aria-label="₽ (руб.)" title="₽ (руб.)"></b><small><br />
однотипные окна</small></th -->
<th style="text-align:right;">Стоимость,&nbsp;<b class="glyphicon glyphicon-ruble" aria-label="₽ (руб.)" title="₽ (руб.)"></b><small><br />
окна для типового проёма</small></th>
<th style="text-align:center;">Скидка</th>
<th>Итого<small><br />
за типовое окно с учётом скидки</small></th>
</tr>
</thead>
<tbody id="offers">
{% include "price/price_offers_for_one_window_frame.html" %}
</tbody>
</table></div>
</form></span><p id="shadow_buffer"></p>
{# --- Баннер: НАЧАЛО --- #}
<div class="row"><div class="col-md-12 col-xs-12"><hr class="dotted-black" />{% include "ad/bannet-wide.html" %}</div></div>
{# --- Баннер: конец --- #}
<div class="row">
{% include "report/report_last_user_visit.html" %}
{% include "report/report_log_user_visit.html" %}
</div>
</div>
{# модальное окно #}
<div class="modal fade bs-example-modal-sm" id="modal-exclamation" tabindex="-1" role="dialog">
<div class="modal-dialog modal-sm">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
<h4 class="modal-title" id="mySmallModalLabel">Внимание:</h4>
</div>
<div class="modal-body">
<p>Извините, для сравнения коммерческих предложений окон нужно выбрать не менее двух и не более шести позиций.</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Спасибо, все понятно.</button>
</div>
</div>
</div>
</div>
{% endblock %}
{% comment %}
{% block Top_Nav_Bar %}
{# ОТЛАДКА, ГАСИМ ВЕРХНЕЕ МЕНЮ #}
{% endblock %}
{% endcomment %}
@@ -0,0 +1,141 @@
{% load static %}{% load filters %}{% for CurOffer in PRICE_FRAME %}
<tr>
<td colspan="7" style="font-size:xx-small;border-top:double black;border-bottom: solid 1px white;padding: 0;"></td>
</tr>{% for CurInOffer in CurOffer.DIM %}
<tr>
{% if forloop.first %}
<th rowspan="{% if CurOffer.DIM|length == 1 %}2{% else %}{{ CurOffer.DIM|length }}{% endif %}" title="Добавить коммерческое предложение окна к сравнению">{# красивые чекбоксы BEGIN #}<div class="checkbox"><label><input id="CHK{{ CurOffer.SETS_ID }}" type="checkbox" name="ForCompare" value="{{ CurOffer.SETS_ID }}" onChange="ChangeCountCheckedBox({{ CurOffer.SETS_ID }});" /><span class="cr"><i class="cr-icon glyphicon glyphicon-ok"></i></span></label></div>{# красивые чекбоксы END #}</th>
<td rowspan="{% if CurOffer.DIM|length == 1 %}2{% else %}{{ CurOffer.DIM|length }}{% endif %}"{% if CurOffer.IS_COMMERCIAL %} style="background-image: url(/media/{{ CurOffer.MERCHANT_LOGO }})"{% endif %} title="Краткая спецификация коммерческого предложения">
<span itemprop="description">
<h3 class="set-name shake-trigger" id="btn{{ CurOffer.SETS_ID }}"><a href="javascript://" onclick="show_dtl({{ CurOffer.SETS_ID }})">{{ CurOffer.MERCHANT }}  {{ CurOffer.SETS_NAME }}<i class="glyphicon glyphicon-chevron-down shake-vertical"></i></a></h3>
<DiV id="dtl{{ CurOffer.SETS_ID }}" class="collapse">■ Профиль: <a href="/catalog/profile/{{ CurOffer.PVC_ID }}-{{ CurOffer.PVC_MANUFACTURER_T }}/{{ CurOffer.PVC_ID }}-{{ CurOffer.PVC_NAME_T }}">{{ CurOffer.PVC_NAME|safe }}</a> (<a href="/catalog/profile/{{ CurOffer.PVC_ID }}-{{ CurOffer.PVC_MANUFACTURER_T }}">{{ CurOffer.PVC_MANUFACTURER }}</a>)
&nbsp;{{ CurOffer.GLAZING_NAME_B|safe }} <nobr>({{ CurOffer.GLAZING_MARK }})</nobr>
&nbsp;Тонирование:&nbsp;{{ CurOffer.GLAZING_TONING }}
{% if CurOffer.PVC_SEAL %}■&nbsp;Уплотнитель:&nbsp;{{ CurOffer.PVC_SEAL }}
{% endif %}{% if CurOffer.SETS_IMPLEMENT %}■&nbsp;Фурнитура:&nbsp;{{ CurOffer.SETS_IMPLEMENT|capfirst|safe }}
{% endif %}{% if CurOffer.SETS_IMPLEMENT_R %}■&nbsp;Ручки:&nbsp;{{ CurOffer.SETS_IMPLEMENT_R|capfirst|safe }}
{% endif %}{% if CurOffer.SETS_IMPLEMENT_P %}■&nbsp;Петли:&nbsp;{{ CurOffer.SETS_IMPLEMENT_P|capfirst|safe }}
{% endif %}{% if CurOffer.SETS_IMPLEMENT_Z %}■&nbsp;Запоры:&nbsp;{{ CurOffer.SETS_IMPLEMENT_Z|capfirst|safe }}
{% endif %}{% if CurOffer.SETS_IMPLEMENT_O %}■&nbsp;Ограничители:&nbsp;{{ CurOffer.SETS_IMPLEMENT_O|capfirst|safe }}
{% endif %}{% if CurOffer.SETS_IMPLEMENT_F %}■&nbsp;Фиксаторы:&nbsp;{{ CurOffer.SETS_IMPLEMENT_F|capfirst|safe }}
{% endif %}{% if CurOffer.SETS_CLIMATE_CONTROL %}<span {% if CurOffer.SETS_CLIMATE_CONTROL|capfirst != "Нет" or CurOffer.SETS_CLIMATE_CONTROL|length > 3 %}class="bullet-green"{% endif %}>■&nbsp;Климат-контроль:&nbsp;{{ CurOffer.SETS_CLIMATE_CONTROL|capfirst|safe }}</span>
{% endif %}{% if CurOffer.SETS_SILL %}<span {% if CurOffer.SETS_SILL|capfirst == "Нет" or CurOffer.SETS_SILL|length < 3 %}class="bullet-red"{% endif %}>&nbsp;Подоконник:&nbsp;{{ CurOffer.SETS_SILL|capfirst|safe }}</span>
{% endif %}{% if CurOffer.SETS_SLOPE %}<span {% if CurOffer.SETS_SLOPE|capfirst == "Нет" or CurOffer.SETS_SLOPE|length < 3 %}class="bullet-red"{% endif %}>&nbsp;Откос:&nbsp;{{ CurOffer.SETS_SLOPE|capfirst|safe }}</span>
{% endif %}{% if CurOffer.SETS_PANES %}<span {% if CurOffer.SETS_PANES|capfirst == "Нет" or CurOffer.SETS_PANES|length < 3 %}class="bullet-red"{% endif %}>&nbsp;Водоотлив:&nbsp;{{ CurOffer.SETS_PANES|capfirst|safe }}</span>
{% endif %}{% if CurOffer.SETS_UNINSTALL_INSTALL %}<span {% if CurOffer.SETS_UNINSTALL_INSTALL_B %}class="bullet-green"{% else %}class="bullet-red"{% endif %}>&nbsp;Демонтаж/монтаж:&nbsp;{{ CurOffer.SETS_UNINSTALL_INSTALL|capfirst|safe }}</span>
{% endif %}{% if CurOffer.SETS_DELIVERY %}<span {% if CurOffer.SETS_DELIVERY_B %}class="bullet-green"{% else %}class="bullet-red"{% endif %}>&nbsp;Доставка:&nbsp;{{ CurOffer.SETS_DELIVERY|capfirst|safe }}</span>{% endif %}{% comment %} {% if CurOffer.SETS_OTHER %}■&nbsp;Прочие условия:&nbsp;{{ CurOffer.SETS_OTHER|capfirst|safe }}
{% endif %}{% endcomment %}
</DiV>
<!-- Дата обновления -->
<nobr class="badge badge4price" title="Дата обновления коммерческого предложения окон — {{ CurOffer.SETS_DATA_MODIFY|date:"d.M.Y" }}"><b class="glyphicon glyphicon-calendar"></b> {{ CurOffer.SETS_DATA_MODIFY|date:"d.M.Y" }}</nobr>
<!-- Звездочки рейтинга -->
<nobr class="badge badge4price" title="Рейтинг «Окнардии»{% if CurOffer.SETS_RATING > -0.1 %} — {{ CurOffer.SETS_RATING|stringformat:".2f" }} баллов{% endif %}"><a
href="javascript://"
id-set="{{ CurOffer.SETS_ID }}"
data-trigger="focus" tabindex="0"
title="{% if CurOffer.SETS_RATING > 0.01 %}<b> Рейтинг {{ CurOffer.SETS_RATING|stringformat:".2f" }}</b> для оконого набора «{{ CurOffer.SETS_NAME }}» компании «{{ CurOffer.MERCHANT }}» состоит&nbsp;из:{% else %}Рейтинг не присвоен{% endif %}"
data-toggle="popover">рейтинг</a>:&nbsp;{% for Star in CurOffer.SETS_RATING_STARTS %}{% if Star == 0 %}<b class="glyphicon glyphicon-star-empty"></b>{% else %}<b class="glyphicon glyphicon-star"></b>{% endif %}{% endfor %} {% if CurOffer.SETS_RATING > -0.1 %} {{ CurOffer.SETS_RATING|stringformat:".2f" }}{% endif %}</nobr>
</span>
<span itemprop="brand" itemscope itemtype="http://schema.org/Brand">
<meta itemprop="name" content="{{ CurOffer.MERCHANT }}" />
<meta itemprop="logo" content="https://oknardia.ru/media/{{ CurOffer.MERCHANT_LOGO }}" />
</span></td>
<!--- Конец большой ячейки со спецификацией оконного предложения --->
{% endif %}
<td class="rnw" title="Схема открывания окна или двери {{ CurInOffer.WIDTH|stringformat:".0f" }}0x{{ CurInOffer.HIGHT|stringformat:".0f" }}0 мм."><img src="{% static CurInOffer.IMG_MINI %}" alt="Схема открывания: {{ CurInOffer.DESCRIPTION }} — {{ CurInOffer.WIDTH|stringformat:".0f" }}0x{{ CurInOffer.HIGHT|stringformat:".0f" }}0 мм." /></td>
{% if forloop.first %}<!-- FIRST -->
<td class="rnw" title="Стоимость {{ CurOffer.TOTAL|stringformat:".2f" }} рублей за все окна квартиры {{ APART|safe }}.">{{ CurOffer.TOTAL|stringformat:".2f"|price_format }}</td>
<th{% if CurOffer.DISCOUNT_COLOR2 != "" %} style="background-color:{{ CurOffer.DISCOUNT_COLOR2 }};"{% endif %} title="{% if CurOffer.DISCOUNT < 0.1 %}Нет скидки{% else %}Скидка — {{ CurOffer.DISCOUNT|stringformat:".1f" }}%{% endif %}">{% if CurOffer.DISCOUNT < 0.1 %}{% else %}&minus;{{ CurOffer.DISCOUNT|stringformat:".1f" }}%{% endif %}</th>
<th{% if CurOffer.DISCOUNT_COLOR1 != "" %} style="background-color:{{ CurOffer.DISCOUNT_COLOR1 }};"{% endif %} itemprop="offers" itemscope itemtype="http://schema.org/Offer" title="Итого за все окна с учетом скидки: {{ CurOffer.FIN_PRICE|stringformat:".2f" }} рублей">
Итого: {{ CurOffer.FIN_PRICE|stringformat:".2f"|price_format }}&thinsp;<small class="glyphicon glyphicon-ruble" aria-label="₽ (руб.)" title="₽ (руб.)"></small>
<meta itemprop="price" content="{{ CurOffer.FIN_PRICE }}" />
<meta itemprop="priceCurrency" content="RUB" />
</th>
{% if CurOffer.DIM|length == 1 %}
</tr><tr>
<td colspan="2">&nbsp;</td>
<td colspan="3">
{% endif %}
{% elif forloop.counter0 == 1 %}
<td colspan="3" rowspan="{{ CurOffer.DIM|length|add:"-1" }}">
{% endif %}
{% if forloop.counter0 == 1 or CurOffer.DIM|length == 1 %}
<stong>{{ CurOffer.OFFICE_NAME }}</stong><br />
<div id="hid{{ CurOffer.SETS_ID }}" class="collapse in">
<a class="btn btn-info btn-xs shake-trigger" data-toggle="collapse" data-target="#tel{{ CurOffer.SETS_ID }}" onclick="show_phone_num({{ CurOffer.SETS_ID }})"><b class="glyphicon glyphicon-phone-alt shake-vertical"></b> Показать телефон</a>
</div>
<div id="tel{{ CurOffer.SETS_ID }}" class="collapse">
<small>{{ CurOffer.OFFICE_ADDRESS }}</small><br /> <b class="glyphicon glyphicon-phone-alt"></b> {{ CurOffer.OFFICE_PHONES }}<br />
<small><nobr>Пожалуйста, скажите, что нашли цены на oknardia.ru</nobr></small>
</div>
</td>
{% endif %}
</tr>{% endfor %}<!----- ПРОВЕРКА --->{% endfor %}
<Tr id="preloader_{{ N }}">
<tH colspan="7"><center><img src="{% static "img/preloader.gif" %}" alt="подгружаю цены на окна..." height="64" width="64" /></center></tH>
</Tr>
<tR id="load_and_banner_{{ N }}">
<!-- сюда подгружаем следующий фрейм прайслиста --->
<script type="application/javascript">
function get_next_pricelist_frame( ) {
try{yaCounter32997984.reachGoal("MoreOffer");}catch(e){}
// заменяем ряд с кнопочками кнопочками на рекламу
// $("#load_and_banner_{{ N }}").html("<th colspan=10>РЕКЛАМА TMP</th>");
// добавляем ячейки с дополнительными ценами и новые кнопочки
$("#load_and_banner_{{ N }}").css({"display":"none"});
$("#preloader_{{ N }}").css({"display":"table-row","transition":"1s"});// подсчитать сколько прочеканных оферов для сравнения
$("#tmp").load(
"/next_price_one_flap_frame/idW{{ WIN_ID }}N{{ N }}",
function (){
window.frame = $("#tmp").html();
$("#price-list > tbody").append(window.frame);
}
);
window.setTimeout(
function show_adv() {
$("#load_and_banner_{{ N }}").html("<td colspan='8' bgcolor='#CCCCCC' align='center'><center><a href='https://api.ozon.ru/partner-tools.affiliates/pankarta/11052421746600/link?partner=e-serg&&utm_content=banner&width=728&height=90' target='_blank'><img src='https://api.ozon.ru/partner-tools.affiliates/pankarta/11052421746600/image?partner=e-serg&utm_content=banner&width=728&height=90' width='728px' height='90px'></a><small><br/>↑ Здесь могла бы быть ваша реклама ↑</small></center></td>");
$("#load_and_banner_{{ N }}").css({"display":"table-row","transition":"all 5s"});
$("#preloader_{{ N }}").css({"display":"none","transition":"1s"});
// (adsbygoogle=window.adsbygoogle || []).push({});
window.count = 0;
for (var i=0; i < ForCompare.length; i++)
if (ForCompare[i].checked)
window.count++;
$("#NumberOfSelected").html(window.count);
$('[data-toggle="popover"]').popover({ // реинициализировать подгружаемые поповеры
placement: "top",
html: true,
content: function () {
// сохранить текущий контекст
var _this = this;
$('#shadow_buffer').load('/show_rating_components/' + $(_this).attr("id-set"),
function (response, status, xhr) {
if (status == "error")
$(_this).attr({'data-content': "Ошибка " + xhr.status + " (" + xhr.statusText + ")"});
else
$(_this).attr({'data-content': response});
$(_this).popover('show');
try{yaCounter32997984.reachGoal("ShowRating");}catch(e){};
});
}
});
},
2800);
}
</script>
<td colspan="2"><a class="btn btn-info btn-block" data-toggle="collapse" onclick="Click2CompareOffers();">Сравнить выбранные (<span id="NumberOfSelected">0</span>) <small class="glyphicon-stats glyphicon"></small></a><p id="tmp"></p></td>
<td colspan="5">{% if N != "-1" %}<a href="javascript://" class="btn btn-info btn-block" onclick="get_next_pricelist_frame();">Ещё коммерческие предложения окон &nbsp;<small class="glyphicon-refresh glyphicon"></small></a>{% endif %}</td>
</tR>
+83
View File
@@ -0,0 +1,83 @@
<!--- Информация о строении --->{% load filters %}
<table id="collapse_it" class="table table-condensed collapse">
<tbody>{% if ADDRESS %}
<tr>
<td colspan="8" style="background:whitesmoke;border-top:double;">Дом по адресу: <strong>{{ ADDRESS }}</strong></td>
</tr>{% endif %}<tr>
<td>Проект:</td>
<td>{{ SERIA|safe }}</td>
<td>&nbsp;&nbsp;&nbsp;</td>
<td>Площадь участка, м²:</td>
<td><nobr>{{ LAND|capfirst|default:"Нет данных"|price_format }}</nobr></td>
<td>&nbsp;&nbsp;&nbsp;</td>
<td>Кадастровый номер участка:</td>
<td><nobr>{{ CADASTRE_NUM|capfirst }}</nobr></td>
</tr><tr>
<td>Типовая серия:</td>
<td><nobr>{{ SERIA_BASE|safe|default:"Нет данных" }}</nobr></td>
<td></td>
<td>Общаяя площадь помещений, м²:</td>
<td><nobr>{{ TOTAL_AREA|capfirst|default:"Нет данных"|price_format }}</nobr></td>
<td></td>
<td>Инвентарный номер:</td>
<td><nobr>{{ INVENTORY_NUM|capfirst|default:"Нет данных" }}</nobr></td>
</tr><tr>
<td>Число квартир:</td>
<td><nobr>{{ NUM_APARTMENTS|capfirst|default:"Нет данных" }}</nobr></td>
<td></td>
<td colspan="2">из них:</td>
<td></td>
<td>Тип здания:</td>
<td><nobr>{{ TYPE_BUILDING|capfirst|default:"Нет данных" }}</nobr></td>
</tr><tr>
<td>Этажей:</td>
<td>{{ STOREYS|capfirst|default:"Нет данных" }}</td>
<td></td>
<td><nobr>• помещений общего пользования, м²:</nobr></td>
<td><nobr>{{ COMMON_AREA|default:"Нет данных"|price_format }}</nobr></td>
<td></td>
<td>Класс энергоэффективности:</td>
<td><nobr>{{ ENERGY_EFFICIENCY|capfirst|default:"Нет данных" }}</nobr></td>
</tr><tr>
<td>Подъездов:</td>
<td>{{ NUM_ENTERANCES|capfirst|default:"Нет данных" }}</td>
<td></td>
<td>• нежилых помещений, м²:</td>
<td><nobr>{{ UNINHABITED_AREA|capfirst|default:"Нет данных"|price_format }}</nobr></td>
<td></td>
<td>Число счетов:</td>
<td>{{ NUM_ACCOUNTS|capfirst|default:"Нет данных"|price_format }}</td>
</tr><tr>
<td>Лифтов:</td>
<td>{{ NUM_ELEVATORS|capfirst|default:"Нет данных" }}</td>
<td></td>
<td>• жилых помещений, м²:</td>
<td>{{ RESIDENTIAL_AREA|capfirst|default:"Нет данных"|price_format }}</td>
<td></td>
<td>Число жителей:</td>
<td>{{ NUM_RESIDENTS|capfirst|default:"Нет данных"|price_format }}</td>
</tr><tr>
<td><nobr>Грузовых лифтов:</nobr></td>
<td><nobr>Нет данных</nobr></td>
<td></td>
<td>• приватизированных помещений, м²:</td>
<td><nobr>{{ PRIVATE_AREA|capfirst|default:"Нет данных"|price_format }}</nobr></td>
<td></td>
<td rowspan="3">Управляющая компания:</td>
<td rowspan="3">{{ MANAGEMENT_CO|safe|default:"Нет данных" }}</td>
</tr><tr>
<td>Год постройки:</td>
<td>{{ COMMISSIONING_YEAR|capfirst|default:"Нет данных" }}</td>
<td></td>
<td>• государственных помещений, м²:</td>
<td><nobr>{{ GOVERNMENT_AREA|capfirst|default:"Нет данных"|price_format }}</nobr></td>
<td rowspan="2" style="border-top:none;"></td>
</tr><tr>
<td>Cтепень износа:</td>
<td title="Износ фундамента: {{ CONDITION_FOUNDATION|capfirst|default:"" }} // Износ несущих конструкций: {{ CONDITION_WALL|capfirst|default:"" }} // Износ перекрытий: {{ CONDITION_OVERLAP|capfirst|default:"" }}">{{ CONDITION_HOUSE|capfirst|default:"Нет данных" }}</td>
<td></td>
<td>• мунициальных помещений, м²:</td>
<td><nobr>{{ MUNICIPAL_AREA|capfirst|default:"Нет данных"|price_format }}</nobr></td>
</tr>
</tbody>
</table>
+265
View File
@@ -0,0 +1,265 @@
{% extends "base.html" %}
{% load static %}
{% load filters %}
{% block Title %} Сравнение характеристик оконных профилей: {% for PROFILE in LIST_PROFILE %}{% if forloop.first %}{% else %}{% if forloop.last %} и {% else %}, {% endif %}{% endif %}{{ PROFILE }}{% endfor %}. Сравнение характеристик стеклопакетов: {% for GLAZING in LIST_GLAZING %}{% if forloop.first %}{% else %}{% if forloop.last %} и {% else %}, {% endif %}{% endif %}{{ GLAZING }}{% endfor %}. Сравнение предложений окон: {% for MERCANT in LIST_MERCHANT %}{% if forloop.first %}{% else %}{% if forloop.last %} и {% else %}, {% endif %}{% endif %}{{ MERCANT }}{% endfor %}.{% endblock %}
{% block Add_Body_Attribute %} style="padding-top:70px;"{% endblock %}
{% block Date4Meta %}{{ META_DATA_PUBLISH|date:"c" }}{% endblock %}
{% block Last4Meta %}{{ META_DATA_PUBLISH|date:"c" }}{% endblock %}
{% block Description %}Сравнение характеристик окон от поставщиков: {% for MERCANT in LIST_MERCHANT %}{% if forloop.first %}{% else %}{% if forloop.last %} и {% else %}, {% endif %}{% endif %}{{ MERCANT }}{% endfor %}. Сравнение профилей пластиковых окон: {% for PROFILE in LIST_PROFILE %}{% if forloop.first %}{% else %}{% if forloop.last %} и {% else %}, {% endif %}{% endif %}{{ PROFILE }}{% endfor %}. Сравнение характеристик стеклопакетов: {% for GLAZING in LIST_GLAZING %}{% if forloop.first %}{% else %}{% if forloop.last %} и {% else %}, {% endif %}{% endif %}{{ GLAZING }}{% endfor %}.{% endblock %}
{% block Keywords %}сравнение профилей пластиковых окон, {% for PROFILE in LIST_PROFILE %}{{ PROFILE }}, {% endfor %}сравнение стеклопакетов, {% for GLAZING in LIST_GLAZING %}{{ GLAZING }}, {% endfor %}сравнение поставщиков пластиковых окон, {% for MERCANT in LIST_MERCHANT %}{{ MERCANT }}, {% endfor %}характеристики пластиковых окон.{% endblock %}
{% block Top_JS3%}<script type="text/javascript">
$(function () {
$('[data-toggle="popover"]').popover({
placement: "bottom",
html: true,
content: function () {
// сохранить текущий контекст
let _this = this;
$('#shadow_buffer').load('/show_rating_components/' + $(_this).attr("id"), function (response, status, xhr) {
if (status == "error")
$(_this).attr({'data-content': "Ошибка " + xhr.status + " (" + xhr.statusText + ")"});
else
$(_this).attr({'data-content': response});
$(_this).popover('show');
try{yaCounter32997984.reachGoal("ShowRating");}catch(e){};
});
}
})
})
</script>{% endblock %}
{% block Top_CSS1 %}{% endblock %}
{% block Main_Content %}<!--- ------------------------------------------------------------------------------------------------------------------------- --->
<div class="row col-xs-12">
<div class="col-md-9 col-xs-8">
<h1>Сравнении оконных наборов:{% for Count in SET_LIST %}{% if forloop.first %} {% else %}{% if forloop.last %} и&nbsp;{% else %}, {% endif %}{% endif %}{{ Count.SET_NAME }}{% if forloop.last %}.{% endif %}{% endfor %}</h1>
<p>Оконный набор&nbsp;&mdash; это комплект оконного профиля (рамы и&nbsp;сворки), стеклопакета, фурнитуры и&nbsp;уплотнителей&nbsp;&mdash; готовое окно в&nbsp;сборе для установки в&nbsp;проём. В&nbsp;набор может входить отлив, подоконник, откос, встраиваемые системы <nobr>климат-контроля</nobr>, оконная фурнитура открывания, запоры, уплотнители, москитная сетка&hellip; а&nbsp;также сопутствующие услуги: демонтаж старых и&nbsp;установка новых окон, доставка, гарантийное обслуживание, уборка, вынос и&nbsp;утилизация строительного мусора, защитное укрытие мебели на&nbsp;время монтажа и&nbsp;другое.</p>
<h4>В&nbsp;таблицу сравнения услуг и&nbsp;условий, входящих в&nbsp;оконные наборы, добавлены следующие производители и&nbsp;поставщики:</h4>
<ul>{% for MERCANT in LIST_MERCHANT %}
<li>{{ MERCANT }}.</li>{% endfor %}
</ul>
<h4>В&nbsp;средней части таблице вы&nbsp;сможете сравнить характеристики оконных профилей в&nbsp;наборах:</h4>
<ul>{% for PROFILE in LIST_PROFILE %}
<li>{{ PROFILE }}.</li>{% endfor %}
</ul>
<h4>Нижний блок таблицы посвящен характеристикам и&nbsp;сравнению стеклопакетов в&nbsp;наборах. Формулы выбранных стеклопакетов:</h4>
<ul>{% for GLAZING in LIST_GLAZING %}
<li>{{ GLAZING }}.</li>{% endfor %}
</ul>
</div>
{# реклама Oknardia 250x250 СБОКУ #}<div class="col-md-2 col-xs-3 float-right">{% include "ad/bannet-250x250.html" %}</div>
</div>
<div class="raw" style="padding: 1em 0;">
<div class="col-sm-12">
<table class="table col-md-12 table-condensed" id="compare_table">
<thead>
<tr style="background:white;">
<th class="col-sm-2">Поставщик:<br /><small style="font-size:small;font-weight:100;">компания, предлагающая установку окон</small></th>{% for Count in SET_LIST %}
<th class="col-xs-1" title="Установку окон предлагает компания «{{ Count.MERCHANT }}»"><h2 style="font-size:1em;margin:0;font-weight:bold;">{{ Count.MERCHANT }}</h2><br />
<img src="http://oknardia.ru/media/{{ Count.MERCHANT_LOGO }}" style="height: 25px;width:auto;" alt="{{ Count.MERCHANT }}"><br />
<div class="url"><nobr>{% if Count.IS_COMMERCIAL %}<a href="{{ Count.MERCHANT_URL }}" target="_blank" rel="nofollow">{{ Count.MERCHANT_URL_SHOT|truncatechars:30 }}</a>{% else %}{{ Count.MERCHANT_URL_SHOT|truncatechars:30 }}{% endif %}</nobr><br /><a href="/catalog/company/{{ Count.MERCHANT_ID }}-{{ Count.MERCHANT_T }}/">в&nbsp;каталоге</a></div>
</th>{% endfor %}
</tr><tr>
<th>Название набора:</th>{% for Count in SET_LIST %}
<th title="Название оконного набора: «{{ Count.SET_NAME }}»">{{ Count.SET_NAME|truncatechars:25 }}</th>{% endfor %}
</tr><tr class="rating">
<th><nobr>Рейтиг «Окнардии»:</nobr></th>{% for Count in SET_LIST %}
<td{% if Count.RATING_SET_COLOR != "" %} style="background:{{ Count.RATING_SET_COLOR }};"{% endif %}>
<nobr title="Рейтинг «Окнардии» для окон набора «{{ Count.SET_NAME }}» компании «{{ Count.MERCHANT }}» — {% if Count.RATING_SET_N > 0.1 %}{{ Count.RATING_SET_N|stringformat:".2f" }}{% else %}не присвоен{% endif %}"><!-- НАЧАЛО звездочки рейтинга -->{% for Star in Count.RATING_SET %}{% if Star == 0 %}<i class="glyphicon glyphicon-star-empty"></i>{% else %}<b class="glyphicon glyphicon-star"></b>{% endif %}{% endfor %}<!-- КОНЕЦ звездочки рейтинга НАЧАЛО бедж --> {% if Count.RATING_SET_N > 0.1 %}<tt class="badge">{{ Count.RATING_SET_N|stringformat:".2f" }}</tt>{% endif %}<!-- КОНЕЦ бедж --> <a
href="javascript://"
id="{{ Count.SET_ID }}"
title="{% if Count.RATING_SET_N > 0.01 %}<b> Рейтинг {{ Count.RATING_SET_N|stringformat:".2f" }}</b> для оконого набора «{{ Count.SET_NAME }}» компании «{{ Count.MERCHANT }}» состоит&nbsp;из:{% else %}Рейтинг не присвоен{% endif %}"
data-toggle="popover">детали</a></nobr></td>{% endfor %}
</tr>
</thead>
<tbody>
<tr>
<th>Фурнитура:</th>{% for Count in SET_LIST %}
<td title="Оконнaя фурнитура и установочные изделия: {{ Count.SET_IMPLEMENTS_ALL|capfirst|safe }}">{{ Count.SET_IMPLEMENTS_ALL|capfirst|safe }}</td>{% endfor %}
</tr><tr>
<th><sup></sup>Ручки:</th>{% for Count in SET_LIST %}
<td title="Фурнитура оконных ручек: {% if Count.SET_IMPLEMENTS_HANDLES == "" %}{{ Count.SET_IMPLEMENTS_ALL|capfirst|safe }}{% else %}{{ Count.SET_IMPLEMENTS_HANDLES|capfirst|safe }}{% endif %}">{% if Count.SET_IMPLEMENTS_HANDLES == "" %}{{ Count.SET_IMPLEMENTS_ALL|capfirst|safe }}{% else %}{{ Count.SET_IMPLEMENTS_HANDLES|capfirst|safe }}{% endif %}</td>{% endfor %}
</tr><tr>
<th><sup></sup>Петли:</th>{% for Count in SET_LIST %}
<td title="Фурнитура оконных петель: {% if Count.SET_IMPLEMENTS_HINGES == "" %}{{ Count.SET_IMPLEMENTS_ALL|capfirst|safe }}{% else %}{{ Count.SET_IMPLEMENTS_HINGES|capfirst|safe }}{% endif %}">{% if Count.SET_IMPLEMENTS_HINGES == "" %}{{ Count.SET_IMPLEMENTS_ALL|capfirst|safe }}{% else %}{{ Count.SET_IMPLEMENTS_HINGES|capfirst|safe }}{% endif %}</td>{% endfor %}
</tr><tr>
<th><sup></sup>Механизмы запирания:</th>{% for Count in SET_LIST %}
<td title="Фурнитура механизма закрывания окна: {% if Count.SET_IMPLEMENTS_LATCH == "" %}{{ Count.SET_IMPLEMENTS_ALL|capfirst|safe }}{% else %}{{ Count.SET_IMPLEMENTS_LATCH|capfirst|safe }}{% endif %}">{% if Count.SET_IMPLEMENTS_LATCH == "" %}{{ Count.SET_IMPLEMENTS_ALL|capfirst|safe }}{% else %}{{ Count.SET_IMPLEMENTS_LATCH|capfirst|safe }}{% endif %}</td>{% endfor %}
</tr><tr>
<th><sup></sup>Ограничители:</th>{% for Count in SET_LIST %}
<td{% if not Count.SET_IMPLEMENTS_LIMITER == "" %} title="Фурнитура ораничителей открывания окна: {{ Count.SET_IMPLEMENTS_LIMITER|capfirst|safe }}"{% endif %}>{% if Count.SET_IMPLEMENTS_LIMITER == "" %}—{% else %}{{ Count.SET_IMPLEMENTS_LIMITER|capfirst|safe }}{% endif %}</td>{% endfor %}
</tr><tr>
<th><sup></sup>Фиксаторы открывания:</th>{% for Count in SET_LIST %}
<td{% if not Count.SET_IMPLEMENTS_CATCH == "" %} title="Фурнитура фиксатора открывания окна: {{ Count.SET_IMPLEMENTS_CATCH|capfirst|safe }}"{% endif %}>{% if Count.SET_IMPLEMENTS_CATCH == "" %}—{% else %}{{ Count.SET_IMPLEMENTS_CATCH|capfirst|safe }}{% endif %}</td>{% endfor %}
</tr><tr>
<th>Климат-контроль:</th>{% for Count in SET_LIST %}
<td{% if Count.SET_CLIMATE_CONTROL|length > 3 %} style="background:#7fff7f;"{% endif %} title="Оконная система климат-контроля: {{ Count.SET_CLIMATE_CONTROL|capfirst|safe }}">{{ Count.SET_CLIMATE_CONTROL|capfirst|safe }}</td>{% endfor %}
</tr><tr>
<th>Подоконники:</th>{% for Count in SET_LIST %}
<td{% if Count.SET_STILL|capfirst == "Нет" or Count.SET_STILL|length < 4 %} style="background:#ff7f7f;"{% endif %} title="Подоконники: {{ Count.SET_STILL|capfirst|safe }}">{{ Count.SET_STILL|capfirst|safe }}</td>{% endfor %}
</tr><tr>
<th>Водоотливы:</th>{% for Count in SET_LIST %}
<td{% if Count.SET_PANES|capfirst == "Нет" or Count.SET_PANES|length < 4 %} style="background:#ff7f7f;"{% endif %} title="Водоотливы: {{ Count.SET_PANES|capfirst|safe }}">{{ Count.SET_PANES|capfirst|safe }}</td>{% endfor %}
</tr><tr>
<th>Откосы:</th>{% for Count in SET_LIST %}
<td{% if Count.SET_SLOPE|capfirst == "Нет" or Count.SET_SLOPE|length < 4 %} style="background:#ff7f7f;"{% endif %} title="Откосы: {{ Count.SET_SLOPE|capfirst|safe }}">{{ Count.SET_SLOPE|capfirst|safe }}</td>{% endfor %}
</tr><tr>
<th>Доставка:</th>{% for Count in SET_LIST %}
<td style="background:{% if Count.SET_DELIVERY_B %}#7fff7f{% else %}#ff7f7f{% endif %};" title="Условия доставки окон: {{ Count.SET_DELIVERY|capfirst }}">{{ Count.SET_DELIVERY|capfirst|safe }}</td>{% endfor %}
</tr><tr>
<th>Демонтаж/Монтаж:</th>{% for Count in SET_LIST %}
<td style="background:{% if Count.SET_UNINSTALL_INSTALL_B %}#7fff7f{% else %}#ff7f7f{% endif %};" title="Демонтаж старых и монтаж новых окон: {{ Count.SET_UNINSTALL_INSTALL|capfirst|safe }}">{{ Count.SET_UNINSTALL_INSTALL|capfirst|safe }}</td>{% endfor %}
</tr><tr>
<th>Прочие условия:</th>{% for Count in SET_LIST %}
<td{% if not Count.SET_OTHER_CONDITIONS == "" %} title="Прочие условия окон набора «{{ Count.SET_NAME }}» от компании «{{ Count.MERCHANT }}»: {{ Count.SET_OTHER_CONDITIONS|capfirst|safe }}"{% endif %}><small>{{ Count.SET_OTHER_CONDITIONS|capfirst|safe }}</small></td>{% endfor %}
</tr>
</tbody>
<thead>
<tr>
<th class="centre" colspan="{{ SET_LIST|length|add:"1" }}">{% include "ad/bannet-wide.html" %}</th>
</tr><tr>
<th class="centre" colspan="{{ SET_LIST|length|add:"1" }}">Профиль, используемый в наборе</th>
</tr>
</thead>
<tbody>
<tr>
<th>Название профиля:</th>{% for Count in SET_LIST %}
<th><h3 style="font-size:1em;margin:0;" title="Название оконного профиля (модель): {{ Count.PROFILE_NAME|capfirst|safe }}"><a href="/catalog/profile/{{ Count.PROFILE_ID }}-{{ Count.PROFILE_MANUFACTURER_T }}/{{ Count.PROFILE_ID }}-{{ Count.PROFILE_NAME_T }}/">{{ Count.PROFILE_NAME|capfirst|safe }}</a></h3></th>{% endfor %}
</tr><tr>
<th>Производитель профиля:</th>{% for Count in SET_LIST %}
<td title="Производитель оконного профилы: {{ Count.PROFILE_MANUFACTURER|capfirst|safe }}"><a href="/catalog/profile/{{ Count.PROFILE_ID }}-{{ Count.PROFILE_MANUFACTURER_T }}/">{{ Count.PROFILE_MANUFACTURER|capfirst|safe }}</a></td>{% endfor %}
</tr><tr>
<th>Цвет:</th>{% for Count in SET_LIST %}
<td{% if not Count.PROFILE_NUM_COLOR == "" %} title="Цвет оконного профиля: {{ Count.PROFILE_NUM_COLOR|capfirst }}"{% endif %}>{% if Count.PROFILE_NUM_COLOR == "" %}—{% else %}{{ Count.PROFILE_NUM_COLOR|capfirst }}{% endif %}</td>{% endfor %}
</tr><tr>
<th>Число камер рамы/створки (<i>шт.</i>):</th>{% for Count in SET_LIST %}
<td{% if not Count.PROFILE_NUM_CAMERAS_COLOR == "" %} style="background:{{ Count.PROFILE_NUM_CAMERAS_COLOR }};"{% endif %}{% if Count.PROFILE_NUM_CAMERAS != "" %} title="Число камер профиля оконной рамы/створки: {{ Count.PROFILE_NUM_CAMERAS }} шт."{% endif %}>{{ Count.PROFILE_NUM_CAMERAS }}</td>{% endfor %}
</tr><tr>
<th>Контуров уплотненения (<i>шт.</i>):</th>{% for Count in SET_LIST %}
<td{% if Count.PROFILE_NUM_SEALS_COLOR != "" %} style="background:{{ Count.PROFILE_NUM_SEALS_COLOR }};"{% endif %}{% if Count.PROFILE_NUM_SEALS > 0 %} title="Число контуров уплотнения створки к раме: {{ Count.PROFILE_NUM_SEALS }} шт."{% endif %}>{% if Count.PROFILE_NUM_SEALS > 0 %}{{ Count.PROFILE_NUM_SEALS }}{% else %}—{% endif %}</td>{% endfor %}
</tr><tr>
<th>Монтажная ширина профиля (<i>мм</i>):</th>{% for Count in SET_LIST %}
<td{% if Count.PROFILE_THICKNESS_COLOR != "" %} style="background:{{ Count.PROFILE_THICKNESS_COLOR }};"{% endif %}{% if Count.PROFILE_THICKNESS > 5 %} title="Монтажная ширина оконного профиля: {{ Count.PROFILE_THICKNESS }} мм."{% endif %}>{% if Count.PROFILE_THICKNESS > 5 %}{{ Count.PROFILE_THICKNESS }}{% else %}—{% endif %}</td>{% endfor %}
</tr><tr>
<th>Максимальная толщина стеклопакета (<i>мм</i>):</th>{% for Count in SET_LIST %}
<td{% if Count.PROFILE_GLAZING_THICKNESS_COLOR != "" %} style="background:{{ Count.PROFILE_GLAZING_THICKNESS_COLOR }};"{% endif %}{% if Count.PROFILE_GLAZING_THICKNESS > 4 %} title="Максимальная толщина стеклопакета для уставноки с створку: {{ Count.PROFILE_GLAZING_THICKNESS }} мм"{% endif %}>{% if Count.PROFILE_GLAZING_THICKNESS > 4 %}{{ Count.PROFILE_GLAZING_THICKNESS }}{% else %}—{% endif %}</td>{% endfor %}
</tr><tr>
<th>Сопротивление теплопередаче <nobr><i>Ro</i> (<i>м²×°C/Вт</i>):</nobr></th>{% for Count in SET_LIST %}
<td{% if Count.PROFILE_HEAT_TRANSFER_COLOR != "" %} style="background:{{ Count.PROFILE_HEAT_TRANSFER_COLOR }};"{% endif %}{% if Count.PROFILE_HEAT_TRANSFER > 0.1 %} title="Сопротивление теплопередаче (Ro) оконного профиля: {{ Count.PROFILE_HEAT_TRANSFER|stringformat:".2f" }} м²×°C/Вт"{% endif %}>{% if Count.PROFILE_HEAT_TRANSFER > 0.1 %}{{ Count.PROFILE_HEAT_TRANSFER|stringformat:".2f" }}{% else %}—{% endif %}</td>{% endfor %}
</tr><tr>
<th>Коэффициент звукоизоляции, (<i>дБ</i>):</th>{% for Count in SET_LIST %}
<td{% if Count.PROFILE_SOUND_PROOFING_COLOR != "" %} style="background:{{ Count.PROFILE_SOUND_PROOFING_COLOR }};"{% endif %}{% if Count.PROFILE_SOUND_PROOFING > 1 %} title="Коэффициент звукоизоляции оконного профиля: {{ Count.PROFILE_SOUND_PROOFING|stringformat:".1f" }} дБ"{% endif %}>{% if Count.PROFILE_SOUND_PROOFING > 1 %}{{ Count.PROFILE_SOUND_PROOFING|stringformat:".1f" }}{% else %}—{% endif %}</td>{% endfor %}
</tr><tr>
<th>Высота в световом проеме, рама+створка (<i>мм</i>):</th>{% for Count in SET_LIST %}
<td{% if Count.PROFILE_HEIGHT_COLOR != "" %} style="background:{{ Count.PROFILE_HEIGHT_COLOR }};"{% endif %}{% if Count.PROFILE_HEIGHT > 15 %} title="Высота в световом проеме, рама+створка: {{ Count.PROFILE_HEIGHT }} мм"{% endif %}>{% if Count.PROFILE_HEIGHT > 15 %}{{ Count.PROFILE_HEIGHT }}{% else %}—{% endif %}</td>{% endfor %}
</tr><tr>
<th>Фальц рамы (<i>мм</i>):</th>{% for Count in SET_LIST %}
<td{% if Count.PROFILE_RABBET_COLOR != "" %} style="background:{{ Count.PROFILE_RABBET_COLOR }};"{% endif %}{% if Count.PROFILE_RABBET > 1 %} title="Высота фальца рамы: {{ Count.PROFILE_RABBET }} мм"{% endif %}>{% if Count.PROFILE_RABBET > 1 %}{{ Count.PROFILE_RABBET }}{% else %}—{% endif %}</td>{% endfor %}
</tr><tr>
<th>Армирование профиля:</th>{% for Count in SET_LIST %}
<td title="Армирование или другие особенности профиля: {{ Count.PROFILE_REINFORCEMENT }}">{{ Count.PROFILE_REINFORCEMENT }}</td>{% endfor %}
</tr><tr>
<th>Штапик:</th>{% for Count in SET_LIST %}
<td{% if Count.PROFILE_FILLET != "" %} title="Характеристики штапика: {{ Count.PROFILE_FILLET }}"{% endif %}>{% if Count.PROFILE_FILLET != "" %}{{ Count.PROFILE_FILLET }}{% else %}—{% endif %}</td>{% endfor %}
</tr><tr>
<th>Уплотнитель:</th>{% for Count in SET_LIST %}
<td{% if Count.PROFILE_SEAL_DESCRIPTION != "" %} title="Хараектеристики уплотнитель стеклопакета и контуров рама-створка: {{ Count.PROFILE_SEAL_DESCRIPTION|lower }}"{% endif %}>{% if Count.PROFILE_SEAL_DESCRIPTION != "" %}{{ Count.PROFILE_SEAL_DESCRIPTION|capfirst }}{% else %}—{% endif %}</td>{% endfor %}
</tr><tr>
<th>Прочие характеристики:</th>{% for Count in SET_LIST %}
<td{% if Count.PROFILE_OTHER != "" %} title="Прочие характеристики рамы и створки: {{ Count.PROFILE_OTHER }}"{% endif %}><small>{% if Count.PROFILE_OTHER != "" %}{{ Count.PROFILE_OTHER }}{% else %}—{% endif %}</small></td>{% endfor %}
</tr>
</tbody>
<thead>
<tr>
<th class="centre" colspan="{{ SET_LIST|length|add:"1" }}">{% include "ad/bannet-wide.html" %}</th>
</tr><tr>
<th class="centre" colspan="{{ SET_LIST|length|add:"1" }}">Стеклопакет, используемый в наборе</th>
</tr>
</thead>
<tbody>
<tr>
<th>Краткое описание:</th>{% for Count in SET_LIST %}
<td title="Краткое описание стекопакета: {{ Count.GLAZING_BRIEF_DESCRIPTION|safe|lower }}"><h3 style="font-size:small;margin:0;">{{ Count.GLAZING_BRIEF_DESCRIPTION|safe|capfirst }}</h3></td>{% endfor %}
</tr><tr>
<th>Производитель стеклопакета:</th>{% for Count in SET_LIST %}
<td{% if not Count.GLAZING_MANUFACTURER == "//" %} title="Компания производитель стеклопакета: {{ Count.GLAZING_MANUFACTURER|capfirst }}"{% endif %}>{% if Count.GLAZING_MANUFACTURER == "—//—" %}—{% else %}{{ Count.GLAZING_MANUFACTURER|capfirst }}{% endif %}</td>{% endfor %}
</tr><tr>
<th>Схема стеклопакета:</th>{% for Count in SET_LIST %}
<td title="Схема стеклопакета: {{ Count.GLAZING_MARK }}">{{ Count.GLAZING_MARK }}</td>{% endfor %}
</tr><tr>
<th>Камер в стеклопакете:</th>{% for Count in SET_LIST %}
<td{% if Count.GLAZING_CAMERAS_COLOR != "" %} style="background:{{ Count.GLAZING_CAMERAS_COLOR }};"{% endif %}{% if not Count.GLAZING_CAMERAS_NUM < 1 %} title="Количество камер в стеклопакете: {{ Count.GLAZING_CAMERAS_NUM }}"{% endif %}>{% if Count.GLAZING_CAMERAS_NUM < 1 %}{% else %}{{ Count.GLAZING_CAMERAS_NUM }}{% endif %}</td>{% endfor %}
</tr><tr>
<th>Толщина стеклопакета <i>(мм)</i>:</th>{% for Count in SET_LIST %}
<td{% if Count.GLAZING_THICKNESS_COLOR != "" %} style="background:{{ Count.GLAZING_THICKNESS_COLOR }};"{% endif %}{% if not Count.GLAZING_THICKNESS < 3 %} title="Общая толщина стеклопакета: {{ Count.GLAZING_THICKNESS }} мм"{% endif %}>{% if Count.GLAZING_THICKNESS < 3 %}{% else %}{{ Count.GLAZING_THICKNESS }}{% endif %}</td>{% endfor %}
</tr><tr>
<th>Сопротивление теплопередаче <nobr><i>Ro</i> (<i>м²×°C/Вт</i>):</nobr></th>{% for Count in SET_LIST %}
<td{% if Count.GLAZING_HEAT_TRANSFER_COLOR != "" %} style="background:{{ Count.GLAZING_HEAT_TRANSFER_COLOR }};"{% endif %}{% if not Count.GLAZING_HEAT_TRANSFER < 0.1 %} title="Сопротивление теплопередаче (Ro) стеклопакета: {{ Count.GLAZING_HEAT_TRANSFER|stringformat:".2f" }} м²×°C/Вт"{% endif %}>{% if Count.GLAZING_HEAT_TRANSFER < 0.1 %}{% else %}{{ Count.GLAZING_HEAT_TRANSFER|stringformat:".2f" }}{% endif %}</td>{% endfor %}
</tr><tr>
<th>Коэффициент звукоизоляции (<i>дБ</i>):</th>{% for Count in SET_LIST %}
<td{% if Count.GLAZING_SOUNDPROOFING_COLOR != "" %} style="background:{{ Count.GLAZING_SOUNDPROOFING_COLOR }};"{% endif %}{% if not Count.GLAZING_SOUNDPROOFING < 10 %} title="Коэффициент звукоизоляции стеклопакета: {{ Count.GLAZING_SOUNDPROOFING|stringformat:".1f" }} дБ"{% endif %}>{% if Count.GLAZING_SOUNDPROOFING < 10 %}{% else %}{{ Count.GLAZING_SOUNDPROOFING|stringformat:".1f" }}{% endif %}</td>{% endfor %}
</tr><tr>
<th>Коэффициент светопропускания (<i>%</i>):</th>{% for Count in SET_LIST %}
<td{% if Count.GLAZING_LIGHT_TRANSMISSION_COLOR != "" %} style="background:{{ Count.GLAZING_LIGHT_TRANSMISSION_COLOR }};"{% endif %}{% if not Count.GLAZING_LIGHT_TRANSMISSION < 1 %} title="Коэффициент светопропускания стеклопакета: {{ Count.GLAZING_LIGHT_TRANSMISSION|stringformat:".0f" }}%"{% endif %}>{% if Count.GLAZING_LIGHT_TRANSMISSION < 1 %}{% else %}{{ Count.GLAZING_LIGHT_TRANSMISSION|stringformat:".0f" }}{% endif %}</td>{% endfor %}
</tr><tr>
<th>Коэффициент солнцепропускания (<i>%</i>)</th>{% for Count in SET_LIST %}
<td{% if Count.GLAZING_PASSING_SUN_COLOR != "" %} style="background:{{ Count.GLAZING_PASSING_SUN_COLOR }};"{% endif %}{% if not Count.GLAZING_PASSING_SUN < 1 %} title="Коэффициент солнцепропускания стеклопакета: {{ Count.GLAZING_PASSING_SUN|stringformat:".0f" }}%"{% endif %}>{% if Count.GLAZING_PASSING_SUN < 1 %}{% else %}{{ Count.GLAZING_PASSING_SUN|stringformat:".0f" }}{% endif %}</td>{% endfor %}
</tr><tr>
<th>Коэффициент светоотражения, внешний/внутренний (<i>%</i>):</th>{% for Count in SET_LIST %}
<td{% if not Count.GLAZING_LIGHT_REFLECTION == "/" %} title="Коэффициент светоотражения стеклопакета (внешний/внутренний): {{ Count.GLAZING_LIGHT_REFLECTION }}%"{% endif %}>{% if Count.GLAZING_LIGHT_REFLECTION == "—/—" %}—&nbsp;/&nbsp;—{% else %}{{ Count.GLAZING_LIGHT_REFLECTION }}{% endif %}</td>{% endfor %}
</tr><tr>
<th>Коэффициент теплоотражения/теплопоглощения (<i>%</i>):</th>{% for Count in SET_LIST %}
<td{% if not Count.GLAZING_REFLECTION_AND_ABSORPTION == "/" %} title="Коэффициент теплоотражения/теплопоглощения стеклопакета: {{ Count.GLAZING_REFLECTION_AND_ABSORPTION }}%"{% endif %}>{% if Count.GLAZING_REFLECTION_AND_ABSORPTION == "—/—" %}—&nbsp;/&nbsp;—{% else %}{{ Count.GLAZING_REFLECTION_AND_ABSORPTION }}{% endif %}</td>{% endfor %}
</tr><tr>
<th>Тонирование стеклопакета:</th>{% for Count in SET_LIST %}
<td title="Тонирование стеклопакета: {{ Count.GLAZING_TONING|lower }}">{{ Count.GLAZING_TONING|capfirst }}</td>{% endfor %}
</tr>
</tbody>
{% if SET_LIST|length > 2 %}<tbody style="border-bottom: solid 3px !important;">
<tr>
<th></th>{% for Count in SET_LIST %}
<th title="Удалить набор компании «{{ Count.MERCHANT }}» ({{ Count.SET_NAME }}) из сравнения"><a href="/compare_offers/{{ Count.URL_W_DEL }}"><b class="glyphicon glyphicon-trash"></b></a></th>{% endfor %}
</tr>
</tbody>{% endif %}
</table>
</div>
</div>
<div class="raw" style="padding: 1em 0;">
<div class="col-xs-7 col-md-offset-1" id="add2compare">{% if SET_LIST|length < 6 %}
<h4>Добавить к сравнению оконные наборы:</h4>
<UL>{% for Count in LIST_TO_ADD %}
<li><a href="/compare_offers/{{ Count.ID_LIST }}"><b class="glyphicon glyphicon-plus-sign"></b>{{ Count.MERCHANT }} — {{ Count.SET_NAME }}</a> <nobr style="padding: 0 1ex">{% for Star in Count.R_STAR %}{% if Star == 0 %}<i class="glyphicon glyphicon-star-empty"></i>{% else %}<b class="glyphicon glyphicon-star"></b>{% endif %}{% endfor %} {% if Count.R > 0.02 %}<tt class="badge">{{ Count.R|stringformat:".2f" }}</tt>{% endif %}</nobr>{% if Count.DATA_MODIFY != "" %}<small style="font-size: xx-small;"> — обновлен {{ Count.DATA_MODIFY }}</small>{% endif %}</li> {% endfor %}
</UL>{% endif %}
</div><dIV class="col-xs-4">
<h4>Цветовое кодирование:</h4>
<p><small>Помочь разобраться в&nbsp;комплектациях, условиях поставки, характеристиках оконных профилей и&nbsp;стеклопакетов поможет цвет фона ячеек таблицы. <span style="background:#83ff83;">Зеленый цвет</span>&nbsp;&mdash; лучшие значения, чем насыщение цвет, тем более высокие потребительская характеристика у&nbsp;того или иного параметра. <span style="background:#ff7f7f;">Красный цвет</span>&nbsp;&mdash; отсутствие отдельных элементов или услуг комплексного предложения.</small></p>
{# реклама Oknardia 250x250 СБОКУ #}{% include "ad/bannet-250x250.html" %}
</dIV>
</div>
{# --- Баннер: НАЧАЛО --- #}
<div class="row"><div class="col-md-12 col-xs-12"><hr class="dotted-black" />{% include "ad/bannet-wide.html" %}</div></div>
{# --- Баннер: конец --- #}
<div class="row">
{% include "report/report_last_user_visit.html" %}
{% include "report/report_log_user_visit.html" %}
</div>
<p id="shadow_buffer"></p>
<!--- ------------------------------------------------------------------------------------------------------------------------- --->{% endblock %}
{% comment %}
{% block Top_Nav_Bar %}
{# ОТЛАДКА, ГАСИМ ВЕРХНЕЕ МЕНЮ #}
{% endblock %}
{% endcomment %}
+20
View File
@@ -0,0 +1,20 @@
{# отображение рейтинга #}{% load static %}
<table class="pop-tab" width="100%" style="border: none !important;">
<tbody style="border: none !important;">
<tr style="border: none !important; border-left: none !important;">
<td style="border: none !important;">Услуги:</td>
<td style="border: none !important;"><nobr><!-- НАЧАЛО звездочки рейтинга -->{% for Star in RATING_SERVIZ_STARS %}{% if Star == 0 %}<i class="glyphicon glyphicon-star-empty"></i>{% else %}<b class="glyphicon glyphicon-star"></b>{% endif %}{% endfor %}<!-- КОНЕЦ звездочки рейтинга НАЧАЛО бедж --> {% if RATING_SERVIZ > 0 %}<tt class="badge">{{ RATING_SERVIZ|stringformat:".2f" }}</tt>{% else %}<tt class="badge">не присвоен</tt>{% endif %}<!-- КОНЕЦ бедж --></nobr></td>
</tr><tr style="border: none !important;">
<td style="border: none !important;">Оконный&nbsp;профиль:</td>
<td style="border: none !important;"><nobr><!-- НАЧАЛО звездочки рейтинга -->{% for Star in RATING_PVC_STARS %}{% if Star == 0 %}<i class="glyphicon glyphicon-star-empty"></i>{% else %}<b class="glyphicon glyphicon-star"></b>{% endif %}{% endfor %}<!-- КОНЕЦ звездочки рейтинга НАЧАЛО бедж --> {% if RATING_PVC > 0 %}<tt class="badge">{{ RATING_PVC|stringformat:".2f" }}</tt>{% else %}<tt class="badge">не присвоен</tt>{% endif %}<!-- КОНЕЦ бедж --></nobr></td>
</tr><tr style="border: none !important;">
<td style="border: none !important;">Стеклопакет:</td>
<td style="border: none !important;"><nobr><!-- НАЧАЛО звездочки рейтинга -->{% for Star in RATING_GLAZ_STARS %}{% if Star == 0 %}<i class="glyphicon glyphicon-star-empty"></i>{% else %}<b class="glyphicon glyphicon-star"></b>{% endif %}{% endfor %}<!-- КОНЕЦ звездочки рейтинга НАЧАЛО бедж --> {% if RATING_GLAZ > 0 %}<tt class="badge">{{ RATING_GLAZ|stringformat:".2f" }}</tt>{% else %}<tt class="badge">не присвоен</tt>{% endif %}<!-- КОНЕЦ бедж --></nobr></td>
</tr><tr style="border: none !important;">
<td style="border: none !important;">От&nbsp;пользователей:</td>
<td style="border: none !important;"><nobr><!-- НАЧАЛО звездочки рейтинга -->{% for Star in RATING_OFFER_STARS %}{% if Star == 0 %}<i class="glyphicon glyphicon-star-empty"></i>{% else %}<b class="glyphicon glyphicon-star"></b>{% endif %}{% endfor %}<!-- КОНЕЦ звездочки рейтинга НАЧАЛО бедж --> {% if RATING_OFFER > 0 %}<tt class="badge">{{ RATING_OFFER|stringformat:".2f" }}</tt>{% else %}<tt class="badge">не присвоен</tt>{% endif %}<!-- КОНЕЦ бедж --></nobr></td>
</tr><tr style="border: none !important;">
<td colspan="2" style="background:ghostwhite;padding:1ex;border: none !important;"><small>В базе {{ NUM_OFFERS }}. Обновление цен {{ DATA_OFFER_UPDATE }}.<!-- Время исполнения: {{ ticks|stringformat:".4f" }}--></small></td>
</tr>
</tbody>
</table>
@@ -7,10 +7,6 @@
{% block Add_Body_Attribute %} style="padding-top:70px;"{% endblock %}
{% block Date4Meta %}{{ META_DATA_PUBLISH|date:"c" }}{% endblock %}
{% block Last4Meta %}{{ META_DATA_PUBLISH|date:"c" }}{% endblock %}
{% block Description %}Дома серии {{ THIS_SERIA_NAME }}: детальная информация{% endblock %}
{% block Keywords %} {{ THIS_SERIA_NAME }}, серия {{ THIS_SERIA_NAME }}, проект {{ THIS_SERIA_NAME }}, года постройки зданий серии {{ THIS_SERIA_NAME }}, размеры окон в домах серии {{ THIS_SERIA_NAME }}, оконные проемы зданий серии {{ THIS_SERIA_NAME }}, дома серии {{ THIS_SERIA_NAME }} на карте, установка окон, цены на пластиковые окна{% endblock %}
@@ -72,7 +68,7 @@
<div class="row">
<div class="col-md-9"><a name="s_graph"></a>
<h2 class="header">Здания серия {{ THIS_SERIA_NAME }}: ввод в эксплуатацию по годам</h2>
<h2 class="header">Здания серия {{ THIS_SERIA_NAME }}: ввод в&nbsp;эксплуатацию по&nbsp;годам</h2>
</div>
<div class="col-md-9 col-md-offset-1" style="height:300px;font-size:large;">
{% include 'seria_info/yaer_graph.html' %}
@@ -84,7 +80,7 @@
<div class="row">
<div class="col-md-7"><a name="s_map"></a>
<h2 class="header">Строения серии {{ THIS_SERIA_NAME }} на карте</h2>
<h2 class="header">Строения серии {{ THIS_SERIA_NAME }} на&nbsp;карте</h2>
</div>
<div class="col-md-7 col-lg-offset-1">
<p><small>Чтобы посмотреть цены на&nbsp;установку и&nbsp;замену окон от&nbsp;партнёров &laquo;Окнардия&raquo; в&nbsp;своей квартире: найдите дом на&nbsp;карте; кликните на&nbsp;него; перейдите по&nbsp;ссылке &laquo;Смотреть коммерческие предложения&raquo;. При необходимости смените типовую планировку квартиры (на&nbsp;странице ценовой выдачи, справа от&nbsp;изображения типовых проёмов и&nbsp;схем открывания).</small></p>
@@ -94,22 +90,23 @@
<diV class="col-md-4">
<h3 class="header">Статистика <nobr>серии {{ THIS_SERIA_NAME }}</nobr></h3>
<p>Совокупно во всех зданиях типового проекта:</p>
<p>Совокупно во&nbsp;всех зданиях типового проекта:</p>
<ul>
<li><strong>{{ ACCOUNTS|price_format }}</strong> квартир.</li>
<li>Проживает <strong>{{ APARTMENTS|price_format }}</strong> семей <small>(<strong>{{ RESIDENTS|price_format }}</strong> человек)</small>.</li>
<li><strong>{{ RESIDENTIAL_M2|stringformat:".1f"|price_format }} м²</strong> жилых помещений.</li>
<li><strong>{{ MUNICIPAL_M2|stringformat:".1f"|price_format }} м²</strong> — муниципальное жильё.</li>
<li><strong>{{ GOVERNMENT_M2|stringformat:".1f"|price_format }} м²</strong> занимают государственные и городские службы, учреждения бытового обслуживания, магазины, офисы и тому подобное.</li>
<li>Максимальный износ жилого фонда серии {{ THIS_SERIA_NAME }} <strong>{{ CONDITION_MAX|stringformat:".2f" }}%</strong>. Минимальный <strong>{{ CONDITION_MIN|stringformat:".2f" }}%</strong>. </li>
<li><strong>{{ MUNICIPAL_M2|stringformat:".1f"|price_format }} м²</strong>&nbsp;— муниципальное жильё.</li>
<li><strong>{{ GOVERNMENT_M2|stringformat:".1f"|price_format }} м²</strong> занимают государственные и&nbsp;городские службы, учреждения бытового обслуживания, магазины, офисы и&nbsp;тому подобное.</li>
<li>Максимальный износ жилого фонда серии {{ THIS_SERIA_NAME }}&nbsp;<strong>{{ CONDITION_MAX|stringformat:".2f" }}%</strong>. Минимальный&nbsp;<strong>{{ CONDITION_MIN|stringformat:".2f" }}%</strong>. </li>
</ul>
</diV>
</div>
<div class="row">
<div class="col-md-4 col-lg-offset-8" style="margin-top: 1em;">{# <div class="col-md-4"> #}
<h5>Информация о&nbsp;других, отличных от&nbsp;{{ THIS_SERIA_NAME }}, типовых сериях в&nbsp;базе &laquo;Окнардия&raquo;, типовых планировках квартир и&nbsp;оконных проёмах в&nbsp;них, а также рекомендации по замене окон:</h5>
<h5>Информация о&nbsp;других, отличных от&nbsp;{{ THIS_SERIA_NAME }}, типовых сериях в&nbsp;базе &laquo;Окнардия&raquo;, типовых планировках квартир и&nbsp;оконных проёмах в&nbsp;них, а&nbsp;также рекомендации по&nbsp;замене&nbsp;окон:</h5>
<div class="href_d">{% include 'seria_info/seria_nav.html' %}</div>
<p style="text-align:right;padding-top:1em;padding-bottom:1em;"><a href="/stat_all/">Агрегированная информация<br />по&nbsp;типовым сериям домов<br />в&nbsp;базе &laquo;Окнардия&raquo;</a>.</p>
</div>
</div>
<div class="row">
+2 -2
View File
@@ -23,12 +23,12 @@ $(window).load(function(){let images = $('.half');images.each(function(i){$(this
{% block Main_Content %}<div class="container-fluid">
<div class="row">
<div class="col-md-9"><h1>Статистика</h1></div>
<div class="col-md-9"><h1>Типовые серии домов в&nbsp;базе &laquo;Окнардия&raquo;</h1></div>
</div>
<div class="row">
<div class="col-md-10"><a name="s_graph"></a>
<h2 class="header">Распределение типовых серии в жилом фонде<sup><small style="color: darkred;">*</small></sup></h2>
<h2 class="header">Распределение типовых серий в&nbsp;жилом фонде<sup><small style="color: darkred;">*</small></sup></h2>
</div>
<script type="text/javascript" src="https://www.google.com/jsapi"></script>
<script type="text/javascript">
+27
View File
@@ -0,0 +1,27 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="robots" content="noindex, nofollow" />
<title>ОКНАРДИЯ :: Служебное</title>
</head>
<body>
<h1>Служебные ссылки</h1>
<ul>
<li><b><a href="/service/tmp">Страница для тестирования верстки текста в блоге</a></b></li>
</ul><ul>
<li><b><a href="/service/make_sitemaps">Пересоздать файлы sitemaps.xml</a></b> (исполняется около 10 минут)</li>
<li><a href="/service/make_JavaScripts4maps">Пересоздать JavaScript для карт на основе API Яндекс.Крат</a> (исполняется около 1 минуты)</li>
<li><a href="/service/make_SeriaInfoRoot">Построить id корневых серий</a> (исполняется около 1 часа)</li>
</ul><ul>
<li><a href="/service/del_CachingTemplate">Удалить кеширующие шаблоны</a> (для страниц про серии домов)</li>
</ul><ul>
<li><a href="/service/make_FillGeoCode">Добавить GeoCode для адресов где его нет</a></li>
</ul><ul>
<li><a href="/service/make_Rating">Пересчитать рейтинги Профилей</a></li>
</ul>
</body>
</html>
+141
View File
@@ -0,0 +1,141 @@
<!DOCTYPE html>{% load static %}
<html lang="ru-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 name="viewport" content="width=device-width, initial-scale=1"/>
<meta name="description" content="
{% block Description %}{{ META_DESCRIPTION|default:"" }}Служебный интерфейс «Окнардия» закрыт{% endblock %}"/>
<meta name="keywords"
content="{% block Keywords %}цены на пластиковые окна, агрегатор окон, доступ закрыт{{ META_KEYWORDS|default:"" }}{% endblock %}"/>
<meta name="author" content="OKNARDIA.RU{% block Author4Meta %}{% endblock %}"/>
<meta name="copyright" lang="ru" content="OKNARDIA.RU{% block CopyrightAuthor4Meta %}{% endblock %}"/>
<meta name="robots" content="index,follow"/>
<meta name="document-state" content="{{ META_DOCUMENT_STATE|default:"Static" }}"/>
<meta name="generator" content="OKNARDIA 0.3β by Python/Django"/>
<title>ОКНАРДИЯ: Нет доступа в служебный интерфейс</title>
<link href="{% static 'css/bootstrap.min.css' %}" rel="stylesheet" type="text/css" />{# <link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet" type="text/css" />#}
<script src="{% static 'js/jquery-2.1.1.min.js' %}" type="text/javascript"></script>{# <script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js" type="text/javascript"></script>#}
<script src="{% static 'js/bootstrap.min.js' %}" type="text/javascript"></script>{# <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js" type="text/javascript"></script>#}{% block Top_JS1 %}{% endblock %}{% block Top_JS2 %}{% endblock %}{% block Top_JS3 %}{% endblock %}{% block Top_JS4 %}{% endblock %}{% block Top_JS5 %}{% endblock %}{% block Top_Meta1 %}{% endblock %}
<link href="https://fonts.googleapis.com/css?family=Open+Sans+Condensed:300" rel="stylesheet">
</head>
<body>
<div class="row">
<div class="col-xs-8 col-xs-offset-4">
<p>&nbsp;<br>&nbsp;<br></p>
<h1 style="font-family: 'Open Sans Condensed', sans-serif;
font-size: 5em;
font-weight: 900;
text-shadow: 1px 1px 6px silver;">Служебный интерфейс закрыт</h1>
<p style="font-family: 'Open Sans Condensed', sans-serif;font-size: 3ex;">Доступ только для админов! Поисковикам и
пользователям лезть сюда не за чем…</p>
</div>
</div>
<div style="height: 100% !important; width: 100% !important; background-image: url('/static/img/cubex.png');
background-repeat: no-repeat; background-position: 10% 85%; z-index:-2; position:absolute !important;
bottom: 0; right: 0;">
<div style="position: absolute; bottom: 0; right: 0;padding-bottom: 1ex;padding-right: 2ex;">
<script type="text/javascript">
{# <!-- Google Analylics --> #}(function (i, s, o, g, r, a, m) {
i['GoogleAnalyticsObject'] = r;
i[r] = i[r] || function () {
(i[r].q = i[r].q || []).push(arguments)
}, i[r].l = 1 * new Date();
a = s.createElement(o), m = s.getElementsByTagName(o)[0];
a.async = 1;
a.src = g;
m.parentNode.insertBefore(a, m)
})(window, document, 'script', '//www.google-analytics.com/analytics.js', 'ga');
ga('create', 'UA-9116991-5', 'auto');
ga('send', 'pageview');
{# <!-- Rating@Mail.ru counter --> #}var _tmr = _tmr || [];
_tmr.push({id: "2018432", 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 = (d.location.protocol == "https:" ? "https:" : "http:") + "//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");
{# <!-- LiveInternet counter --> #}
</script>
<noscript>
<div style="position:absolute;left:-10000px;">
{# <!-- Rating@Mail.ru nosript --> #}<img src="//top-fwz1.mail.ru/counter?id=2018432;js=na"
style="border:0;height:1px;width:1px" alt=""/>
{# <!-- Yandex.Metrika counter --> #}<img src="//mc.yandex.ru/watch/32997984"
style="border:0;height:1px;width:1px"
alt=""/>{# <!-- /Yandex.Metrika counter --> #}
</div>
</noscript>
{#<!-- Rating@Mail.ru logo -->#}<a target="_blank" href="http://top.mail.ru/jump?from=2018432"><img
src="//top-fwz1.mail.ru/counter?id=2018432;t=216;l=1" style="border:0" rel="nofollow"
alt="Рейтинг@Mail.ru"></a>{#<!-- //Rating@Mail.ru logo -->#}
{# <!-- Yandex.Metrika informer --> #}<a href="https://metrika.yandex.ru/stat/?id=32997984&amp;from=informer"
target="_blank" rel="nofollow"><img
src="https://informer.yandex.ru/informer/32997984/3_0_E0E0E0FF_C0C0C0FF_0_pageviews"
style="width:88px; height:31px; border:0;" alt="Яндекс.Метрика"
title="Яндекс.Метрика: данные за сегодня (просмотры, визиты и уникальные посетители)"
onclick="try{Ya.Metrika.informer({i:this,id:32997984,lang:'ru'});return false}catch(e){}"/></a>{# <!-- /Yandex.Metrika informer --> #}
{# <!-- begin of Top100 code --> #}<span id="rambler"><script id="top100Counter" type="text/javascript"
src="//counter.rambler.ru/top100.jcn?3148853"></script><noscript>
<a href="http://top100.rambler.ru/navi/3148853/"><img src="http://counter.rambler.ru/top100.cnt?3148853"
alt="Rambler's Top100" border="0"/></a>
</noscript></span>{# <!-- end of Top100 code --> #}
<script type="text/javascript"><!--
{#<!--LiveInternet counter-->#}document.write("<a href='//www.liveinternet.ru/click' target=_blank><img src='//counter.yadro.ru/hit?t50.2;r" + escape(document.referrer) + ((typeof(screen) == "undefined") ? "" : ";s" + screen.width + "*" + screen.height + "*" + (screen.colorDepth ? screen.colorDepth : screen.pixelDepth)) + ";u" + escape(document.URL) + ";" + Math.random() + "' alt='' title='LiveInternet' style='border:0;'><\/a>");
{# <!-- Yandex.Metrika counter --> #}(function (d, w, c) {
(w[c] = w[c] || []).push(function () {
try {
w.yaCounter32997984 = new Ya.Metrika({
id: 32997984,
clickmap: true,
trackLinks: true,
accurateTrackBounce: true,
webvisor: true,
trackHash: true
});
} catch (e) {
}
});
var n = d.getElementsByTagName("script")[0], s = d.createElement("script"), f = function () {
n.parentNode.insertBefore(s, n);
};
s.type = "text/javascript";
s.async = true;
s.src = "https://mc.yandex.ru/metrika/watch.js";
if (w.opera == "[object Opera]") {
d.addEventListener("DOMContentLoaded", f, false);
} else {
f();
}
})(document, window, "yandex_metrika_callbacks");
//--></script>
{# <!--/LiveInternet--> #}
</div>
<div style="position: absolute; bottom: 0; left: 0;padding-bottom: 1ex;padding-left: 2ex;font-size: x-small">
© <a href="/">oknardia.ru</a>, 2015-{% now "Y" %}.
</div>
</div>
</body>
</html>
+137
View File
@@ -0,0 +1,137 @@
{% extends "base.html" %}
{% load static %}
{% load filters %}
{% load humanize %}
{% block Title %}: Тарифы и услуги{% endblock %}
{% block Add_Body_Attribute %} style="padding-top:70px;"{% endblock %}
{% block Description %}Тарифы и услуги маркетплейс-агрегатора Окнардия. Размещение предложений пластиковых и деревянных окон, обновление цен на окна, рекламные баннеры и виджеты на сайт оконной компании.{% endblock %}
{% block Keywords %}типовые проекты зданий, панельное строительство, {% for CountSeria in SERIA_NAV_DIM %}серия {{ CountSeria.SERIA_R }}, {{ CountSeria.SERIA_R }}, {% endfor %}, года простойки, регионы построки, распространенность{% endblock %}
{% block Top_JS1%}
<script type="text/javascript">
$(window).load(function(){var images = $('.half');images.each(function(i){$(this).width($(this).width()/2);});});
</script>
{% endblock %}
{% block Main_Content %}<!--- Контент НАЧАЛО --->
<dIv class="container-fluid" itemscope itemtype="http://schema.org/Article">
<div class="row">
<div class="col-md-11">
<ol class="breadcrumb">
<li><a href="/">Главная</a></li>
<li><a href="/blog/">Блог</a></li>
<li><a href="/blog/P1">Стр.2</a></li>
<li class="active">Питерские хрущёвки <nobr>1ЛГ-507</nobr> в&nbsp;базе &laquo;Окнардии&raquo;</li>
</ol>
</div>
<div class="header_blog">
<p class="col-xs-4"><span>{% now "d.F.Y (l) H:i" %}</span><br /><small><img src="/media/img_avatar/avatar_eserg_160x160.png" alt="Аватар: oknardia (администрация)" title="Аватар: oknardia (администрация)" /> <i>администрация</i></small></p>
<script type="text/javascript">(function() {
if (window.pluso)if (typeof window.pluso.start == "function") return;
if (window.ifpluso==undefined) { window.ifpluso = 1;
var d = document, s = d.createElement('script'), g = 'getElementsByTagName';
s.type = 'text/javascript'; s.charset='UTF-8'; s.async = true;
s.src = ('https:' == window.location.protocol ? 'https' : 'http') + '://share.pluso.ru/pluso-like.js';
var h=d[g]('body')[0];
h.appendChild(s);
}})();</script>
<p class="col-xs-7 pluso" style="" data-background="white" data-options="medium,square,line,horizontal,counter,theme=08" data-services="facebook,vkontakte,twitter,odnoklassniki,google,moimir,print,googlebookmark,tumblr,pinterest,email"></p>
<h1 class="col-md-11" itemprop="name">ЗАГОЛОВОК</h1>
</div>
</div>
<DiV class="row">
<DIv class="col-md-11">
<dIv style="padding-top: 1ex;font-size:120%" itemprop="articleBody">
<!--- Пост в блоге :: начало --->
<p>Против некоторых позиций в&nbsp;тарифах &laquo;Окнардии&raquo; давно стояла метка &laquo;с&nbsp;11.2017&raquo;. Она появилась почти два года назад, вместе с&nbsp;тарифами нашего оконного агрегатора когда закончилось <nobr>бета-тестирование</nobr>. И&nbsp;вот этот 11.2017 наступил. А&nbsp;значит цены на&nbsp;размещение цен на&nbsp;размещение оконных предложений меняются. Как и&nbsp;было обещано.</p>
<p><center><img src="/media/img_for_blog/tariff-changing.gif" /></center></p>
<p>Но&nbsp;перед тем, как рассказать детали напомним, что было сделано за&nbsp;эти два года</p><cut />
<ul>
<li>Размещено 22 оконных набора в&nbsp;предложениях. Размещаются как пластиковые, так и&nbsp;деревянные окна.</li>
<li>С&nbsp;&laquo;Окнардией&raquo; работают уже семь <nobr>оконных-компании</nobr> партнёра.</li>
<li>&mdash;&nbsp;Добавлен функционал сравнения характеристик оконных предложений и&nbsp;отдельных компонентов этих предложений.</li>
<li>Создан <a href="/catalog/profile/">каталог профилей</a>, <a href="/catalog/standard_opening/">стандартных проёмов</a>, <a href="/catalog/seria/">типовых серий домов</a>, <a href="https://oknardia.ru/catalog/company/">оконных компаний</a>. В&nbsp;будущем будет каталог стеклопакетов и&nbsp;фурнитуры&hellip; планов много.</li>
<li>Построен <a href="/blogpost/17/Nagljadnoe-sravnenie-harakteristik-okonnyh-profilej/">алгоритм расчёта реальных рейтингов</a> предложений, профилей, стеклопакетов и&nbsp;сервиса компаний. Рейтинги не&nbsp;на&nbsp;базе &laquo;общественного голосования&raquo;, &laquo;опросов&raquo; или измерением <nobr>&laquo;интернет-популярности&raquo;</nobr>, а&nbsp;на&nbsp;базе физических характеристик и&nbsp;измеримых параметров. Таким образом&nbsp;&mdash; это объектовые рейтинги.</li>
<li>Разработан <a href="https://widget.oknardia.ru/">виджет</a>, который позволяет оконной компании реализовать функционал &laquo;Окнардии&raquo; на&nbsp;собственном сайте.</li>
<li>Расширены <nobr><a href="/blogpost/16/Novye-media-vozmozhnosti-uchastnikam-Oknardii-/">медиа-возможности</a></nobr> проекта: баннеры, посты в&nbsp;блоге, специальное выделение.</li>
</ul>
<h3>Теперь о&nbsp;том, что меняется:</h3>
<ul>
<li>Повысились цены на&nbsp;<nobr>бизнес-пакеты</nobr>. В&nbsp;том числе и&nbsp;стартовый. Эти расценки анонсировались в&nbsp;примечаниях к&nbsp;каждому пакету, так что сюрпризом эти изменения не&nbsp;стали. В&nbsp;том числе стало недоступно бесплатное размещение полных наборов&nbsp;&mdash; коммерческих предложений для всех типовых проёмов нашей базы.</li>
<li>Введён новый пакет для бесплатного размещения. В&nbsp;его рамках возможно размещать цен только для третьи набора. Это 20&ndash;25 расчётов цен типовых проёмов. Выбравшим бесплатный, стартовый набор можно следовать двумя стратегиями: разместить цены на&nbsp;полный набор постепенно в&nbsp;течении квартала или обновлять цены только для проёмов, распространённых в&nbsp;вашем регионе.</li>
</ul>
<h3>И&nbsp;что не&nbsp;меняется:</h3>
<ul>
<li>Коммерческие предложения останутся в&nbsp;агрегаторе навсегда. А&nbsp;значит навсегда останутся детальные характеристики предложений, описание оконной компании и&nbsp;ссылки на&nbsp;её сайт.</li>
<li>Тарифы на&nbsp;медийное размещения остались без изменений.</li>
</ul>
<p><a href="/tariff">Новые тарифы размещены на&nbsp;сайте</a>. Скачать шаблоны для платного и&nbsp;бесплатного размещения <a href="/blogpost/2/My_zhdem_vashi_prajs-listy%21/">можно здесь</a>.</p>
&downarrow; &uparrow; &longrightarrow; &longleftarrow; &nearr; &nearr; &searr; &swarr; &nwarr;
<!--- Пост в блоге :: конец --->
</dIv>
</DIv>
</DiV>
<!-- Листалка: НАЧАЛО --><div class="row">
<div class="col-md-11">
<hr class="dotted-black" />
<nav aria-label="перелистывание записей блога">
<ul class="pager">
<li class="previous"><a href="/blogpost/9/Dobavleno-opisanie-serii-1LG-504"><span aria-hidden="true">&larr;</span> Предыдущая запись</a></li>
<li class="previous"></li>
<li class="next"><a href="/blogpost/11/Rejting-Oknardii-i-kak-on-ustroen-(chast-1)">Следующая запись <span aria-hidden="true">&rarr;</span></a></li>
</ul>
</nav>
</div>
</div><!--- Листалка: КОНЕЦ --->
</dIv>
<!---/ Контент КОНЕЦ --->
<div class="row col-md-12">
<div class="col-md-8" >
<details>
<summary>Коменты внутри</summary>
<p>
<details>
<summary>Первый</summary>
<p>Текст комментария 1</p>
</details>
<details>
<summary>Второй</summary>
<p>Текст комментария 2</p>
</details>
<details>
<summary>И внутри...</summary>
<p>
<details>
<summary>Третий</summary>
<p>Текст комментария 3</p>
</details>
</p>
</details>
</p>
</details>
</div>
</div>
{% endblock %}
{% comment %}
{% block Top_Nav_Bar %}
{# ОТЛАДКА, ГАСИМ ВЕРХНЕЕ МЕНЮ #}
{% endblock %}
{% endcomment %}
+275 -76
View File
@@ -1,12 +1,15 @@
# -*- coding: utf-8 -*-
__author__ = 'Sergei Erjemin'
# from transliterate import translit
from PIL import Image, ImageDraw
from oknardia.settings import *
import django.utils.dateformat
import django.utils.timezone
import os
import re
import math
import re
import urllib3
import xml.dom.minidom
def safe_html_spec_symbols(s: str) -> str:
@@ -37,6 +40,7 @@ def safe_html_spec_symbols(s: str) -> str:
result = result.replace('<br>', ' ')
return result
# def Rus2Lat(RusString):
# return translit(re.sub(
# r'<[\s\S]*?>', '', re.sub(r'&[\S]*?;', '-', RusString)
@@ -54,9 +58,9 @@ def safe_html_spec_symbols(s: str) -> str:
#
#
# # Суммирует все цифры в строке через произвольные (не цифровые) разделители
# def SummThrought(StringWSlash):
# StringWSlash = re.sub( r"[^0-9]", u",", StringWSlash)
# ListTerms = StringWSlash.split(u',')
# def sum_through(string_w_slash):
# string_w_slash = re.sub( r"[^0-9]", u",", string_w_slash)
# ListTerms = string_w_slash.split(u',')
# Summ = 0
# for Count in ListTerms:
# try:
@@ -76,15 +80,17 @@ def get_rating_set_for_stars(rating: float = 0.) -> list:
# return []
rating_set = []
for CountStar in range(RARING_STAR):
if RARING_SET_MIN+CountStar * (RARING_SET_MAX - RARING_SET_MIN) / RARING_STAR + 1. <= rating:
if RARING_SET_MIN + CountStar * (RARING_SET_MAX - RARING_SET_MIN) / RARING_STAR + 1. <= rating:
rating_set.append(1)
else:
rating_set.append(0)
return rating_set
#
#
# # рассчитывает дистанцию в км. между двумя геокоординатами
# def GetGeoDistance(lon1, lat1, lat2, lon2):
# def get_geo_distance(lon1, lat1, lat2, lon2):
# lonA, latA, latB, lonB = map(math.radians, [lon1, lat1, lat2, lon2])
# distance = 2 * math.asin(math.sqrt(math.sin((latB - latA) / 2) ** 2 + math.cos(latA) * math.cos(latB) * math.sin(
# (lonB - lonA) / 2) ** 2)) * 6371.032 # РАДИУС ЗЕМЛИ 6371.032 КМ.
@@ -109,33 +115,34 @@ def get_flaps_for_big_pictures(query_set) -> dict:
:return: dict: dict -- словарь с размерами картинок для больших картинок
"""
result = {}
mount_max_h = 0 # максимальная высота оконного проема в квартире
door_h = 0 # высота двери
for i in query_set: # найдём максимальную высоту проёма окна и двери
mount_max_h = 0 # максимальная высота оконного проема в квартире
door_h = 0 # высота двери
for i in query_set: # найдём максимальную высоту проёма окна и двери
if i.iWinHight >= mount_max_h:
mount_max_h = i.iWinHight
if i.bIsDoor >= door_h:
if i.bIsDoor and i.iWinHight >= door_h:
door_h = i.iWinHight
# Проверяем есть ли папка для хранения картинки конфигурации проема и схемы открывания для данной серии.
if not os.path.exists(f"{STATIC_BASE_PATH}/{PATH_FOR_IMG}/{PATH_FOR_BIGIMGFLAPCONFIG}"):
# создаем такую папку если ее нет
os.makedirs(f"{STATIC_BASE_PATH}/{PATH_FOR_IMG}/{PATH_FOR_BIGIMGFLAPCONFIG}")
flaps_dim = [] # список словарей, через который будут передаваться данные в шаблон
mount_bulk = 0 # размещение дверной перемычки
flaps_dim = [] # список словарей, через который будут передаваться данные в шаблон
mount_bulk = 0 # размещение дверной перемычки
i_through = -1
for i in query_set:
img_file_name = "%03dx%03dH%03d" % (i.iWinWidth, i.iWinHight, mount_max_h)
# img_file_name = f"{i.iWinWidth:03d}x{i.iWinHight:03d}H{mount_max_h:03d}"
if i.bIsDoor:
img_file_name += u"D" # добавляем букву D если это дверь
img_file_name += u"D" # добавляем букву D если это дверь
else:
img_file_name += u"W" # добавляем букву W если это окно
img_file_name += u"W" # добавляем букву W если это окно
if i.bIsNearDoor:
img_file_name += u"N" # добавляем букву N если это окно рядом с дверью
mount_bulk = i.iWinHight # размещение дверной перемычки
img_file_name += u"N" # добавляем букву N если это окно рядом с дверью
mount_bulk = i.iWinHight # размещение дверной перемычки
else:
img_file_name += u"-" # добавляем символ отдельное окно (не рядом с дверью)
img_file_name += u"-" # добавляем символ отдельное окно (не рядом с дверью)
# маскируем символы схемы открывания, которые не допустимы в названии файлов
# print(img_file_name)
img_file_name += i.sFlapConfig
img_file_name = img_file_name.replace(">", "G")
img_file_name = img_file_name.replace("<", "L")
@@ -143,11 +150,12 @@ def get_flaps_for_big_pictures(query_set) -> dict:
img_file_name = img_file_name.replace("]", ")")
img_file_name = img_file_name.replace("|", "I")
img_file_name = f"{PATH_FOR_BIGIMGFLAPCONFIG}/{img_file_name}.png"
# print(img_file_name)
# проверяем, есть ли файл с нужной картинкой схемам открывания?
if not os.path.isfile(f"{STATIC_BASE_PATH}/{PATH_FOR_IMG}/{img_file_name}"):
# картинки нет, вызываем функцию для ее создания
make_big_img_win_flap(f"{STATIC_BASE_PATH}/{PATH_FOR_IMG}/{img_file_name}", i.iWinWidth, i.iWinHight,
i.bIsDoor, i.sFlapConfig, mount_max_h, mount_bulk, door_h)
make_big_img_win_flap(f"{STATIC_BASE_PATH}/{PATH_FOR_IMG}/{img_file_name}", i.iWinWidth,
i.iWinHight, i.bIsDoor, i.sFlapConfig, mount_max_h, mount_bulk, door_h)
# чтобы получить разноцветные маркеры меток количества проемов
# получаем последовательность тип: AB, CD, E, FG, H
q_local = ""
@@ -158,12 +166,14 @@ def get_flaps_for_big_pictures(query_set) -> dict:
'url2img': f"img/{img_file_name}",
'iWinWidth': i.iWinWidth,
'iWinHight': i.iWinHight,
'iWinWidth_mm': int(i.iWinWidth*10),
'iWinHight_mm': int(i.iWinHight*10),
'iWinDepth': i.iWinDepth,
'iQuantity': i.iQuantity,
'sDescription': i.sDescripion,
'id': i.id,
'qStr': q_local,
'W': int((i.iWinWidth*250) / mount_max_h)
'W': int((i.iWinWidth * 250) / mount_max_h)
})
result.update({'FLAP_DIM': flaps_dim,
'WIN_DIM': query_set})
@@ -184,19 +194,24 @@ def make_big_img_win_flap(img_file_name_with_path: str, width: int, height: int,
:param height_door: int -- высота дверного проема
:return: None
"""
# width = int(width)
# height = int(height)
# height_max = int(height_max)
# height_mount_bulk = int(height_mount_bulk)
# height_door = int(height_door)
# создаем картинку с нужными размерами
img = Image.new("RGBA", (width * PICT_H / height_max, PICT_H), (255, 255, 255, 0))
# print(img_file_name_with_path)
img = Image.new("RGBA", (int(width * PICT_H / height_max), PICT_H), (255, 255, 255, 0))
print(img_file_name_with_path)
# находим крайние точки периметра (если окно -- выравнено вверх; если дверь -- вниз)
top = 0
left = 0
bottom = img.size[1]
right = img.size[0]
draw = ImageDraw.Draw(img)
draw.rectangle((left, top, right-1, bottom-1), fill=(200, 200, 200, 50), outline=None)
if is_door: # это дверь. Выравнивание по нижней кромке
draw.rectangle((left, top, right - 1, bottom - 1), fill=(200, 200, 200, 50), outline=None)
if is_door: # это дверь. Выравнивание по нижней кромке
top = bottom - (height * PICT_H / height_max)
else: # это не дверь... Выравниваем по верхней кромке
else: # это не дверь... Выравниваем по верхней кромке
if height < height_door:
top = bottom - (height_door * PICT_H / height_max)
bottom = top + (height * PICT_H / height_max)
@@ -205,21 +220,21 @@ def make_big_img_win_flap(img_file_name_with_path: str, width: int, height: int,
draw.line((left, bottom, right, bottom), fill=(125, 125, 125), width=25)
draw.line((left, top, right, top), fill=(125, 125, 125), width=25)
draw.line((left, top, left, bottom), fill=(125, 125, 125), width=25)
draw.line((right, top, right, bottom-6), fill=(125, 125, 125), width=25)
draw.line((right, top, right, bottom - 6), fill=(125, 125, 125), width=25)
dim_flap = flap_analiz(flap_config)
############################################################
# НАЧИНАЕМ ОТРИСОВКУ СТВОРОК ОКНА НА КРТИНКУ
############################################################
v_ratio_max = 0 # число всех долей (частей) для построения пропорций по вертикали
for i in dim_flap: # цикл по рядам створок
v_ratio_max = 0 # число всех долей (частей) для построения пропорций по вертикали
for i in dim_flap: # цикл по рядам створок
v_ratio_max += i["vRatio"]
local_top = top
local_bottom = top
for i in dim_flap: # цикл по рядам створок
local_bottom += i["vRatio"]*flap_h/v_ratio_max
h_ratio_max = 0 # число всех долей (частей) для построения пропорций по горизонтали
for i in dim_flap: # цикл по рядам створок
local_bottom += i["vRatio"] * flap_h / v_ratio_max
h_ratio_max = 0 # число всех долей (частей) для построения пропорций по горизонтали
for j in i["row"]:
h_ratio_max += j["hRatio"]
local_right = 0
@@ -227,12 +242,12 @@ def make_big_img_win_flap(img_file_name_with_path: str, width: int, height: int,
for j in i["row"]:
local_right += j["hRatio"] * right / h_ratio_max
# отрисовка схему открывания створки
if "M" in j["flap"] or "m" in j["flap"] or "м" in j["flap"] or "М" in j["flap"]: # москитная сетка
if "M" in j["flap"] or "m" in j["flap"] or "м" in j["flap"] or "М" in j["flap"]: # москитная сетка
for k in range(local_left + 3, local_right - 3, 12):
draw.line((k, local_top + 7, k, local_bottom - 7), fill=(225, 225, 225, 255), width=2)
for k in range(local_top + 3, local_bottom - 3, 12):
draw.line((local_left + 7, k, local_right - 7, k), fill=(225, 225, 225), width=2)
if is_door: # Это дверь. Выравнивание по нижней кромке
if is_door: # Это дверь. Выравнивание по нижней кромке
top = bottom - (height * PICT_H / height_max)
# рисуем серединную перегородку =
draw.line((left, top + (height_mount_bulk * PICT_H / height_max) - 8,
@@ -242,40 +257,40 @@ def make_big_img_win_flap(img_file_name_with_path: str, width: int, height: int,
right - 12, bottom - 12), fill=(125, 125, 125), width=3)
draw.line((right - 6, top + (height_mount_bulk * PICT_H / height_max) - 4,
left + 12, bottom - 12), fill=(125, 125, 125), width=3)
if "|" in j["flap"]: # вертикальная перегородка |
if "|" in j["flap"]: # вертикальная перегородка |
for k in range(j["flap"].count("|") + 1):
draw.line((local_left + k * (local_right - local_left) / (j["flap"].count("|") + 1), local_top,
local_left + k * (local_right - local_left) / (j["flap"].count("|") + 1), local_bottom),
fill=(125, 125, 125), width=4)
if "=" in j["flap"]: # горизонтальная перегородка =
if "=" in j["flap"]: # горизонтальная перегородка =
for k in range(j["flap"].count("=") + 1):
draw.line((local_left, local_top + k * (local_bottom - local_top) / (j["flap"].count("=") + 1),
local_right, local_top + k * (local_bottom - local_top) / (j["flap"].count("=") + 1)),
fill=(125, 125, 125), width=4)
if "V" in j["flap"]: # откидное открывание V
if "V" in j["flap"]: # откидное открывание V
draw.line((local_right - 12, local_bottom - 12, (local_right + local_left) / 2, local_top + 12),
fill=(225, 125, 125), width=1)
draw.line((local_left + 12, local_bottom - 12, (local_right + local_left) / 2, local_top + 12),
fill=(225, 125, 125), width=1)
if ">" in j["flap"] or "G" in j["flap"]: # поворотное влево >
if ">" in j["flap"] or "G" in j["flap"]: # поворотное влево >
draw.line((local_left + 12, local_top + 12, local_right - 12, (local_bottom + local_top) / 2),
fill=(225, 125, 125), width=1)
draw.line((local_left + 12, local_bottom - 12, local_right - 12, (local_bottom + local_top) / 2),
fill=(225, 125, 125), width=1)
if "<" in j["flap"] or "L" in j["flap"]: # поворотное вправо <
if "<" in j["flap"] or "L" in j["flap"]: # поворотное вправо <
draw.line((local_right - 12, local_bottom - 12, local_left + 12, (local_bottom + local_top) / 2),
fill=(225, 125, 125), width=1)
draw.line((local_right - 12, local_top + 12, local_left + 12, (local_bottom + local_top) / 2),
fill=(225, 125, 125), width=1)
if "X" in j["flap"] or "x" in j["flap"] or "х" in j["flap"] or "Х" in j["flap"] or \
"+" in j["flap"]: # глухое окно +
"+" in j["flap"]: # глухое окно +
draw.line(((local_right + local_left) / 2 - 11, (local_bottom + local_top) / 2,
(local_right + local_left) / 2 + 11, (local_bottom + local_top) / 2),
fill=(225, 125, 125), width=2)
draw.line(((local_right + local_left) / 2, (local_bottom + local_top) / 2 - 11,
(local_right + local_left) / 2, (local_bottom + local_top) / 2 + 11),
fill=(225, 125, 125), width=2)
if u"Z" in j["flap"] or "z" in j["flap"] or "S" in j["flap"] or "s" in j["flap"]: # РАСШИРИТЕЛЬ (спейсер)
if u"Z" in j["flap"] or "z" in j["flap"] or "S" in j["flap"] or "s" in j["flap"]: # РАСШИРИТЕЛЬ (спейсер)
draw.line(((local_left * 3 + local_right) / 4, (local_top * 3 + local_bottom) / 4,
(local_right * 3 - local_left) / 4, (local_top * 3 + local_bottom) / 4),
fill=(225, 125, 125), width=4)
@@ -296,15 +311,15 @@ def make_big_img_win_flap(img_file_name_with_path: str, width: int, height: int,
# второй проход -- рисуем тоненькую рамочку внутри толстых линий
local_top = top
local_bottom = top
for i in dim_flap: # цикл по рядам створок
for i in dim_flap: # цикл по рядам створок
local_bottom += i["vRatio"] * flap_h / v_ratio_max
h_ratio_max = 0 # число всех долей (частей) для построения пропорций по горизонтали
h_ratio_max = 0 # число всех долей (частей) для построения пропорций по горизонтали
for j in i["row"]:
h_ratio_max += j["hRatio"]
local_right = 0
local_left = 0
for j in i["row"]:
local_right += j["hRatio"]*right/h_ratio_max
local_right += j["hRatio"] * right / h_ratio_max
# Отрисовка створки. ПЕРИМЕТР
draw.rectangle((local_left, local_top, local_right, local_bottom), fill=None, outline=(0, 0, 0))
local_left = local_right
@@ -317,11 +332,11 @@ def make_big_img_win_flap(img_file_name_with_path: str, width: int, height: int,
fill=(255, 255, 255, 0), outline=None)
# для окон ниже чем дверь рисуем прямоугольник над проемом
draw.rectangle((left, 0, right - 1, top), fill=(255, 255, 255, 0), outline=None)
if is_door: # Это дверь. Надо нарисовать сверху прямоугольник, на случай если есть очень высокое окно
if is_door: # Это дверь. Надо нарисовать сверху прямоугольник, на случай если есть очень высокое окно
draw.rectangle((left, 0, right - 1, (height_max - height) * PICT_H / height_max),
fill=(255, 255, 255, 0), outline=None)
# рисуем внешнюю тоненькую окантовку
draw.rectangle((left, top, right-1, bottom-1), fill=None, outline=(0, 0, 0))
draw.rectangle((left, top, right - 1, bottom - 1), fill=None, outline=(0, 0, 0))
del draw
# сохраняем картинку
# img.info = {"Comment": "123456"}
@@ -331,20 +346,20 @@ def make_big_img_win_flap(img_file_name_with_path: str, width: int, height: int,
def flap_analiz(flap_config: str) -> list:
# анализ схем открывания.
dim_flap = [] # массив для хранения полной схемы открывания
dim_flap = [] # массив для хранения полной схемы открывания
i_end = len(flap_config)
j = -1 # счётчик горизонтальных рядов (формула, оконный блок)
k = -1 # счётчик вертикальных рядов внутри горизонтальных (створки)
in_flap = False # признак, где идёт разбор: внутри ли сворки или снаружи (схемы открывания или описания рядов)
j = -1 # счётчик горизонтальных рядов (формула, оконный блок)
k = -1 # счётчик вертикальных рядов внутри горизонтальных (створки)
in_flap = False # признак, где идёт разбор: внутри ли сворки или снаружи (схемы открывания или описания рядов)
# начинаем разбор
for i in range(0, i_end):
# посимвольный разбор строки
if i == 0 or flap_config[i - 1] == ".":
dim_flap.append({}) # надо создать новый ряд (фрамуги или ряд створок)
dim_flap.append({}) # надо создать новый ряд (фрамуги или ряд створок)
j += 1
k = -1
dim_flap[j].update({"row": []}) # добавляем в ряд пустой список створок
dim_flap[j].update({"vRatio": 1}) # добавляем число характеризующее пропорцию ряда относительно других
dim_flap[j].update({"row": []}) # добавляем в ряд пустой список створок
dim_flap[j].update({"vRatio": 1}) # добавляем число характеризующее пропорцию ряда относительно других
if not in_flap and (flap_config[i].isdigit() and flap_config[i - 1].isdigit()):
dim_flap[j].update({"vRatio": dim_flap[j]["vRatio"] * 10 + int(flap_config[i])})
continue
@@ -364,34 +379,218 @@ def flap_analiz(flap_config: str) -> list:
in_flap = False
continue
# символ увеличения пропорции
if in_flap and (flap_config[i] == "-" or flap_config[i] == "_"): # для управления пропорциями створок
if in_flap and (flap_config[i] == "-" or flap_config[i] == "_"): # для управления пропорциями створок
dim_flap[j]["row"][k]["hRatio"] += 1
continue
if in_flap and (flap_config[i].isdigit() and flap_config[i - 1].isdigit()):
dim_flap[j]["row"][k].update({"hRatio": dim_flap[j]["row"][k]["hRatio"]*10 + int(flap_config[i])})
dim_flap[j]["row"][k].update({"hRatio": dim_flap[j]["row"][k]["hRatio"] * 10 + int(flap_config[i])})
continue
if in_flap and flap_config[i].isdigit():
dim_flap[j]["row"][k].update({"hRatio": int(flap_config[i])})
continue
if in_flap and (flap_config[i] == "V" # откидное открывание
or flap_config[i] == ">" # поворотное влево
or flap_config[i] == "G" # ^
or flap_config[i] == "L" # поворотное вправо
or flap_config[i] == "<" # ^
or flap_config[i] == "|" # вертикальная перегородка
or flap_config[i] == "=" # горизонтальная перегородка
or flap_config[i] == "X" # глухое окно
or flap_config[i] == "x" # ^
or flap_config[i] == u"х" # ^
or flap_config[i] == u"Х" # ^
or flap_config[i] == "+" # ^
or flap_config[i] == "M" # москитная сетка
or flap_config[i] == "m" # ^
or flap_config[i] == "м" # ^
or flap_config[i] == "М" # ^
or flap_config[i] == "Z" # расширитель (спейсер)
or flap_config[i] == "S" # ^
or flap_config[i] == "z" # ^
or flap_config[i] == "s"): # ^
if in_flap and (flap_config[i] == "V" # откидное открывание
or flap_config[i] == ">" # поворотное влево
or flap_config[i] == "G" # ^
or flap_config[i] == "L" # поворотное вправо
or flap_config[i] == "<" # ^
or flap_config[i] == "|" # вертикальная перегородка
or flap_config[i] == "=" # горизонтальная перегородка
or flap_config[i] == "X" # глухое окно
or flap_config[i] == "x" # ^
or flap_config[i] == "х" # ^
or flap_config[i] == "Х" # ^
or flap_config[i] == "+" # ^
or flap_config[i] == "M" # москитная сетка
or flap_config[i] == "m" # ^
or flap_config[i] == "м" # ^
or flap_config[i] == "М" # ^
or flap_config[i] == "Z" # расширитель (спейсер)
or flap_config[i] == "S" # ^
or flap_config[i] == "z" # ^
or flap_config[i] == "s"): # ^
dim_flap[j]["row"][k].update({"flap": dim_flap[j]["row"][k]["flap"] + flap_config[i]})
return dim_flap
def make_flap_mini_pictures(path_to_img_file: str, str_flap_config: str) -> None:
""" Функция создает файл мини-картинки схем открывания окна
:param path_to_img_file: путь к файлу с изображением
:param str_flap_config: строка с описанием схемы открывания
:param is_door:
:return:
"""
# print(path_to_img_file, str_flap_config, is_door)
dim_flap = flap_analiz(str_flap_config)
v_ratio_max = 0 # число всех долей (частей) для построения пропорций по вертикали
h_ratio_max = 0 # число всех долей (частей) для построения пропорций по горизонтали
for i in dim_flap:
local_h_ratio_max = 0
v_ratio_max += i["vRatio"]
for j in i["row"]:
local_h_ratio_max += j["hRatio"]
if local_h_ratio_max > h_ratio_max:
h_ratio_max = local_h_ratio_max
img = Image.new("RGBA", (h_ratio_max * PICT_MINWI + (h_ratio_max + 1) * 3, PICT_MINIH + 6), (255, 255, 255, 0))
top = 0
left = 0
bottom = img.size[1]
right = img.size[0]
flap_h = bottom - top
draw = ImageDraw.Draw(img)
draw.rectangle((left, top, right, bottom), fill=(200, 200, 200, 50), outline=None)
# рисуем внешнюю рамку
draw.line((left, bottom - 1, right, bottom - 1), fill=(125, 125, 125), width=5) # нижняя
draw.line((left, top, right, top), fill=(125, 125, 125), width=5) # верхняя
draw.line((left, top, left, bottom), fill=(125, 125, 125), width=5) # левая
draw.line((right - 1, top, right - 1, bottom), fill=(125, 125, 125), width=5) # правая
############################################################
# НАЧИНАЕМ ОТРИСОВКУ СТВОРОК ОКНА НА КРТИНКУ
############################################################
local_top = top
local_bottom = top
for i in dim_flap: # цикл по рядам створок
local_bottom += i["vRatio"] * flap_h / v_ratio_max
local_right = 0
local_left = 0
for j in i["row"]:
local_right += j["hRatio"] * right / h_ratio_max
# отрисовка схему открывания створки
if "X" in j["flap"] or "x" in j["flap"] or "х" in j["flap"] or "Х" in j["flap"] or "+" in j["flap"]:
# глухое окно (рисуем крестик)
draw.line(((local_right + local_left) / 2 - 5, (local_bottom + local_top) / 2,
(local_right + local_left) / 2 + 5, (local_bottom + local_top) / 2),
fill=(105, 105, 225), width=1)
draw.line(((local_right + local_left) / 2, (local_bottom + local_top) / 2 - 5,
(local_right + local_left) / 2, (local_bottom + local_top) / 2 + 5),
fill=(105, 105, 225), width=1)
if "V" in j["flap"]: # откидное открывание
draw.line((local_right - 3, local_bottom - 4, (local_right + local_left) / 2, local_top + 3),
fill=(125, 225, 125), width=1)
draw.line((local_left + 3, local_bottom - 4, (local_right + local_left) / 2, local_top + 3),
fill=(125, 225, 125), width=1)
if ">" in j["flap"] or "G" in j["flap"]: # поворотное влево
draw.line((local_left + 3, local_top + 3, local_right - 3, (local_bottom + local_top) / 2),
fill=(225, 125, 125), width=1)
draw.line((local_left + 3, local_bottom - 3, local_right - 3, (local_bottom + local_top) / 2),
fill=(225, 125, 125), width=1)
if "<" in j["flap"] or "L" in j["flap"]: # поворотное вправо
draw.line((local_right - 3, local_bottom - 3, local_left + 3, (local_bottom + local_top) / 2),
fill=(225, 125, 125), width=1)
draw.line((local_right - 3, local_top + 3, local_left + 3, (local_bottom + local_top) / 2),
fill=(225, 125, 125), width=1)
if "Z" in j["flap"] or "S" in j["flap"] or "z" in j["flap"] or "s" in j["flap"]: # расширитель (спейсер)
draw.line(((local_left * 3 + local_right) / 4, (local_top * 3 + local_bottom) / 4,
(local_right * 3 - local_left) / 4, (local_top * 3 + local_bottom) / 4),
fill=(225, 125, 125), width=1)
draw.line(((local_left * 3 + local_right) / 4, (local_bottom * 3 + local_top) / 4,
(local_right * 3 - local_left) / 4, (local_bottom * 3 + local_top) / 4),
fill=(225, 125, 125), width=1)
draw.line(((local_left * 3 + local_right) / 4, (local_top * 3 + local_bottom) / 4,
(local_right * 3 - local_left) / 4, (local_bottom * 3 + local_top) / 4),
fill=(225, 125, 125), width=1)
if "M" in j["flap"] or "m" in j["flap"] or "м" in j["flap"] or "М" in j["flap"]: # москитная сетка
draw.rectangle((local_left, local_top, local_right, local_bottom), fill=(16, 125, 16, 50), outline=None)
# Отрисовка створки. ПЕРИМЕТР
draw.line((local_left, local_bottom, local_right, local_bottom), fill=(125, 125, 125), width=3)
draw.line((local_left, local_top, local_right, local_top), fill=(125, 125, 125), width=3)
draw.line((local_left, local_top, local_left, local_bottom), fill=(125, 125, 125), width=3)
draw.line((local_right, local_top, local_right, local_bottom), fill=(125, 125, 125), width=3)
local_left = local_right
local_top = local_bottom
del draw
# сохраняем картинку
img.save(path_to_img_file)
return
def get_flaps_for_mini_pictures(flap_config: str) -> str:
"""
Функция возвращает строку с именем файла мини-картинки для схемы открывания полученной в flap_config
:param flap_config: str - строка с схемой открывания.
:return: str - строка с именем файла мини-картинки.
"""
image_file_name = flap_config.upper()
image_file_name = image_file_name.replace(">", "G")
image_file_name = image_file_name.replace("<", "L")
image_file_name = image_file_name.replace("|", "I")
image_file_name = image_file_name.replace("[", "(")
image_file_name = image_file_name.replace("]", ")")
image_file_name = image_file_name.replace("/", "-")
image_file_name = image_file_name.replace("\\", "-")
image_file_name = image_file_name.replace(".", "-") + ".png"
image_file_name = f"{PATH_FOR_IMG}/{PATH_FOR_IMGFLAPCONFIG}/{image_file_name}"
if not os.path.isfile(f"{STATIC_BASE_PATH}/{image_file_name}"):
make_flap_mini_pictures(f"{STATIC_BASE_PATH}/{image_file_name}", flap_config.upper())
return image_file_name
def get_geo_distance(lon1: float, lat1: float, lat2: float, lon2: float) -> float:
""" Функция возвращает расстояние в км. между двумя геокоординатами.
:param lon1: float - долгота первой точки.
:param lat1: float - широта первой точки.
:param lat2: float - широта второй точки.
:param lon2: float - долгота второй точки.
:return: float - расстояние в км. между двумя геокоординатами.
"""
lon_a, lat_a, lat_b, lon_b = map(math.radians, [lon1, lat1, lat2, lon2])
distance = 2 * math.asin(math.sqrt(math.sin((lat_b - lat_a) / 2) ** 2 + math.cos(lat_a) * math.cos(lat_b)
* math.sin((lon_b - lon_a) / 2) ** 2)) * 6371.032 # РАДИУС ЗЕМЛИ 6371.032 КМ.
return distance
def get_yandex_geocode_by_address(address_string: str) -> list:
""" Функция получает от Яндекс-Карт геокоординаты соответсвующее адресу.
:param address_string: str -- строка с адресом (utf-8)
:return: geocode: list -- [Долгота (longitude), Широта (latitude)]
"""
geocode = [0, 0]
http = urllib3.PoolManager()
response_api = http.request('GET', f"http://geocode-maps.yandex.ru/1.x/?apikey={YANDEX_MAPS_API_KEY}"
f"&geocode={address_string}")
# print(response_api.data.decode('utf-8'))
try:
data = xml.dom.minidom.parseString(response_api.data.decode('utf-8'))
data = data.getElementsByTagName('pos')[0]
data = data.childNodes[0].data
data = tuple(data.split())
geocode[0] = float(data[0]) # Долгота (longitude): Восточная + (E) // Западная - (W)
geocode[1] = float(data[1]) # Широта (latitude): Северная + (N) // Южная - (S)
# print(geocode)
return geocode
except:
# Тут может быть много разных типов ошибок urllub3 связанных с получением данных от Яндекс-Карт
# Перечень исключений: https://urllib3.readthedocs.io/en/stable/reference/urllib3.exceptions.html
# Возвращаем нулевые координаты, как признак, что данные не получены.
return [0, 0]
def sum_through(string_w_slash: str) -> int:
""" Суммирует все цифры (числа) в строке через произвольные (не цифровые) разделители
:param string_w_slash: str -- строка с цифрами (числами) через разделители
:return: summ: int -- сумма цифр (чисел) в строке
"""
string_w_slash = re.sub(r"[^0-9]", ",", string_w_slash)
list_terms = string_w_slash.split(',')
sum_result = 0
for Count in list_terms:
try:
sum_result += int(Count)
except ValueError:
pass
return sum_result
def touch_reload_wsgi(s: str = ''):
""" Функция перезагружает WSGI-сервер.
:return: None
"""
with open(TOUCH_RELOAD, 'a', encoding="utf-8") as f:
f.write(f'\nreload wsgi by cash-template {s}'
f' {django.utils.dateformat.format(django.utils.timezone.now(), "Y-m-d H:i:s")}')
+244 -4
View File
@@ -5,9 +5,11 @@ from django.http import HttpRequest, HttpResponse
from django.template.loader import render_to_string
from django.utils import timezone
from oknardia.settings import *
from oknardia.models import PVCprofiles, Seria_Info, Win_MountDim, Building_Info
from oknardia.models import PVCprofiles, Seria_Info, Win_MountDim, Building_Info, MerchantBrand
from web.report1 import get_last_all_user_visit_list, get_last_user_visit_cookies, get_last_user_visit_list
from web.add_func import normalize, get_rating_set_for_stars, get_flaps_for_big_pictures
from web.add_func import normalize, get_rating_set_for_stars, get_flaps_for_big_pictures,\
get_flaps_for_mini_pictures, touch_reload_wsgi
import django.utils.dateformat
import time
import json
import random
@@ -465,7 +467,7 @@ def catalog_seria(request: HttpRequest) -> HttpResponse:
return render(request, "catalog/catalog_seria.html", to_template)
def catalog_seria_info(request: HttpRequest, seria_name_translit: str = "II-49", seria_id: int = 12) -> HttpResponse:
def catalog_seria_info(request: HttpRequest, seria_name_translit: None, seria_id: int = 843) -> HttpResponse:
"""
КАТАЛОГ ТИПОВЫЙ СЕРИЙ: страница детальной информацией по серии зданий
@@ -488,6 +490,7 @@ def catalog_seria_info(request: HttpRequest, seria_name_translit: str = "II-49",
light_template_w_path = f"{TEMPLATES[0]['DIRS'][0]}/{light_template}"
# print(f"{TEMPLATES[0]['DIRS'][0]}/{light_template}")
# print(light_template_w_path)
# print(light_template_w_path)
if os.path.isfile(light_template_w_path):
is_hard_template = False
else:
@@ -613,6 +616,7 @@ def catalog_seria_info(request: HttpRequest, seria_name_translit: str = "II-49",
# file.write(AA.encode('utf-8'))
file.write(string_prerender)
file.close()
touch_reload_wsgi(light_template_w_path)
else:
seria_name = Seria_Info.objects.get(id=seria_id).sName
to_template.update({'THIS_SERIA_NAME': seria_name})
@@ -683,7 +687,8 @@ def all_seria_nav(seria_id: int, q_seria) -> (int, dict):
if count_seria.id == seria_id:
this_return.update({"THIS_SERIA_NAME": count_seria.sName,
"THIS_SERIA_DESCRIPTION": count_seria.sSeriaDescription})
one_seria.update({"SERIA_L": ""})
# one_seria.update({"SERIA_L": ""})
one_seria.update({"SERIA_L": pytils.translit.slugify(count_seria.sName)})
else:
one_seria.update({"SERIA_L": pytils.translit.slugify(count_seria.sName)})
seria_nav_dim.append(one_seria)
@@ -809,3 +814,238 @@ def seria_info_geo_code(seria_id: str = '12') -> dict:
"CONDITION_MIN": condition_min})
# print(seria_to_geo)
return data_return
def standard_opening(request: HttpRequest) -> HttpResponse:
time_start = time.time()
to_template = {} # словарь, для передачи шаблону
q_seria = Seria_Info.objects.raw('SELECT oknardia_seria_info.id, oknardia_seria_info.sName '
'FROM oknardia_seria_info '
'WHERE oknardia_seria_info.id = oknardia_seria_info.kRoot_id '
'ORDER BY oknardia_seria_info.sName;')
to_template.update({'SERIAS': list(q_seria)})
q_win_opening = Win_MountDim.objects.raw(
'SELECT oknardia_win_mountdim.*,'
' oknardia_seria_info.sName,'
' oknardia_seria_info.id AS ID_Seria '
'FROM oknardia_win_mountdim'
' INNER JOIN oknardia_mountdim2apartment'
' ON oknardia_win_mountdim.id = oknardia_mountdim2apartment.kMountDim_id'
' RIGHT OUTER JOIN oknardia_apartment_type'
' ON oknardia_apartment_type.id = oknardia_mountdim2apartment.kApartment_id'
' RIGHT OUTER JOIN oknardia_seria_info'
' ON oknardia_apartment_type.kSeria_id = oknardia_seria_info.id '
'WHERE oknardia_seria_info.id = oknardia_seria_info.kRoot_id '
'GROUP BY oknardia_win_mountdim.iWinWidth, oknardia_win_mountdim.iWinHight,'
' oknardia_win_mountdim.bIsDoor, oknardia_win_mountdim.bIsNearDoor,'
' oknardia_win_mountdim.sFlapConfig, oknardia_win_mountdim.id,'
' oknardia_seria_info.sName, oknardia_seria_info.id '
'ORDER BY oknardia_win_mountdim.iWinWidth DESC,'
' oknardia_win_mountdim.iWinHight DESC,'
' oknardia_win_mountdim.bIsNearDoor,'
' oknardia_win_mountdim.bIsDoor,'
' oknardia_win_mountdim.id,'
' oknardia_seria_info.sName;')
list_windows_opening = []
tmp_id = 0
for i in q_win_opening:
if tmp_id != i.id:
tmp_id = i.id
image_file_name = get_flaps_for_mini_pictures(i.sFlapConfig)
list_windows_opening.append({
"ID": i.id,
"INCLUDING_IN_SERIA": [{
"ID": i.ID_Seria,
"NAME_T": pytils.translit.slugify(i.sName),
"NAME": i.sName
}],
"INCLUDING_IN_SERIA_ID": [],
"URL2IMG": image_file_name,
"FLAP_CONFIG": i.sFlapConfig,
"DESCRIPTION": i.sDescripion.split(" для")[0].split(" (")[0],
"DESCRIPTION_L": i.sDescripion,
"IS_DOOR": i.bIsDoor,
"IS_NEAR_DOOR": i.bIsNearDoor,
"H": i.iWinHight * 10,
"W": i.iWinWidth * 10
})
else:
list_windows_opening[-1]["INCLUDING_IN_SERIA"].append({
"ID": i.ID_Seria,
"NAME_T": pytils.translit.slugify(i.sName),
"NAME": i.sName
})
to_template.update({
'LIST_WIN_OPENING': list_windows_opening,
# получаем последние визиты клиента через куки
'LAST_VISIT': get_last_user_visit_list(get_last_user_visit_cookies(request)[:3]),
# получаем последние визиты всех посетителей из базы
# id2log, log_visit = get_last_all_user_visit_list()
'LOG_VISIT': get_last_all_user_visit_list(),
'ticks': float(time.time() - time_start)
})
return render(request, "catalog/catalog_standard_opening.html", to_template)
def catalog_company(request: HttpRequest) -> HttpResponse:
time_start = time.time()
to_template = {} # словарь, для передачи шаблону
q_company = MerchantBrand.objects.raw('SELECT'
' oknardia_merchantbrand.id,'
' oknardia_merchantbrand.sMerchantName,'
' oknardia_merchantbrand.pMerchantLogo,'
' oknardia_merchantbrand.sMerchantMainURL,'
' COUNT(oknardia_priceoffer.id) AS NumOffers,'
' AVG(oknardia_priceoffer.fOfferPrice) AS PriceAVG,'
' MAX(oknardia_priceoffer.dOfferModify) AS lastUpdate,'
' Q.NumSets,'
' Q.RatingAVG,'
' 1 AS STARS '
'FROM (SELECT'
' COUNT(oknardia_setkit.sSetName) AS NumSets,'
' oknardia_merchantoffice.kMerchantName_id AS Q_ID,'
' AVG(oknardia_setkit.fSetRating) AS RatingAVG'
' FROM oknardia_merchantoffice'
' INNER JOIN oknardia_ouruser'
' ON oknardia_ouruser.kMerchantOffice_id = oknardia_merchantoffice.id'
' INNER JOIN oknardia_setkit'
' ON oknardia_setkit.kSet2User_id = oknardia_ouruser.id'
' GROUP BY oknardia_merchantoffice.id,'
' oknardia_merchantoffice.kMerchantName_id) AS Q,'
' oknardia_ouruser'
' INNER JOIN oknardia_merchantoffice'
' ON oknardia_ouruser.kMerchantOffice_id = oknardia_merchantoffice.id'
' INNER JOIN oknardia_priceoffer'
' ON oknardia_priceoffer.kOfferFromUser_id = oknardia_ouruser.id'
' INNER JOIN oknardia_merchantbrand'
' ON oknardia_merchantoffice.kMerchantName_id = oknardia_merchantbrand.id'
' WHERE Q_ID = oknardia_merchantoffice.kMerchantName_id '
'GROUP BY oknardia_merchantoffice.kMerchantName_id '
'ORDER BY Q.RatingAVG DESC;')
list_company = list(q_company)
for i in list_company:
i.STARS = get_rating_set_for_stars(i.RatingAVG)
i.NumSets = pytils.numeral.get_plural(i.NumSets, u"оконный набор, оконных набора, оконных наборов")
i.NumOffers = pytils.numeral.get_plural(i.NumOffers, u"вариант, варианта, вариантов")
i.lastUpdate = pytils.dt.distance_of_time_in_words(int(django.utils.dateformat.format(i.lastUpdate, 'U')))
i.sMerchantMainURL = pytils.translit.slugify(i.sMerchantName)
# print("NAME:", i.sMerchantName, "\tNumSets:", i.NumSets, "\tNumOffers:", i.NumOffers,
# "\t:AverageRating:", i.RatingAVG, "\tAveragePrice:", i.PriceAVG, "\tSTARS:", i.STARS)
to_template.update({
'COMPANIES': list_company,
# получаем последние визиты клиента через куки
'LAST_VISIT': get_last_user_visit_list(get_last_user_visit_cookies(request)[:3]),
# получаем последние визиты всех посетителей из базы
# id2log, log_visit = get_last_all_user_visit_list()
'LOG_VISIT': get_last_all_user_visit_list(),
'ticks': float(time.time() - time_start)
})
return render(request, "catalog/catalog_company.html", to_template)
def catalog_company_detail(request: HttpRequest, company_id: str, company_name_slug: str) -> HttpResponse:
time_start = time.time()
to_template = {} # словарь, для передачи шаблону
company_id = int(company_id)
q_by_id = MerchantBrand.objects.get(id=company_id)
if pytils.translit.slugify(q_by_id.sMerchantName) != company_name_slug:
return redirect('/catalog/company/%d-%s' % (company_id, pytils.translit.slugify(q_by_id.sMerchantName)))
to_template.update({'COMPANY': q_by_id.sMerchantName})
to_template.update({'COMPANY_ID': company_id})
to_template.update({'COMPANY_T': company_name_slug})
list_not = [u"нет", u"", ""]
to_template.update({'LIST_NOT': list_not})
q_sets = MerchantBrand.objects.raw(f"SELECT"
f" COUNT(oknardia_priceoffer.id) AS NumOffers,"
f" AVG(oknardia_priceoffer.fOfferPrice) AS priceAVG,"
f" MAX(oknardia_priceoffer.dOfferModify) AS lastUpdate,"
f" MIN(oknardia_priceoffer.dOfferCreate) AS earlyCreation,"
f" oknardia_merchantbrand.*,"
f" oknardia_merchantoffice.*,"
f" oknardia_merchantoffice.id AS idMERCH,"
f" oknardia_setkit.*,"
f" oknardia_setkit.id AS idSET,"
f" oknardia_pvcprofiles.*,"
f" oknardia_pvcprofiles.id AS idPVC,"
f" oknardia_glazing.*, "
f" oknardia_glazing.id AS idGLAZ "
f"FROM oknardia_ouruser"
f" INNER JOIN oknardia_merchantoffice"
f" ON oknardia_ouruser.kMerchantOffice_id = oknardia_merchantoffice.id"
f" INNER JOIN oknardia_merchantbrand"
f" ON oknardia_merchantoffice.kMerchantName_id = oknardia_merchantbrand.id"
f" INNER JOIN oknardia_setkit"
f" ON oknardia_setkit.kSet2User_id = oknardia_ouruser.id"
f" INNER JOIN oknardia_priceoffer"
f" ON oknardia_priceoffer.kOffer2SetKit_id = oknardia_setkit.id"
f" INNER JOIN oknardia_pvcprofiles"
f" ON oknardia_setkit.kSet2PVCprofiles_id = oknardia_pvcprofiles.id"
f" INNER JOIN oknardia_glazing"
f" ON oknardia_setkit.kSet2Glazing_id = oknardia_glazing.id "
f"WHERE oknardia_merchantbrand.id = {company_id} "
f"AND oknardia_priceoffer.sOfferActive = TRUE "
f"GROUP BY oknardia_merchantoffice.id,"
f" oknardia_setkit.id,"
f" oknardia_setkit.fSetRating "
f"ORDER BY oknardia_setkit.fSetRating DESC;")
list_sets = list(q_sets)
for i in list_sets:
i.sMerchantMainURL = {"URL": i.sMerchantMainURL,
"URL_VIEW": re.sub(r"(?:^http://|^https://|/$|www\.)", "", i.sMerchantMainURL)}
k = random.randint(1, int(len(i.sOfficeEmails)/2) - 1)
i.sOfficeEmails = [i.sOfficeEmails[0:k], i.sOfficeEmails[k:-k], i.sOfficeEmails[-k:]]
to_template.update({'IMG_FOR_BLOG': i.pMerchantLogo})
i.fSetRating = {"RATING": i.fSetRating,
"STARS": get_rating_set_for_stars(i.fSetRating)}
i.lastUpdate = pytils.dt.distance_of_time_in_words(int(django.utils.dateformat.format(i.lastUpdate, 'U')))
i.earlyCreation = pytils.dt.distance_of_time_in_words(int(django.utils.dateformat.format(i.earlyCreation, 'U')))
i.sProfileName = {"NAME": i.sProfileName,
"NAME_T": pytils.translit.slugify(i.sProfileName)}
i.sProfileManufacturer = {"NAME": i.sProfileManufacturer,
"NAME_T": pytils.translit.slugify(i.sProfileManufacturer)}
i.fProfileSeals = pytils.numeral.sum_string(i.fProfileSeals, pytils.numeral.MALE, u"контур, контура, контуров")
if i.sSetImplementCatch.lower() in list_not:
i.sSetImplementCatch = ""
if i.sSetClimateControl.lower() in list_not:
i.sSetClimateControl = ""
if len(i.sProfileReinforcement) > 0:
i.sProfileReinforcement = i.sProfileReinforcement[0].lower()+i.sProfileReinforcement[1:]
if len(i.sSetSill) > 0:
i.sSetSill = i.sSetSill[0].lower()+i.sSetSill[1:]
if len(i.sSetPanes) > 0:
i.sSetPanes = i.sSetPanes[0].lower()+i.sSetPanes[1:]
if len(i.sSetSlope) > 0:
i.sSetSlope = i.sSetSlope[0].lower()+i.sSetSlope[1:]
if len(i.sSetUninstallInstall) > 0:
i.sSetUninstallInstall = i.sSetUninstallInstall[0].lower()+i.sSetUninstallInstall[1:]
if len(i.sSetDelivery) > 0:
i.sSetDelivery = i.sSetDelivery[0].lower()+i.sSetDelivery[1:]
if len(i.sSetOtherConditions) > 0:
i.sSetOtherConditions = i.sSetOtherConditions[0].lower()+i.sSetOtherConditions[1:]
to_template.update({
'SETS': list_sets,
# получаем последние визиты клиента через куки
'LAST_VISIT': get_last_user_visit_list(get_last_user_visit_cookies(request)[:3]),
# получаем последние визиты всех посетителей из базы
# id2log, log_visit = get_last_all_user_visit_list()
'LOG_VISIT': get_last_all_user_visit_list(),
'ticks': float(time.time() - time_start)
})
return render(request, "catalog/catalog_company_detail.html", to_template)
def report_all_info_seria_redirect(request: HttpRequest, seria_id: str = "12") -> HttpResponse:
""" Переадресация старых URL, т.к. их сколько-то есть (было) во внешних ссылках
:param request: HttpRequest -- запрос
:param seria_id: str -- id серии типового строительства
:return:
"""
try:
seria_id = int(seria_id)
q_seria = Seria_Info.objects.get(id=seria_id)
if q_seria.id == q_seria.kRoot_id:
return redirect("f/catalog/seria/{pytils.translit.slugify(q_seria.sName)}/all{seria_id}")
except (Seria_Info.DoesNotExist, ValueError):
return redirect("/catalog/seria")
return redirect("/catalog/seria")
+2 -2
View File
@@ -5,6 +5,7 @@ from django.core.exceptions import ObjectDoesNotExist
from time import time
from oknardia.settings import *
from oknardia.models import Seria_Info
from web.catalog import all_seria_nav
# from oknardia.catalog import all_seria_nav
import math
import os
@@ -60,7 +61,6 @@ def statistic_menu(request: HttpRequest) -> HttpResponse:
:return: HttpResponse -- исходящий http-ответ
"""
time_start = time()
template = "seria_info/all_stat.html"
to_template = {}
seria_id, for_seria_nav = seria_nav(0)
to_template.update(for_seria_nav)
@@ -93,4 +93,4 @@ def statistic_menu(request: HttpRequest) -> HttpResponse:
# print(data2pie)
to_template.update({'DATA2PIE': data2pie})
to_template.update({'ticks': float(time()-time_start)})
return render(request, template, to_template)
return render(request, "seria_info/all_stat.html", to_template)
+712
View File
@@ -0,0 +1,712 @@
# -*- coding: utf-8 -*-
from django.core.exceptions import ObjectDoesNotExist
from django.shortcuts import render, redirect
from django.http import HttpRequest, HttpResponse
from oknardia.models import Win_MountDim, PriceOffer, Apartment_Type, Seria_Info, LogVisitPriceReport
from oknardia.settings import *
from web.report1 import get_last_all_user_visit_list, get_last_user_visit_cookies, get_last_user_visit_list
from web.add_func import normalize, get_rating_set_for_stars, get_flaps_for_big_pictures, get_flaps_for_mini_pictures, \
get_geo_distance
import django.utils.dateformat
import time
import os
import re
import json
import pytils
def report_price_frame(apartment_id: int, mount_dim_per_offer: int, address_longitude: float, address_latitude: float,
frame_begin_n: int = 0, brand_id: int = 0, win_id: int = 0) -> dict:
""" Формируем выдачу цен для фрейма
:param apartment_id: int -- ID типа квартиры, для которой получаем ценовые предложения
:param mount_dim_per_offer: int -- число различных оконых проемов в этой квартире (чтобы отсеять предложения,
в которых не представлены все проемы)
:param address_longitude: float -- долгота адреса (геокоордината), чтобы рассчитать удаленность компании
предоставившей коммерческие предложения
:param address_latitude: float -- широта адреса (геокоордината), чтобы рассчитать удаленность компании
предоставившей коммерческие предложения
:param frame_begin_n: int -- Номер записи с которой начинается фрейм с ценами (с какого предложения начинать)
:param brand_id: int -- ID бренда, если выбран бренд, то отображаем только предложения этого бренда
(нужно для виджета, где отображаются предложения только от одной компании)
если 0, то отображаем все предложения
:param win_id: int -- ID окна, если выбрано окно, то отображаем только предложения этого окна
:return: dict -- словарь данных для отображения в фрейме (цены предложений и их характеристики)
"""
# ценовая выдача
time_for_meta = 0 # время для мета-данных в HTML-кода
apartment_id = int(apartment_id)
mount_dim_per_offer = int(mount_dim_per_offer)
address_longitude = float(address_longitude)
address_latitude = float(address_latitude)
frame_begin_n = int(frame_begin_n)
brand_id = int(brand_id)
win_id = int(win_id)
add_to_sql_for_widget = ""
offer_per_frame = OFFER_PER_FRAME
if brand_id != 0:
# Это вывод для выджета. Нужны цены только по определенному поставщику
add_to_sql_for_widget = f" AND oknardia_merchantbrand.id = {brand_id} "
offer_per_frame = 1000 # Фреймовый вывод не нужен... фигачим сразу целую 1000 предложений.
if int(apartment_id) == 0 and int(win_id) != 0:
# если выводим цены только для одного проема
offer_per_frame = OFFER_PER_FRAME_FOR_ONE_FLAP
q_price_offer = PriceOffer.objects.raw(
f"SELECT"
f" oknardia_priceoffer.id, oknardia_priceoffer.iOfferImpressions,"
f" oknardia_priceoffer.fOfferPrice, oknardia_priceoffer.dOfferModify,"
f" oknardia_priceoffer.fOfferRating, oknardia_priceoffer.sOfferFlapConfig,"
f" oknardia_priceoffer.iOfferViews, oknardia_priceoffer.sOfferActive,"
f" oknardia_win_mountdim.sDescripion, oknardia_win_mountdim.id AS mID, "
f" oknardia_win_mountdim.bIsNearDoor, oknardia_win_mountdim.bIsDoor,"
f" oknardia_win_mountdim.iWinWidth, oknardia_win_mountdim.iWinHight,"
f" oknardia_setkit.id AS setID,"
f" oknardia_setkit.sSetName, oknardia_setkit.dSetModify,"
f" oknardia_setkit.sSetClimateControl, oknardia_setkit.sSetSill,"
f" oknardia_setkit.sSetImplementAll, oknardia_setkit.sSetImplementHandles,"
f" oknardia_setkit.sSetImplementHinges, oknardia_setkit.sSetImplementLatch,"
f" oknardia_setkit.sSetImplementLimiter, oknardia_setkit.sSetImplementCatch,"
f" oknardia_setkit.sSetPanes, oknardia_setkit.sSetSlope,"
f" oknardia_setkit.sSetOtherConditions, oknardia_setkit.sSetActive,"
f" oknardia_setkit.bSetDelivery, oknardia_setkit.sSetDelivery,"
f" oknardia_setkit.sSetUninstallInstall, oknardia_setkit.bSetUninstallInstall,"
f" oknardia_setkit.fSetRating, oknardia_setkit.iSetNumEval,"
f" oknardia_setkit.iSetImpressions, oknardia_setkit.iSetViews,"
f" (oknardia_setkit.dSetCommercialUntil > NOW()) AS bCommercial,"
f" oknardia_merchantoffice.sOfficePhones, "
f" oknardia_merchantoffice.sOfficeDiscountMetaFormula,"
f" oknardia_merchantoffice.sOfficeName, oknardia_merchantoffice.sOfficeAddress,"
f" oknardia_glazing.fGlazingRating,"
f" oknardia_glazing.sGlazingName, oknardia_glazing.sGlazingBriefDescription,"
f" oknardia_glazing.sGlazingMark, oknardia_glazing.sGlazingToning,"
f" oknardia_pvcprofiles.sProfileBriefDescription, oknardia_pvcprofiles.id AS pwc_id,"
f" oknardia_pvcprofiles.sProfileReinforcement, oknardia_pvcprofiles.sProfileSealDescription,"
f" oknardia_pvcprofiles.sProfileName, oknardia_pvcprofiles.sProfileColor,"
f" oknardia_pvcprofiles.fProfileRating, oknardia_pvcprofiles.sProfileManufacturer,"
f" oknardia_merchantbrand.sMerchantName, oknardia_merchantbrand.pMerchantLogo,"
f" oknardia_merchantbrand.sMerchantMainURL, oknardia_merchantbrand.id AS brand_id,"
f" 1 AS iQuantity, 0 AS fOfficeGeoCode_Longitude, 0 AS fOfficeGeoCode_Latitude "
f"FROM oknardia_priceoffer"
f" INNER JOIN oknardia_win_mountdim"
f" ON oknardia_priceoffer.kOffer2MountDim_id = oknardia_win_mountdim.id"
f" INNER JOIN oknardia_setkit"
f" ON oknardia_priceoffer.kOffer2SetKit_id = oknardia_setkit.id"
f" INNER JOIN oknardia_ouruser"
f" ON oknardia_setkit.kSet2User_id = oknardia_ouruser.id"
f" INNER JOIN oknardia_merchantoffice"
f" ON oknardia_ouruser.kMerchantOffice_id = oknardia_merchantoffice.id"
f" INNER JOIN oknardia_glazing"
f" ON oknardia_setkit.kSet2Glazing_id = oknardia_glazing.id"
f" INNER JOIN oknardia_pvcprofiles"
f" ON oknardia_setkit.kSet2PVCprofiles_id = oknardia_pvcprofiles.id"
f" INNER JOIN oknardia_merchantbrand"
f" ON oknardia_merchantoffice.kMerchantName_id = oknardia_merchantbrand.id "
f"WHERE oknardia_priceoffer.sOfferActive IS TRUE"
f" AND oknardia_setkit.sSetActive IS TRUE "
f" AND oknardia_win_mountdim.id = {int(win_id)}"
f" {add_to_sql_for_widget} "
f"ORDER BY"
f" oknardia_priceoffer.dOfferModify DESC "
f"LIMIT {int(frame_begin_n)}, 10000;")
else:
# если выводим цены для типовой квартиры
# print("Нужно несколько окон для квартиры")
q_price_offer = PriceOffer.objects.raw(
f"SELECT"
f" oknardia_priceoffer.*,"
f" oknardia_win_mountdim.*,"
f" oknardia_setkit.*,"
f" oknardia_merchantoffice.*,"
f" oknardia_glazing.*,"
f" oknardia_pvcprofiles.*,"
f" oknardia_merchantbrand.*,"
f" oknardia_mountdim2apartment.iQuantity,"
f" oknardia_win_mountdim.id AS mID, "
f" oknardia_setkit.id AS setID,"
f" (oknardia_setkit.dSetCommercialUntil > NOW()) AS bCommercial,"
f" oknardia_pvcprofiles.id AS pwc_id,"
f" oknardia_merchantbrand.id AS brand_id "
f"FROM oknardia_priceoffer"
f" INNER JOIN oknardia_win_mountdim"
f" ON oknardia_priceoffer.kOffer2MountDim_id = oknardia_win_mountdim.id"
f" INNER JOIN oknardia_setkit"
f" ON oknardia_priceoffer.kOffer2SetKit_id = oknardia_setkit.id"
f" INNER JOIN oknardia_ouruser"
f" ON oknardia_setkit.kSet2User_id = oknardia_ouruser.id"
f" INNER JOIN oknardia_merchantoffice"
f" ON oknardia_ouruser.kMerchantOffice_id = oknardia_merchantoffice.id"
f" INNER JOIN oknardia_glazing"
f" ON oknardia_setkit.kSet2Glazing_id = oknardia_glazing.id"
f" INNER JOIN oknardia_pvcprofiles"
f" ON oknardia_setkit.kSet2PVCprofiles_id = oknardia_pvcprofiles.id"
f" INNER JOIN oknardia_mountdim2apartment"
f" ON oknardia_mountdim2apartment.kMountDim_id = oknardia_win_mountdim.id"
f" INNER JOIN oknardia_merchantbrand"
f" ON oknardia_merchantoffice.kMerchantName_id = oknardia_merchantbrand.id "
f"WHERE oknardia_priceoffer.sOfferActive IS TRUE"
f" AND oknardia_mountdim2apartment.kApartment_id = {int(apartment_id)}"
f" AND oknardia_setkit.sSetActive IS TRUE {add_to_sql_for_widget} "
f"ORDER BY"
f" oknardia_setkit.dSetCreate DESC, " # Сейчас окна в наборе собираются через это
f" oknardia_win_mountdim.bIsNearDoor DESC,"
f" oknardia_win_mountdim.bIsDoor DESC,"
f" oknardia_win_mountdim.iWinWidth,"
f" oknardia_win_mountdim.iWinHight DESC "
f"LIMIT {int(frame_begin_n)} , 10000;")
# print list(qPO)
price_frame = []
count_mount_dim_in_offer = 0
dim_in_offer = []
total = 0
cur_bullet = 0
previous_set_id = 0
count_mount_dim_in_frame_page = 0
# Так как получен QuerySet начиная с frame_begin_n, то для того чтобы получить frame_begin_n следующего фрйма
# считаем не от нуля, а от старого frame_begin_n
n_begin = int(frame_begin_n)
# Проверяем есть ли папка для хранения мини-картинки конфигурации схемы открывания.
if not os.path.exists(f"{STATIC_BASE_PATH}/{PATH_FOR_IMG}/{PATH_FOR_IMGFLAPCONFIG}"):
# создаем такую папку если её нет
os.makedirs(f"{STATIC_BASE_PATH}/{PATH_FOR_IMG}/{PATH_FOR_IMGFLAPCONFIG}")
# print(">>>>>>>>>>>>>", apartment_id)
for i2 in q_price_offer:
n_begin += 1
count_mount_dim_in_frame_page += 1
# Случается, что в том или ином наборе поставщиком просчитаны не все проёмы.
# Чтобы не происходило формирование предложения (офера) из окон разных наборов делаем проверку
# является ли текущий проём в предложении из того же набора, что и предыдущий.
# Если он из другого набора, то удаляем предыдущий проём из предложения и начинаем
# формирование предложения сначала.
if count_mount_dim_in_offer == 0:
previous_set_id = i2.setID
else:
if previous_set_id != i2.setID:
# print("Сбой в наборе. Обнуляем набор")
previous_set_id = i2.setID
count_mount_dim_in_offer = 0
total = 0
cur_bullet = 0
dim_in_offer.pop()
dim_in_offer = []
# print("mID:", i2.mID, " || pID:", i2.id, " || price:", i2.fOfferPrice, " || N:", i2.iQuantity,
# " || set:", i2.sSetName, " || merchant:", i2.sOfficeName)
total += i2.fOfferPrice * i2.iQuantity
image_file = get_flaps_for_mini_pictures(i2.sOfferFlapConfig)
dim_in_offer.append({
'PRICE': i2.fOfferPrice,
'FLAP': i2.sOfferFlapConfig,
'DESCRIPTION': i2.sDescripion,
'WIDTH': i2.iWinWidth,
'HIGHT': i2.iWinHight,
'ID': i2.id,
'IMG_MINI': image_file,
'QUANTITY': i2.iQuantity,
'BULLET': [chr(65 + cur_bullet + i) for i in range(i2.iQuantity)],
# 'BULLET': range(CurBullet, CurBullet+i2.iQuantity),
'SUBTOTAL': i2.fOfferPrice * i2.iQuantity,
})
cur_bullet += i2.iQuantity
count_mount_dim_in_offer += 1
if count_mount_dim_in_offer == mount_dim_per_offer:
# print("-----------------")
# узнаем скидку через разбор формулы на метаязыке
discount = 0
try:
meta_keys = eval(i2.sOfficeDiscountMetaFormula)
if KEY_DICSOUNT in meta_keys:
# скидки рассчитываются исходя из общей суммы
for CountVal in sorted(meta_keys[KEY_DICSOUNT]):
# print(CountVal, "::", meta_keys[KEY_DICSOUNT][CountVal])
if float(total) > float(CountVal):
discount = meta_keys[KEY_DICSOUNT][CountVal]
# # DiscountTXT += u"!!%d!!" % Discount
# print("Значит DISCOUNT: ", Discount)
except (ValueError, TypeError):
pass
fin_price = total * (100 - discount) / 100
# уточняем, есть ли в принципе геокоординаты?
if int(i2.fOfficeGeoCode_Longitude) != 0 and int(i2.fOfficeGeoCode_Latitude) != 0 and \
int(address_longitude) != 0 and int(address_latitude) != 0:
# рассчитываем дистанцию между адресом дома и офиса.
distance = get_geo_distance(i2.fOfficeGeoCode_Longitude, i2.fOfficeGeoCode_Latitude, address_longitude,
address_latitude)
# т.к. из-за изменений в api яндекс карт поменялась местами широта-долгота и вообще, то
# порядок переменных строчной выше... На самом деле должно быть как в закоментированной
# строке ниже
# distance = get_geo_distance(i2.fOfficeGeoCode_Longitude, i2.fOfficeGeoCode_Latitude,
# address_longitude, address_latitude)
else:
distance = -1
# print(discount)
if discount > 99 or discount < 0.1:
discount_color1 = ""
discount_color2 = ""
else:
color_ratio = (discount + 0.) / 100
discount_color1 = f"#{255 - int(color_ratio * 128):02x}ff{255 - int(color_ratio * 128):02x}"
discount_color2 = f"#{255 - int(color_ratio * 255):02x}ff{255 - int(color_ratio * 255):02x}"
# print(discount_color1, discount_color2)
price_frame.append({
'DISTANCE': distance,
'DIM': dim_in_offer,
'TOTAL': total,
'DISCOUNT': discount,
'DISCOUNT_COLOR1': discount_color1,
'DISCOUNT_COLOR2': discount_color2,
'FIN_PRICE': fin_price,
'OFFICE_NAME': i2.sOfficeName,
'OFFICE_ADDRESS': i2.sOfficeAddress,
'OFFICE_PHONES': i2.sOfficePhones,
'MERCHANT': i2.sMerchantName,
'MERCHANT_LOGO': i2.pMerchantLogo,
'MERCHANT_URL': i2.sMerchantMainURL,
'MERCHANT_URL_SHOT': re.sub(r"(?:^http://|^https://|/$|www\.)", "", i2.sMerchantMainURL),
'SETS_NAME': i2.sSetName,
'GLAZING_NAME_B': i2.sGlazingBriefDescription,
'GLAZING_MARK': i2.sGlazingMark,
'GLAZING_TONING': i2.sGlazingToning,
'PVC_ID': i2.pwc_id,
'PVC_NAME': i2.sProfileName,
'PVC_NAME_T': pytils.translit.slugify(i2.sProfileName).lower(),
'PVC_MANUFACTURER': i2.sProfileManufacturer,
'PVC_MANUFACTURER_T': pytils.translit.slugify(i2.sProfileManufacturer).lower(),
'PVC_SEAL': i2.sProfileSealDescription,
'SETS_CLIMATE_CONTROL': i2.sSetClimateControl,
'SETS_SILL': i2.sSetSill,
'SETS_IMPLEMENT': i2.sSetImplementAll,
'SETS_IMPLEMENT_R': i2.sSetImplementHandles,
'SETS_IMPLEMENT_P': i2.sSetImplementHinges,
'SETS_IMPLEMENT_Z': i2.sSetImplementLatch,
'SETS_IMPLEMENT_O': i2.sSetImplementLimiter,
'SETS_IMPLEMENT_F': i2.sSetImplementCatch,
'SETS_PANES': i2.sSetPanes,
'SETS_SLOPE': i2.sSetSlope,
'SETS_DELIVERY': i2.sSetDelivery,
'SETS_DELIVERY_B': i2.bSetDelivery,
'SETS_OTHER': i2.sSetOtherConditions,
'SETS_ID': i2.setID,
'SETS_UNINSTALL_INSTALL': i2.sSetUninstallInstall,
'SETS_UNINSTALL_INSTALL_B': i2.bSetUninstallInstall,
'SETS_RATING': i2.fSetRating,
'SETS_RATING_STARTS': get_rating_set_for_stars(i2.fSetRating),
'SETS_DATA_MODIFY': i2.dOfferModify,
'IS_COMMERCIAL': i2.bCommercial,
})
if len(price_frame) == offer_per_frame:
break
count_mount_dim_in_offer = 0
dim_in_offer = []
total = 0
cur_bullet = 0
# узнаем дату-время самого свежего ценового предложения для размещения в META-тега
if time_for_meta == 0 or django.utils.dateformat.format(time_for_meta, 'U') < \
django.utils.dateformat.format(i2.dOfferModify, 'U'):
time_for_meta = i2.dOfferModify
if time_for_meta == 0 or django.utils.dateformat.format(time_for_meta, 'U') < \
django.utils.dateformat.format(i2.dSetModify, 'U'):
time_for_meta = i2.dSetModify
# массив ценовых предложений (что бы предложения на одной дистанции были в случайном порядке)
# random.shuffle(PriceFrame)
# сортируем по удаленности
price_frame = sorted(price_frame, key=lambda item: item['DISTANCE'])
if len(price_frame) < offer_per_frame:
n_begin = '-1'
return {'META_DATA_PUBLISH': time_for_meta, 'PRICE_FRAME': price_frame, 'N': n_begin}
def report_one_win_price(request: HttpRequest, win_width_mm: str = '670', win_height_mm: str = '2160',
win_id: str = '16') -> HttpResponse:
""" Формируем выдачу цен для единичного ТИПОВОГО окна (т.е. проема из серийного дома).
:param request: HttpRequest -- входящий http-запрос
:param win_width_mm: str -- Ширина проема в миллиметрах (это SEO-параметр, в реальности он будет получен из базы)
:param win_height_mm: str -- Высота проема в миллиметрах (это SEO-параметр, в реальности он будет получен из базы)
:param win_id: str -- ID проема (см. таблицу oknardia_win_mountdim)
:return response: HttpResponse -- исходящий http-ответ
"""
time_start = time.time()
to_template = {}
try:
# т.к. для вызова GetFlapDim4BigPictures нужно иметь внутри queryset поле iQuantity нельзя использовать
# простой запрос (см. следующую строку).
# qWinInfo = Win_MountDim.objects.filter(id=int(win_id))
# Придется сделать запрос немного сложнее:
q_win_info = Win_MountDim.objects.raw(
f'SELECT oknardia_win_mountdim.iWinWidth,'
f' oknardia_win_mountdim.iWinHight, oknardia_win_mountdim.iWinDepth,'
f' oknardia_win_mountdim.sFlapConfig, oknardia_win_mountdim.bIsNearDoor,'
f' oknardia_win_mountdim.bIsDoor, oknardia_win_mountdim.sDescripion,'
f' oknardia_win_mountdim.id, 0 as iQuantity '
f'FROM oknardia_win_mountdim '
f'WHERE oknardia_win_mountdim.id = {int(win_id)};'
)
list_win_info = list(q_win_info)
# Если размеры типового проема не совпадают с размерами из базы, то подменяем
# на правильные и перевызываем страницу
if (list_win_info[0].iWinWidth * 10 != int(win_width_mm)) or \
(list_win_info[0].iWinHight * 10 != int(win_height_mm)):
return redirect(f"/tsena-odnogo-okna/{list_win_info[0].iWinWidth * 10}x{list_win_info[0].iWinHight * 10}"
f"mm/tip{win_id}")
except (ObjectDoesNotExist, ValueError, IndexError, TypeError):
return redirect("/tsena-odnogo-okna/670x2160mm/tip16")
# все хорошо, засылаем картинку в шаблон
to_template.update(get_flaps_for_big_pictures(list_win_info))
# получаем варианты схемы открывания (для графиков)
q_offer_flap_variation = PriceOffer.objects.raw(
f'SELECT'
f' COUNT(oknardia_priceoffer.sOfferFlapConfig) AS id,'
f' "" AS IMG_MINI,'
f' "" AS STR_NUM,'
f' oknardia_priceoffer.sOfferFlapConfig '
f'FROM oknardia_priceoffer '
f'WHERE oknardia_priceoffer.sOfferActive <> 0'
f' AND oknardia_priceoffer.kOffer2MountDim_id = {int(win_id)} '
f'GROUP BY oknardia_priceoffer.sOfferFlapConfig,'
f' oknardia_priceoffer.sOfferActive,'
f' oknardia_priceoffer.kOffer2MountDim_id '
f'ORDER BY id DESC;'
)
list_offer_flap_variation = list(q_offer_flap_variation)
for i in range(0, len(list_offer_flap_variation)):
if i < 3:
list_offer_flap_variation[i].STR_NUM = "вариант " + pytils.numeral.in_words(i + 1)
elif i == 3:
list_offer_flap_variation[i].STR_NUM = "остальные варианты"
continue
else:
list_offer_flap_variation[3].id += list_offer_flap_variation[i].id
continue
list_offer_flap_variation[i].IMG_MINI = get_flaps_for_mini_pictures(
list_offer_flap_variation[i].sOfferFlapConfig
)
to_template.update({'LIST_FLAP_VARIATION': list_offer_flap_variation[:4]})
to_template.update({'NUM_FLAP_VARIATION_IN_WORD': pytils.numeral.sum_string(len(list_offer_flap_variation),
pytils.numeral.MALE,
("вариант схемы",
"варианта схем",
"вариантов схем"))})
#
q = PriceOffer.objects.raw(f'SELECT'
f' COUNT(oknardia_priceoffer.kOfferFromUser_id) AS id,'
f' oknardia_priceoffer.kOfferFromUser_id,'
f' oknardia_priceoffer.kOffer2MountDim_id '
f'FROM oknardia_priceoffer '
f'WHERE oknardia_priceoffer.kOffer2MountDim_id = {int(win_id)} '
f'GROUP BY oknardia_priceoffer.kOffer2MountDim_id,'
f' oknardia_priceoffer.kOfferFromUser_id;')
to_template.update({'NUM_TOTAL_FIRM_N_WORD': pytils.numeral.get_plural(len(list(q)),
("компании", "компаний", "компаний"))})
q = PriceOffer.objects.filter(kOffer2MountDim_id=int(win_id))
to_template.update({'NUM_TOTAL_OFFER_N_WORD': pytils.numeral.get_plural(q.count(),
("готовый расчёт", "готовых расчёта",
"готовых расчётов"))})
to_template.update({'NUM_ARCHIVE_OFFER': q.filter(sOfferActive=0).count()})
#
q_seria_for_win = PriceOffer.objects.raw(
f'SELECT'
f' oknardia_seria_info.sName, oknardia_seria_info.id AS id,'
f' "" AS sNameLat,'
f' COUNT(oknardia_mountdim2apartment.id) AS num_variation_of_apartment '
f'FROM oknardia_apartment_type'
f' INNER JOIN oknardia_mountdim2apartment'
f' ON oknardia_mountdim2apartment.kApartment_id = oknardia_apartment_type.id'
f' INNER JOIN oknardia_win_mountdim'
f' ON oknardia_mountdim2apartment.kMountDim_id = oknardia_win_mountdim.id'
f' INNER JOIN oknardia_seria_info'
f' ON oknardia_apartment_type.kSeria_id = oknardia_seria_info.id '
f'WHERE oknardia_win_mountdim.id = {int(win_id)} '
f'GROUP BY oknardia_win_mountdim.id,'
f' oknardia_seria_info.sName,'
f' oknardia_seria_info.id '
f'ORDER BY oknardia_seria_info.sName;'
)
list_seria_for_win = list(q_seria_for_win)
for i in list_seria_for_win:
i.sNameLat = pytils.translit.slugify(i.sName)
i.num_variation_of_apartment = pytils.numeral.sum_string(i.num_variation_of_apartment,
pytils.numeral.MALE,
("типовую планировку квартиры",
"типовые планировки квартир",
"типовых планировок квартир"))
to_template.update(report_price_frame(0, 1, 0, 0, 0, 0, int(win_id)))
to_template.update({
'SERIA_FOR_WIN': list_seria_for_win,
'WIN_ID': int(win_id),
'MOUNT_DIM_PER_OFFER': 1,
# получаем последние визиты клиента через куки
'LAST_VISIT': get_last_user_visit_list(get_last_user_visit_cookies(request)[:3]),
# получаем последние визиты всех посетителей из базы
# id2log, log_visit = get_last_all_user_visit_list()
'LOG_VISIT': get_last_all_user_visit_list(),
'ticks': float(time.time() - time_start)
})
return render(request, "price/price_offers_for_one_window.html", to_template)
def next_one_win_price(request: HttpRequest, win_id='16', frame_begin_n="0"):
""" Возвращает очередной фреймом ценовых предложений для выдачи с одиночным окном.
:param request: HttpRequest -- входящий http-запрос
:param win_id: str -- id типового окна
:param frame_begin_n: str -- Номер записи с которой начинается фрейм с ценами
:return: HttpResponse --
"""
time_start = time.time()
to_template = report_price_frame(0, 1, 0, 0, int(frame_begin_n), 0, int(win_id))
to_template.update({'MOUNT_DIM_PER_OFFER': 1,
'WIN_ID': int(win_id),
'ticks': float(time.time() - time_start)})
return render(request, "price/price_offers_for_one_window_frame.html", to_template)
def report_price(request: HttpRequest, build_id: str = "22427", apart_id: str = "61",
slug: str = "g-moskva-ul-novorossijskaya-d-16") -> HttpResponse:
""" Страница с расчетом стоимости окон
:param request: HttpRequest -- входящий http-запрос
:param build_id: str - id здания (адрес в таблице oknardia_building_info.id)
:param apart_id: str - id типовой планировки квартиры (в таблице oknardia_apartment_type.id)
:param slug: str - slug адреса здания
:return: response: HttpResponse
"""
time_start = time.time()
msg = ""
to_template = {}
try:
build_id = int(build_id)
apart_id = int(apart_id)
except ValueError:
return redirect("/")
try:
# получаем все типы квартир для данного адреса (а заодно и попутную информацию о площади дома и пр.)
q_apart = Apartment_Type.objects.raw(
f'SELECT'
f' oknardia_apartment_type.sNameApartment, oknardia_apartment_type.id,'
f' oknardia_apartment_type.iSort, oknardia_seria_info.kRoot_id,'
f' oknardia_building_info.kSeria_Link_id, oknardia_building_info.sAddress,'
f' oknardia_building_info.fGeoCode_Latitude, oknardia_building_info.fGeoCode_Longitude,'
f' oknardia_building_info.fTotal_Area, oknardia_building_info.sCadastre_Num_Area,'
f' oknardia_building_info.fLand_Area, oknardia_building_info.sInventory_Num,'
f' oknardia_building_info.iNum_Apartments, oknardia_building_info.sType,'
f' oknardia_building_info.iStoreys, oknardia_building_info.fCommon_Area,'
f' oknardia_building_info.sEnergy_Efficiency, oknardia_building_info.iEntrances_Porchs,'
f' oknardia_building_info.fUninhabited_Area, oknardia_building_info.sManagement_Co,'
f' oknardia_building_info.iElevators, oknardia_building_info.fResidential_Area,'
f' oknardia_building_info.iNum_Residents, oknardia_building_info.fPrivate_Area,'
f' oknardia_building_info.iNum_Accounts, oknardia_building_info.iCommissioning_year,'
f' oknardia_building_info.fGovernment_Area, oknardia_building_info.fCondition_House,'
f' oknardia_building_info.fCondition_Foundation, oknardia_building_info.fCondition_Walls,'
f' oknardia_building_info.fCondition_Overlap, oknardia_building_info.fMunicipal_Area,'
f' oknardia_building_info.sSerias_Project '
f'FROM oknardia_seria_info '
f'INNER JOIN oknardia_apartment_type'
f' ON oknardia_seria_info.kRoot_id = oknardia_apartment_type.kSeria_id '
f' INNER JOIN oknardia_building_info'
f' ON oknardia_building_info.kSeria_Link_id = oknardia_seria_info.id '
f'WHERE oknardia_building_info.id = {build_id} '
f'ORDER BY oknardia_apartment_type.iSort;')
list_apart = list(q_apart)
# если кто-то нахимичит ID квартиры не для этого дома, то сделаем так, что он будет от этого дома!
apart_inside = False
for i in q_apart:
if i.id == apart_id:
apart_inside = True
break
if not apart_inside or slug != pytils.translit.slugify(list_apart[0].sAddress):
# Переадресация 302, если с apart_id (ID-квартиры нахимичили) или slug-ом.
# Нужно для склейки парных URL в поисковиках
# При переходе с карты apart_id выставляем в 0. Из-за этого тоже нужно 302-переадресация.
return redirect(f"/{build_id}/{list_apart[0].id}/{pytils.translit.slugify(list_apart[0].sAddress)}")
address_latitude = list_apart[0].fGeoCode_Latitude
address_longitude = list_apart[0].fGeoCode_Longitude
to_template.update({'BUILD_ID': build_id})
to_template.update({'APPARTMENT_ID': apart_id})
to_template.update({'ADDRESS_LAT': address_latitude})
to_template.update({'ADDRESS_LON': address_longitude})
to_template.update({'ADDRESS': list_apart[0].sAddress})
to_template.update({'ADDRESS_T': pytils.translit.slugify(list_apart[0].sAddress)})
to_template.update({'SERIA': list_apart[0].sSerias_Project})
# данные нужные для отображения информации о доме (метраж, число подъездов и пр.)
to_template.update({'CADASTRE_NUM': list_apart[0].sCadastre_Num_Area})
to_template.update({'INVENTORY_NUM': list_apart[0].sInventory_Num})
to_template.update({'TYPE_BUILDING': list_apart[0].sType})
to_template.update({'ENERGY_EFFICIENCY': list_apart[0].sEnergy_Efficiency})
if list_apart[0].fTotal_Area != -1.0:
to_template.update({'TOTAL_AREA': f"{list_apart[0].fTotal_Area:.1f}"})
if list_apart[0].fLand_Area != -1.0:
to_template.update({'LAND': f"{list_apart[0].fLand_Area:.1f}"})
if list_apart[0].iNum_Apartments != -1:
to_template.update({'NUM_APARTMENTS': list_apart[0].iNum_Apartments})
if list_apart[0].iStoreys != -1:
to_template.update({'STOREYS': list_apart[0].iStoreys})
if list_apart[0].fCommon_Area != -1.0:
to_template.update({'COMMON_AREA': f"{list_apart[0].fCommon_Area:.1f}"})
if list_apart[0].iEntrances_Porchs != -1:
to_template.update({'NUM_ENTERANCES': list_apart[0].iEntrances_Porchs})
if list_apart[0].fUninhabited_Area != -1.0:
to_template.update({'UNINHABITED_AREA': f"{list_apart[0].fUninhabited_Area:.1f}"})
if list_apart[0].sManagement_Co != u"N/A":
to_template.update({'MANAGEMENT_CO': list_apart[0].sManagement_Co})
if list_apart[0].iElevators != -1:
to_template.update({'NUM_ELEVATORS': list_apart[0].iElevators})
if list_apart[0].fResidential_Area != -1.0:
to_template.update({'RESIDENTIAL_AREA': f"{list_apart[0].fResidential_Area:.1f}"})
if list_apart[0].iNum_Residents != -1:
to_template.update({'NUM_RESIDENTS': list_apart[0].iNum_Residents})
if list_apart[0].fPrivate_Area != -1.0:
to_template.update({'PRIVATE_AREA': f"{list_apart[0].fPrivate_Area:.1f}"})
if list_apart[0].iNum_Accounts != -1:
to_template.update({'NUM_ACCOUNTS': list_apart[0].iNum_Accounts})
if list_apart[0].iCommissioning_year != "N/A":
to_template.update({'COMMISSIONING_YEAR': list_apart[0].iCommissioning_year})
if list_apart[0].fGovernment_Area != -1.0:
to_template.update({'GOVERNMENT_AREA': f"{list_apart[0].fGovernment_Area:.1f}"})
if list_apart[0].fCondition_House != -1.0:
to_template.update({'CONDITION_HOUSE': f"{list_apart[0].fCondition_House:.0f}%"})
if list_apart[0].fCondition_Foundation != -1.0:
to_template.update({'CONDITION_FOUNDATION': f"{list_apart[0].fCondition_Foundation:.0f}%"})
if list_apart[0].fCondition_Walls != -1.0:
to_template.update({'CONDITION_WALL': f"{list_apart[0].fCondition_Walls:.0f}%"})
if list_apart[0].fCondition_Overlap != -1.0:
to_template.update({'CONDITION_OVERLAP': f"{list_apart[0].fCondition_Overlap:.0f}%"})
if list_apart[0].fMunicipal_Area != -1.0:
to_template.update({'MUNICIPAL_AREA': f"{list_apart[0].fMunicipal_Area:.1f}"})
# заполняем массив квартир для отправки в шаблон
apart_in_building = []
for apartment_count in q_apart:
apartment_in = {}
if apartment_count.id != apart_id:
apartment_in.update({'APT_ID': apartment_count.id})
else:
apartment_in.update({'APT_ID': "!"})
apartment_in.update({'APT_NAME': apartment_count.sNameApartment})
apart_in_building.append(apartment_in)
to_template.update({'APARTMENT_IN_BUILDING': apart_in_building})
# узнаем базовую серию дома
q_base_seria = Seria_Info.objects.get(id=list_apart[0].kRoot_id)
base_seria_slug = pytils.translit.slugify(q_base_seria.sName)
to_template.update({'BASE_SERIA': q_base_seria.sName,
'BASE_SERIA_LAT': base_seria_slug,
'BASE_SERIA_ID': q_base_seria.id})
# except (ValueError, IndexError, TypeError, ObjectDoesNotExist):
except ObjectDoesNotExist:
return redirect("/")
###############################################
# получаем массив окон для данной квартиры...
try:
q_md = Win_MountDim.objects.raw(
f'SELECT'
f' oknardia_apartment_type.sNameApartment, oknardia_win_mountdim.iWinWidth,'
f' oknardia_win_mountdim.iWinHight, oknardia_win_mountdim.iWinDepth,'
f' oknardia_win_mountdim.sFlapConfig, oknardia_win_mountdim.bIsNearDoor,'
f' oknardia_win_mountdim.bIsDoor, oknardia_win_mountdim.sDescripion,'
f' oknardia_win_mountdim.id, oknardia_mountdim2apartment.iQuantity '
f'FROM oknardia_mountdim2apartment '
f'INNER JOIN oknardia_apartment_type'
f' ON oknardia_mountdim2apartment.kApartment_id = oknardia_apartment_type.id'
f' INNER JOIN oknardia_win_mountdim'
f' ON oknardia_mountdim2apartment.kMountDim_id = oknardia_win_mountdim.id '
f'WHERE oknardia_mountdim2apartment.kApartment_id = {apart_id} '
f'GROUP BY'
f' oknardia_apartment_type.sNameApartment, oknardia_win_mountdim.iWinWidth,'
f' oknardia_win_mountdim.iWinHight, oknardia_win_mountdim.iWinDepth,'
f' oknardia_win_mountdim.sFlapConfig, oknardia_win_mountdim.bIsNearDoor,'
f' oknardia_win_mountdim.bIsDoor, oknardia_win_mountdim.sDescripion,'
f' oknardia_apartment_type.bApartmentCheck, oknardia_win_mountdim.dMountXYZModify,'
f' oknardia_apartment_type.dApartmentModify, oknardia_win_mountdim.iWinLimit,'
f' oknardia_win_mountdim.id, oknardia_mountdim2apartment.iQuantity '
f'ORDER BY'
f' oknardia_win_mountdim.bIsNearDoor DESC,'
f' oknardia_win_mountdim.bIsDoor DESC,'
f' oknardia_win_mountdim.iWinWidth,'
f' oknardia_win_mountdim.iWinHight DESC;')
list_mount_dim_per_offer = list(q_md)
mount_dim_per_offer = len(list_mount_dim_per_offer)
to_template.update({'APART': list_mount_dim_per_offer[0].sNameApartment})
# получаем данные для отрисовки больших картинок с проемами.
to_template.update(get_flaps_for_big_pictures(q_md))
# <---
except (ValueError, IndexError, TypeError, ObjectDoesNotExist):
return redirect("/")
# получаем данные для фрейма ценовых предложений
price_frame = report_price_frame(apart_id, mount_dim_per_offer, address_longitude, address_latitude)
to_template.update(price_frame)
# print u"строк в querySet:", CountMountDimInFramePage
# dimension_to_template.update({'DISCOUNT_TXT': DiscountTXT})
to_template.update({'MOUNT_DIM_PER_OFFER': mount_dim_per_offer,
'META_DESCRIPTION': "Окнардия ",
'META_KEYWORDS': "Окнардия+ ",
'MSG': msg})
# получаем последние визиты всех посетителей из базы
log_visit = get_last_all_user_visit_list()
id_last_visit_log = log_visit[0]['id'] + 1
# print("id_last_visit_log:", id_last_visit_log)
to_template.update({'LOG_VISIT': log_visit})
if id_last_visit_log > MAX_LEN_RING_LOG_BUFFER: # максимальный размер циклического буфера
id_last_visit_log = 1 # ставим в начало буфера
try:
log_entry = LogVisitPriceReport.objects.get(id=id_last_visit_log)
log_entry.sLogAddress = to_template["ADDRESS"]
log_entry.sLogNameApartment = to_template["APART"]
log_entry.sLogURL = f"/{build_id}/{apart_id}/{to_template['ADDRESS_T']}"
log_entry.dLogVisitTime = time.time()
log_entry.save() # UPDATE
except ObjectDoesNotExist:
log_entry = LogVisitPriceReport(
sLogAddress=to_template["ADDRESS"],
sLogNameApartment=to_template["APART"],
sLogURL=f"/{build_id}/{apart_id}/{to_template['ADDRESS_T']}",
dLogVisitTime=time.time()
)
log_entry.save() # INSERT
# получаем последние визиты клиента через куки
last_visit = get_last_user_visit_cookies(request)
to_template.update({'LAST_VISIT': get_last_user_visit_list(last_visit)})
# подготавливаем данные о текущем посещении для помещения в cookie
Item = {
"LastURL": f"/{build_id}/{apart_id}/{to_template['ADDRESS_T']}",
"LastAddress": to_template["ADDRESS"],
"LastApart": to_template["APART"],
"Time": time.time()}
last_visit.insert(0, Item) # Добавляем текущий Item в начало
last_visit = json.dumps(last_visit[:3]) # упаковываем json без пробелов (три записи)
# print u"сейчас запишем вот эту куку:", LastVisit
to_template.update({'ticks': float(time.time() - time_start)})
response = render(request, "price/price_list.html", to_template)
response.set_cookie("LastVisit", last_visit, max_age=7862400) # ставим или перезаписываем куки (91 день)
return response
def next_price_frame(request: HttpRequest, apart_id: str = "1", mount_dim_per_offer: str = "1",
address_longitude: str = "0.", address_latitude: str = "0.",
frame_begin_n: str = "0") -> HttpResponse:
""" Возвращает очередным фреймом ценовых предложений.
:param request: HttpRequest -- входящий HTTP-запрос
:param apart_id: str -- ID типовой квартиры, для которой получаем ценовые предложения
:param mount_dim_per_offer: str -- число различных оконных проемов в этой квартире (чтобы отсеять предложения,
в которых не представлены все проемы)
:param address_longitude: str -- долгота адреса (геокоордината), для которого получаем ценовые предложения, чтобы
рассчитать удаленность компании предоставившей коммерческие предложения
:param address_latitude: str -- широта адреса (геокоордината), для которого получаем ценовые предложения, чтобы
рассчитать удаленность компании предоставившей коммерческие предложения
:param frame_begin_n: str -- Номер записи с которой начинается фрейм с ценами
:return: HttpResponse -- HTTP-ответ
"""
time_start = time.time()
# получаем данные для фрейма ценовых предложений
price_frame = report_price_frame(int(apart_id), int(mount_dim_per_offer), float(address_longitude),
float(address_latitude), int(frame_begin_n))
to_template = price_frame
to_template.update({'APPARTMENT_ID': apart_id,
'MOUNT_DIM_PER_OFFER': mount_dim_per_offer,
'ADDRESS_LAT': address_latitude,
'ADDRESS_LON': address_longitude,
'ticks': float(time.time() - time_start)})
return render(request, "price/price_list_frame.html", to_template)
+541 -9
View File
@@ -1,12 +1,16 @@
# -*- coding: utf-8 -*-
# from django.shortcuts import render, redirect
from django.http import HttpRequest # , HttpResponse
from django.shortcuts import render, redirect
from django.http import HttpRequest, HttpResponse
from django.utils.dateformat import format
from oknardia.models import LogVisitPriceReport
# from oknardia.settings import *
# from web.add_func import normalize, get_rating_set_for_stars
from oknardia.models import LogVisitPriceReport, SetKit
from oknardia.settings import *
from web.add_func import normalize, get_rating_set_for_stars, sum_through
# from time import time
import django.utils.dateformat
import time
import json
import re
import pytils
@@ -50,19 +54,547 @@ def get_last_all_user_visit_list() -> list:
:return: list -- список четырех последних посещений ценовых предложений всеми пользователями
"""
result_list_visit = []
id_fourth_visit = 0 # id четвертого посещения??? Зачем??? Не помню хоть убей!!!
# id_last_visit = 0 # id четвертого посещения??? Зачем??? Не помню хоть убей!!!
try:
q_log_visit = LogVisitPriceReport.objects.all().order_by('-dLogVisitTime')[:4]
for i in q_log_visit:
if id_fourth_visit == 0:
id_fourth_visit = i.id
# if id_last_visit == 0:
# id_last_visit = i.id
result_list_visit.append({
"Time": pytils.dt.distance_of_time_in_words(int(format(i.dLogVisitTime, 'U'))),
"id": i.id,
"Time": pytils.dt.distance_of_time_in_words(int(django.utils.dateformat.format(i.dLogVisitTime, 'U'))),
"LogURL": i.sLogURL,
"LogAddress": i.sLogAddress,
"LogApart": i.sLogNameApartment
})
except LogVisitPriceReport.DoesNotExist:
pass
# return id_fourth_visit+1, list_visit
# return id_last_visit+1, list_visit
return result_list_visit
def compare_offers(request: HttpRequest, to_compare: str = "1,2") -> HttpResponse:
""" Сравнение нескольких коммерческих предложений (оконных набора).
:param request: HttpRequest -- входящий http-запрос
:param to_compare: str -- список ,через запятую, id оконных наборов (Set) для сравнения
:return: HttpResponse --
"""
time_start = time.time()
to_template = {}
try:
# Этот блок нужен для 302-переадресации, когда разные URL отдают одинаковые страницы.
# Например, такое происходит для страницы: /compare_offers/1,2 и /compare_offers/2,1
# т.е. сравнивают одни и те же наборы, но они указаны в строке сравнения в разном порядке
# ----------------------------------------------------------------------------------------------
# получил строку: to_compare
# превращаем в список
list_input = to_compare.split(",")
list_fin = []
# убираем мусор и создаем список только из целых чисел (listResult)
for i in list_input:
try:
list_fin.append(int(i))
except ValueError:
continue
if len(list_fin) < 2:
return redirect("/compare_offers/1,2")
# Сортируем этот список (list_fin)
list_fin.sort()
# Превращаем список list_fin в строку list_fine (разделитель -- запятая)
list_fine = ','.join(map(str, list_fin))
# Сравниваем входной порядок параметров и отсоветованный. Если не совпадает, то переадресация-302
if to_compare != list_fine:
return redirect(f"/compare_offers/{list_fine}")
try:
q_set_kit = SetKit.objects.raw(
f"SELECT "
f"oknardia_setkit.id, oknardia_setkit.sSetName, oknardia_setkit.sSetDescription,"
f"oknardia_setkit.sSetClimateControl, oknardia_setkit.sSetSill, oknardia_setkit.sSetImplementAll,"
f"oknardia_setkit.sSetImplementHandles, oknardia_setkit.sSetImplementHinges,"
f"oknardia_setkit.sSetImplementLatch, oknardia_setkit.sSetImplementLimiter,"
f"oknardia_setkit.sSetImplementCatch, oknardia_setkit.sSetPanes, oknardia_setkit.sSetSlope,"
f"oknardia_setkit.sSetDelivery, oknardia_setkit.bSetDelivery, oknardia_setkit.sSetUninstallInstall,"
f"oknardia_setkit.bSetUninstallInstall, oknardia_setkit.sSetOtherConditions,"
f"oknardia_setkit.fSetRating, oknardia_setkit.iSetNumEval, oknardia_setkit.iSetImpressions,"
f"oknardia_setkit.iSetViews, oknardia_setkit.sSetActive, oknardia_setkit.dSetModify,"
f"(oknardia_setkit.dSetCommercialUntil > NOW()) AS bCommercial,"
f"oknardia_glazing.sGlazingReflectionAndAbsorptionOfHeat, oknardia_glazing.sGlazingBriefDescription,"
f"oknardia_glazing.sGlazingDescription, oknardia_glazing.fGlazingSoundproofing,"
f"oknardia_glazing.fGlazingRating, oknardia_glazing.sGlazingMark,"
f"oknardia_glazing.fGlazingHeatTransfer, oknardia_glazing.fGlazingLightTransmission,"
f"oknardia_glazing.fGlazingPassingSun, oknardia_glazing.sGlazingLightReflectance,"
f"oknardia_glazing.sGlazingManufacturer, oknardia_glazing.iGlazingCamerasN,"
f"oknardia_glazing.sGlazingToning, oknardia_glazing.iGlazingThickness,"
f"oknardia_merchantoffice.dOfficeDataCreate, oknardia_merchantoffice.sOfficeName,"
f"oknardia_merchantoffice.sOfficeStatus, oknardia_merchantoffice.sOfficePhones,"
f"oknardia_merchantoffice.sOfficeEmails, oknardia_merchantoffice.sOfficeDescription,"
f"oknardia_merchantoffice.sOfficeDiscountMetaFormula, oknardia_merchantoffice.fOfficeGeoCode_Latitude,"
f"oknardia_merchantoffice.fOfficeGeoCode_Longitude, oknardia_merchantoffice.sOfficeAddress,"
f"oknardia_ouruser.sUserAvatarImg, oknardia_ouruser.sUserJobTitle, oknardia_ouruser.bUserSubscribe,"
f"oknardia_ouruser.sUserPhone, oknardia_ouruser.sUserStatus, oknardia_merchantbrand.id AS MERCHANT_ID,"
f"oknardia_merchantbrand.sMerchantMainURL, oknardia_merchantbrand.sMerchantName,"
f"oknardia_merchantbrand.pMerchantLogo, oknardia_pvcprofiles.id AS PROFILE_ID,"
f"oknardia_pvcprofiles.sProfileName, oknardia_pvcprofiles.sProfileBriefDescription,"
f"oknardia_pvcprofiles.sProfileReinforcement, oknardia_pvcprofiles.sProfileDescription,"
f"oknardia_pvcprofiles.fProfileHeatTransf, oknardia_pvcprofiles.sProfileSealDescription,"
f"oknardia_pvcprofiles.fProfileSeals, oknardia_pvcprofiles.fProfileSoundproofing,"
f"oknardia_pvcprofiles.iProfileCameras, oknardia_pvcprofiles.iProfileGlazingThickness,"
f"oknardia_pvcprofiles.iProfileHeight, oknardia_pvcprofiles.iProfileRabbet,"
f"oknardia_pvcprofiles.iProfileThickness, oknardia_pvcprofiles.sProfileColor,"
f"oknardia_pvcprofiles.sProfileFillet, oknardia_pvcprofiles.sProfileManufacturer,"
f"oknardia_pvcprofiles.sProfileOther, oknardia_pvcprofiles.fProfileRating "
f"FROM oknardia_setkit"
f" INNER JOIN oknardia_pvcprofiles"
f" ON oknardia_setkit.kSet2PVCprofiles_id = oknardia_pvcprofiles.id"
f" INNER JOIN oknardia_glazing"
f" ON oknardia_setkit.kSet2Glazing_id = oknardia_glazing.id"
f" INNER JOIN oknardia_ouruser"
f" ON oknardia_setkit.kSet2User_id = oknardia_ouruser.id"
f" INNER JOIN oknardia_merchantoffice"
f" ON oknardia_ouruser.kMerchantOffice_id = oknardia_merchantoffice.id"
f" INNER JOIN oknardia_merchantbrand"
f" ON oknardia_merchantoffice.kMerchantName_id = oknardia_merchantbrand.id "
f"WHERE oknardia_setkit.id IN ({to_compare})")
except SetKit.DoesNotExist:
return redirect("/compare_offers/1,2")
list_set_kit = list(q_set_kit)
if len(list_set_kit) == 0:
return redirect("/compare_offers/1,2")
if len(list_set_kit) == 1:
return redirect(f"/specification_set/{list_set_kit[0].id}")
except (ValueError, TypeError):
return render("/compare_offers/1,2")
# ПРЕДВАРИТЕЛЬНЫЙ "ПРОГОН"
# Для того, чтобы "покрасить" ячейки таблицы сравнения в цвета, нужно для некоторых полей найти min и max...
ini_max = -100000
ini_min = 1000000
max_i_profile_cameras = max_f_profile_seals = max_i_profile_thickness = max_i_profile_glazing_thickness = \
max_f_profile_heat_transf = max_f_profile_soundproofing = max_i_profile_rabbet = max_i_profile_height = \
max_i_glazing_cameras_n = max_i_glazing_thickness = max_f_glazing_heat_transfer = max_rating_set = \
max_f_glazing_soundproofing = max_f_glazing_light_transmission = max_f_glazing_passing_sun = ini_max
min_i_profile_cameras = min_f_profile_seals = min_i_profile_thickness = min_i_profile_glazing_thickness = \
min_f_profile_heat_transf = min_f_profile_soundproofing = min_i_profile_rabbet = min_i_profile_height = \
min_i_glazing_cameras_n = min_i_glazing_thickness = min_f_glazing_heat_transfer = min_rating_set = \
min_f_glazing_soundproofing = min_f_glazing_light_transmission = min_f_glazing_passing_sun = ini_min
list_of_merchant_name = []
list_of_profile_name = []
list_of_glazing_brief_description = []
for i in list_set_kit:
if i.sMerchantName not in list_of_merchant_name:
list_of_merchant_name.append(i.sMerchantName)
if i.sProfileName not in list_of_profile_name:
list_of_profile_name.append(i.sProfileName)
if i.sGlazingMark not in list_of_glazing_brief_description:
list_of_glazing_brief_description.append(i.sGlazingMark)
profile_num_cameras = sum_through(i.iProfileCameras)
if profile_num_cameras > 0: # Общее число камер профиля (рама+створка)
if profile_num_cameras > max_i_profile_cameras:
max_i_profile_cameras = profile_num_cameras
if profile_num_cameras < min_i_profile_cameras:
min_i_profile_cameras = profile_num_cameras
if i.iProfileThickness > 0: # Контуров уплотнения
if i.fProfileSeals > max_f_profile_seals:
max_f_profile_seals = i.fProfileSeals
if i.fProfileSeals < min_f_profile_seals:
min_f_profile_seals = i.fProfileSeals
if i.iProfileThickness > 10: # Монтажная ширина профиля
if i.iProfileThickness > max_i_profile_thickness:
max_i_profile_thickness = i.iProfileThickness
if i.iProfileThickness < min_i_profile_thickness:
min_i_profile_thickness = i.iProfileThickness
if i.iProfileGlazingThickness > 4: # Максимальная толщина стеклопакета
if i.iProfileGlazingThickness > max_i_profile_glazing_thickness:
max_i_profile_glazing_thickness = i.iProfileGlazingThickness
if i.iProfileGlazingThickness < min_i_profile_glazing_thickness:
min_i_profile_glazing_thickness = i.iProfileGlazingThickness
if i.fProfileHeatTransf > 0: # Сопротивление теплопередаче
if i.fProfileHeatTransf > max_f_profile_heat_transf:
max_f_profile_heat_transf = i.fProfileHeatTransf
if i.fProfileHeatTransf < min_f_profile_heat_transf:
min_f_profile_heat_transf = i.fProfileHeatTransf
if i.fProfileSoundproofing > 0: # Коэффициент звукоизоляции
if i.fProfileSoundproofing > max_f_profile_soundproofing:
max_f_profile_soundproofing = i.fProfileSoundproofing
if i.fProfileSoundproofing < min_f_profile_soundproofing:
min_f_profile_soundproofing = i.fProfileSoundproofing
if i.iProfileRabbet > 1: # Фальц
if i.iProfileRabbet > max_i_profile_rabbet:
max_i_profile_rabbet = i.iProfileRabbet
if i.iProfileRabbet < min_i_profile_rabbet:
min_i_profile_rabbet = i.iProfileRabbet
if i.iProfileHeight > 12: # Высота в световом проеме
if i.iProfileHeight > max_i_profile_height:
max_i_profile_height = i.iProfileHeight
if i.iProfileHeight < min_i_profile_height:
min_i_profile_height = i.iProfileHeight
if i.iGlazingCamerasN > 0: # Камер стеклопакета
if i.iGlazingCamerasN > max_i_glazing_cameras_n:
max_i_glazing_cameras_n = i.iGlazingCamerasN
if i.iGlazingCamerasN < min_i_glazing_cameras_n:
min_i_glazing_cameras_n = i.iGlazingCamerasN
if i.iGlazingThickness > 4: # Толщина стеклопакета
if i.iGlazingThickness > max_i_glazing_thickness:
max_i_glazing_thickness = i.iGlazingThickness
if i.iGlazingThickness < min_i_glazing_thickness:
min_i_glazing_thickness = i.iGlazingThickness
if i.fGlazingHeatTransfer > 0.05: # Сопротивление теплопередаче стеклопакета Ro (м²×°C/Вт)
if i.fGlazingHeatTransfer > max_f_glazing_heat_transfer:
max_f_glazing_heat_transfer = i.fGlazingHeatTransfer
if i.fGlazingHeatTransfer < min_f_glazing_heat_transfer:
min_f_glazing_heat_transfer = i.fGlazingHeatTransfer
if i.fGlazingSoundproofing > 5: # Коэффициент звукоизоляции стеклопакета
if i.fGlazingSoundproofing > max_f_glazing_soundproofing:
max_f_glazing_soundproofing = i.fGlazingSoundproofing
if i.fGlazingSoundproofing < min_f_glazing_soundproofing:
min_f_glazing_soundproofing = i.fGlazingSoundproofing
if i.fGlazingLightTransmission > 5: # Коэффициент светопропускания стеклопакета
if i.fGlazingLightTransmission > max_f_glazing_light_transmission:
max_f_glazing_light_transmission = i.fGlazingLightTransmission
if i.fGlazingLightTransmission < min_f_glazing_light_transmission:
min_f_glazing_light_transmission = i.fGlazingLightTransmission
if i.fGlazingPassingSun > 5: # Коэффициент солнцепропускания стеклопакета
if i.fGlazingPassingSun > max_f_glazing_passing_sun:
max_f_glazing_passing_sun = i.fGlazingPassingSun
if i.fGlazingPassingSun < min_f_glazing_passing_sun:
min_f_glazing_passing_sun = i.fGlazingPassingSun
if i.fSetRating > 0.05: # Рейтинг НАБОРА!
if i.fSetRating > max_rating_set:
max_rating_set = i.fSetRating
if i.fSetRating < min_rating_set:
min_rating_set = i.fSetRating
# ОКОНЧАТЕЛЬНЫЙ ПРОГОН
# Передаём данные из SQL-запроса шаблон. Иногда надо вычислять цвета и прочее.
# Много макаронного стиля кодинга, из-за того что иначе придется передавать в функции большие массивы QuerySet.
# А это жрет много памяти.
dim = []
for i in list_set_kit:
# построим массив "цветов" для рейтинга "Общее число камер профиля (рама+створка)" (чем больше, тем лучше)
profile_num_cameras = sum_through(i.iProfileCameras)
if max_i_profile_cameras == ini_max or min_i_profile_cameras == ini_min or profile_num_cameras <= 1 \
or profile_num_cameras == min_i_profile_cameras or max_i_profile_cameras - min_i_profile_cameras < 0.001:
profile_num_cameras_color = None
else:
color_ratio = (profile_num_cameras - min_i_profile_cameras) / (
max_i_profile_cameras - min_i_profile_cameras)
profile_num_cameras_color = f"#{255 - int(color_ratio * 128):02x}ff{255 - int(color_ratio * 128):02x}"
# построим массив "цветов" для рейтинга "Контуров уплотнения" (чем больше, тем лучше)
if max_f_profile_seals == ini_max or min_f_profile_seals == ini_min or i.fProfileSeals <= 0 \
or i.fProfileSeals == min_f_profile_seals or max_f_profile_seals - min_f_profile_seals < 0.001:
profile_seals_color = None
else:
color_ratio = (i.fProfileSeals - min_f_profile_seals) / (max_f_profile_seals - min_f_profile_seals)
profile_seals_color = f"#{255 - int(color_ratio * 128):02x}ff{255 - int(color_ratio * 128):02x}"
# построим массив "цветов" для рейтинга "Монтажная ширина профиля" (чем больше, тем лучше)
if max_i_profile_thickness == ini_max or min_i_profile_thickness == ini_min or i.iProfileThickness <= 10 \
or i.iProfileThickness == min_i_profile_thickness \
or max_i_profile_thickness - min_i_profile_thickness < 0.001:
profile_thickness_color = None
else:
color_ratio = (i.iProfileThickness - min_i_profile_thickness) / (max_i_profile_thickness
- min_i_profile_thickness)
profile_thickness_color = f"#{255 - int(color_ratio * 128):02x}ff{255 - int(color_ratio * 128):02x}"
# построим массив "цветов" для рейтинга "Максимальная толщина стеклопакета" (чем больше, тем лучше)
if max_i_profile_glazing_thickness == ini_max or min_i_profile_glazing_thickness == ini_min \
or i.iProfileGlazingThickness <= 4 or i.iProfileGlazingThickness == min_i_profile_glazing_thickness \
or max_i_profile_glazing_thickness - min_i_profile_glazing_thickness < 0.001:
profile_glazing_thickness_color = None
else:
color_ratio = (i.iProfileGlazingThickness
- min_i_profile_glazing_thickness) / (max_i_profile_glazing_thickness
- min_i_profile_glazing_thickness)
profile_glazing_thickness_color = f"#{255 - int(color_ratio * 128):02x}ff{255 - int(color_ratio * 128):02x}"
# построим массив "цветов" для рейтинга "Сопротивление теплопередаче" (чем больше, тем лучше)
if max_f_profile_heat_transf == ini_max or min_f_profile_heat_transf == ini_min \
or i.fProfileHeatTransf == min_f_profile_heat_transf \
or max_f_profile_heat_transf - min_f_profile_heat_transf < 0.001:
profile_heat_transf_color = None
else:
color_ratio = (i.fProfileHeatTransf - min_f_profile_heat_transf) / (max_f_profile_heat_transf
- min_f_profile_heat_transf)
profile_heat_transf_color = f"#{255 - int(color_ratio * 128):02x}ff{255 - int(color_ratio * 128):02x}"
# построим массив "цветов" для рейтинга "Коэффициент звукоизоляции" (чем больше, тем лучше)
if max_f_profile_soundproofing == ini_max or min_f_profile_soundproofing == ini_min \
or i.fProfileSoundproofing == min_f_profile_soundproofing \
or max_f_profile_soundproofing - min_f_profile_soundproofing < 0.001:
profile_soundproofing_color = None
else:
color_ratio = (i.fProfileSoundproofing - min_f_profile_soundproofing) / (max_f_profile_soundproofing
- min_f_profile_soundproofing)
profile_soundproofing_color = f"#{255 - int(color_ratio * 128):02x}ff{255 - int(color_ratio * 128):02x}"
# построим массив "цветов" для рейтинга "Фальц" (чем больше, тем лучше)
if max_i_profile_rabbet == ini_max or min_i_profile_rabbet == ini_min or i.iProfileRabbet <= 1 \
or i.iProfileRabbet == min_i_profile_rabbet or max_i_profile_rabbet - min_i_profile_rabbet < 0.001:
profile_rabbet_color = None
else:
color_ratio = (i.iProfileRabbet - min_i_profile_rabbet) / (max_i_profile_rabbet - min_i_profile_rabbet)
profile_rabbet_color = f"#{255 - int(color_ratio * 128):02x}ff{255 - int(color_ratio * 128):02x}"
# построим массив "цветов" для рейтинга "Высота в световом проеме" (чем меньше, тем лучше)
if max_i_profile_rabbet == ini_max or min_i_profile_height == ini_min or i.iProfileHeight <= 12 \
or i.iProfileHeight == max_i_profile_height or max_i_profile_height - min_i_profile_height < 0.01:
profile_height_color = None
else:
color_ratio = (i.iProfileHeight - min_i_profile_height) / (max_i_profile_height - min_i_profile_height)
profile_height_color = f"#{127 + int(color_ratio * 128):02x}ff{127 + int(color_ratio * 128):02x}"
# построим массив "цветов" для рейтинга "Камер стеклопакета" (чем больше, тем лучше)
if max_i_glazing_cameras_n == ini_max or min_i_profile_height == ini_min \
or i.iGlazingCamerasN == min_i_glazing_cameras_n \
or max_i_glazing_cameras_n - min_i_glazing_cameras_n < 0.001:
glazing_cameras_n_color = None
else:
color_ratio = (i.iGlazingCamerasN - min_i_glazing_cameras_n) / (max_i_glazing_cameras_n
- min_i_glazing_cameras_n)
glazing_cameras_n_color = f"#{255 - int(color_ratio * 128):02x}ff{255 - int(color_ratio * 128):02x}"
# построим массив "цветов" для рейтинга "Толщина стеклопакета" (чем больше, тем лучше)
if max_i_glazing_thickness == ini_max or min_i_glazing_thickness == ini_min or i.iGlazingThickness <= 3 \
or i.iGlazingThickness == min_i_glazing_thickness \
or max_i_glazing_thickness - min_i_glazing_thickness < 0.001:
glazing_thickness_color = None
else:
color_ratio = (i.iGlazingThickness - min_i_glazing_thickness) / (max_i_glazing_thickness
- min_i_glazing_thickness)
glazing_thickness_color = f"#{255 - int(color_ratio * 128):02x}ff{255 - int(color_ratio * 128):02x}"
# построим массив "цветов" для рейтинга "Сопротивление теплопередаче стеклопакета" (чем больше, тем лучше)
if max_f_glazing_heat_transfer == ini_max or min_f_glazing_heat_transfer == ini_min \
or i.fGlazingHeatTransfer <= 0.05 or i.fGlazingHeatTransfer == min_f_glazing_heat_transfer \
or max_f_glazing_heat_transfer - min_f_glazing_heat_transfer < 0.001:
glazing_heat_transfer_color = None
else:
color_ratio = (i.fGlazingHeatTransfer - min_f_glazing_heat_transfer) / (max_f_glazing_heat_transfer
- min_f_glazing_heat_transfer)
glazing_heat_transfer_color = f"#{255 - int(color_ratio * 128):02x}ff{255 - int(color_ratio * 128):02x}"
# построим массив "цветов" для рейтинга "Коэффициент звукоизоляции стеклопакета" (чем больше, тем лучше)
if max_f_glazing_soundproofing == ini_max or min_f_glazing_soundproofing == ini_min \
or i.fGlazingSoundproofing <= 5 or i.fGlazingSoundproofing == min_f_glazing_heat_transfer \
or max_f_glazing_soundproofing - min_f_glazing_soundproofing < 0.001:
glazing_soundproofing_color = None
else:
color_ratio = (i.fGlazingSoundproofing - min_f_glazing_soundproofing) / (max_f_glazing_soundproofing
- min_f_glazing_soundproofing)
glazing_soundproofing_color = f"#{255 - int(color_ratio * 128):02x}ff{255 - int(color_ratio * 128):02x}"
# построим массив "цветов" для рейтинга "Коэффициент светопропускания стеклопакета" (чем больше, тем лучше)
if max_f_glazing_light_transmission == ini_max or min_f_glazing_light_transmission == ini_min \
or i.fGlazingLightTransmission <= 5 or i.fGlazingLightTransmission == min_f_glazing_light_transmission \
or max_f_glazing_light_transmission - min_f_glazing_light_transmission < 0.002:
glazing_light_transmission_color = None
else:
color_ratio = (i.fGlazingLightTransmission
- min_f_glazing_light_transmission) / (max_f_glazing_light_transmission
- min_f_glazing_light_transmission)
glazing_light_transmission_color = f"#{255 - int(color_ratio * 128):02x}ff{255 - int(color_ratio * 128):02x}"
# построим массив "цветов" для рейтинга "Коэффициент солнцепропускания стеклопакета" (чем меньше, тем лучше)
if max_f_glazing_passing_sun == ini_max or min_f_glazing_passing_sun == ini_min or i.fGlazingPassingSun <= 5 \
or i.fGlazingPassingSun == max_f_glazing_passing_sun \
or max_f_glazing_passing_sun - min_f_glazing_passing_sun < 0.0001:
glazing_passing_sun_color = None
else:
color_ratio = (i.fGlazingPassingSun - min_f_glazing_passing_sun) / (max_f_glazing_passing_sun
- min_f_glazing_passing_sun)
glazing_passing_sun_color = f"#{127 + int(color_ratio * 128):02x}ff{127 + int(color_ratio * 128):02x}"
########################################################################
# построим массив цветов "звездочек" для рейтинга наборов
if i.fSetRating > RARING_SET_MAX:
rating_set_n = RARING_SET_MAX
rating_set_color = "#80ff80"
elif i.fSetRating < RARING_SET_MIN + 0.05 or max_rating_set - min_rating_set < 0.001:
rating_set_n = RARING_SET_MIN
rating_set_color = ""
else:
try:
rating_set_n = i.fSetRating * (RARING_SET_MAX - RARING_SET_MIN) / RARING_STAR
color_ratio = (i.fSetRating - min_rating_set) / (max_rating_set - min_rating_set)
rating_set_color = f"#{255 - int(color_ratio * 128):02x}ff{255 - int(color_ratio * 128):02x}"
except (ZeroDivisionError, TypeError):
rating_set_color = None
rating_set_n = RARING_SET_MIN
# print RatingSet
list2_del = f",{to_compare},"
dim.append({
"MERCHANT": i.sMerchantName,
"MERCHANT_ID": i.MERCHANT_ID,
"IS_COMMERCIAL": i.bCommercial,
"MERCHANT_T": pytils.translit.slugify(i.sMerchantName),
'MERCHANT_URL': i.sMerchantMainURL,
'MERCHANT_URL_SHOT': re.sub("(?:^http://|^https://|/$|www\.)", "", i.sMerchantMainURL),
"SET_NAME": i.sSetName,
"MERCHANT_LOGO": i.pMerchantLogo,
"RATING_SET": get_rating_set_for_stars(i.fSetRating),
"RATING_SET_N": rating_set_n,
"RATING_SET_COLOR": rating_set_color,
"PROFILE_ID": i.PROFILE_ID,
"PROFILE_NAME": i.sProfileName,
"PROFILE_NAME_T": pytils.translit.slugify(i.sProfileName),
"PROFILE_MANUFACTURER": i.sProfileManufacturer,
"PROFILE_MANUFACTURER_T": pytils.translit.slugify(i.sProfileManufacturer),
"PROFILE_NUM_COLOR": i.sProfileColor,
"PROFILE_NUM_CAMERAS": i.iProfileCameras, # Число камер рамы/створки
"PROFILE_NUM_CAMERAS_COLOR": profile_num_cameras_color, # Число камер рамы/створки ЦВЕТА
"PROFILE_THICKNESS": i.iProfileThickness, # Монтажная ширина профиля
"PROFILE_THICKNESS_COLOR": profile_thickness_color, # Окраска Монтажная ширина профиля ЦВЕТА
"PROFILE_GLAZING_THICKNESS": i.iProfileGlazingThickness, # Максимальная толщина стеклопакета
"PROFILE_GLAZING_THICKNESS_COLOR": profile_glazing_thickness_color, # Макс-толщина стеклопакета ЦВЕТА
"PROFILE_HEAT_TRANSFER": i.fProfileHeatTransf, # Сопротивление теплопередаче
"PROFILE_HEAT_TRANSFER_COLOR": profile_heat_transf_color, # Сопротивление теплопередаче ЦВЕТА
"PROFILE_NUM_SEALS": i.fProfileSeals, # Контуров уплотнения
"PROFILE_NUM_SEALS_COLOR": profile_seals_color, # Контуров уплотнения ЦВЕТА
"PROFILE_SEAL_DESCRIPTION": i.sProfileSealDescription,
"PROFILE_SOUND_PROOFING": i.fProfileSoundproofing, # Коэффициент звукоизоляции
"PROFILE_SOUND_PROOFING_COLOR": profile_soundproofing_color, # Коэффициент звукоизоляции ЦВЕТА
"PROFILE_HEIGHT": i.iProfileHeight, # Высота в световом проеме
"PROFILE_HEIGHT_COLOR": profile_height_color, # Высота в световом проеме ЦВЕТА
"PROFILE_RABBET": i.iProfileRabbet, # Фальц
"PROFILE_RABBET_COLOR": profile_rabbet_color, # Фальц ЦВЕТА
"PROFILE_FILLET": i.sProfileFillet, # Штапик
"PROFILE_REINFORCEMENT": i.sProfileReinforcement, # Армирование профиля
"PROFILE_OTHER": i.sProfileOther,
"SET_ID": i.id, # id-набора
"SET_CLIMATE_CONTROL": i.sSetClimateControl, # климат контроль
"SET_STILL": i.sSetSill, # Подоконник
"SET_IMPLEMENTS_ALL": i.sSetImplementAll, # Фурнитура
"SET_IMPLEMENTS_HANDLES": i.sSetImplementHandles, # Фурнитура: Ручки
"SET_IMPLEMENTS_HINGES": i.sSetImplementHinges, # Фурнитура: Петли
"SET_IMPLEMENTS_LATCH": i.sSetImplementLatch, # Фурнитура: механизма запирания (запор)
"SET_IMPLEMENTS_LIMITER": i.sSetImplementLimiter, # Фурнитура: Ограничитель
"SET_IMPLEMENTS_CATCH": i.sSetImplementCatch, # Фурнитура: Фиксаторы открывания
"SET_PANES": i.sSetPanes, # Водоотлив
"SET_SLOPE": i.sSetSlope, # Откос
"SET_DELIVERY": i.sSetDelivery, # Доставка (условия
"SET_DELIVERY_B": i.bSetDelivery, # Доставка (да/нет)
"SET_UNINSTALL_INSTALL": i.sSetUninstallInstall, # Монтаж/демонтаж (условия)
"SET_UNINSTALL_INSTALL_B": i.bSetUninstallInstall, # Монтаж/демонтаж (да/нет)
"SET_OTHER_CONDITIONS": i.sSetOtherConditions, # Прочие условия
"GLAZING_CAMERAS_NUM": i.iGlazingCamerasN, # Камер стеклопакета
"GLAZING_CAMERAS_COLOR": glazing_cameras_n_color, # Камер стеклопакета ЦВЕТА
"GLAZING_THICKNESS": i.iGlazingThickness, # Толщина стеклопакета
"GLAZING_THICKNESS_COLOR": glazing_thickness_color, # Толщина стеклопакета
"GLAZING_BRIEF_DESCRIPTION": re.sub(u",[\s\d]+мм", "", i.sGlazingBriefDescription), # Кратко о стеклопакете
"GLAZING_MARK": i.sGlazingMark, # Схема, марка, маркировка, модель стеклопакета
"GLAZING_MANUFACTURER": i.sGlazingManufacturer, # Производитель стеклопакета
"GLAZING_HEAT_TRANSFER": i.fGlazingHeatTransfer, # Сопротивление теплопередаче стеклопакета Ro (м²×°C/Вт)
"GLAZING_HEAT_TRANSFER_COLOR": glazing_heat_transfer_color, # Сопротивление теплопередаче стеклопакета ЦВЕТ
"GLAZING_SOUNDPROOFING": i.fGlazingSoundproofing, # Коэффициент звукоизоляции стеклопакета
"GLAZING_SOUNDPROOFING_COLOR": glazing_soundproofing_color, # Коэффициент звукоизоляции стеклопакета ЦВЕТА
"GLAZING_LIGHT_TRANSMISSION": i.fGlazingLightTransmission, # Коэффициент светопропускания стеклопакета
"GLAZING_LIGHT_TRANSMISSION_COLOR": glazing_light_transmission_color, # Коэффициент светопропускания ЦВЕТА
"GLAZING_LIGHT_REFLECTION": i.sGlazingLightReflectance, # Коэффициент светоотражения внешний/внутренний
"GLAZING_PASSING_SUN": i.fGlazingPassingSun, # Коэффициент солнцепропускания стеклопакета
"GLAZING_PASSING_SUN_COLOR": glazing_passing_sun_color, # Коэффициент солнцепропускания ЦВЕТ
"GLAZING_REFLECTION_AND_ABSORPTION": i.sGlazingReflectionAndAbsorptionOfHeat,
# Коэффициент теплоотражения/теплопоглощения стеклопакета
"GLAZING_TONING": i.sGlazingToning, # Тонирование стеклопакета
"URL_W_DEL": list2_del.replace(f",{i.id},", ",")[1:-1] # Тонирование стеклопакета
})
to_template.update({'SET_LIST': dim,
'LIST_MERCHANT': list_of_merchant_name,
'LIST_PROFILE': list_of_profile_name,
'LIST_GLAZING': list_of_glazing_brief_description})
# Предложения для добавления в сравнения:
if len(list_set_kit) < 7:
try:
q_set_kit = SetKit.objects.raw(
f"SELECT "
f" oknardia_setkit.id, oknardia_setkit.sSetName,"
f" oknardia_setkit.dSetModify, oknardia_setkit.fSetRating,"
f" oknardia_merchantbrand.sMerchantName,"
f" MAX(oknardia_priceoffer.dOfferModify) AS dLastData,"
f" TO_DAYS(NOW()) - TO_DAYS(MAX(oknardia_priceoffer.dOfferModify)) AS deltaData "
f"FROM oknardia_ouruser"
f" INNER JOIN oknardia_setkit"
f" ON oknardia_ouruser.id = oknardia_setkit.kSet2User_id"
f" INNER JOIN oknardia_merchantoffice"
f" ON oknardia_merchantoffice.id = oknardia_ouruser.kMerchantOffice_id"
f" INNER JOIN oknardia_merchantbrand"
f" ON oknardia_merchantbrand.id = oknardia_merchantoffice.kMerchantName_id"
f" INNER JOIN oknardia_priceoffer"
f" ON oknardia_setkit.id = oknardia_priceoffer.kOffer2SetKit_id "
f"WHERE oknardia_setkit.id NOT IN (%s) "
f"GROUP BY oknardia_setkit.id,"
f" oknardia_setkit.sSetName,"
f" oknardia_merchantbrand.sMerchantName,"
f" oknardia_setkit.fSetRating "
f"ORDER BY dLastData DESC "
f"LIMIT 25;" % to_compare)
dim = []
for i in q_set_kit:
if i.deltaData < 100:
early_data = pytils.dt.distance_of_time_in_words(
int(django.utils.dateformat.format(i.dLastData, 'U')), accuracy=2
)
else:
early_data = ""
dim.append({
"ID_LIST": "%s,%d" % (to_compare, i.id),
"SET_NAME": i.sSetName,
"MERCHANT": i.sMerchantName,
"DATA_MODIFY": early_data,
"R": i.fSetRating,
"R_STAR": get_rating_set_for_stars(i.fSetRating),
})
to_template.update({'LIST_TO_ADD': dim})
except SetKit.DoesNotExist:
pass
to_template.update({
# получаем последние визиты клиента через куки
'LAST_VISIT': get_last_user_visit_list(get_last_user_visit_cookies(request)[:3]),
# получаем последние визиты всех посетителей из базы
# id2log, log_visit = get_last_all_user_visit_list()
'LOG_VISIT': get_last_all_user_visit_list(),
'ticks': float(time.time() - time_start)
})
return render(request, "report/report_compare_set.html", to_template)
def show_rating_components(request: HttpRequest, win_set: str = "1") -> HttpResponse:
""" Показать состав рейтинга оконного предложения (компоненты рейтинга)
:param request: HttpRequest -- входящий http-запрос
:param win_set: str -- id оконного набора, для которого показать состав рейтинга
:return: HttpResponse --
"""
time_start = time.time()
to_template = {}
try:
win_set = int(win_set)
except ValueError:
win_set = 1
q = SetKit.objects.raw(
f"SELECT oknardia_pvcprofiles.fProfileRating, oknardia_glazing.fGlazingRating,"
f" oknardia_setkit.fSetRating, oknardia_setkit.id, MAX(oknardia_priceoffer.dOfferModify) AS dPriceModify,"
f" COUNT(oknardia_priceoffer.id) AS NumOffer, AVG(oknardia_priceoffer.fOfferRating) AS fOfferRatingAvg "
f"FROM oknardia_setkit"
f" INNER JOIN oknardia_glazing"
f" ON oknardia_setkit.kSet2Glazing_id = oknardia_glazing.id"
f" INNER JOIN oknardia_pvcprofiles"
f" ON oknardia_setkit.kSet2PVCprofiles_id = oknardia_pvcprofiles.id"
f" INNER JOIN oknardia_priceoffer"
f" ON oknardia_priceoffer.kOffer2SetKit_id = oknardia_setkit.id "
f"WHERE oknardia_setkit.id = {win_set} "
f"GROUP BY oknardia_pvcprofiles.fProfileRating,"
f" oknardia_glazing.fGlazingRating,"
f" oknardia_setkit.fSetRating,"
f" oknardia_setkit.id;")
raring_list = list(q)
f_rating_service = raring_list[0].fSetRating - RARING_WEIGHT_PVC_PROFILE_IN_SET * normalize(
raring_list[0].fProfileRating, val_max=RARING_PVC_PROFILE_MAX
)
f_rating_service -= RARING_WEIGHT_GLAZING_IN_SET * normalize(raring_list[0].fGlazingRating,
val_max=RARING_GLAZING_MAX)
f_rating_service = normalize(f_rating_service,
val_max=RARING_SET_MAX-RARING_WEIGHT_PVC_PROFILE_IN_SET-RARING_WEIGHT_GLAZING_IN_SET)
f_rating_service *= RARING_SET_MAX
to_template.update({'RATING_SERVIZ': f_rating_service,
'RATING_SERVIZ_STARS': get_rating_set_for_stars(f_rating_service),
'RATING_GLAZ': raring_list[0].fGlazingRating,
'RATING_GLAZ_STARS': get_rating_set_for_stars(raring_list[0].fGlazingRating),
'RATING_PVC': raring_list[0].fProfileRating,
'RATING_PVC_STARS': get_rating_set_for_stars(raring_list[0].fProfileRating),
'RATING_OFFER': raring_list[0].fOfferRatingAvg,
'RATING_OFFER_STARS': get_rating_set_for_stars(raring_list[0].fOfferRatingAvg),
'DATA_OFFER_UPDATE': pytils.dt.distance_of_time_in_words(
int(django.utils.dateformat.format(raring_list[0].dPriceModify, 'U')), accuracy=2),
'NUM_OFFERS': pytils.numeral.get_plural(raring_list[0].NumOffer,
"коммерческое предложение, коммерческих предложения,"
" коммерческих предложений"),
'TEST': win_set,
'ticks': float(time.time() - time_start)})
return render(request, "report/show_rating_components.html", to_template)
+224
View File
@@ -0,0 +1,224 @@
# -*- coding: utf-8 -*-
from django.shortcuts import render, redirect
from django.http import HttpRequest, HttpResponse
from oknardia.models import PVCprofiles, Seria_Info, Win_MountDim, Building_Info, SetKit
from datetime import datetime, timezone
import django.utils.dateformat
import django.utils.timezone
from oknardia.settings import *
import time
import random
import pytils
# Главная страница для вызова служебных процедур.
def service(request: HttpRequest) -> HttpResponse:
""" Страница для вызова служебных процедур
:param request: HttpRequest
:return: HttpResponse
"""
time_start = time.time()
# проверка на аутентификацию
print(request.user.is_authenticated)
if not request.user.is_authenticated:
return redirect("/service/not-denice")
return render(request, "service/index.html", {'ticks': float(time.time()-time_start)})
# страничка, на которую переадресует служебный интерфейс, если нет аутентификации.
def not_denice(request):
time_start = time.time()
return render(request, "service/not_denice.html", {'ticks': float(time.time()-time_start)})
def tmp(request: HttpRequest) -> HttpResponse:
""" Страница для тестирования верстки текста в блоге
:param request:
:return:
"""
t_start = time.time()
return render(request, "service/tmp.html", {'TAU': float(time.time()-t_start)})
SITEMAP_MAX_ITEM = 40000 # максимальное число URL-ов в sitemap.xml -- 50000
SITEMAP_MAX_FILE_SIZE = 5242880 # максимальный размер файла sitemap.xml -- 10Mb (10485760 байт)
SITEMAP_MAX_FILES_QTY = 998 # максимальный число вложенных sitemap.xml -- 1000
def str_time() -> str:
""" Возвращает текущее время в ISO 8601 со смещением от текущего часового пояса
"""
return django.utils.dateformat.format(django.utils.timezone.now(), 'c')
def make_site_maps (request: HttpRequest) -> HttpResponse:
"""Функция создания sitemap.xml ... периодически надо вызывать через crone
:param request: request
:return: HttpResponse ( msg )
"""
msg = ""
time_start = time.time()
count_total_item = 0
count_item_per_file = 0
count_file = 0
# форматирование даты-времени в ISO 8601 со смещением от текущего часового пояса
# str_time = django.utils.dateformat.format(django.utils.timezone.now(), 'c') # форматирование даты в ISO 8601
# ПОЛУЧАЕМ ВСЕ СТРАНИЧКИ С ЦЕНАМИ ДЛЯ ОДИНОЧНОГО ПРОЕМА
q1 = Win_MountDim.objects.raw("SELECT"
" oknardia_win_mountdim.iWinWidth,"
" oknardia_win_mountdim.iWinHight,"
" oknardia_win_mountdim.id,"
" COUNT(oknardia_priceoffer.kOffer2MountDim_id) AS NumOffer,"
" oknardia_win_mountdim.sFlapConfig "
"FROM oknardia_priceoffer"
" INNER JOIN oknardia_win_mountdim"
" ON oknardia_priceoffer.kOffer2MountDim_id = oknardia_win_mountdim.id "
"GROUP BY oknardia_win_mountdim.id,"
" oknardia_win_mountdim.iWinWidth,"
" oknardia_win_mountdim.iWinHight,"
" oknardia_win_mountdim.sFlapConfig "
"ORDER BY COUNT(oknardia_priceoffer.kOffer2MountDim_id);")
for i in q1:
msg += f" <url>\n" \
f" <loc>https://oknardia.ru/tsena-odnogo-okna/{int(i.iWinWidth*10)}x{int(i.iWinHight*10)}mm/tip{i.id}</loc>\n"\
f" <lastmod>{str_time()}</lastmod>\n <changefreq>weekly</changefreq>\n <priority>0.5</priority>\n" \
f" </url>\n"
count_total_item += 1
# print "~~~> ", countTotalItem, " ::: /compare_offers/", Count
count_item_per_file += 1
if (count_item_per_file > SITEMAP_MAX_ITEM) or (len(msg) > SITEMAP_MAX_FILE_SIZE):
# Файл sitemap.xml заполнен... нужно записать и продолжить записывать в следующем
msg = f"<?xml version='1.0' encoding='UTF-8'?>" \
f"<urlset xmlns='http://www.sitemaps.org/schemas/sitemap/0.9'>\n{msg}</urlset>"
with open(f"{SITEMAP_ROOT}sitemap{count_file:04d}.xml", "w", encoding="utf-8") as f:
f.write(msg)
count_item_per_file = 0
count_file += 1 # счетчик файлов
if count_file > SITEMAP_MAX_FILES_QTY: # максимально число файлов SITEMAP_MAX_FILES_QTY
break
msg = "" # обнулить буфер для записи файла
# ВСЕ СТРАНИЧКИ С ЦЕНОВЫМИ ПРЕДЛОЖЕНИЯМИ ПО АДРЕСАМ
q1 = Building_Info.objects.raw(
"SELECT DISTINCT oknardia_building_info.sAddress, oknardia_building_info.id as id,"
" oknardia_apartment_type.id AS ap_id "
"FROM oknardia_building_info"
" INNER JOIN oknardia_seria_info"
" ON oknardia_building_info.kSeria_Link_id = oknardia_seria_info.id"
" INNER JOIN oknardia_apartment_type"
" ON oknardia_apartment_type.kSeria_id = oknardia_seria_info.kRoot_id "
"ORDER BY oknardia_building_info.id;")
list_build = list(q1)
random.shuffle(list_build) # перемешиваем случайным образом, чтобы поисковики видели изменения sitemap
for i in list_build:
msg += f" <url>\n <loc>https://oknardia.ru/{i.id}/{i.ap_id}/{pytils.translit.slugify(i.sAddress)}</loc>\n" \
f" <lastmod>{str_time()}</lastmod>\n <changefreq>weekly</changefreq>\n <priority>0.5</priority>\n" \
f" </url>\n"
count_total_item += 1
# print("===> ", count_total_item, " ::: ", i.id, '/', i.ap_id, '/', pytils.translit.slugify(i.sAddress), sep="")
count_item_per_file += 1
if (count_item_per_file > SITEMAP_MAX_ITEM) or (len(msg) > SITEMAP_MAX_FILE_SIZE):
# Файл sitemap.xml заполнен... нужно записать и продолжить записывать в следующем
msg = f"<?xml version='1.0' encoding='UTF-8'?>\n" \
f"<urlset xmlns='http://www.sitemaps.org/schemas/sitemap/0.9'>\n{msg}</urlset>"
with open(f"{SITEMAP_ROOT}sitemap{count_file:04d}.xml", "w", encoding="utf-8") as f:
f.write(msg)
count_item_per_file = 0
count_file += 1 # счетчик файлов
if count_file > SITEMAP_MAX_FILES_QTY: # максимально число файлов SITEMAP_MAX_FILES_QTY
break
msg = "" # обнулить буфер для записи файла
# ДОБАВЛЯЕМ В SITEMAP ВСЕ СТРАНИЧКИ СО СРВНЕНИЕМ НАБОРОВ
dim_comp = compare()
random.shuffle(dim_comp)
for i in dim_comp:
msg += f" <url>\n <loc>https://oknardia.ru/compare_offers/{i}</loc>\n <lastmod>{str_time()}</lastmod>\n" \
f" <changefreq>weekly</changefreq>\n <priority>0.45</priority>\n </url>\n"
count_total_item += 1
count_item_per_file += 1
if (count_item_per_file > SITEMAP_MAX_ITEM) or (len(msg) > SITEMAP_MAX_FILE_SIZE):
# Файл sitemap.xml заполнен... нужно записать и продолжить записывать в следующем
msg = f"<?xml version='1.0' encoding='UTF-8'?>\n" \
f"<urlset xmlns='http://www.sitemaps.org/schemas/sitemap/0.9'>\n{msg}</urlset>"
with open(f"{SITEMAP_ROOT}sitemap{count_file:04d}.xml", "w", encoding="utf-8") as f:
f.write(msg)
count_item_per_file = 0
count_file += 1 # счетчик файлов
msg = "" # обнулить буфер для записи файла
if count_file > SITEMAP_MAX_FILES_QTY: # максимально число файлов SITEMAP_MAX_FILES_QTY
break
# ЗАВЕРШАЕМ
if count_file == 0:
# Все ссылки уместились в один sitemap.xml... просто его записать
with open(f"{SITEMAP_ROOT}sitemap.xml", "w", encoding="utf-8") as f:
f.write(f"<?xml version='1.0' encoding='UTF-8'?>\n"
f"<urlset xmlns='http://www.sitemaps.org/schemas/sitemap/0.9'>\n{msg}</urlset>")
print(SITEMAP_ROOT)
msg = f"Создан единственный sitemap.xml\nВсего ссылок: {count_total_item:06d}"
else:
# Файлов sitemap.xml много.
# Создаем завершающий файл sitemap
with open(f"{SITEMAP_ROOT}sitemap{count_file:04d}.xml", "w", encoding="utf-8") as f:
f.write(f"<?xml version='1.0' encoding='UTF-8'?>\n<urlset "
f"xmlns='http://www.sitemaps.org/schemas/sitemap/0.9'>\n{msg}</urlset>")
# Создаём объединяющий sitemap.xml с перечислением всего множества sitemap-файлов...
msg = "<?xml version='1.0' encoding='UTF-8'?>\n" \
"<sitemapindex xmlns='http://www.sitemaps.org/schemas/sitemap/0.9'>\n"
for i in range(0, count_file+1):
msg += f" <sitemap>\n <loc>https://oknardia.ru/sitemap{i:04d}.xml</loc>\n" \
f" <lastmod>{str_time()}</lastmod>\n </sitemap>\n"
msg += u"</sitemapindex>"
with open(f"{SITEMAP_ROOT}sitemap.xml", "w", encoding="utf-8") as f:
f.write(msg)
msg = f"Создан каскадный sitemap.xml\nВсего вложенных файлов: {count_file+1:04d}\n" \
f"Всего ссылок: {count_total_item:08d}"
print(msg)
return HttpResponse(f"<pre>{msg}\n\nвремя выполнения: {float(time.time()-time_start)} сек.</pre>")
def compare() -> list:
""" Возвращает список сравнения из всех возможных вариантов сравнения оконных наборов (из доступных в базе)
:return: список сравнения
"""
q_set_kit = SetKit.objects.raw('SELECT oknardia_setkit.id, oknardia_setkit.sSetActive '
'FROM oknardia_setkit '
'WHERE oknardia_setkit.sSetActive = TRUE')
count = 0
dim_comp = []
l_set_kit = list(q_set_kit)
for i1 in l_set_kit:
for i2 in l_set_kit:
if i1.id >= i2.id:
continue
dim_comp.append(f"{i1.id},{i2.id}")
count += 1
for i3 in l_set_kit:
if i2.id >= i3.id:
continue
dim_comp.append(f"{i1.id},{i2.id},{i3.id}")
count += 1
for i4 in l_set_kit:
if i3.id >= i4.id:
continue
dim_comp.append(f"{i1.id},{i2.id},{i3.id},{i4.id}")
count += 1
for i5 in l_set_kit:
if i4.id >= i5.id:
continue
dim_comp.append(f"{i1.id},{i2.id},{i3.id},{i4.id},{i5.id}")
count += 1
for i6 in l_set_kit:
if i5.id >= i6.id:
continue
dim_comp.append(f"{i1.id},{i2.id},{i3.id},{i4.id},{i5.id},{i6.id}")
count += 1
# random.shuffle(dim_comp)
# for i1 in dim_comp:
# print(i1)
# print(f"---------------{count}---------------")
return dim_comp
+198 -5
View File
@@ -1,10 +1,14 @@
# -*- coding: utf-8 -*-
from django.shortcuts import render
from django.shortcuts import render, redirect
from django.http import HttpRequest, HttpResponse
from django.core.mail import send_mail
from smtplib import SMTPException
from oknardia.models import Seria_Info, Building_Info, Apartment_Type
from web.add_func import get_yandex_geocode_by_address, get_geo_distance
import json
import datetime
import time
import pytils
# from django.core.context_processors import csrf
@@ -17,7 +21,6 @@ def main_init(request: HttpRequest) -> HttpResponse:
"""
to_template = {} # словарь, для передачи шаблону
num_viz = 0 # как будто первый визит
template = "index.html" # шаблон
# проверяем куки числа визита
if "NumVisit" in request.COOKIES:
# стоят куки, и это не первый визит
@@ -41,7 +44,7 @@ def main_init(request: HttpRequest) -> HttpResponse:
to_template.update({'META_DOCUMENT_STATE': u"Static"}) # Эта страничка статичная (в шаблон)
to_template.update({'NV': num_viz})
# to_template.update(csrf(request)) # токен, для метода POST и GET
response = render(request, template, to_template)
response = render(request, "index.html", to_template)
response.set_cookie("NumVisit", num_viz, max_age=604800) # ставим или перезаписываем куки (неделя)
return response
@@ -53,7 +56,6 @@ def tariff(request: HttpRequest) -> HttpResponse:
:return response: исходящий http-ответ
"""
to_template = {} # для передачи в шаблон
template = "tariff.html" # шаблон
if request.method == 'POST':
# print request.POST
if 'tariff' in request.POST and 'email_' in request.POST \
@@ -91,7 +93,7 @@ def tariff(request: HttpRequest) -> HttpResponse:
# Что-то пошло не так и почта не отправилась. Надо подумать что в этим делать
to_template.update({'SENDER': "Error!"})
pass
return render(request, template, to_template)
return render(request, "tariff.html", to_template)
def contact(request: HttpRequest) -> HttpResponse:
@@ -101,3 +103,194 @@ def contact(request: HttpRequest) -> HttpResponse:
:return response: исходящий http-ответ
"""
return render(request, "contact.html", {})
def get_address(request: HttpRequest) -> HttpResponse:
""" Вызывается после ввода пользователем адреса. Получает строку с адресом методом POST
ВНИМАНИЕ ТЕХНИЧЕСКИЙ ДОЛГ: Заменить GET на POST
:param request: request
:return: response
░▒▓█▓▒░ ░▒▓█▓▒░ ░▒▓█▓▒░ ░▒▓█▓▒░ ░▒▓█▓▒░ ░▒▓█▓▒░ ░▒▓█▓▒░ ░▒▓█▓▒░"""
time_start = time.time()
if request.method != 'POST':
return redirect("/")
if 'address' not in request.POST:
return redirect("/")
addr = request.POST['address']
to_template = {}
try:
q = Building_Info.objects.get(sAddress=addr)
# Если QuerySet не содержит GeoCode (такое бывает, что в Яндекс-Картах не было каких-то данных),
# то пробуем получить GeoCode повторно (вдруг, у Яндекс-Карт расширилась база адресов)
if int(q.fGeoCode_Longitude) != 0 and int(q.fGeoCode_Latitude != 0):
# print("координаты не ноль")
to_template.update({'LATITUDE': str(q.fGeoCode_Latitude).replace(",", "."),
'LONGITUDE': str(q.fGeoCode_Longitude).replace(",", ".")})
geocode = [q.fGeoCode_Latitude, q.fGeoCode_Longitude]
else:
# print("координаты ноль")
geocode = get_yandex_geocode_by_address(addr)
# print("получен геокод", geocode)
to_template.update({'LATITUDE': str(geocode[0]).replace(",", "."),
'LONGITUDE': str(geocode[1]).replace(",", ".")})
except:
# print("тут")
geocode = get_yandex_geocode_by_address(addr)
# print(geocode)
to_template.update({'LATITUDE': str(geocode[0]).replace(",", ".")})
to_template.update({'LONGITUDE': str(geocode[1]).replace(",", ".")})
q = Building_Info.objects.raw(
f"SELECT oknardia_building_info.*, "
f"ABS({geocode[0]} - oknardia_building_info.fGeoCode_Latitude) + "
f"ABS({geocode[1]} - oknardia_building_info.fGeoCode_Longitude) AS R2 "
f"FROM oknardia_building_info "
f"ORDER BY R2 "
f"LIMIT 1;")[0]
if q.R2 > 0.67: # Если расстояние между точками больше 670 метров, то не показываем результат
to_template.update({'ticks': float(time.time()-time_start)})
to_template.update({'addr': addr})
return render(request, "popup/popup_incorrect_address.html", to_template)
addr = q.sAddress
# print("addr", addr)
to_template.update({'ADDRESS_ID': q.id,
'SERIA': q.sSerias_Project})
if q.fTotal_Area < 0:
to_template.update({'TOTAL_AREA': "Нет данных"})
else:
to_template.update({'TOTAL_AREA': f"{q.fTotal_Area: .1f}"})
to_template.update({'CADASTRE_NUM': q.sCadastre_Num_Area})
if q.fLand_Area < 0:
to_template.update({'LAND': "Нет данных"})
else:
to_template.update({'LAND': f"{q.fLand_Area: .1f}"})
to_template.update({'INVENTORY_NUM': q.sInventory_Num})
if q.iNum_Apartments < 0:
to_template.update({'NUM_APARTMENTS': "Нет данных"})
else:
to_template.update({'NUM_APARTMENTS': q.iNum_Apartments})
to_template.update({'TYPE_BUILDING': q.sType})
if q.iNum_Apartments < 0:
to_template.update({'STOREYS': "Нет данных"})
else:
to_template.update({'STOREYS': q.iStoreys})
if q.fCommon_Area < 0:
to_template.update({'COMMON_AREA': "Нет данных"})
else:
to_template.update({'COMMON_AREA': f"{q.fCommon_Area: .1f}"})
to_template.update({'ENERGY_EFFICIENCY': q.sEnergy_Efficiency})
if q.iEntrances_Porchs < 0:
to_template.update({'NUM_ENTERANCES': "Нет"})
else:
to_template.update({'NUM_ENTERANCES': q.iEntrances_Porchs})
if q.fUninhabited_Area < 0:
to_template.update({'UNINHABITED_AREA': "Нет данных"})
else:
to_template.update({'UNINHABITED_AREA': f"{q.fUninhabited_Area: .1f}"})
if q.sManagement_Co == u"N/A":
to_template.update({'MANAGEMENT_CO': "Нет данных"})
else:
to_template.update({'MANAGEMENT_CO': q.sManagement_Co})
if q.iElevators < 0:
to_template.update({'NUM_ELEVATORS': "Нет данных"})
else:
to_template.update({'NUM_ELEVATORS': q.iElevators})
if q.fResidential_Area < 0:
to_template.update({'RESIDENTIAL_AREA': "Нет данных"})
else:
to_template.update({'RESIDENTIAL_AREA': f"{q.fResidential_Area: .1f}"})
if q.iNum_Residents < 0:
to_template.update({'NUM_RESIDENTS': "Нет данных"})
else:
to_template.update({'NUM_RESIDENTS': q.iNum_Residents})
if q.fPrivate_Area < 0:
to_template.update({'PRIVATE_AREA': "Нет данных"})
else:
to_template.update({'PRIVATE_AREA': f"{q.fPrivate_Area:.1f}"})
if q.iNum_Accounts < 0:
to_template.update({'NUM_ACCOUNTS': "Нет данных"})
else:
to_template.update({'NUM_ACCOUNTS': q.iNum_Accounts})
if q.iCommissioning_year == "N/A":
to_template.update({'COMMISSIONING_YEAR': "Нет данных"})
else:
to_template.update({'COMMISSIONING_YEAR': q.iCommissioning_year})
if q.fGovernment_Area < 0:
to_template.update({'GOVERNMENT_AREA': "Нет данных"})
else:
to_template.update({'GOVERNMENT_AREA': f"{q.fGovernment_Area: .1f}"})
if q.fCondition_House < 0:
to_template.update({'CONDITION_HOUSE': "Нет данных"})
else:
to_template.update({'CONDITION_HOUSE': f"{q.fCondition_House: .0f}%"})
if q.fCondition_Foundation < 0:
to_template.update({'CONDITION_FOUNDATION': "Нет данных"})
else:
to_template.update({'CONDITION_FOUNDATION': f"{q.fCondition_Foundation: .0f}%"})
if q.fCondition_Walls < 0:
to_template.update({'CONDITION_WALL': u"Нет данных"})
else:
to_template.update({'CONDITION_WALL': f"{q.fCondition_Walls: .0f}%"})
if q.fCondition_Overlap < 0:
to_template.update({'CONDITION_OVERLAP': "Нет данных"})
else:
to_template.update({'CONDITION_OVERLAP': f"{q.fCondition_Overlap: .0f}%"})
if q.fMunicipal_Area < 0:
to_template.update({'MUNICIPAL_AREA': "Нет данных"})
else:
to_template.update({'MUNICIPAL_AREA': f"{q.fMunicipal_Area: .1f}"})
to_template.update({'URL2REFOEMAGKH': q.sURL})
# Пробуем получить базовую серию дома. Для этого рекурсивно раскручиваем записи в таблице Seria_Info
idd = q.kSeria_Link_id
all_apartment_in_seria = False
while idd is not None:
# рекурсивно движемся по дерву потомок→предок серий домов.
q1 = Seria_Info.objects.get(id=idd)
# получаем список типовых квартир для серии дома с id == idd
all_apartment_in_seria = Apartment_Type.objects.filter(kSeria_id=idd).order_by("iSort")
# проверяем есть-ли что-то в списке типовых квартир.
if bool(all_apartment_in_seria):
# список типовых квартир не нулевой
to_template.update({'LIST_APART': all_apartment_in_seria})
break
idd = q1.kParent_id
# проверяем, был ли получен список квартир
if not bool(all_apartment_in_seria):
# Если списка квартир нет, нужно получить список ближайших адресов, для которых есть цены.
q = Building_Info.objects.raw(
f"SELECT"
f" oknardia_building_info.sAddress, oknardia_building_info.id,"
f" oknardia_building_info.fGeoCode_Longitude, oknardia_building_info.fGeoCode_Latitude,"
f" oknardia_seria_info.kRoot_id, oknardia_seria_info.sName,"
f" COUNT(oknardia_apartment_type.sNameApartment) AS NumApart,"
f" ABS({geocode[0]} - oknardia_building_info.fGeoCode_Latitude)"
f" + ABS({geocode[1]} - oknardia_building_info.fGeoCode_Longitude) AS R2 "
f"FROM oknardia_building_info"
f" INNER JOIN oknardia_seria_info"
f" ON oknardia_building_info.kSeria_Link_id = oknardia_seria_info.id"
f" INNER JOIN oknardia_apartment_type"
f" ON oknardia_seria_info.kRoot_id = oknardia_apartment_type.kSeria_id "
f"WHERE oknardia_building_info.fGeoCode_Longitude <> 0.0"
f" AND oknardia_building_info.fGeoCode_Latitude <> 0.0 "
f"GROUP BY oknardia_seria_info.sName,"
f" oknardia_seria_info.kRoot_id,"
f" oknardia_building_info.id,"
f" oknardia_building_info.sAddress,"
f" oknardia_building_info.fGeoCode_Longitude,"
f" oknardia_building_info.fGeoCode_Latitude "
f"ORDER BY R2 "
f"LIMIT 5;")
q = list(q)
for i in q:
i.R2 = get_geo_distance(i.fGeoCode_Longitude, i.fGeoCode_Latitude, geocode[0], geocode[1])
# print i.id, i.sAddress, i.sName, i.R2
# сортируем список по R2 (дистанция от текущего адреса, до домов по которым данные известны)
sorted(q, key=lambda item: item.R2)
to_template.update({'NEAR_KNOWN_ADDRESS': q})
# print q
to_template.update({'SERIA_BASE': q1.sName,
'addr': addr,
'addr_T': pytils.translit.slugify(addr),
'ticks': float(time.time()-time_start)})
return render(request, "popup/popup_show_apartment_variants.html", to_template)
+1
View File
@@ -0,0 +1 @@
e8819d2fd6d3
+4
View File
@@ -0,0 +1,4 @@
<?xml version="1.0"?>
<users>
<user>027F51EEB3278BF010983153FDB6F9A8</user>
</users>
+8
View File
@@ -0,0 +1,8 @@
АВТОРЫ
ДИЗАЙН: Сергей Еремин
КОД HTML: Сергей Еремин
КОД CSS: Сергей Еремин
КОД SVG: Сергей Еремин
КОД JavaScript: Сергей Еремин
ФОТОРГАФИИ:
ГРАФИКА:
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 387 B

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 529 B

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 635 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="120" height="120" viewBox="0 0 5.81 5.81" shape-rendering="geometricPrecision" image-rendering="optimizeQuality" fill-rule="evenodd"><path d="M2.25 4.2h-.02H0V1.69h2.25v.98h3.54V4.2z" fill="#393185"/><g fill="#fefefe" class="E"><path d="M1.45 3.55h.16l-.08-.3-.08.3zm.27.43l-.05-.21h-.28l-.06.21h-.26l.32-1.03h.33l.33 1.03h-.33z"/><path d="M1.07 2.95v1.03H.76v-.4H.52v.4H.2V2.95h.32v.38h.24v-.38zm.98 0H1.7l-.18-.39-.1.15v.24h-.3V1.92h.3v.43l.28-.43h.28l-.26.38z"/><path d="M.63 2.74c.06 0 .09-.02.12-.07.02-.04.03-.13.03-.25 0-.18-.05-.27-.14-.27-.11 0-.16.1-.16.31 0 .19.05.28.15.28zm0 .23c-.14 0-.26-.04-.35-.14-.09-.09-.14-.22-.14-.38a.58.58 0 0 1 .13-.38c.09-.1.21-.15.37-.15.14 0 .26.04.35.14.09.09.13.22.13.38s-.04.29-.14.39c-.09.1-.2.14-.35.14z"/></g><g fill="#d2cde7" class="E"><path d="M5.22 3.17H5.1c-.06 0-.1.01-.12.03-.01.02-.02.05-.02.07 0 .04.01.06.03.08.03.01.07.02.12.02h.11v-.2zm0 .42h-.14l-.17.39h-.29l.22-.43c-.14-.05-.21-.15-.21-.28 0-.1.03-.18.1-.24.07-.05.16-.08.27-.08h.54v1.03h-.32v-.39z"/><path d="M4.04 3.87v.11h-.3V2.95h.3v.42.19c.03-.08.07-.15.11-.22l.16-.28v-.11h.31v1.03h-.31v-.41c0-.05 0-.12.01-.19-.03.07-.07.14-.11.22l-.17.27z"/><path d="M3.04 3.76h.28L3.2 3.2h-.02l-.14.56zm-.25 0l.23-.8h.38l.23.8h.11v.44h-.2l-.02-.21H2.9l-.02.21h-.2v-.44h.11z"/><path d="M2.36 3.38h.11c.09 0 .13-.03.13-.1s-.04-.1-.12-.1h-.12v.2zm.01.22v.38h-.32V2.95h.43c.11 0 .2.01.25.04.06.02.1.06.14.11.03.05.05.11.05.18 0 .09-.04.17-.1.23a.41.41 0 0 1-.28.09h-.17z"/></g><path d="M3.09 1.9c0 .13-.05.2-.14.2a.11.11 0 0 1-.1-.05c-.03-.04-.04-.09-.04-.15 0-.13.05-.2.14-.2a.11.11 0 0 1 .1.05c.03.03.04.08.04.15zM3 1.9c0-.05 0-.09-.01-.11-.01-.01-.02-.02-.04-.02s-.03.01-.04.03c-.01.01-.01.05-.01.1s0 .08.01.1.02.03.04.03.03-.01.04-.03C3 1.99 3 1.95 3 1.9zm.25-.19h.09v.16l.09-.16h.09l-.1.14.1.24h-.08l-.07-.18-.03.05v.13h-.09v-.38zm.66.19c0 .13-.04.2-.14.2-.04 0-.08-.02-.1-.05-.02-.04-.04-.09-.04-.15 0-.13.05-.2.14-.2.05 0 .08.02.11.05.02.03.03.08.03.15zm-.09 0c0-.05 0-.09-.01-.11-.01-.01-.02-.02-.04-.02-.01 0-.03.01-.03.03-.01.01-.02.05-.02.1s.01.08.01.1c.01.02.03.03.04.03.02 0 .03-.01.04-.03.01-.01.01-.05.01-.1zm.26-.19h.08v.15h.09v-.15h.09v.38h-.09v-.16h-.09v.16h-.08v-.38zm.43 0h.09v.15h.09v-.15h.08v.38h-.08v-.16H4.6v.16h-.09v-.38zm.44.38v-.38h.08v.14h.03c.06 0 .09.01.11.03.03.02.04.05.04.09 0 .03-.01.06-.03.08-.01.02-.02.03-.04.03-.02.01-.05.01-.08.01h-.11zm.08-.06h.03c.01 0 .02 0 .03-.01.01 0 .01 0 .02-.01 0-.01.01-.02.01-.04s-.01-.03-.01-.04c-.01-.01-.03-.02-.05-.02h-.03v.12zm.22-.32h.08v.38h-.08v-.38zm.34.34v.04h-.08v-.38h.08v.21l.01-.02.09-.15v-.04h.08v.38h-.08v-.2-.01l-.01.02-.09.15zm-.04-.45h.05c0 .02.02.03.04.03.03 0 .04-.01.05-.03h.05c-.02.05-.05.07-.1.07-.04 0-.08-.02-.09-.07zm-3 .58h.09l.11.39h-.09l-.02-.08h-.11l-.02.08h-.07l.11-.39zm.07.24l-.03-.16-.04.16h.07zm.5-.24v.07h-.15v.32h-.09v-.39h.24zm.14 0h.11c.03 0 .06 0 .07.01.02.01.04.02.05.04s.02.04.02.07-.01.05-.02.07-.02.03-.04.04-.04.01-.08.01h-.03v.15h-.08v-.39zm.08.06v.12h.03c.02 0 .03-.01.04-.02s.01-.02.01-.04c0-.01 0-.03-.01-.03 0-.01-.01-.02-.01-.02-.01 0-.02-.01-.03-.01h-.03zm.31-.06h.24v.07h-.15v.09h.11v.06h-.11v.1h.15v.07h-.24v-.39zm.62 0v.07h-.15v.32h-.09v-.39h.24zm.2 0h.09l.11.39h-.09l-.02-.08h-.11l-.01.08h-.08l.11-.39zm.08.24l-.04-.16-.04.16h.08zm.2-.24h.24v.07h-.08v.32h-.09v-.32h-.07v-.07zm.64.19c0 .14-.05.2-.14.2-.05 0-.08-.01-.1-.05-.03-.03-.04-.08-.04-.15 0-.13.05-.19.14-.19.04 0 .08.01.1.05.03.03.04.08.04.14zm-.09 0c0-.05 0-.08-.01-.1s-.02-.03-.04-.03-.03.01-.04.03-.01.05-.01.1 0 .09.01.11c.01.01.02.02.04.02s.03 0 .04-.02.01-.05.01-.11zm.25-.19h.11c.04 0 .06 0 .08.01s.03.02.05.04c.01.02.02.04.02.07s-.01.05-.02.07-.02.03-.04.04-.05.01-.08.01h-.03v.15h-.09v-.39zm.09.06v.12h.02c.03 0 .04-.01.05-.02 0-.01.01-.02.01-.04 0-.01-.01-.03-.01-.03-.01-.01-.01-.02-.02-.02s-.02-.01-.03-.01h-.02z" fill="#7e71b1" class="E"/></svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

+1
View File
@@ -0,0 +1 @@
google-site-verification: googleacb673eb0c31969f.html
+1
View File
@@ -0,0 +1 @@
google-site-verification: googleea5d443319529114.html
+1
View File
@@ -0,0 +1 @@
mailru-domain: T2hJTKvdCl8xIbEm
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 152 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 396 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 337 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 297 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 245 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 351 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 283 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Some files were not shown because too many files have changed in this diff Show More