Вьюшка: "Каталог / Типовые серии зданий / Информация по серии" -- готово

This commit is contained in:
2022-12-26 17:19:01 +03:00
parent 74a28470c1
commit 2eea80f557
10 changed files with 928 additions and 39 deletions

View File

@@ -28,10 +28,10 @@ SECRET_KEY = 'django-insecure-pd&1$j6z*1w#(j*16b+(@@#&2)+@x^^ot4)zqt-e67*1+$^qch
# SECURITY WARNING: don't run with debug turned on in production! # SECURITY WARNING: don't run with debug turned on in production!
# ПРЕДУПРЕЖДЕНИЕ БЕЗОПАСНОСТИ: не работайте в режиме DEBUG в продашене! # ПРЕДУПРЕЖДЕНИЕ БЕЗОПАСНОСТИ: не работайте в режиме DEBUG в продашене!
if socket.gethostname() in MY_HOST_DEV: if socket.gethostname() in MY_HOST_DEV:
DEBUG = True DEBUG = TEMPLATE_DEBUG = True
else: else:
# Все остальные хосты (подразумевается продакшн) # Все остальные хосты (подразумевается продакшн)
DEBUG = False DEBUG = TEMPLATE_DEBUG = False
ALLOWED_HOSTS = MY_ALLOWED_HOSTS ALLOWED_HOSTS = MY_ALLOWED_HOSTS
@@ -181,11 +181,11 @@ OFFER_PER_FRAME = 5
OFFER_PER_FRAME_FOR_ONE_FLAP = 10 OFFER_PER_FRAME_FOR_ONE_FLAP = 10
# папка для хранения изображений # папка для хранения изображений
PATH_FOR_IMG = "img" PATH_FOR_IMG = "img"
PATH_FOR_IMG_BLOG = u"img_for_blog/" PATH_FOR_IMG_BLOG = "img_for_blog/"
PATH_FOR_IMG_AVATAR = u"img_avatar/" PATH_FOR_IMG_AVATAR = "img_avatar/"
PATH_FOR_IMG_LOGOS = u"logos_img/" PATH_FOR_IMG_LOGOS = "logos_img/"
PATH_FOR_IMG_APARTMENT = u"img_apart/" PATH_FOR_IMG_APARTMENT = "img_apart/"
PATH_FOR_IMG_SERIA = u"img_seria/" PATH_FOR_IMG_SERIA = "img_seria/"
# папка для хранения мини-картинок со схемами открывания внутри PATH_FOR_IMG # папка для хранения мини-картинок со схемами открывания внутри PATH_FOR_IMG
PATH_FOR_BIGIMGFLAPCONFIG = "_flap.cfg" PATH_FOR_BIGIMGFLAPCONFIG = "_flap.cfg"
@@ -195,7 +195,7 @@ PATH_FOR_JS = "js"
PATH_FOR_JS_MAP = "js/4maps" PATH_FOR_JS_MAP = "js/4maps"
SUFFIX_FOR_JS_MAP = "_seria_on_map.js" SUFFIX_FOR_JS_MAP = "_seria_on_map.js"
SUFFIX_FOR_MINI_JS_MAP = "_seria_on_map.mini.js" SUFFIX_FOR_MINI_JS_MAP = "_seria_on_map.mini.js"
PATH_FOR_SERIA_INFO_HTML_INCLUDE = "SeriaInfo/prepared/" PATH_FOR_SERIA_INFO_HTML_INCLUDE = "seria_info/prepared/"
# переменные # переменные
# высота картинки # высота картинки

View File

@@ -61,6 +61,7 @@ urlpatterns = [
catalog.catalog_profile_manufacture), catalog.catalog_profile_manufacture),
# --- --- Каталог серий типового строительства # --- --- Каталог серий типового строительства
re_path(r'^catalog/seria[/*]$', catalog.catalog_seria), 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),
] ]

View File

@@ -0,0 +1,18 @@
{# Отрисовка больших картинок с проемами и схамаи открывания #}{% load static %}{% if WIN_DIM %}
{% for I_WIN_DIM in FLAP_DIM %}
<div class="win_discr pull-left" id="flap{{ forloop.counter0 }}">
<div><img src="{% static I_WIN_DIM.url2img %}" alt="{{ I_WIN_DIM.sDescription }}. Размер {{ I_WIN_DIM.iWinWidth|stringformat:".0f" }}0x{{ I_WIN_DIM.iWinHight|stringformat:".0f" }}0 (Ш х В, мм.). Типовая схема открывания." title="{{ I_WIN_DIM.sDescription }}. Размер {{ I_WIN_DIM.iWinWidth|stringformat:".0f" }}0x{{ I_WIN_DIM.iWinHight|stringformat:".0f" }}0 (Ш х В, мм.). Типовая схема открывания." itemprop="image" /></div>
<div class="caption" style="width:{{ I_WIN_DIM.W }}px;min-width:13ex;">
<nobr>{{ I_WIN_DIM.iWinWidth|stringformat:".0f" }}0×{{ I_WIN_DIM.iWinHight|stringformat:".0f" }}0&thinsp;мм.</nobr><br />{% if not I_WIN_DIM.iQuantity == 0 %}
<nobr><b>{{ I_WIN_DIM.iQuantity }}&thinsp;шт.</b>{% for I_II in I_WIN_DIM.qStr %}<span class="color-bullet" style="background-image:url('{% static 'img/svg/mark' %}{{ I_II }}.svg');"></span>{% endfor %}</nobr><br />{% endif %}
{{ I_WIN_DIM.sDescription }}{% if not I_WIN_DIM.iQuantity == 0 %}<br />
<a href="/tsena-odnogo-okna/{{ I_WIN_DIM.iWinWidth|stringformat:".0f" }}0x{{ I_WIN_DIM.iWinHight|stringformat:".0f" }}0mm/tip{{ I_WIN_DIM.id }}">цены только этого типового окна</a>{% endif %}
</div>
</div>{% endfor %}{% comment %}
<script type="text/javascript">
$(document).ready(function(){
var maxHeight = 0;
{% for I_WIN_DIM in FLAP_DIM %}if (maxHeight < $('#flap{{ forloop.counter0 }}').height()) maxHeight = $('#flap{{ forloop.counter0 }}').height();
{% endfor %}{% for I_WIN_DIM in FLAP_DIM %}$('#flap{{ forloop.counter0 }}').height(maxHeight-240);{% endfor %}
});
</script>{% endcomment %}{% else %}<h1>Нет данных о проемах и рекомендованных схемах открывания окон</h1>{% endif %}

View File

@@ -0,0 +1,120 @@
{% extends "base.html" %}
{% load static %}
{% load filters %}
{% load humanize %}
{% block Title %}серия {{ THIS_SERIA_NAME }} — типовые проёмы, размеры окон, схемы открывания, планировки, здания на карте, история и статистика{% 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 %}Информация по зданиям серии {{ THIS_SERIA_NAME }} и установке окон в них{% endblock %}
{% block Keywords %}серии {{ THIS_SERIA_NAME }}, серия {{ THIS_SERIA_NAME }}, проект {{ THIS_SERIA_NAME }}, года простойки, размеры окон, размеры проемов, оконные проемы, {{ THIS_SERIA_NAME }} на карте, установка окон, цены на пластиковые окна{% 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">
{# <!--- Хлебные крошки: НАЧАЛО --> #}<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/seria/">Типовые серии домов</a></li>
<li>Серия {{ THIS_SERIA_NAME }}</li>
</ol>
<h1>СЕРИЯ {{ THIS_SERIA_NAME }}</h1>
</div>
</div>{# <!--- Хлебные крошки: КОНЕЦ ---> #}
<div class="row">
<div class="col-md-12">{{ THIS_SERIA_DESCRIPTION|safe }}</div>
</div>
<div class="row">
<div class="col-lg-10">
<h2 class="header">Окна <nobr>в серии {{ THIS_SERIA_NAME }}</nobr>: типовые размеры и схемы открывания</h2>
</div>
<div class="col-lg-12" style="padding:1em 0 0 0;margin-left:-1em">
{% include 'report/show_big_flap_pictures.html' %}
</div>
</div>
<div class="row">
<div class="col-lg-8 col-xs-12 col-md-offset-1">
<h3 class="header">Оконные проёмы в типовых квартирах <nobr>серии {{ THIS_SERIA_NAME }}</nobr></h3>
</div>
<div class="col-lg-8 col-xs-12 col-md-offset-1">
<!--- прешаблон начало ---><table style="padding:2px;">{% templatetag openblock %} for row in TABLE_OF_WINDOWS {% templatetag closeblock %}
<tr class="tr2">
<td>{% templatetag openvariable %} row.APART_NAME|safe {% templatetag closevariable %}</td>{% templatetag openblock %} for col in row.WIN_IN_APART {% templatetag closeblock %}
<td class="cntr">{% templatetag openblock %} if col.WIN_ID {% templatetag closeblock %}<nobr title="{% templatetag openvariable %} col.WIN_Q {% templatetag closevariable %} × {% templatetag openvariable %} col.WIN_DESCRIPTION {% templatetag closevariable %}: {% templatetag openvariable %} col.WIN_WIDTH {% templatetag closevariable %}шт.: {% templatetag openvariable %} col.WIN_HEIGHT {% templatetag closevariable %} (Ш×В, см.). Схема открывания: {% templatetag openvariable %} col.WIN_FLAPCFG {% templatetag closevariable %}">{% templatetag openblock %} for I_II in col.WIN_NUM {% templatetag closeblock %}<span style="background-image:url('{% static 'img/svg/mark' %}{% templatetag openvariable %} I_II {% templatetag closevariable %}.svg');">&nbsp;</span>{% templatetag openblock %} endfor {% templatetag closeblock %}</nobr>{% templatetag openblock %} else {% templatetag closeblock %}—{% templatetag openblock %} endif {% templatetag closeblock %}</td>{% templatetag openblock %} endfor {% templatetag closeblock %}
<td style="background:#f9f9f9;"><a href="#{% templatetag openvariable %} row.APART_ID {% templatetag closevariable %}" class="badge" title="Оконных предложений для квартиры: {% templatetag openvariable %} row.NUM_OFFERS {% templatetag closevariable %}"><small class="glyphicon glyphicon-tags" aria-hidden="true"></small>&nbsp;{% templatetag openvariable %} row.NUM_OFFERS {% templatetag closevariable %}</a></td>
</tr>{% templatetag openblock %} endfor {% templatetag closeblock %}
<tr class="trZ">
<td style="font-size: xx-small;vertical-align:text-top">© 2015-{% now "Y" %}, данные: oknardia.ru</td>{% templatetag openblock %} for i in WIN_OFFER_AND_MERCHANT {% templatetag closeblock %}
<td class="cntr" style="background:#f9f9f9;"><a href="/tsena-odnogo-okna/{% templatetag openvariable %} i.WIN_W|floatformat:0 {% templatetag closevariable %}0x{% templatetag openvariable %} i.WIN_H|floatformat:0 {% templatetag closevariable %}0mm/tip{% templatetag openvariable %} i.WIN_ID {% templatetag closevariable %}" class="badge" title="Ценовых предложений для окна: {% templatetag openvariable %} i.WIN_OFFER {% templatetag closevariable %}"><small class="glyphicon glyphicon-tags" aria-hidden="true"></small>&nbsp;{% templatetag openvariable %} i.WIN_OFFER {% templatetag closevariable %}</a></td>{% templatetag openblock %} endfor {% templatetag closeblock %}
<td></td>
</tr>
</table>
<!--- прешаблон конец ---></div>
</div>
<div class="row">
<div class="col-md-9"><a name="s_graph"></a>
<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' %}
</div>
<div class="col-md-9 col-md-offset-1">
<div style="font-size: xx-small;float: right">© 2015-{% now "Y" %}, данные: oknardia.ru</div>
</div>
</div>
<div class="row">
<div class="col-md-7"><a name="s_map"></a>
<h2 class="header">Серия {{ THIS_SERIA_NAME }}: жилой фонд проекта на карте</h2>
</div>
<div class="col-md-7 col-lg-offset-1">
<p><small>Чтобы посмотреть цены на&nbsp;установку и&nbsp;замену окон от&nbsp;партнёров &laquo;Окнардия&raquo; в&nbsp;своей квартире: найдите дом на&nbsp;карте; кликните на&nbsp;него; перейдите по&nbsp;ссылке &laquo;Смотреть коммерческие предложения&raquo;. При необходимости смените типовую планировку квартиры (на&nbsp;странице ценовой выдачи, справа от&nbsp;изображения типовых проёмов и&nbsp;схем открывания).</small></p>
<div style="height:350px;">{% include 'seria_info/geo_map.html' %}</div>
<div style="font-size: xx-small;float: right">© 2015-{% now "Y" %}, данные: oknardia.ru</div>
</div>
<diV class="col-md-4">
<h3 class="header">Статистика <nobr>серии {{ THIS_SERIA_NAME }}</nobr></h3>
<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>
</ul>
</diV>
</div>
<div class="row">
<div class="col-md-4 col-lg-offset-8" style="margin-top: 1em;">{# <div class="col-md-4"> #}
<h5>Информация о&nbsp;других, отличных от&nbsp;{{ THIS_SERIA_NAME }}, типовых сериях в&nbsp;базе &laquo;Окнардия&raquo;, типовых планировках квартир и&nbsp;оконных проёмах в&nbsp;них, а также рекомендации по замене окон:</h5>
<div class="href_d">{% include 'seria_info/seria_nav.html' %}</div>
</div>
</div>
<div class="row">
{% templatetag openblock %}include "report/report_last_user_visit.html" {% templatetag closeblock %}
{% templatetag openblock %} include "report/report_log_user_visit.html" {% templatetag closeblock %}
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,56 @@
{# Скрипт Yandex.Chart для отображения клястеров на карте #}{% load static %}
{% block Top_JS5 %}<script src="https://api-maps.yandex.ru/2.1/?lang=ru_RU" type="text/javascript"></script>
{% if MAP_JS %}<script src="{% static '' %}{{ MAP_JS }}" charset="utf-8" type="text/javascript"></script>{% else %}<script type="text/javascript">
let points = [{% for count in DATA4GEO %}{% if forloop.last %}[{{ count.LONGITUDE|stringformat:"f" }},{{ count.LATITUDE|stringformat:"f" }}]{% else %}[{{ count.LONGITUDE|stringformat:"f" }},{{ count.LATITUDE|stringformat:"f" }}],{% endif %}{% endfor %}];
let forURL = [{% for count in DATA4GEO %}{{ count.ADDR_ID }}{# ,rus: '{{ count.ADDR_RUS }}',lat: '{{ count.ADDR_LAT }}' #}{% if not forloop.last %},{% endif %}{% endfor %}];
ymaps.ready(function () {
var myMap = new ymaps.Map('SeriaMap', {
center: [55.75, 37.57],
zoom: 10,
behaviors: ['default', 'scrollZoom'],
controls: [ 'rulerControl', 'zoomControl', 'geolocationControl', 'fullscreenControl' ]
});
// Создадим кластеризатор, вызвав функцию-конструктор.
clusterer = new ymaps.Clusterer({
preset: 'islands#invertedGrayClusterIcons',
groupByCoordinates: false,
hasHint: false,
viewportMargin: 0,
zoomMargin: 16,
clusterDisableClickZoom: false,
gridSize: 80
});
geoObjects = [];
add_str1 = '<a href="/';
add_str2 = '/0/">Смотреть коммерческие предложения</a>';
add_str3 = '<b>Здание серии {{ THIS_SERIA_NAME }}</b>';
// Данные передаются в конструктор метки.
for(var i = 0, len = points.length; i < len; i++) {
geoObjects[i] = new ymaps.Placemark( points[i],
{ // Содержимое иконки, балуна и хинта.
balloonContent: add_str1 + forURL[i] + add_str2,
hintContent: add_str3
},
{ preset:'islands#circleIcon',iconColor: 'silver'} );
geoObjects[i].events
.add('mouseenter', function (e) {
e.get('target').options.set('preset', 'islands#yellowCircleIcon');
})
.add('mouseleave', function (e) {
e.get('target').options.set('preset', 'islands#grayCircleIcon');
});
}
// Добавляем метки в кластеризатор.
clusterer.add(geoObjects);
myMap.geoObjects.add(clusterer);
// позиционирование карты так, чтобы на ней были видны все объекты кластера.
myMap.setBounds(clusterer.getBounds(), { checkZoomRange: true });
});
</script>{% endif %}{% endblock %}
<div id="SeriaMap" style="height:100%;width:100%;">
<noscript>
<p>Для отображения картографических данных по серии {{ THIS_SERIA_NAME }} с&nbsp;помощью &laquo;Яндекс.Карт&raquo; нужно включить поддержку JavaScript.</p>
</noscript>
</div>

View File

@@ -0,0 +1 @@
{# Выводит навигацию по сериям домов #}{% for CountSeria in SERIA_NAV_DIM %}{% if CountSeria.SERIA_L == "" %}<span style="background-color:cornsilk;"><nobr>{{ CountSeria.SERIA_R }}</nobr></span>{% else %}<span><a href="/catalog/seria/{{ CountSeria.SERIA_L }}/all{{ CountSeria.ID2URL }}/"><nobr>{{ CountSeria.SERIA_R }}</nobr></a></span>{% endif %}{% endfor %}

View File

@@ -0,0 +1,58 @@
{# Скрипт Google.Chart для рисования графика #}{% block Top_JS3 %}
<script type="text/javascript" src="https://www.google.com/jsapi" type="text/javascript"></script>{# <script type="text/javascript" src="https://www.gstatic.com/charts/loader.js" type="text/javascript"></script> #}
<script type="text/javascript">
// google.charts.load('current', {'packages':['bar']});
// google.charts.setOnLoadCallback(drawChart);
google.load("visualization", "1", {packages:["corechart"]});
google.setOnLoadCallback(drawChart);
function drawChart() {
let data = google.visualization.arrayToDataTable([
["Год", "Введено в эксплуатацию", {role:'style'}],{% for row in DATA4GRAPH %}
["{{ row.YEAR }}",{{ row.NUMS }},"color: #99{{ row.CLRS }}99"]{% if not forloop.last %},{% endif %}{% endfor %}
]);
let view = new google.visualization.DataView(data);
view.setColumns([0, 1,
{ calc: "stringify",
sourceColumn: 1,
type: "string",
role: "annotation" },
2
]);
let options = {
animation:{
duration: 1500,
easing: 'in',
startup: true
},
backgroundColor: "#EEEEEE",
bar: {groupWidth: "76.4%"},
chartArea: {left: "2%", top: "5%", width: '96%', height: '85%'},
dataOpacity: 0.76,
explorer:{
maxZoomIn: 0.20,
maxZoomOut: 32 },
vAxis: {
baselineColor:'grey',
gridlines:{color: 'silver', count: 7},
minorGridlines:{color: '#dddddd', count: 3},
textPosition: 'in',
textStyle: {fontSize: 10}
},
hAxis: { textStyle: {fontSize: 10} },
isStacked: true,
tooltip: {
textStyle:{color: 'grey', fontSize: 10 },
trigger: 'selection'
},
annotations: {textStyle: { fontSize: 8, bold: true, color: 'black', opacity: 0.8 }},
legend: { position: "none" }
};
let chart = new google.visualization.ColumnChart(document.getElementById("graph"));
chart.draw(data, options);
}
</script>{% endblock %}
<div id="graph" style="width:100%;height:100%">
<p>Для отображения гистограммы ввода в&nbsp;эксплуатацию зданий серии {{ THIS_SERIA_NAME }} нужна поддержка SVG.</p>
<noscript>
<p>Для визуализации отображения картографической информации с&nbsp;помощью Google.Charts нужно включить поддержку JavaScript.</p>
</noscript></div>

View File

@@ -1,7 +1,10 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
__author__ = 'Sergei Erjemin' __author__ = 'Sergei Erjemin'
# from transliterate import translit # from transliterate import translit
from PIL import Image, ImageDraw
from oknardia.settings import * from oknardia.settings import *
import os
import re import re
import math import math
@@ -97,3 +100,298 @@ def normalize(val: float, val_max: int = 5, val_min: int = 0) -> float:
:return: float: float -- нормализованное значение :return: float: float -- нормализованное значение
""" """
return float(val - val_min) / float(val_max - val_min) return float(val - val_min) / float(val_max - val_min)
def get_flaps_for_big_pictures(query_set) -> dict:
""" Возвращает словарь с размерами картинок для больших картинок.
:param query_set: QuerySet -- QuerySet с объектами
:return: dict: dict -- словарь с размерами картинок для больших картинок
"""
result = {}
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:
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 # размещение дверной перемычки
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 если это дверь
else:
img_file_name += u"W" # добавляем букву W если это окно
if i.bIsNearDoor:
img_file_name += u"N" # добавляем букву N если это окно рядом с дверью
mount_bulk = i.iWinHight # размещение дверной перемычки
else:
img_file_name += u"-" # добавляем символ отдельное окно (не рядом с дверью)
# маскируем символы схемы открывания, которые не допустимы в названии файлов
img_file_name += i.sFlapConfig
img_file_name = img_file_name.replace(">", "G")
img_file_name = img_file_name.replace("<", "L")
img_file_name = img_file_name.replace("[", "(")
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"
# проверяем, есть ли файл с нужной картинкой схемам открывания?
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)
# чтобы получить разноцветные маркеры меток количества проемов
# получаем последовательность тип: AB, CD, E, FG, H
q_local = ""
for i_local in range(0, i.iQuantity):
i_through += 1
q_local += chr(65 + i_through) # 65 -- код "A"
flaps_dim.append({
'url2img': f"img/{img_file_name}",
'iWinWidth': i.iWinWidth,
'iWinHight': i.iWinHight,
'iWinDepth': i.iWinDepth,
'iQuantity': i.iQuantity,
'sDescription': i.sDescripion,
'id': i.id,
'qStr': q_local,
'W': int((i.iWinWidth*250) / mount_max_h)
})
result.update({'FLAP_DIM': flaps_dim,
'WIN_DIM': query_set})
return result
def make_big_img_win_flap(img_file_name_with_path: str, width: int, height: int, is_door: bool,
flap_config: str, height_max: int, height_mount_bulk: int, height_door: int) -> None:
"""
Функция создает png-картинку схемы открывания окна или двери
:param img_file_name_with_path: str -- полное имя файла картинки
:param width: int -- ширина окна или двери (см.)
:param height: int -- высота окна или двери
:param is_door: bool -- True - дверь, False - окно
:param flap_config: str -- схема открывания
:param height_max: int -- максимальная высота окна или двери (высота картинки)
:param height_mount_bulk: int -- высота расположения дверной перемычки
:param height_door: int -- высота дверного проема
:return: None
"""
# создаем картинку с нужными размерами
img = Image.new("RGBA", (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: # это дверь. Выравнивание по нижней кромке
top = bottom - (height * PICT_H / height_max)
else: # это не дверь... Выравниваем по верхней кромке
if height < height_door:
top = bottom - (height_door * PICT_H / height_max)
bottom = top + (height * PICT_H / height_max)
flap_h = bottom - top
# рисуем внешнюю рамку
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)
dim_flap = flap_analiz(flap_config)
############################################################
# НАЧИНАЕМ ОТРИСОВКУ СТВОРОК ОКНА НА КРТИНКУ
############################################################
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 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
# отрисовка схему открывания створки
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: # Это дверь. Выравнивание по нижней кромке
top = bottom - (height * PICT_H / height_max)
# рисуем серединную перегородку =
draw.line((left, top + (height_mount_bulk * PICT_H / height_max) - 8,
right - 2, top + (height_mount_bulk * PICT_H / height_max) - 8),
fill=(125, 125, 125), width=8)
draw.line((left + 6, top + (height_mount_bulk * PICT_H / height_max) - 4,
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"]: # вертикальная перегородка |
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"]: # горизонтальная перегородка =
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
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"]: # поворотное влево >
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"]: # поворотное вправо <
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"]: # глухое окно +
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"]: # РАСШИРИТЕЛЬ (спейсер)
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)
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=4)
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=4)
# Отрисовка створки. ПЕРИМЕТР.
draw.line((local_left, local_bottom, local_right, local_bottom), fill=(125, 125, 125), width=18)
draw.line((local_left, local_top, local_right, local_top), fill=(125, 125, 125), width=18)
draw.line((local_left, local_top, local_left, local_bottom), fill=(125, 125, 125), width=18)
draw.line((local_right, local_top, local_right, local_bottom), fill=(125, 125, 125), width=18)
local_left = local_right
local_top = local_bottom
# второй проход -- рисуем тоненькую рамочку внутри толстых линий
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 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
# Отрисовка створки. ПЕРИМЕТР
draw.rectangle((local_left, local_top, local_right, local_bottom), fill=None, outline=(0, 0, 0))
local_left = local_right
local_top = local_bottom
# рисуем крест-на-крест перечеркивание всего проема (просто так, для отладки)
# draw.line((left+6, top+6, right-6, bottom-6), fill=(200,200,200), width=1)
# draw.line((left+6, bottom-6, right-6, top+6), fill=(200,200,200), width=1)
# для оконо ниже чем самый высокий проем рисуем прямоугольник под подоконником
draw.rectangle((left, top + (height * PICT_H / height_max), right - 1, PICT_H),
fill=(255, 255, 255, 0), outline=None)
# для окон ниже чем дверь рисуем прямоугольник над проемом
draw.rectangle((left, 0, right - 1, top), fill=(255, 255, 255, 0), outline=None)
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))
del draw
# сохраняем картинку
# img.info = {"Comment": "123456"}
img.save(img_file_name_with_path)
return
def flap_analiz(flap_config: str) -> list:
# анализ схем открывания.
dim_flap = [] # массив для хранения полной схемы открывания
i_end = len(flap_config)
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({}) # надо создать новый ряд (фрамуги или ряд створок)
j += 1
k = -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
if not in_flap and flap_config[i].isdigit():
dim_flap[j].update({"vRatio": int(flap_config[i])})
continue
# получен символ начала описания створки
if flap_config[i] == "[" and not in_flap:
in_flap = True
dim_flap[j]["row"].append({})
k += 1
dim_flap[j]["row"][k].update({"flap": ""})
dim_flap[j]["row"][k].update({"hRatio": 1})
continue
# получен символ окончание описания створки
if in_flap and flap_config[i] == "]":
in_flap = False
continue
# символ увеличения пропорции
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])})
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"): # ^
dim_flap[j]["row"][k].update({"flap": dim_flap[j]["row"][k]["flap"] + flap_config[i]})
return dim_flap

View File

@@ -2,15 +2,18 @@
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from django.shortcuts import render, redirect from django.shortcuts import render, redirect
from django.http import HttpRequest, HttpResponse from django.http import HttpRequest, HttpResponse
from django.template.loader import render_to_string
from django.utils import timezone from django.utils import timezone
from oknardia.settings import * from oknardia.settings import *
from oknardia.models import PVCprofiles, Seria_Info from oknardia.models import PVCprofiles, Seria_Info, Win_MountDim, Building_Info
from web.report1 import get_last_all_user_visit_list, get_last_user_visit_cookies, get_last_user_visit_list 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 from web.add_func import normalize, get_rating_set_for_stars, get_flaps_for_big_pictures
import time import time
import json import json
import random import random
import re import re
import os
import math
import pytils import pytils
@@ -193,7 +196,7 @@ def catalog_profile_model(request: HttpRequest, manufacture_id: int, manufacture
list_other = [] list_other = []
for i in q_pvc_by_id.sProfileOther.split(";"): for i in q_pvc_by_id.sProfileOther.split(";"):
j = i.find(":") j = i.find(":")
list_other.append(u"<b>" + i[:j+1] + u"</b>" + i[j+1:]) list_other.append(u"<b>" + i[:j + 1] + u"</b>" + i[j + 1:])
to_template.update({"LIST_OTHER": list_other}) to_template.update({"LIST_OTHER": list_other})
q_merchant = PVCprofiles.objects.raw(f"SELECT" q_merchant = PVCprofiles.objects.raw(f"SELECT"
f" COUNT(oknardia_priceoffer.id) AS offers_by_merchant," f" COUNT(oknardia_priceoffer.id) AS offers_by_merchant,"
@@ -279,7 +282,7 @@ def catalog_profile_model(request: HttpRequest, manufacture_id: int, manufacture
# получаем последние визиты всех посетителей из базы # получаем последние визиты всех посетителей из базы
# id2log, log_visit = get_last_all_user_visit_list() # id2log, log_visit = get_last_all_user_visit_list()
'LOG_VISIT': get_last_all_user_visit_list(), 'LOG_VISIT': get_last_all_user_visit_list(),
'ticks': float(time.time()-time_start) 'ticks': float(time.time() - time_start)
}) })
return render(request, "catalog/catalog_of_profiles_model.html", to_template) return render(request, "catalog/catalog_of_profiles_model.html", to_template)
@@ -300,7 +303,7 @@ def catalog_profile_manufacture(request: HttpRequest, manufacture_id: int, manuf
return redirect(f'/catalog/profile/{manufacture_id}-' return redirect(f'/catalog/profile/{manufacture_id}-'
f'{pytils.translit.slugify(q_pvc_by_id.sProfileManufacturer)}') f'{pytils.translit.slugify(q_pvc_by_id.sProfileManufacturer)}')
else: else:
q_pvc_by_id = PVCprofiles.objects.order_by('id')\ q_pvc_by_id = PVCprofiles.objects.order_by('id') \
.filter(sProfileManufacturer=q_pvc_by_id.sProfileManufacturer).first() .filter(sProfileManufacturer=q_pvc_by_id.sProfileManufacturer).first()
if q_pvc_by_id.id != manufacture_id: if q_pvc_by_id.id != manufacture_id:
return redirect(f'/catalog/profile/{q_pvc_by_id.id}-' return redirect(f'/catalog/profile/{q_pvc_by_id.id}-'
@@ -332,7 +335,7 @@ def catalog_profile_manufacture(request: HttpRequest, manufacture_id: int, manuf
0, re.IGNORECASE)}) 0, re.IGNORECASE)})
to_template.update({'TIZER': re.sub(r'<script[\s\S]*?</script>|<style[\s\S]*?</style>|<iframe[\s\S]*?</iframe>', to_template.update({'TIZER': re.sub(r'<script[\s\S]*?</script>|<style[\s\S]*?</style>|<iframe[\s\S]*?</iframe>',
'', to_template["CONTENT"], 0, re.IGNORECASE)}) '', to_template["CONTENT"], 0, re.IGNORECASE)})
except (ObjectDoesNotExist, IndexError, TypeError, KeyError, ): except (ObjectDoesNotExist, IndexError, TypeError, KeyError,):
pass pass
q_profiles = PVCprofiles.objects.raw( q_profiles = PVCprofiles.objects.raw(
f"SELECT oknardia_pvcprofiles.id," f"SELECT oknardia_pvcprofiles.id,"
@@ -373,8 +376,8 @@ def catalog_profile_manufacture(request: HttpRequest, manufacture_id: int, manuf
to_template.update({ to_template.update({
'OFFERS_BY_MAUFACTURE': q_share_of_offers.offers_by_maufacture, 'OFFERS_BY_MAUFACTURE': q_share_of_offers.offers_by_maufacture,
'OFFERS_OTHER': q_share_of_offers.offers_other, 'OFFERS_OTHER': q_share_of_offers.offers_other,
'OFFERS_ANGLE': 90+180*normalize(q_share_of_offers.offers_by_maufacture, 'OFFERS_ANGLE': 90 + 180 * normalize(q_share_of_offers.offers_by_maufacture,
q_share_of_offers.offers_other + q_share_of_offers.offers_by_maufacture) q_share_of_offers.offers_other + q_share_of_offers.offers_by_maufacture)
}) })
if q_share_of_offers is not None and q_share_of_offers.offers_by_maufacture != 0: if q_share_of_offers is not None and q_share_of_offers.offers_by_maufacture != 0:
q_merchant = PVCprofiles.objects.raw( q_merchant = PVCprofiles.objects.raw(
@@ -410,15 +413,15 @@ def catalog_profile_manufacture(request: HttpRequest, manufacture_id: int, manuf
"MERCHANT_OFFERS": i.offers_by_merchant "MERCHANT_OFFERS": i.offers_by_merchant
}) })
to_template.update({'MERCHANTS': list_merchant}) to_template.update({'MERCHANTS': list_merchant})
except (ObjectDoesNotExist, IndexError, TypeError): # вообще-то, запрос q_share_of_offers всегда что-то вернёт, except (ObjectDoesNotExist, IndexError, TypeError): # вообще-то, запрос q_share_of_offers всегда что-то вернёт,
pass # но на всякий случай pass # но на всякий случай
to_template.update({ to_template.update({
# получаем последние визиты клиента через куки # получаем последние визиты клиента через куки
'LAST_VISIT': get_last_user_visit_list(get_last_user_visit_cookies(request)[:3]), 'LAST_VISIT': get_last_user_visit_list(get_last_user_visit_cookies(request)[:3]),
# получаем последние визиты всех посетителей из базы # получаем последние визиты всех посетителей из базы
# id2log, log_visit = get_last_all_user_visit_list() # id2log, log_visit = get_last_all_user_visit_list()
'LOG_VISIT': get_last_all_user_visit_list(), 'LOG_VISIT': get_last_all_user_visit_list(),
'ticks': float(time.time()-time_start) 'ticks': float(time.time() - time_start)
}) })
return render(request, "catalog/catalog_of_profiles_manufacture.html", to_template) return render(request, "catalog/catalog_of_profiles_manufacture.html", to_template)
@@ -449,7 +452,7 @@ def catalog_seria(request: HttpRequest) -> HttpResponse:
"NAME_T": pytils.translit.slugify(i.sName) "NAME_T": pytils.translit.slugify(i.sName)
}) })
to_template = {'SERIAS': list_seria} to_template = {'SERIAS': list_seria}
except (ObjectDoesNotExist, ): except (ObjectDoesNotExist,):
to_template = {} to_template = {}
to_template.update({ to_template.update({
# получаем последние визиты клиента через куки # получаем последние визиты клиента через куки
@@ -460,3 +463,349 @@ def catalog_seria(request: HttpRequest) -> HttpResponse:
'ticks': float(time.time() - time_start) 'ticks': float(time.time() - time_start)
}) })
return render(request, "catalog/catalog_seria.html", to_template) 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:
"""
КАТАЛОГ ТИПОВЫЙ СЕРИЙ: страница детальной информацией по серии зданий
:param request: HttpRequest -- входящий http-запрос
:param seria_name_translit: str -- имя серии здания (транслитерированное pytils.translit.slugify())
:param seria_id: int -- id серии
:return response: HttpResponse -- исходящий http-ответ
"""
time_start = time.time()
msg = ""
try:
seria_id = int(seria_id)
q_seria = Seria_Info.objects.get(id=seria_id)
if q_seria.id != q_seria.kRoot_id or seria_name_translit != pytils.translit.slugify(q_seria.sName):
return redirect(f"/catalog/seria/{pytils.translit.slugify(q_seria.sName)}/all{seria_id}")
except(ObjectDoesNotExist, ValueError,):
return redirect("/catalog/")
# если есть "облегченный" шаблон с частичным пре-рендером, то используем его.
light_template = f"{PATH_FOR_SERIA_INFO_HTML_INCLUDE}{str(seria_id)}_id.html"
light_template_w_path = f"{TEMPLATES[0]['DIRS'][0]}/{light_template}"
# print(f"{TEMPLATES[0]['DIRS'][0]}/{light_template}")
# print(light_template_w_path)
if os.path.isfile(light_template_w_path):
is_hard_template = False
else:
is_hard_template = True
to_template = {}
# получаем проемы использующиеся в данной серии домов
q_windows_in_seria = Win_MountDim.objects.raw(
f"SELECT DISTINCT"
f" oknardia_win_mountdim.iWinWidth, oknardia_win_mountdim.iWinHight,"
f" oknardia_win_mountdim.sDescripion, oknardia_win_mountdim.bIsDoor,"
f" oknardia_win_mountdim.bIsNearDoor, oknardia_win_mountdim.sFlapConfig,"
f" oknardia_win_mountdim.iWinDepth, oknardia_win_mountdim.id,"
f" 1 AS iQuantity "
f"FROM oknardia_mountdim2apartment"
f" INNER JOIN oknardia_win_mountdim"
f" ON oknardia_mountdim2apartment.kMountDim_id = oknardia_win_mountdim.id"
f" INNER JOIN oknardia_apartment_type"
f" ON oknardia_mountdim2apartment.kApartment_id = oknardia_apartment_type.id "
f"WHERE oknardia_apartment_type.kSeria_id = {seria_id}"
f" ORDER BY oknardia_win_mountdim.bIsNearDoor DESC,"
f" oknardia_win_mountdim.bIsDoor DESC,"
f" oknardia_win_mountdim.iWinWidth,"
f" oknardia_win_mountdim.iWinHight DESC;")
if is_hard_template:
# Получаем данные для отрисовки больших картинок с проёмами и передаём в "тяжёлый" шаблон
to_template.update(get_flaps_for_big_pictures(q_windows_in_seria))
# формируем строку для включения в SQL-запрос вида "(2,8,16,46,1)"
str_for_sql_in = "("
for count in q_windows_in_seria:
str_for_sql_in += str(count.id) + ","
str_for_sql_in = str_for_sql_in[:-1] + ")"
# print StringForSqlIN
# Получаем данные для таблички Окон по типам квартирах в серии дома
# " IFNULL(oknardia_mountdim2apartment.iQuantity, 0) AS iQuantity," \
# tStart2 = time.time() # замер времени
q_win_in_apartment_in_seria = Win_MountDim.objects.raw(
f"SELECT"
f" oknardia_win_mountdim.id,"
f" oknardia_apartment_type.sNameApartment,"
f" oknardia_win_mountdim.iWinWidth,"
f" oknardia_win_mountdim.iWinHight,"
f" oknardia_apartment_type.id AS id_apart,"
f" IFNULL(oknardia_mountdim2apartment.iQuantity, 0) AS iQuantity,"
f" COUNT(oknardia_priceoffer.id) AS NumOffers "
f"FROM oknardia_apartment_type"
f" INNER JOIN oknardia_win_mountdim"
f" LEFT OUTER JOIN oknardia_mountdim2apartment"
f" ON oknardia_mountdim2apartment.kMountDim_id = oknardia_win_mountdim.id"
f" AND oknardia_mountdim2apartment.kApartment_id = oknardia_apartment_type.id"
f" LEFT OUTER JOIN oknardia_priceoffer"
f" ON oknardia_priceoffer.kOffer2MountDim_id = oknardia_win_mountdim.id"
f" LEFT OUTER JOIN oknardia_ouruser"
f" ON oknardia_ouruser.id = oknardia_priceoffer.kOfferFromUser_id "
f"WHERE oknardia_apartment_type.kSeria_id = {seria_id} "
f"AND oknardia_win_mountdim.id IN {str_for_sql_in} "
f"GROUP BY oknardia_apartment_type.id,"
f" oknardia_apartment_type.sNameApartment,"
f" oknardia_win_mountdim.id,"
f" oknardia_mountdim2apartment.iQuantity "
f"ORDER BY oknardia_apartment_type.iSort,"
f" oknardia_win_mountdim.bIsNearDoor DESC,"
f" oknardia_win_mountdim.bIsDoor DESC,"
f" oknardia_win_mountdim.iWinWidth,"
f" oknardia_win_mountdim.iWinHight DESC;")
list_win_in_seria = list(q_windows_in_seria)
total_column = len(list_win_in_seria) - 1
count_column = 0
min_offer_in_row = 1000000000
table_of_win_in_seria_by_apartmment = []
row_for_table = []
offer_and_merchant_per_win = [
{
"WIN_OFFER": 0,
"WIN_MERCHANT": 0,
"WIN_W": list_win_in_seria[i].iWinWidth,
"WIN_H": list_win_in_seria[i].iWinHight,
"WIN_ID": list_win_in_seria[i].id
} for i in range(total_column + 1)]
for count in q_win_in_apartment_in_seria:
if count.iQuantity != 0:
row_for_table.append({
"WIN_NUM": [chr(65 + count_column)],
"WIN_Q": count.iQuantity,
"WIN_ID": count.id,
"WIN_WIDTH": list_win_in_seria[count_column].iWinWidth,
"WIN_HEIGHT": list_win_in_seria[count_column].iWinHight,
"WIN_DESCRIPTION": list_win_in_seria[count_column].sDescripion,
"WIN_FLAPCFG": list_win_in_seria[count_column].sFlapConfig
})
if min_offer_in_row > count.NumOffers:
min_offer_in_row = count.NumOffers
if offer_and_merchant_per_win[count_column]["WIN_OFFER"] < count.NumOffers:
offer_and_merchant_per_win[count_column]["WIN_OFFER"] = count.NumOffers
else:
row_for_table.append({"WIN_NUM": ""})
if count_column < total_column:
count_column += 1
else:
# print row_for_table
table_of_win_in_seria_by_apartmment.append({"WIN_IN_APART": row_for_table,
"APART_NAME": count.sNameApartment,
"APART_ID": count.id_apart,
"NUM_OFFERS": min_offer_in_row})
count_column = 0
min_offer_in_row = 10000
row_for_table = []
# print(table_of_win_in_seria_by_apartmment)
# print(f"==============>{float(time.time()-tStart2)}<==============")
# print NumOffersPerColumn, NumMerchantPerColumn
to_template.update({"WIN_OFFER_AND_MERCHANT": offer_and_merchant_per_win,
"TABLE_OF_WINDOWS": table_of_win_in_seria_by_apartmment})
# для "тяжелого шаблона" получаем навигацию страницы, данные для карты и графика ввода в эксплуатацию
if is_hard_template:
# если вызывается "тяжелый" шаблон, то нужно подготовить тяжелые данные для построения навигации
seria_id, for_seria_nav = seria_nav(seria_id)
to_template.update(for_seria_nav) # данные для навигации по сериям
to_template.update(seria_info_year(seria_id)) # данные для графика ввода зданий серии в эксплуатацию
to_template.update(seria_info_geo_code(seria_id)) # данные для карты
# т.к. обрабатывается "тяжелый шаблон" надо создать "легкий шаблон"
# для его использования в будущем.
string_prerender = render_to_string("seria_info/all_seria_info_pre_light.html", to_template)
file = open(light_template_w_path, 'w')
# file.write(AA.encode('utf-8'))
file.write(string_prerender)
file.close()
else:
seria_name = Seria_Info.objects.get(id=seria_id).sName
to_template.update({'THIS_SERIA_NAME': seria_name})
# to_template.update({'LOG_VISIT': GetLastAllUserVisitSeriaList(SeriaName),
# 'ticks': float(time.time()-time_start)})
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, light_template, to_template)
def seria_nav(seria_id: int = 12) -> (int, dict):
"""
Возвращает корректный seria_id и кортеж для построения навигации по сериям дома
:param seria_id: id серии
:return:
"""
q_seria = Seria_Info.objects.raw(
'SELECT oknardia_seria_info.id,'
' oknardia_seria_info.sName,'
' oknardia_seria_info.sSeriaDescription,'
' oknardia_seria_info.kRoot_id,'
' oknardia_seria_info.kParent_id '
'FROM oknardia_seria_info '
'WHERE oknardia_seria_info.id = oknardia_seria_info.kRoot_id '
'ORDER BY oknardia_seria_info.sName;')
error_seria = True
for count_seria in q_seria:
if count_seria.id == int(seria_id):
error_seria = False
break
if error_seria:
# Ошибочный seria_id. Такой базовой серии нет и надо ее найти.
try:
query = Seria_Info.objects.get(id=int(seria_id))
if query.kRoot_id is not None:
# базовая серия прописана в kRoot_id
seria_id = query.kRoot_id
else:
# == корневой нет
# == ищем методом наименьших расстояний"
min_min = 100000000
min_id = seria_id
for count_seria in q_seria:
if math.fabs(int(seria_id) - count_seria.id) < min_min:
min_min = math.fabs(int(seria_id) - count_seria.id)
min_id = count_seria.id
seria_id = min_id
except ObjectDoesNotExist:
seria_id = q_seria[0].id
# print(f"-->{seria_id}<--")
return all_seria_nav(seria_id, q_seria)
def all_seria_nav(seria_id: int, q_seria) -> (int, dict):
seria_nav_dim = []
this_return = {}
for count_seria in q_seria:
one_seria = {}
one_seria.update({"SERIA_R": count_seria.sName, "ID2URL": count_seria.id})
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": ""})
else:
one_seria.update({"SERIA_L": pytils.translit.slugify(count_seria.sName)})
seria_nav_dim.append(one_seria)
this_return.update({"SERIA_NAV_DIM": seria_nav_dim})
return seria_id, this_return
def seria_info_year(seria_id: int = 12) -> dict:
""" Возвращает данные для графика распределения сдачи серии в эксплуатацию
:param seria_id: int -- id серии для которой нужно получить данные
:return: dict -- данные для графика распределения сдачи серии в эксплуатацию типа:
{"DATA4GRAPH": [{'YEAR': 1997, 'NUMS': 1, 'CLRS': '99'},
{'YEAR': 1998, 'NUMS': 15, 'CLRS': 'сс'},
{'YEAR': 1998, 'NUMS': 10, 'CLRS': 'a9'}
]
}
"""
seria_in_years = []
query = Seria_Info.objects.raw(
f"SELECT oknardia_building_info.iCommissioning_year as id,"
f" COUNT(oknardia_building_info.iCommissioning_year) AS NumInYear "
f"FROM oknardia_building_info"
f" INNER JOIN oknardia_seria_info"
f" ON oknardia_building_info.kSeria_Link_id = oknardia_seria_info.id "
f"WHERE oknardia_seria_info.kRoot_id = {seria_id} "
f"GROUP BY oknardia_building_info.iCommissioning_year;"
)
max_per_year = 0
graph_color_light = 0xCC # самый светлый цвет на графике (максимальное значение)
graph_color_dark = 0x99 # самый темный цвет на графике (минимальное значение)
for YearCount in query:
if int(YearCount.NumInYear) > max_per_year:
max_per_year = int(YearCount.NumInYear)
# print("max", MaxPerYear)
for YearCount in query:
data_of_year = {}
try:
data_of_year.update({
"YEAR": int(YearCount.id),
"NUMS": YearCount.NumInYear,
"CLRS": str(hex(int(graph_color_dark + YearCount.NumInYear * (
graph_color_light - graph_color_dark) / max_per_year)))[2:]
})
except ValueError:
continue
seria_in_years.append(data_of_year)
# print(seria_in_years)
return {"DATA4GRAPH": seria_in_years}
def seria_info_geo_code(seria_id: str = '12') -> dict:
""" Возвращает массив геокоординат зданий одной серии
:param seria_id: str -- id серии для которой нужно получить данные
:return: dict -- массив геокоординат зданий серии
"""
data_return = {}
seria_to_geo = []
municipal_m2 = 0 # муниципальный фонд (кв.м)
residential_m2 = 0 # жилой фонд (кв.м)
government_m2 = 0 # государственные учреждения занимают (кв.м.)
residents = 0 # количество жильцов
apartments = 0 # число квартиры
accounts = 0 # количество лицевых счетов
condition_max = 0 # максимальное значение показателя состояния здания
condition_min = 1000000 # минимальное значение показателя состояния здания
query = Building_Info.objects.raw(
f"SELECT"
f" oknardia_building_info.id,"
f" oknardia_seria_info.kRoot_id as SerId,"
f" oknardia_building_info.sAddress,"
f" oknardia_building_info.fResidential_Area,"
f" oknardia_building_info.fMunicipal_Area,"
f" oknardia_building_info.fGovernment_Area,"
f" oknardia_building_info.iNum_Residents,"
f" oknardia_building_info.iNum_Apartments,"
f" oknardia_building_info.iNum_Accounts,"
f" oknardia_building_info.fCondition_House,"
f" oknardia_building_info.fGeoCode_Latitude,"
f" oknardia_building_info.fGeoCode_Longitude "
f"FROM oknardia_building_info"
f" INNER JOIN oknardia_seria_info"
f" ON oknardia_building_info.kSeria_Link_id = oknardia_seria_info.id "
f"WHERE oknardia_seria_info.kRoot_id IN ({seria_id});"
)
for count in query:
if int(count.fGeoCode_Latitude) != 0 and int(count.fGeoCode_Longitude) != 0:
seria_to_geo.append({"LATITUDE": count.fGeoCode_Latitude,
"LONGITUDE": count.fGeoCode_Longitude,
"ADDR_ID": count.id,
"ADDR_LAT": pytils.translit.slugify(count.sAddress),
"ADDR_RUS": count.sAddress,
"SER_ID": count.SerId
})
if count.fMunicipal_Area > 0:
municipal_m2 += count.fMunicipal_Area
if count.fResidential_Area > 0:
residential_m2 += count.fResidential_Area
if count.fGovernment_Area > 0:
government_m2 += count.fGovernment_Area
if count.iNum_Residents > 0:
residents += count.iNum_Residents
if count.iNum_Residents > 0:
residents += count.iNum_Residents
if count.iNum_Apartments > 0:
apartments += count.iNum_Apartments
if count.iNum_Accounts > 0:
accounts += count.iNum_Accounts
if count.fCondition_House > 0:
if count.fCondition_House > condition_max:
condition_max = count.fCondition_House
if count.fCondition_House < condition_min:
condition_min = count.fCondition_House
data_return.update({"DATA4GEO": seria_to_geo,
"MUNICIPAL_M2": municipal_m2,
"RESIDENTIAL_M2": residential_m2,
"GOVERNMENT_M2": government_m2,
"RESIDENTS": residents,
"APARTMENTS": apartments,
"ACCOUNTS": accounts,
"CONDITION_MAX": condition_max,
"CONDITION_MIN": condition_min})
# print(seria_to_geo)
return data_return

View File

@@ -3,14 +3,15 @@ from django.shortcuts import render
from django.http import HttpRequest, HttpResponse from django.http import HttpRequest, HttpResponse
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from time import time from time import time
from oknardia.models import Seria_Info
from oknardia.settings import * from oknardia.settings import *
from oknardia.models import Seria_Info
# from oknardia.catalog import all_seria_nav
import math import math
import os import os
import pytils # вместо Rus2Lat(smth) --> pytils.translit.slugify(smth).lower() import pytils # вместо Rus2Lat(smth) --> pytils.translit.slugify(smth).lower()
# возвращает корректный SeriaID и кортеж для построения навигации по сериям дома # возвращает корректный seria_id и кортеж для построения навигации по сериям дома
def seria_nav(i_seria_id: int = 12) -> (int, dict): def seria_nav(i_seria_id: int = 12) -> (int, dict):
query_seria = Seria_Info.objects.raw( query_seria = Seria_Info.objects.raw(
'SELECT oknardia_seria_info.id,' 'SELECT oknardia_seria_info.id,'
@@ -27,7 +28,7 @@ def seria_nav(i_seria_id: int = 12) -> (int, dict):
error_seria = False error_seria = False
break break
if error_seria: if error_seria:
# Ошибочный SeriaID. Такой базовой серии нет и надо ее найти. # Ошибочный seria_id. Такой базовой серии нет и надо ее найти.
try: try:
query = Seria_Info.objects.get(id=int(i_seria_id)) query = Seria_Info.objects.get(id=int(i_seria_id))
if query.kRoot_id is None: if query.kRoot_id is None:
@@ -45,21 +46,8 @@ def seria_nav(i_seria_id: int = 12) -> (int, dict):
i_seria_id = min_id i_seria_id = min_id
except ObjectDoesNotExist: except ObjectDoesNotExist:
i_seria_id = query_seria[0].id i_seria_id = query_seria[0].id
# print("-->", SeriaID, "<--") # print(f"-->{seria_id}<--")
seria_nav_dim = [] return all_seria_nav(i_seria_id, query_seria)
this_return = {}
for count_seria in query_seria:
one_seria = {}
one_seria.update({"SERIA_R": count_seria.sName, "ID2URL": count_seria.id})
if count_seria.id == i_seria_id:
this_return.update({"THIS_SERIA_NAME": count_seria.sName,
"THIS_SERIA_DESCRIPTION": count_seria.sSeriaDescription})
one_seria.update({"SERIA_L": ""})
else:
one_seria.update({"SERIA_L": pytils.translit.slugify(count_seria.sName)})
seria_nav_dim.append(one_seria)
this_return.update({"SERIA_NAV_DIM": seria_nav_dim})
return i_seria_id, this_return
def statistic_menu(request: HttpRequest) -> HttpResponse: def statistic_menu(request: HttpRequest) -> HttpResponse: