Compare commits
73 Commits
5bdb97f5e8
...
b43f896c6a
| Author | SHA1 | Date | |
|---|---|---|---|
| b43f896c6a | |||
| 817f63e3fb | |||
| 61f3ecee2a | |||
| bf575e7bec | |||
| f9085e08a1 | |||
| afdf49e9eb | |||
| d14b83ae6f | |||
| 634d26fde7 | |||
| ec8b9d5e86 | |||
| 96ed9a3178 | |||
| cc192d1c8d | |||
| 4659b221c8 | |||
| 51bd05024c | |||
| cbb525141a | |||
| 0b33c7454b | |||
| eb06fb64fe | |||
| ad505acacd | |||
| 590082704d | |||
| 72e2f3259f | |||
| 951c384a94 | |||
| 83c0ea43e8 | |||
| df8109198b | |||
| 448ebaf2c9 | |||
| c3f9a9caad | |||
| c3b8309840 | |||
| 4aff8efcfc | |||
| c99f39a156 | |||
| 70651c9ada | |||
| ea06b3e050 | |||
| e1b07467b4 | |||
| 8be7149af8 | |||
| a269fa85a6 | |||
| dd5b6baf30 | |||
| 6d8159b60d | |||
| 29d29401ba | |||
| 13f79f985c | |||
| a092551e60 | |||
| aaf7da88bc | |||
| 9cf9b9639b | |||
| f9c65c3ad6 | |||
| e019d3f636 | |||
| 0d1017384c | |||
| 0d77db66d5 | |||
| 62c983fedc | |||
| dc081fa9fd | |||
| f892100cf8 | |||
| c958fec1b4 | |||
| 6d9de5f7f0 | |||
| 6ff5e0848d | |||
| e1360261d7 | |||
| df0717a765 | |||
| c317d499f6 | |||
| f96bf35874 | |||
| 01a93e8fea | |||
| 9a6eed3812 | |||
| 8eddf9361b | |||
| e8b879ab0e | |||
| b60ea568e5 | |||
| 5cc3dae0a6 | |||
| a0a3ff88ac | |||
| 64dfdf8245 | |||
| e05ec47b48 | |||
| f95137a25c | |||
| 3df0f6271e | |||
| d82b2ffbd3 | |||
| 8c31e7cc34 | |||
| ac7e164753 | |||
| ce9fcc7511 | |||
| 6972f1f8da | |||
| 35459ca45b | |||
| a3bdb57279 | |||
| 84185bd2f0 | |||
| 6347031a00 |
@@ -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
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
##}
|
||||
|
||||
@@ -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 ----------------
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -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='Глухое')),
|
||||
|
||||
@@ -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 ключи
|
||||
|
||||
- На продакшене используйте отдельные более сильные пароли
|
||||
"""
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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),
|
||||
|
||||
]
|
||||
|
||||
|
||||
@@ -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>Производители и поставщики окон</li>
|
||||
</ol>
|
||||
<h1>Производители и поставщики окон</h1>
|
||||
</div>
|
||||
</div>{# <!--- Хлебные крошки: КОНЕЦ ---> #}
|
||||
<dIv class="row">
|
||||
<div class="col-md-11 col-xs-12">
|
||||
<p>Компании-партнёры «Окнардии», их средний рейтинг, число оконных наборов и вариантов расчёта цен для типовых проёмов, средняя цена окна:</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">Подробнее…</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 %}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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>часы работы:</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> — Офис предоставивший расчеты для оконного агрегатора «Окнардия».</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>Оконный набор: «{{ i.sSetName|safe }}»</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 != "—"%} Число камер рамы и створки — {{ 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> Цвет уплотнителя контуров и стеклопакета {{ i.sProfileSealDescription }}. Используется фурнитура {{ i.sSetImplementAll }}{% if i.sSetImplementHandles != "" %}, ручки {{ i.sSetImplementHandles }}{% endif %}{% if i.sSetImplementCatch != "" %}, фиксатор открывания {{ i.sSetImplementCatch }}{% endif %}. {% if i.sSetClimateControl != "" %} Каждое окно оборудовано системой климат-контроля — {{ 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 %}Подоконники, внешние водоотливы и внутренние откосы не включены в набор. Производитель «{{ COMPANY }}» предполагает, что закзчик этого набора закупит их отдельно у него или других поставщиков.{% elif i.sSetSill|lower in LIST_NOT and i.sSetPanes|lower in LIST_NOT %}Подоконники и внешние водоотливы не включены в набор. Производитель «{{ COMPANY }}» предполагает, что закзчик этого набора закупит их отдельно у него или других поставщиков.{% elif i.sSetSill|lower in LIST_NOT and i.sSetSlope|lower in LIST_NOT %}Внешние водоотливы и внутренние откосы не включены в набор. Производитель «{{ COMPANY }}» предполагает, что закзчик этого набора закупит их отдельно у него или других поставщиков.{% elif i.sSetPanes|lower in LIST_NOT and i.sSetSlope|lower in LIST_NOT %}Подоконники и внутренние откосы не включены в набор. Производитель «{{ COMPANY }}» предполагает, что закзчик этого набора закупит их отдельно у него или других поставщиков.{% elif i.sSetPanes|lower in LIST_NOT %}Внешние оконные водоотливы не включены в набор. Производитель «{{ COMPANY }}» предполагает, что заказчик этого набора закупит их отдельно у него или других поставщиков.{% elif i.sSetSlope|lower in LIST_NOT %}Внутренние откосы и их отделка не включены в набор. Производитель «{{ COMPANY }}» предполагает, что заказчик этого набора закупит их отдельно у него или других поставщиков.{% elif i.sSetSill|lower in LIST_NOT %}Оконные подоконники не включены в набор. Производитель «{{ COMPANY }}» предполагает, что заказчик этого набора закупит их отдельно у него или других поставщиков.{% endif %}</P>
|
||||
<P>{% if i.bSetUninstallInstall %}Установка новых и демонтаж старых оконных конструкций {{ i.sSetUninstallInstall|safe }}. {% endif %}{% if i.bSetDelivery %}Доставка {{ i.sSetDelivery|safe }}. {% endif %}{% if not i.bSetDelivery and not i.bSetUninstallInstall %}В стоимость набора не входит демонтаж и установка. Эти услуги оплачиваются отдельно. {% if i.sSetUninstallInstall not in LIST_NOT %}Примерно {{ i.sSetUninstallInstall|safe }}, или заказчик может провести все работы самостоятельно. {% endif %}Доставка тоже не входит в стоимость, осуществляется заказчиком самостоятельно или оплачивается отдельно. {% if i.sSetUninstallInstall not in LIST_NOT %}{{ i.sSetDelivery|safe|capfirst }}. {% endif %}{% elif not i.bSetDelivery %}Доставка не входит в стоимость, осуществляется заказчиком самостоятельно или оплачивается отдельно. {% if i.sSetUninstallInstall not in LIST_NOT %}{{ i.sSetDelivery|safe|capfirst }}. {% endif %}{% elif not i.bSetUninstallInstall %}В стоимость набора не входит демонтаж и установка. Эти услуги оплачиваются отдельно.{% if i.sSetUninstallInstall not in LIST_NOT %} Примерно {{ i.sSetUninstallInstall|safe }}. Или заказчик может провести все работы самостоятельно.{% endif %}{% endif %}</P>{% if i.sSetOtherConditions not in LIST_NOT %}
|
||||
<P>Прочие условия — {{ i.sSetOtherConditions|safe }}</P>{% endif %}
|
||||
{# Скидки -- {{ i.sOfficeDiscountMetaFormula }}<br>#}
|
||||
|
||||
<p style='background-color:#f0f0f0;color:silver;padding:5ex 10ex;'>Извините, графики ценового распределения для оконного набора «{{ i.sSetName|safe }}» компании «{{ i.sMerchantName }}» подготавливаются.<br /> <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 %}
|
||||
|
||||
@@ -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>Стандартные оконные проёмы и балконные блоки</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">×</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>×</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 %}
|
||||
|
||||
|
||||
@@ -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" %}
|
||||
|
||||
@@ -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 />
|
||||
Если вы ошиблись при указании адреса <a href="/">попробуйте ввести адрес ещё раз</a> или
|
||||
<a href="/stat_all/#s_map">найдите его на карте типовых серий</a> жилого строительства.<br> </li>
|
||||
<li><b>Адрес <i>«{{ addr }}»</i> отсутствует в нашей базе.</b><br />
|
||||
Приносим извинения, но пока нами обработаны только типовые серии Москвы и Подмосковья, Санкт-Петербурга
|
||||
и Ленинградской области.<br />
|
||||
Свяжитесь с нами чтобы мы ускорили оцифровку и поиск партнеров в вашем регионе.</li>
|
||||
</ul>
|
||||
<div style="float:right;font-size:xx-small">TICKs: {{ ticks|floatformat:4 }}</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
@@ -1,85 +1,85 @@
|
||||
{% load static %}
|
||||
{% with APP_INVITATION_STRING_IN_FORM="Город, улица, дом" %}
|
||||
<script type="text/javascript">
|
||||
function set_filter(s) {
|
||||
// переинициализируем autocomplete
|
||||
$('#addr').autocomplete("option", "source", "/autocomplete_addr?csrfmiddlewaretoken={{ csrf_token }}&use_filter=" + s);
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
$('#input_address').submit(function () {
|
||||
try {
|
||||
yaCounter32997984.reachGoal('FIND');
|
||||
ga('send', 'event', 'CLICK', 'ClickSearch', 'SearchAddress');
|
||||
_tmr.push({id: '2018432', type: 'reachGoal', goal: 'ClcSrh'});
|
||||
}
|
||||
catch (e) {
|
||||
}
|
||||
if ($("#addr").val() != "{{ APP_INVITATION_STRING_IN_FORM }}") {
|
||||
$.ajax({
|
||||
url: "/get_address", //Адрес подгружаемой страницы
|
||||
type: "POST", //Тип запроса
|
||||
dataType: "html", //Тип данных
|
||||
data: $("#input_address").serialize(),
|
||||
success: function (html) {
|
||||
$("#box").html(html)
|
||||
}
|
||||
});
|
||||
return false;
|
||||
}
|
||||
});
|
||||
$('#addr').autocomplete({ // автозаполнение
|
||||
source: "/autocomplete_addr?csrfmiddlewaretoken={{ csrf_token }}&use_filter=only_known", // Страница для обработки запросов автозаполнения
|
||||
minLength: 4, // Минимальная длина запроса для срабатывания автозаполнения
|
||||
delay: 160 // Задержка срабатывания автозаполнения
|
||||
});
|
||||
$.fn.fade_obj = function (ops) {
|
||||
var $elem = this;
|
||||
var res = $.extend({delay: 670, speed: 890}, ops);
|
||||
for (var i = 0, pause = 0, l = $elem.length; i < l; i++, pause += res.delay) {
|
||||
$elem.eq(i).delay(pause).fadeIn(res.speed);
|
||||
}
|
||||
return $elem;
|
||||
};
|
||||
$('.show_delayed').fade_obj();
|
||||
});
|
||||
</script>
|
||||
<h1>Выбирайте окна в квартиру<!-- ({{ NV }})-->:</h1>
|
||||
{% if NV <= 1 %}
|
||||
<ol>
|
||||
<li class="show_delayed">Вводите адрес дома или выберите его из подсказки,</li>
|
||||
<li class="show_delayed">укажите тип квартиры,</li>
|
||||
<li class="show_delayed">получите актуальные цены и сравнивайте характеристики предложений.</li>
|
||||
</ol>
|
||||
{% endif %}
|
||||
<form class="show_delayed" method="post" id="input_address">
|
||||
{% csrf_token %}
|
||||
<div class="btn-group" data-toggle="buttons" style="margin:-1em 0 1ex 0;width: 100%;">
|
||||
<label class="btn btn-default btn-xs active" onclick="set_filter('only_known');" style="float:right;">
|
||||
<input type="radio" name="sw-in1" value="notall" checked> с ценами
|
||||
</label>
|
||||
<label class="btn btn-default btn-xs" onclick="set_filter('all');" style="float:right;">
|
||||
<input type="radio" name="sw-in" value="all"> все
|
||||
</label>
|
||||
<span style="padding:2px 5px;font-size:12px;line-height:1.5;float:right;">Подсказывать адреса:</span>
|
||||
</div>
|
||||
<div class="input-group stylish-input-group">
|
||||
<input type="text" class="form-control" value="{{ APP_INVITATION_STRING_IN_FORM }}"
|
||||
id="addr" autocomplete="on" name="address" data-provide="typeahead"
|
||||
onfocus="if(this.value=='{{ APP_INVITATION_STRING_IN_FORM }}'){this.value='';this.style.color='#000';}"
|
||||
onblur= "if(this.value==''){this.value='{{ APP_INVITATION_STRING_IN_FORM }}';this.style.color='#777';}" />
|
||||
<span class="input-group-addon">
|
||||
<span class="glyphicon glyphicon-search"></span>
|
||||
</span>
|
||||
<span class="input-group-btn">
|
||||
<button type="submit" class="btn btn-primary btn-add">Найти</button>
|
||||
</span>
|
||||
</div>
|
||||
{% if LAST_VISIT %}<div><h5>Ваши последние просмотры:</h5>
|
||||
<ul style="font-size:small">{% for ITEM in LAST_VISIT %}
|
||||
<li><a href="{{ ITEM.LastURL }}">{{ ITEM.LastApart }} <small>({{ ITEM.LastAddress }})</small></a> <small style="font-size: xx-small;">{{ ITEM.Time }}</small></li>{% endfor %}
|
||||
</ul>
|
||||
</div>{% endif %}
|
||||
|
||||
</form>
|
||||
{% load static %}
|
||||
{% with APP_INVITATION_STRING_IN_FORM="Город, улица, дом" %}
|
||||
<script type="text/javascript">
|
||||
function set_filter(s) {
|
||||
// переинициализируем autocomplete
|
||||
$('#addr').autocomplete("option", "source", "/autocomplete_addr?csrfmiddlewaretoken={{ csrf_token }}&use_filter=" + s);
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
$('#input_address').submit(function () {
|
||||
try {
|
||||
yaCounter32997984.reachGoal('FIND');
|
||||
ga('send', 'event', 'CLICK', 'ClickSearch', 'SearchAddress');
|
||||
_tmr.push({id: '2018432', type: 'reachGoal', goal: 'ClcSrh'});
|
||||
}
|
||||
catch (e) {
|
||||
}
|
||||
if ($("#addr").val() != "{{ APP_INVITATION_STRING_IN_FORM }}") {
|
||||
$.ajax({
|
||||
url: "/get_address", //Адрес подгружаемой страницы
|
||||
type: "POST", //Тип запроса
|
||||
dataType: "html", //Тип данных
|
||||
data: $("#input_address").serialize(),
|
||||
success: function (html) {
|
||||
$("#box").html(html)
|
||||
}
|
||||
});
|
||||
return false;
|
||||
}
|
||||
});
|
||||
$('#addr').autocomplete({ // автозаполнение
|
||||
source: "/autocomplete_addr?csrfmiddlewaretoken={{ csrf_token }}&use_filter=only_known", // Страница для обработки запросов автозаполнения
|
||||
minLength: 4, // Минимальная длина запроса для срабатывания автозаполнения
|
||||
delay: 160 // Задержка срабатывания автозаполнения
|
||||
});
|
||||
$.fn.fade_obj = function (ops) {
|
||||
var $elem = this;
|
||||
var res = $.extend({delay: 670, speed: 890}, ops);
|
||||
for (var i = 0, pause = 0, l = $elem.length; i < l; i++, pause += res.delay) {
|
||||
$elem.eq(i).delay(pause).fadeIn(res.speed);
|
||||
}
|
||||
return $elem;
|
||||
};
|
||||
$('.show_delayed').fade_obj();
|
||||
});
|
||||
</script>
|
||||
<h1>Узнайте стоимость установки окон в квартиру от разных поставщиков<!-- ({{ NV }})-->:</h1>
|
||||
{% if NV <= 3 %}
|
||||
<ol>
|
||||
<li class="show_delayed">Введите адрес дома (или выберите адрес из подсказки),</li>
|
||||
<li class="show_delayed">укажите тип планировки квартиры</li>
|
||||
<li class="show_delayed">и получите актуальные цены, сравнивайте характеристики предложений и условия установки.</li>
|
||||
</ol>
|
||||
{% endif %}
|
||||
<form class="show_delayed" method="post" id="input_address">
|
||||
{% csrf_token %}
|
||||
<div class="btn-group" data-toggle="buttons" style="margin:-1em 0 1ex 0;width: 100%;">
|
||||
<label class="btn btn-default btn-xs active" onclick="set_filter('only_known');" style="float:right;">
|
||||
<input type="radio" name="sw-in1" value="notall" checked> с ценами
|
||||
</label>
|
||||
<label class="btn btn-default btn-xs" onclick="set_filter('all');" style="float:right;">
|
||||
<input type="radio" name="sw-in" value="all"> все
|
||||
</label>
|
||||
<span style="padding:2px 5px;font-size:12px;line-height:1.5;float:right;">Подсказывать адреса:</span>
|
||||
</div>
|
||||
<div class="input-group stylish-input-group">
|
||||
<input type="text" class="form-control" value="{{ APP_INVITATION_STRING_IN_FORM }}"
|
||||
id="addr" autocomplete="on" name="address" data-provide="typeahead"
|
||||
onfocus="if(this.value=='{{ APP_INVITATION_STRING_IN_FORM }}'){this.value='';this.style.color='#000';}"
|
||||
onblur= "if(this.value==''){this.value='{{ APP_INVITATION_STRING_IN_FORM }}';this.style.color='#777';}" />
|
||||
<span class="input-group-addon">
|
||||
<span class="glyphicon glyphicon-search"></span>
|
||||
</span>
|
||||
<span class="input-group-btn">
|
||||
<button type="submit" class="btn btn-primary btn-add">Найти</button>
|
||||
</span>
|
||||
</div>
|
||||
{% if LAST_VISIT %}<div><h5>Ваши последние просмотры:</h5>
|
||||
<ul style="font-size:small">{% for ITEM in LAST_VISIT %}
|
||||
<li><a href="{{ ITEM.LastURL }}">{{ ITEM.LastApart }} <small>({{ ITEM.LastAddress }})</small></a> <small style="font-size: xx-small;">{{ ITEM.Time }}</small></li>{% endfor %}
|
||||
</ul>
|
||||
</div>{% endif %}
|
||||
|
||||
</form>
|
||||
<p></p>{% endwith %}
|
||||
@@ -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">×</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>К сожалению база «Окнардии» только формируется. Пока у нас нет данных по типовым квартирам для дома по этому адресу. Ближащие адреса, по которым есть информация:</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 }} км.</nobr> <a href="#" onclick="uClick({{ forloop.counter0 }})" id="a{{ forloop.counter0 }}">{{ i.sAddress }}</a>
|
||||
</li>
|
||||
{% endfor %}</ul>
|
||||
<p>С небольшой погрешностью (±6%) мы можем дать ценовые предложения под ваши требования, но потребуется самостоятельный замер проёмов.</p>
|
||||
<button type="button" class="btn btn-default" style="margin:1ex"
|
||||
accesskey="1" onclick="window.location.href='/order_by_dim'">
|
||||
Подобрать предложения по предварительным замерам <span class="glyphicon glyphicon-search"></span>
|
||||
</button>
|
||||
<p style="font-size:xx-small;width:80%"><small>Сделав замер, и сообщив его нам, вы поможете наполнению базы «Окнардии», вашим соседям и сотням людей проживающих в домах таких же серий или проектов. В благодарность за содействие мы подготовим и вышлем самые актуальные предложения по email, а также предоставим купон, предоставляющий скидку на сумму <b>500 рублей</b> при установке окон любым из наших партнёров. Если согласны <a href="/send_order_and_info">перейдите по ссылке</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" %}
|
||||
@@ -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 %} и {% else %}, {% endif %}{% endif %}{{ I_WIN_DIM.iWinWidth|floatformat:0 }}x{{ I_WIN_DIM.iWinHight|floatformat:0 }} см. — {{ I_WIN_DIM.iQuantity }} шт.{% 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>Другие типовые квартиры в этом доме:</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">Таблица содержит цены поставщиков. Клик на название отобразит детальные спецификации каждого предложения: марку профиля рамы и створки, схему стеклопакета, тип фурнитуры, элементы отделки (отлив, подоконник, откос, клапан <nobr>климат-контроля</nobr>) и сопутствующие услуги. Предложения выводятся покадрово, получите следующий кадр кнопкой «Ещё коммерческие предложения окон» под таблицей. Просмотреть и сравнить технические характеристик стеклопакетов, профилей и детальное описание сопутствующих услуг возможно с помощью кнопки «Сравнить выбранные».</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 %}
|
||||
|
||||
@@ -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>)
|
||||
■ {{ CurOffer.GLAZING_NAME_B|safe }} <nobr>({{ CurOffer.GLAZING_MARK }})</nobr>
|
||||
■ Тонирование: {{ CurOffer.GLAZING_TONING }}
|
||||
{% if CurOffer.PVC_SEAL %}■ Уплотнитель: {{ CurOffer.PVC_SEAL }}
|
||||
{% endif %}{% if CurOffer.SETS_IMPLEMENT %}■ Фурнитура: {{ CurOffer.SETS_IMPLEMENT|capfirst|safe }}
|
||||
{% endif %}{% if CurOffer.SETS_IMPLEMENT_R %}■ Ручки: {{ CurOffer.SETS_IMPLEMENT_R|capfirst|safe }}
|
||||
{% endif %}{% if CurOffer.SETS_IMPLEMENT_P %}■ Петли: {{ CurOffer.SETS_IMPLEMENT_P|capfirst|safe }}
|
||||
{% endif %}{% if CurOffer.SETS_IMPLEMENT_Z %}■ Запоры: {{ CurOffer.SETS_IMPLEMENT_Z|capfirst|safe }}
|
||||
{% endif %}{% if CurOffer.SETS_IMPLEMENT_O %}■ Ограничители: {{ CurOffer.SETS_IMPLEMENT_O|capfirst|safe }}
|
||||
{% endif %}{% if CurOffer.SETS_IMPLEMENT_F %}■ Фиксаторы: {{ 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 %}>■ Климат-контроль: {{ 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 %}>■ Подоконник: {{ 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 %}>■ Откос: {{ 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 %}>■ Водоотлив: {{ 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 %}>■ Демонтаж/монтаж: {{ 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 %}>■ Доставка: {{ CurOffer.SETS_DELIVERY|capfirst|safe }}</span>{% endif %}{% comment %} {% if CurOffer.SETS_OTHER %}■ Прочие условия: {{ 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 }} км.</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 }}» состоит из:{% else %}Рейтинг не присвоен{% endif %}"
|
||||
data-toggle="popover">рейтинг</a>: {% 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 %}−{{ 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 }} <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"> </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();">Ещё коммерческие предложения окон <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>
|
||||
@@ -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 }}×{{ I_WIN_DIM.iWinHight_mm|floatformat:0 }}{% endfor %} мм. </li>
|
||||
</ol>
|
||||
<h1>Стандартные оконные проёмы и балконные блоки</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 }}×{{ I_WIN_DIM.iWinHight_mm|floatformat:0 }}{% endfor %} мм. <small>(типовое)</small></h1>
|
||||
</div>
|
||||
<div class="col-md-9">
|
||||
<p>Типовой проём {% for I_WIN_DIM in FLAP_DIM %}{{ I_WIN_DIM.iWinWidth|floatformat:1 }}×{{ I_WIN_DIM.iWinHight|floatformat:1 }}{% endfor %} 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">В таблице представлены только цены поставщиков из базы «Окнардия». Клик на названии набора отобразит детальную спецификацию каждого предложения: профиль рамы и створки, схему стеклопакета, фурнитуру, элементы отлива, подоконника, откоса, системы <nobr>климат-контроля</nobr>) и сопутствующие услуги. Предложения выводятся блоками. Очередной блок выводится кнопкой «Ещё коммерческие предложения окон» под таблицей. Детальные технические характеристики стеклопакетов, профилей и описание сопутствующих услуг можно посмотреть и сравнить с помощью кнопки «Сравнить выбранные».</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;">Стоимость, <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>)
|
||||
■ {{ CurOffer.GLAZING_NAME_B|safe }} <nobr>({{ CurOffer.GLAZING_MARK }})</nobr>
|
||||
■ Тонирование: {{ CurOffer.GLAZING_TONING }}
|
||||
{% if CurOffer.PVC_SEAL %}■ Уплотнитель: {{ CurOffer.PVC_SEAL }}
|
||||
{% endif %}{% if CurOffer.SETS_IMPLEMENT %}■ Фурнитура: {{ CurOffer.SETS_IMPLEMENT|capfirst|safe }}
|
||||
{% endif %}{% if CurOffer.SETS_IMPLEMENT_R %}■ Ручки: {{ CurOffer.SETS_IMPLEMENT_R|capfirst|safe }}
|
||||
{% endif %}{% if CurOffer.SETS_IMPLEMENT_P %}■ Петли: {{ CurOffer.SETS_IMPLEMENT_P|capfirst|safe }}
|
||||
{% endif %}{% if CurOffer.SETS_IMPLEMENT_Z %}■ Запоры: {{ CurOffer.SETS_IMPLEMENT_Z|capfirst|safe }}
|
||||
{% endif %}{% if CurOffer.SETS_IMPLEMENT_O %}■ Ограничители: {{ CurOffer.SETS_IMPLEMENT_O|capfirst|safe }}
|
||||
{% endif %}{% if CurOffer.SETS_IMPLEMENT_F %}■ Фиксаторы: {{ 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 %}>■ Климат-контроль: {{ 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 %}>■ Подоконник: {{ 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 %}>■ Откос: {{ 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 %}>■ Водоотлив: {{ 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 %}>■ Демонтаж/монтаж: {{ 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 %}>■ Доставка: {{ CurOffer.SETS_DELIVERY|capfirst|safe }}</span>{% endif %}{% comment %} {% if CurOffer.SETS_OTHER %}■ Прочие условия: {{ 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 }}» состоит из:{% else %}Рейтинг не присвоен{% endif %}"
|
||||
data-toggle="popover">рейтинг</a>: {% 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 %}−{{ 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 }} <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"> </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();">Ещё коммерческие предложения окон <small class="glyphicon-refresh glyphicon"></small></a>{% endif %}</td>
|
||||
</tR>
|
||||
@@ -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> </td>
|
||||
<td>Площадь участка, м²:</td>
|
||||
<td><nobr>{{ LAND|capfirst|default:"Нет данных"|price_format }}</nobr></td>
|
||||
<td> </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>
|
||||
@@ -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 %} и {% else %}, {% endif %}{% endif %}{{ Count.SET_NAME }}{% if forloop.last %}.{% endif %}{% endfor %}</h1>
|
||||
<p>Оконный набор — это комплект оконного профиля (рамы и сворки), стеклопакета, фурнитуры и уплотнителей — готовое окно в сборе для установки в проём. В набор может входить отлив, подоконник, откос, встраиваемые системы <nobr>климат-контроля</nobr>, оконная фурнитура открывания, запоры, уплотнители, москитная сетка… а также сопутствующие услуги: демонтаж старых и установка новых окон, доставка, гарантийное обслуживание, уборка, вынос и утилизация строительного мусора, защитное укрытие мебели на время монтажа и другое.</p>
|
||||
<h4>В таблицу сравнения услуг и условий, входящих в оконные наборы, добавлены следующие производители и поставщики:</h4>
|
||||
<ul>{% for MERCANT in LIST_MERCHANT %}
|
||||
<li>{{ MERCANT }}.</li>{% endfor %}
|
||||
</ul>
|
||||
<h4>В средней части таблице вы сможете сравнить характеристики оконных профилей в наборах:</h4>
|
||||
<ul>{% for PROFILE in LIST_PROFILE %}
|
||||
<li>{{ PROFILE }}.</li>{% endfor %}
|
||||
</ul>
|
||||
<h4>Нижний блок таблицы посвящен характеристикам и сравнению стеклопакетов в наборах. Формулы выбранных стеклопакетов:</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 }}/">в каталоге</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 }}» состоит из:{% 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 == "—/—" %}— / —{% 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 == "—/—" %}— / —{% 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>Помочь разобраться в комплектациях, условиях поставки, характеристиках оконных профилей и стеклопакетов поможет цвет фона ячеек таблицы. <span style="background:#83ff83;">Зеленый цвет</span> — лучшие значения, чем насыщение цвет, тем более высокие потребительская характеристика у того или иного параметра. <span style="background:#ff7f7f;">Красный цвет</span> — отсутствие отдельных элементов или услуг комплексного предложения.</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 %}
|
||||
|
||||
@@ -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;">Оконный профиль:</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;">От пользователей:</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 }}: ввод в эксплуатацию по годам</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 }} на карте</h2>
|
||||
</div>
|
||||
<div class="col-md-7 col-lg-offset-1">
|
||||
<p><small>Чтобы посмотреть цены на установку и замену окон от партнёров «Окнардия» в своей квартире: найдите дом на карте; кликните на него; перейдите по ссылке «Смотреть коммерческие предложения». При необходимости смените типовую планировку квартиры (на странице ценовой выдачи, справа от изображения типовых проёмов и схем открывания).</small></p>
|
||||
@@ -94,22 +90,23 @@
|
||||
|
||||
<diV class="col-md-4">
|
||||
<h3 class="header">Статистика <nobr>серии {{ THIS_SERIA_NAME }}</nobr></h3>
|
||||
<p>Совокупно во всех зданиях типового проекта:</p>
|
||||
<p>Совокупно во всех зданиях типового проекта:</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> — муниципальное жильё.</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>
|
||||
</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>Информация о других, отличных от {{ THIS_SERIA_NAME }}, типовых сериях в базе «Окнардия», типовых планировках квартир и оконных проёмах в них, а также рекомендации по замене окон:</h5>
|
||||
<h5>Информация о других, отличных от {{ THIS_SERIA_NAME }}, типовых сериях в базе «Окнардия», типовых планировках квартир и оконных проёмах в них, а также рекомендации по замене окон:</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 />по типовым сериям домов<br />в базе «Окнардия»</a>.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
|
||||
@@ -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>Типовые серии домов в базе «Окнардия»</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">Распределение типовых серий в жилом фонде<sup><small style="color: darkred;">*</small></sup></h2>
|
||||
</div>
|
||||
<script type="text/javascript" src="https://www.google.com/jsapi"></script>
|
||||
<script type="text/javascript">
|
||||
|
||||
@@ -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>
|
||||
@@ -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> <br> <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&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>
|
||||
@@ -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> в базе «Окнардии»</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>Против некоторых позиций в тарифах «Окнардии» давно стояла метка «с 11.2017». Она появилась почти два года назад, вместе с тарифами нашего оконного агрегатора когда закончилось <nobr>бета-тестирование</nobr>. И вот этот 11.2017 наступил. А значит цены на размещение цен на размещение оконных предложений меняются. Как и было обещано.</p>
|
||||
<p><center><img src="/media/img_for_blog/tariff-changing.gif" /></center></p>
|
||||
<p>Но перед тем, как рассказать детали напомним, что было сделано за эти два года</p><cut />
|
||||
<ul>
|
||||
<li>Размещено 22 оконных набора в предложениях. Размещаются как пластиковые, так и деревянные окна.</li>
|
||||
<li>С «Окнардией» работают уже семь <nobr>оконных-компании</nobr> партнёра.</li>
|
||||
<li>— Добавлен функционал сравнения характеристик оконных предложений и отдельных компонентов этих предложений.</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>. В будущем будет каталог стеклопакетов и фурнитуры… планов много.</li>
|
||||
<li>Построен <a href="/blogpost/17/Nagljadnoe-sravnenie-harakteristik-okonnyh-profilej/">алгоритм расчёта реальных рейтингов</a> предложений, профилей, стеклопакетов и сервиса компаний. Рейтинги не на базе «общественного голосования», «опросов» или измерением <nobr>«интернет-популярности»</nobr>, а на базе физических характеристик и измеримых параметров. Таким образом — это объектовые рейтинги.</li>
|
||||
<li>Разработан <a href="https://widget.oknardia.ru/">виджет</a>, который позволяет оконной компании реализовать функционал «Окнардии» на собственном сайте.</li>
|
||||
<li>Расширены <nobr><a href="/blogpost/16/Novye-media-vozmozhnosti-uchastnikam-Oknardii-/">медиа-возможности</a></nobr> проекта: баннеры, посты в блоге, специальное выделение.</li>
|
||||
</ul>
|
||||
<h3>Теперь о том, что меняется:</h3>
|
||||
<ul>
|
||||
<li>Повысились цены на <nobr>бизнес-пакеты</nobr>. В том числе и стартовый. Эти расценки анонсировались в примечаниях к каждому пакету, так что сюрпризом эти изменения не стали. В том числе стало недоступно бесплатное размещение полных наборов — коммерческих предложений для всех типовых проёмов нашей базы.</li>
|
||||
<li>Введён новый пакет для бесплатного размещения. В его рамках возможно размещать цен только для третьи набора. Это 20–25 расчётов цен типовых проёмов. Выбравшим бесплатный, стартовый набор можно следовать двумя стратегиями: разместить цены на полный набор постепенно в течении квартала или обновлять цены только для проёмов, распространённых в вашем регионе.</li>
|
||||
</ul>
|
||||
<h3>И что не меняется:</h3>
|
||||
<ul>
|
||||
<li>Коммерческие предложения останутся в агрегаторе навсегда. А значит навсегда останутся детальные характеристики предложений, описание оконной компании и ссылки на её сайт.</li>
|
||||
<li>Тарифы на медийное размещения остались без изменений.</li>
|
||||
</ul>
|
||||
<p><a href="/tariff">Новые тарифы размещены на сайте</a>. Скачать шаблоны для платного и бесплатного размещения <a href="/blogpost/2/My_zhdem_vashi_prajs-listy%21/">можно здесь</a>.</p>
|
||||
↓ ↑ ⟶ ⟵ ↗ ↗ ↘ ↙ ↖
|
||||
|
||||
<!--- Пост в блоге :: конец --->
|
||||
</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">←</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">→</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 %}
|
||||
|
||||
@@ -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")}')
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
e8819d2fd6d3
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0"?>
|
||||
<users>
|
||||
<user>027F51EEB3278BF010983153FDB6F9A8</user>
|
||||
</users>
|
||||
@@ -0,0 +1,8 @@
|
||||
АВТОРЫ
|
||||
ДИЗАЙН: Сергей Еремин
|
||||
КОД HTML: Сергей Еремин
|
||||
КОД CSS: Сергей Еремин
|
||||
КОД SVG: Сергей Еремин
|
||||
КОД JavaScript: Сергей Еремин
|
||||
ФОТОРГАФИИ:
|
||||
ГРАФИКА:
|
||||
|
After Width: | Height: | Size: 387 B |
|
After Width: | Height: | Size: 529 B |
|
After Width: | Height: | Size: 635 B |
@@ -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 |
@@ -0,0 +1 @@
|
||||
google-site-verification: googleacb673eb0c31969f.html
|
||||
@@ -0,0 +1 @@
|
||||
google-site-verification: googleea5d443319529114.html
|
||||
@@ -0,0 +1 @@
|
||||
mailru-domain: T2hJTKvdCl8xIbEm
|
||||
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 75 KiB |
|
After Width: | Height: | Size: 5.1 KiB |
|
After Width: | Height: | Size: 8.2 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 100 KiB |
|
After Width: | Height: | Size: 62 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 70 KiB |
|
After Width: | Height: | Size: 4.6 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 108 KiB |
|
After Width: | Height: | Size: 152 KiB |
|
After Width: | Height: | Size: 396 KiB |
|
After Width: | Height: | Size: 172 KiB |
|
After Width: | Height: | Size: 78 KiB |
|
After Width: | Height: | Size: 106 KiB |
|
After Width: | Height: | Size: 95 KiB |
|
After Width: | Height: | Size: 83 KiB |
|
After Width: | Height: | Size: 68 KiB |
|
After Width: | Height: | Size: 337 KiB |
|
After Width: | Height: | Size: 60 KiB |
|
After Width: | Height: | Size: 297 KiB |
|
After Width: | Height: | Size: 54 KiB |
|
After Width: | Height: | Size: 245 KiB |
|
After Width: | Height: | Size: 79 KiB |
|
After Width: | Height: | Size: 351 KiB |
|
After Width: | Height: | Size: 69 KiB |
|
After Width: | Height: | Size: 283 KiB |
|
After Width: | Height: | Size: 80 KiB |
|
After Width: | Height: | Size: 83 KiB |
|
After Width: | Height: | Size: 65 KiB |
|
After Width: | Height: | Size: 59 KiB |
|
After Width: | Height: | Size: 73 KiB |
|
After Width: | Height: | Size: 84 KiB |
|
After Width: | Height: | Size: 64 KiB |
|
After Width: | Height: | Size: 77 KiB |
|
After Width: | Height: | Size: 75 KiB |
|
After Width: | Height: | Size: 115 KiB |
|
After Width: | Height: | Size: 84 KiB |
|
After Width: | Height: | Size: 76 KiB |
|
After Width: | Height: | Size: 110 KiB |
|
After Width: | Height: | Size: 83 KiB |