mod: raw-SQL --> ORM для цен на наборы кокон в квартиру. SEO-атртибуты + добавлена schema.org

This commit is contained in:
2026-04-27 20:42:00 +03:00
parent 2d830aa897
commit 9097414637
5 changed files with 371 additions and 215 deletions

View File

@@ -6,15 +6,86 @@
{% block Add_Body_Attribute %} style="padding-top:70px;"{% endblock %} {% block Add_Body_Attribute %} style="padding-top:70px;"{% endblock %}
{% block Date4Meta %}{{ META_DATA_PUBLISH|date:"Y-m-d" }}{% endblock %} {# SEO-описание: информативно для поисковиков и людей, но кратно. #}
{% block Description %}Лучшие цены на пластиковые окна для серии {{ APART|safe }} в доме по адресу {{ ADDRESS }}. Сравните {{ PRICE_FRAME|length }} предложений от ведущих компаний, узнайте стоимость окон для вашей квартиры и получите скидку!{% endblock %}
{% block Last4Meta %}{{ META_DATA_PUBLISH|date:"Y-m-d" }}{% endblock %} {# SEO-ключевые слова: расширяем, добавляем вариации, город, преимущества. #}
{% block Keywords %}цены на окна, пластиковые окна, серия {{ BASE_SERIA }}, стоимость окон, окна для {{ BASE_SERIA }}, размеры окон, проемы серии {{ BASE_SERIA }}, окна в {{ APART|safe }}, скидки на окна, {{ ADDRESS }}, оконный профиль, монтаж окон, установка окон, сравнение цен, лучшие предложения, акции, рассрочка, {{ KEYWORDS_EXTRA }}{% 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 %} {% block ADD_TO_HEAD %}{# --- Микроразметка schema.org, Open Graph, Twitter Card, meta-даты --- #}
{# --- JSON-LD микроразметка schema.org --- #}<script type="application/ld+json">
{% 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 %} "@context": "https://schema.org/",
"@type": "BreadcrumbList",
"itemListElement": [
{"@type": "ListItem", "position": 1, "name": "Главная", "item": "{{ request.scheme }}://{{ request.get_host }}/"},
{"@type": "ListItem", "position": 2, "name": "Каталог", "item": "{{ request.scheme }}://{{ request.get_host }}/catalog"},
{"@type": "ListItem", "position": 3, "name": "Цены на окна", "item": "{{ request.scheme }}://{{ request.get_host }}{{ request.path }}"}
]
},
{
"@context": "https://schema.org/",
"@type": "Organization",
"name": "ОКНАРДИЯ — агрегатор цен на окна",
"url": "{{ request.scheme }}://{{ request.get_host }}/",
"logo": "{{ request.scheme }}://{{ request.get_host }}{% static 'img/oknardia_logo.svg' %}",
"description": "Сравнение цен на установку оконных конструкций в типовых жилых домах России",
"contactPoint": {"@type": "ContactPoint", "contactType": "Customer Service"}
},
{
"@context": "https://schema.org/",
"@type": "Product",
"name": "Окна для {{ APART|safe }} ({{ ADDRESS }})",
"size": "{% for I_WIN_DIM in FLAP_DIM %}{{ I_WIN_DIM.iWinWidth|floatformat:0 }}x{{ I_WIN_DIM.iWinHight|floatformat:0 }}мм — {{ I_WIN_DIM.iQuantity }} шт.{% if not forloop.last %}; {% endif %}{% endfor %}",
"description": "Цены на пластиковые окна для серии {{ APART|safe }} по адресу {{ ADDRESS }}. Сравните предложения, комплектации, получите скидки и выберите лучшее решение!",
"image": {"@type": "ImageObject", "url": "{{ request.scheme }}://{{ request.get_host }}{% static 'img/oknardia_logo.svg' %}"},
"brand": {"@type": "Brand", "name": "ОКНАРДИЯ"},
"url": "{{ request.scheme }}://{{ request.get_host }}{{ request.path }}",
"offers": {"@type": "AggregateOffer", "priceCurrency": "RUB", "itemCondition": "https://schema.org/NewCondition", "availability": "https://schema.org/InStock", "offerCount": "{{ PRICE_FRAME|length }}"}
},
{# --- ItemList с Offer для каждого предложения (цена, продавец, рейтинг, дата, внутренняя ссылка, профиль, стеклопакет, список окон) --- #}{
"@context": "https://schema.org/",
"@type": "ItemList",
"itemListElement": [
{% for CurOffer in PRICE_FRAME %}
{
"@type": "Offer",
"position": {{ forloop.counter }},
"name": "{{ CurOffer.SETS_NAME|escapejs }}",
"seller": {"@type": "Organization", "name": "{{ CurOffer.MERCHANT|escapejs }}"},
"windows": [
{% for CurInOffer in CurOffer.DIM %}{"size": "{{ CurInOffer.WIDTH|stringformat:'d' }}x{{ CurInOffer.HIGHT|stringformat:'d' }}", "count": {{ CurInOffer.QUANTITY }}}{% if not forloop.last %}, {% endif %}{% endfor %}
],
"profile": "{{ CurOffer.PVC_NAME|escapejs }}",
"glazing": "{{ CurOffer.GLAZING_NAME_B|escapejs }}",
"price": "{{ CurOffer.FIN_PRICE|stringformat:'d' }}",
"priceCurrency": "RUB",
{% if CurOffer.SETS_RATING %}"aggregateRating": {"@type": "AggregateRating", "ratingValue": "{{ CurOffer.SETS_RATING|stringformat:'.2f' }}"}, {% endif %}
{% if CurOffer.SETS_DATA_MODIFY %}"priceValidUntil": "{{ CurOffer.SETS_DATA_MODIFY|date:'Y-m-d' }}", {% endif %}
"availability": "https://schema.org/InStock",
"itemCondition": "https://schema.org/NewCondition",
"url": "#offer_{{ CurOffer.SETS_ID }}"
}{% if not forloop.last %},
{% endif %}{% endfor %}
]
}
]
</script>
{# --- Open Graph (OG) --- #}<meta property="og:title" content="Цены на окна для {{ APART|safe }} ({{ ADDRESS }})" />
<meta property="og:description" content="Сравните цены, комплектации и получите лучшие предложения на пластиковые окна для серии {{ APART|safe }} по адресу {{ ADDRESS }}!" />
<meta property="og:type" content="website" />
<meta property="og:url" content="{{ request.scheme }}://{{ request.get_host }}{{ request.path }}" />
<meta property="og:image" content="{{ request.scheme }}://{{ request.get_host }}{% static 'img/oknardia_logo.svg' %}" />
<meta property="og:site_name" content="ОКНАРДИЯ — агрегатор цен на окна" />
{# --- Twitter Card --- #}<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="Цены на окна для {{ APART|safe }} ({{ ADDRESS }})" />
<meta name="twitter:description" content="Сравните цены, комплектации и получите лучшие предложения на пластиковые окна для серии {{ APART|safe }} по адресу {{ ADDRESS }}!" />
<meta name="twitter:image" content="{{ request.scheme }}://{{ request.get_host }}{% static 'img/oknardia_logo.svg' %}" />
{# --- Даты публикации и обновления --- #}<meta name="date" content="{{ META_DATA_PUBLISH|date:'Y-m-d' }}" />
<meta property="article:published_time" content="{{ META_DATA_PUBLISH|date:'Y-m-d' }}" />
<meta property="article:modified_time" content="{{ META_DATA_PUBLISH|date:'Y-m-d' }}" />
{% endblock %}
{% block Top_JS3%}<script type="text/javascript"> {% block Top_JS3%}<script type="text/javascript">
function show_phone_num( id ){ // колапсатор для отображения контатной информации постафшика окон function show_phone_num( id ){ // колапсатор для отображения контатной информации постафшика окон
@@ -107,23 +178,20 @@ $(function () { // инициализация и обработка попове
{% block Top_CSS1 %}<link rel="stylesheet" type="text/css" href="{% static "css/csshake-vertical.min.css" %}">{% endblock %} {% block Top_CSS1 %}<link rel="stylesheet" type="text/css" href="{% static "css/csshake-vertical.min.css" %}">{% endblock %}
{% block Main_Content %} {% block Main_Content %}<div class="container-fluid">
<span itemscope itemtype="http://schema.org/Product"> <div class="row">
<div class="row col-md-12">
<div class="col-md-9"> <div class="col-md-9">
<h1>Цены на окна для серии {{ APART|safe }} <small>({{ ADDRESS }})</small></h1> <h1>Цены на окна для серии {{ APART|safe }} <small><nobr>({{ ADDRESS }})</nobr></small></h1>
</div> </div>
<div class="col-md-9"> <div class="col-md-9">
<p>Квартира имеет проёмы (окна и балконные двери) следующих размеров: {% for I_WIN_DIM in FLAP_DIM %}{% if not forloop.first %}{% if forloop.last %} и&nbsp;{% else %}, {% endif %}{% endif %}{{ I_WIN_DIM.iWinWidth|floatformat:0 }}x{{ I_WIN_DIM.iWinHight|floatformat:0 }}&nbsp;см. —&nbsp;{{ I_WIN_DIM.iQuantity }}&nbsp;шт.{% endfor %} Проект (<a href="/catalog/seria/{{ BASE_SERIA_LAT }}/all{{ BASE_SERIA_ID }}">типовая серия {{ BASE_SERIA }}</a>) предполагает следующие схемы открывания окон:</p> <p>Квартира имеет проёмы (окна и балконные двери) следующих размеров: {% for I_WIN_DIM in FLAP_DIM %}{% if not forloop.first %}{% if forloop.last %} и&nbsp;{% else %}, {% endif %}{% endif %}{{ I_WIN_DIM.iWinWidth|floatformat:0 }}x{{ I_WIN_DIM.iWinHight|floatformat:0 }}&nbsp;см. —&nbsp;{{ I_WIN_DIM.iQuantity }}&nbsp;шт.{% endfor %} Проект (<a href="/catalog/seria/{{ BASE_SERIA_LAT }}/all{{ BASE_SERIA_ID }}">типовая серия {{ BASE_SERIA }}</a>) предполагает следующие схемы открывания окон:</p>
</div> </div>
{# Микроразмектка: названеи продукта #}<meta itemprop="name" content="Окна {{ APART|safe }} ({{ ADDRESS }})" /> </div>
</div> </div>
<div class="row col-md-12 ShowBigFlapPictures"> <div class="row ShowBigFlapPictures">
<div class="col-sm-9"> <div class="col-sm-9">{% include 'report/show_big_flap_pictures.html' %}</div>
{% include 'report/show_big_flap_pictures.html' %} <div class="col-sm-3 visible-md visible-lg ap_list">
</div>
<div class="col-sm-3 visible-md visible-lg ap_list">
<h6>Другие типовые квартиры в&nbsp;этом доме:</h6> <h6>Другие типовые квартиры в&nbsp;этом доме:</h6>
<ul>{% for I_APART in APARTMENT_IN_BUILDING %} <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 %} {% 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 %}
@@ -136,8 +204,6 @@ $(function () { // инициализация и обработка попове
<div class="col-md-12"> <div class="col-md-12">
<p id="tab-note">Таблица содержит цены поставщиков. Клик на&nbsp;название отобразит детальные спецификации каждого предложения: марку профиля рамы и&nbsp;створки, схему стеклопакета, тип фурнитуры, элементы отделки (отлив, подоконник, откос, клапан <nobr>климат-контроля</nobr>) и&nbsp;сопутствующие услуги. Предложения выводятся покадрово, получите следующий кадр кнопкой &laquo;Ещё коммерческие предложения окон&raquo; под таблицей. Просмотреть и&nbsp;сравнить технические характеристик стеклопакетов, профилей и&nbsp;детальное описание сопутствующих услуг возможно с&nbsp;помощью кнопки &laquo;Сравнить выбранные&raquo;.</p> <p id="tab-note">Таблица содержит цены поставщиков. Клик на&nbsp;название отобразит детальные спецификации каждого предложения: марку профиля рамы и&nbsp;створки, схему стеклопакета, тип фурнитуры, элементы отделки (отлив, подоконник, откос, клапан <nobr>климат-контроля</nobr>) и&nbsp;сопутствующие услуги. Предложения выводятся покадрово, получите следующий кадр кнопкой &laquo;Ещё коммерческие предложения окон&raquo; под таблицей. Просмотреть и&nbsp;сравнить технические характеристик стеклопакетов, профилей и&nbsp;детальное описание сопутствующих услуг возможно с&nbsp;помощью кнопки &laquo;Сравнить выбранные&raquo;.</p>
</div> </div>
{# Микроразмектка: названеи продукта #}
<meta itemprop="name" content="Окна {{ APART|safe }} ({{ ADDRESS }})"/>
</div> </div>
@@ -168,8 +234,7 @@ $(function () { // инициализация и обработка попове
{% include "price/price_list_frame.html" %} {% include "price/price_list_frame.html" %}
</tbody> </tbody>
</table> </table>
</form> </form>
</span>
{% with SERIA_BASE=BASE_SERIA %}{% include "report/build_info_in_table.html" %}{% endwith %} {% 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"><div class="col-md-12 col-xs-12"><hr class="dotted-black" />{% include "ad/bannet-wide.html" %}</div></div>

View File

@@ -7,8 +7,8 @@
{% if forloop.first %} {% 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> <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="Краткая спецификация коммерческого предложения"> <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"> <span>
<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> <h3 class="set-name shake-trigger" id="btn{{ CurOffer.SETS_ID }}"><a href="javascript://" onclick="show_dtl({{ CurOffer.SETS_ID }})">{{ CurOffer.MERCHANT }}&nbsp; {{ 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>) <DiV id="dtl{{ CurOffer.SETS_ID }}" class="collapse">■ Профиль: <a href="/catalog/profile/{{ CurOffer.PVC_ID }}-{{ CurOffer.PVC_MANUFACTURER_T }}/{{ CurOffer.PVC_ID }}-{{ CurOffer.PVC_NAME_T }}">{{ CurOffer.PVC_NAME|safe }}</a> (<a href="/catalog/profile/{{ CurOffer.PVC_ID }}-{{ CurOffer.PVC_MANUFACTURER_T }}">{{ CurOffer.PVC_MANUFACTURER }}</a>)
&nbsp;{{ CurOffer.GLAZING_NAME_B|safe }} <nobr>({{ CurOffer.GLAZING_MARK }})</nobr> &nbsp;{{ CurOffer.GLAZING_NAME_B|safe }} <nobr>({{ CurOffer.GLAZING_MARK }})</nobr>
@@ -40,10 +40,8 @@
title="{% if CurOffer.SETS_RATING > 0.01 %}<b> Рейтинг {{ CurOffer.SETS_RATING|stringformat:".2f" }}</b> для оконого набора «{{ CurOffer.SETS_NAME }}» компании «{{ CurOffer.MERCHANT }}» состоит&nbsp;из:{% else %}Рейтинг не присвоен{% endif %}" title="{% if CurOffer.SETS_RATING > 0.01 %}<b> Рейтинг {{ CurOffer.SETS_RATING|stringformat:".2f" }}</b> для оконого набора «{{ CurOffer.SETS_NAME }}» компании «{{ CurOffer.MERCHANT }}» состоит&nbsp;из:{% else %}Рейтинг не присвоен{% endif %}"
data-toggle="popover">рейтинг</a>:&nbsp;{% for Star in CurOffer.SETS_RATING_STARTS %}{% if Star == 0 %}<b class="glyphicon glyphicon-star-empty"></b>{% else %}<b class="glyphicon glyphicon-star"></b>{% endif %}{% endfor %} {% if CurOffer.SETS_RATING > -0.1 %} {{ CurOffer.SETS_RATING|stringformat:".2f" }}{% endif %}</nobr> data-toggle="popover">рейтинг</a>:&nbsp;{% for Star in CurOffer.SETS_RATING_STARTS %}{% if Star == 0 %}<b class="glyphicon glyphicon-star-empty"></b>{% else %}<b class="glyphicon glyphicon-star"></b>{% endif %}{% endfor %} {% if CurOffer.SETS_RATING > -0.1 %} {{ CurOffer.SETS_RATING|stringformat:".2f" }}{% endif %}</nobr>
</span> </span>
<span itemprop="brand" itemscope itemtype="http://schema.org/Brand"> {# Удалить: старая микроразметка schema.org (brand, meta) #}
<meta itemprop="name" content="{{ CurOffer.MERCHANT }}" /> </td>
<meta itemprop="logo" content="{{ request.scheme }}://{{ request.get_host }}/media/{{ CurOffer.MERCHANT_LOGO }}" />
</span></td>
<!--- Конец большой ячейки со спецификацией оконного предложения ---> <!--- Конец большой ячейки со спецификацией оконного предложения --->
{% endif %} {% endif %}
@@ -58,10 +56,9 @@
<td class="rnw" title="Стоимость {{ CurOffer.TOTAL|stringformat:".2f" }} рублей за все окна квартиры {{ APART|safe }}.">{{ CurOffer.TOTAL|stringformat:".2f"|price_format }}</td> <td class="rnw" title="Стоимость {{ CurOffer.TOTAL|stringformat:".2f" }} рублей за все окна квартиры {{ APART|safe }}.">{{ CurOffer.TOTAL|stringformat:".2f"|price_format }}</td>
<th{% if CurOffer.DISCOUNT_COLOR2 != "" %} style="background-color:{{ CurOffer.DISCOUNT_COLOR2 }};"{% endif %} title="{% if CurOffer.DISCOUNT < 0.1 %}Нет скидки{% else %}Скидка — {{ CurOffer.DISCOUNT|stringformat:".1f" }}%{% endif %}">{% if CurOffer.DISCOUNT < 0.1 %}{% else %}&minus;{{ CurOffer.DISCOUNT|stringformat:".1f" }}%{% endif %}</th> <th{% if CurOffer.DISCOUNT_COLOR2 != "" %} style="background-color:{{ CurOffer.DISCOUNT_COLOR2 }};"{% endif %} title="{% if CurOffer.DISCOUNT < 0.1 %}Нет скидки{% else %}Скидка — {{ CurOffer.DISCOUNT|stringformat:".1f" }}%{% endif %}">{% if CurOffer.DISCOUNT < 0.1 %}{% else %}&minus;{{ CurOffer.DISCOUNT|stringformat:".1f" }}%{% endif %}</th>
<th{% if CurOffer.DISCOUNT_COLOR1 != "" %} style="background-color:{{ CurOffer.DISCOUNT_COLOR1 }};"{% endif %} itemprop="offers" itemscope itemtype="http://schema.org/Offer" title="Итого за все окна с учетом скидки: {{ CurOffer.FIN_PRICE|stringformat:".2f" }} рублей"> <th{% if CurOffer.DISCOUNT_COLOR1 != "" %} style="background-color:{{ CurOffer.DISCOUNT_COLOR1 }};"{% endif %} title="Итого за все окна с учетом скидки: {{ CurOffer.FIN_PRICE|stringformat:".2f" }} рублей">
Итого: {{ CurOffer.FIN_PRICE|stringformat:".2f"|price_format }}&thinsp;<small class="glyphicon glyphicon-ruble" aria-label="₽ (руб.)" title="₽ (руб.)"></small> Итого: {{ CurOffer.FIN_PRICE|stringformat:".2f"|price_format }}&thinsp;<small class="glyphicon glyphicon-ruble" aria-label="₽ (руб.)" title="₽ (руб.)"></small>
<meta itemprop="price" content="{{ CurOffer.FIN_PRICE }}" /> {# Удалить: старая микроразметка schema.org (meta price, priceCurrency) #}
<meta itemprop="priceCurrency" content="RUB" />
</th> </th>
{% if CurOffer.DIM|length == 1 %} {% if CurOffer.DIM|length == 1 %}

View File

@@ -1,13 +1,13 @@
{# Отрисовка больших картинок с проемами и схамаи открывания #}{% load static %}{% if WIN_DIM %} {# Отрисовка больших картинок с проемами и схемами открывания #}{% load static %}{% if WIN_DIM %}
{% for I_WIN_DIM in FLAP_DIM %} {% for I_WIN_DIM in FLAP_DIM %}
<div class="win_discr pull-left" id="flap{{ forloop.counter0 }}"> <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><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;"> <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>{{ 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 %} <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 /> {{ I_WIN_DIM.sDescription }}{% if not I_WIN_DIM.iQuantity == 0 %}<br />
<a href="/catalog/standard_opening/price-{{ I_WIN_DIM.iWinWidth|stringformat:".0f" }}0x{{ I_WIN_DIM.iWinHight|stringformat:".0f" }}0mm-tip{{ I_WIN_DIM.id }}">цены только этого типового окна</a>{% endif %} <a href="/catalog/standard_opening/price-{{ I_WIN_DIM.iWinWidth|stringformat:".0f" }}0x{{ I_WIN_DIM.iWinHight|stringformat:".0f" }}0mm-tip{{ I_WIN_DIM.id }}">цены только этого типового окна</a>{% endif %}
</div> </div>
</div>{% endfor %}{% comment %} </div>{% endfor %}{% comment %}
<script type="text/javascript"> <script type="text/javascript">
$(document).ready(function(){ $(document).ready(function(){

View File

@@ -4,7 +4,15 @@ from django.db.models import Count
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.utils import timezone from django.utils import timezone
from oknardia.models import Win_MountDim, PriceOffer, Apartment_Type, Seria_Info, LogVisitPriceReport, MountDim2Apartment from oknardia.models import (
Win_MountDim,
PriceOffer,
Apartment_Type,
Seria_Info,
Building_Info,
LogVisitPriceReport,
MountDim2Apartment,
)
from oknardia.settings import * 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.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, \ from web.add_func import normalize, get_rating_set_for_stars, get_flaps_for_big_pictures, get_flaps_for_mini_pictures, \
@@ -18,6 +26,11 @@ from types import SimpleNamespace
import pytils import pytils
def _slugify_lower(value: str | None) -> str:
"""Транслитерирует строку в slug и всегда приводит к нижнему регистру."""
return pytils.translit.slugify((value or "").strip()).lower()
def _one_win_price_canonical_path(win_width_mm: int | str, win_height_mm: int | str, win_id: int | str) -> str: def _one_win_price_canonical_path(win_width_mm: int | str, win_height_mm: int | str, win_id: int | str) -> str:
"""Возвращает канонический путь страницы цен для одного типового окна.""" """Возвращает канонический путь страницы цен для одного типового окна."""
return f"/catalog/standard_opening/price-{int(win_width_mm)}x{int(win_height_mm)}mm-tip{int(win_id)}/" return f"/catalog/standard_opening/price-{int(win_width_mm)}x{int(win_height_mm)}mm-tip{int(win_id)}/"
@@ -34,6 +47,26 @@ def redirect_one_win_price_legacy(request: HttpRequest,
) )
def _append_visit_context(
to_template: dict,
request: HttpRequest,
time_start: float,
log_visit: list | None = None,
last_visit_cookie: list | None = None,
) -> None:
"""Дописывает в контекст стандартный хвост: визиты и время выполнения."""
if log_visit is None:
log_visit = get_last_all_user_visit_list()
if last_visit_cookie is None:
last_visit_cookie = get_last_user_visit_cookies(request)
to_template.update({
'LAST_VISIT': get_last_user_visit_list(last_visit_cookie[:3]),
'LOG_VISIT': log_visit,
'ticks': float(time.perf_counter() - time_start),
})
def report_price_frame(apartment_id: int, mount_dim_per_offer: int, address_longitude: float, address_latitude: float, 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: frame_begin_n: int = 0, brand_id: int = 0, win_id: int = 0) -> dict:
""" Формируем выдачу цен для фрейма """ Формируем выдачу цен для фрейма
@@ -241,9 +274,9 @@ def report_price_frame(apartment_id: int, mount_dim_per_offer: int, address_long
'GLAZING_TONING': offer.sGlazingToning, 'GLAZING_TONING': offer.sGlazingToning,
'PVC_ID': offer.pwc_id, 'PVC_ID': offer.pwc_id,
'PVC_NAME': offer.sProfileName, 'PVC_NAME': offer.sProfileName,
'PVC_NAME_T': pytils.translit.slugify(offer.sProfileName).lower(), 'PVC_NAME_T': _slugify_lower(offer.sProfileName),
'PVC_MANUFACTURER': offer.sProfileManufacturer, 'PVC_MANUFACTURER': offer.sProfileManufacturer,
'PVC_MANUFACTURER_T': pytils.translit.slugify(offer.sProfileManufacturer).lower(), 'PVC_MANUFACTURER_T': _slugify_lower(offer.sProfileManufacturer),
'PVC_SEAL': offer.sProfileSealDescription, 'PVC_SEAL': offer.sProfileSealDescription,
'SETS_CLIMATE_CONTROL': offer.sSetClimateControl, 'SETS_CLIMATE_CONTROL': offer.sSetClimateControl,
'SETS_SILL': offer.sSetSill, 'SETS_SILL': offer.sSetSill,
@@ -283,50 +316,110 @@ def report_price_frame(apartment_id: int, mount_dim_per_offer: int, address_long
return {'META_DATA_PUBLISH': time_for_meta, 'PRICE_FRAME': price_frame, 'N': n_begin} return {'META_DATA_PUBLISH': time_for_meta, 'PRICE_FRAME': price_frame, 'N': n_begin}
else: else:
# если выводим цены для типовой квартиры # если выводим цены для типовой квартиры
# print("Нужно несколько окон для квартиры") # ORM-ветка сохраняет контракт полей для шаблонов price_list.html и price_list_frame.html.
q_price_offer = PriceOffer.objects.raw( quantities_by_mount_dim = {
f"SELECT" row['kMountDim_id']: row['iQuantity']
f" oknardia_priceoffer.*," for row in MountDim2Apartment.objects.filter(kApartment_id=apartment_id).values('kMountDim_id', 'iQuantity')
f" oknardia_win_mountdim.*," }
f" oknardia_setkit.*," if not quantities_by_mount_dim:
f" oknardia_merchantoffice.*," return {'META_DATA_PUBLISH': 0, 'PRICE_FRAME': [], 'N': '-1'}
f" oknardia_glazing.*,"
f" oknardia_pvcprofiles.*," q_price_offer = (
f" oknardia_merchantbrand.*," PriceOffer.objects.filter(
f" oknardia_mountdim2apartment.iQuantity," sOfferActive=True,
f" oknardia_win_mountdim.id AS mID, " kOffer2MountDim_id__in=quantities_by_mount_dim.keys(),
f" oknardia_setkit.id AS setID," kOffer2SetKit__sSetActive=True,
f" (oknardia_setkit.dSetCommercialUntil > CURRENT_TIMESTAMP) AS bCommercial," kOffer2SetKit__kSet2User__kMerchantOffice__isnull=False,
f" oknardia_pvcprofiles.id AS pwc_id," kOffer2SetKit__kSet2User__kMerchantOffice__kMerchantName__isnull=False,
f" oknardia_merchantbrand.id AS brand_id " kOffer2SetKit__kSet2Glazing__isnull=False,
f"FROM oknardia_priceoffer" kOffer2SetKit__kSet2PVCprofiles__isnull=False,
f" INNER JOIN oknardia_win_mountdim" )
f" ON oknardia_priceoffer.kOffer2MountDim_id = oknardia_win_mountdim.id" .select_related(
f" INNER JOIN oknardia_setkit" 'kOffer2MountDim',
f" ON oknardia_priceoffer.kOffer2SetKit_id = oknardia_setkit.id" 'kOffer2SetKit',
f" INNER JOIN oknardia_ouruser" 'kOffer2SetKit__kSet2User__kMerchantOffice__kMerchantName',
f" ON oknardia_setkit.kSet2User_id = oknardia_ouruser.id" 'kOffer2SetKit__kSet2Glazing',
f" INNER JOIN oknardia_merchantoffice" 'kOffer2SetKit__kSet2PVCprofiles',
f" ON oknardia_ouruser.kMerchantOffice_id = oknardia_merchantoffice.id" )
f" INNER JOIN oknardia_glazing" .order_by(
f" ON oknardia_setkit.kSet2Glazing_id = oknardia_glazing.id" '-kOffer2SetKit__dSetCreate',
f" INNER JOIN oknardia_pvcprofiles" '-kOffer2MountDim__bIsNearDoor',
f" ON oknardia_setkit.kSet2PVCprofiles_id = oknardia_pvcprofiles.id" '-kOffer2MountDim__bIsDoor',
f" INNER JOIN oknardia_mountdim2apartment" 'kOffer2MountDim__iWinWidth',
f" ON oknardia_mountdim2apartment.kMountDim_id = oknardia_win_mountdim.id" '-kOffer2MountDim__iWinHight',
f" INNER JOIN oknardia_merchantbrand" 'id',
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)}" if brand_id != 0:
f" AND oknardia_setkit.sSetActive IS TRUE {add_to_sql_for_widget} " q_price_offer = q_price_offer.filter(
f"ORDER BY" kOffer2SetKit__kSet2User__kMerchantOffice__kMerchantName_id=brand_id,
f" oknardia_setkit.dSetCreate DESC, " # Сейчас окна в наборе собираются через это )
f" oknardia_win_mountdim.bIsNearDoor DESC,"
f" oknardia_win_mountdim.bIsDoor DESC," q_price_offer = [
f" oknardia_win_mountdim.iWinWidth," SimpleNamespace(
f" oknardia_win_mountdim.iWinHight DESC " id=offer.id,
f"LIMIT {int(frame_begin_n)} , 10000;") fOfferPrice=offer.fOfferPrice,
# print list(qPO) dOfferModify=offer.dOfferModify,
sOfferFlapConfig=offer.sOfferFlapConfig,
sDescripion=offer.kOffer2MountDim.sDescripion,
iWinWidth=offer.kOffer2MountDim.iWinWidth,
iWinHight=offer.kOffer2MountDim.iWinHight,
iQuantity=quantities_by_mount_dim.get(offer.kOffer2MountDim_id, 0),
setID=offer.kOffer2SetKit.id,
dSetModify=offer.kOffer2SetKit.dSetModify,
dSetCommercialUntil=offer.kOffer2SetKit.dSetCommercialUntil,
bCommercial=bool(
offer.kOffer2SetKit.dSetCommercialUntil
and offer.kOffer2SetKit.dSetCommercialUntil > timezone.now()
),
sSetName=offer.kOffer2SetKit.sSetName,
sSetClimateControl=offer.kOffer2SetKit.sSetClimateControl,
sSetSill=offer.kOffer2SetKit.sSetSill,
sSetImplementAll=offer.kOffer2SetKit.sSetImplementAll,
sSetImplementHandles=offer.kOffer2SetKit.sSetImplementHandles,
sSetImplementHinges=offer.kOffer2SetKit.sSetImplementHinges,
sSetImplementLatch=offer.kOffer2SetKit.sSetImplementLatch,
sSetImplementLimiter=offer.kOffer2SetKit.sSetImplementLimiter,
sSetImplementCatch=offer.kOffer2SetKit.sSetImplementCatch,
sSetPanes=offer.kOffer2SetKit.sSetPanes,
sSetSlope=offer.kOffer2SetKit.sSetSlope,
sSetOtherConditions=offer.kOffer2SetKit.sSetOtherConditions,
sSetDelivery=offer.kOffer2SetKit.sSetDelivery,
bSetDelivery=offer.kOffer2SetKit.bSetDelivery,
sSetUninstallInstall=offer.kOffer2SetKit.sSetUninstallInstall,
bSetUninstallInstall=offer.kOffer2SetKit.bSetUninstallInstall,
fSetRating=offer.kOffer2SetKit.fSetRating,
sOfficePhones=offer.kOffer2SetKit.kSet2User.kMerchantOffice.sOfficePhones,
sOfficeDiscountMetaFormula=(
offer.kOffer2SetKit.kSet2User.kMerchantOffice.sOfficeDiscountMetaFormula or ""
),
sOfficeName=offer.kOffer2SetKit.kSet2User.kMerchantOffice.sOfficeName,
sOfficeAddress=offer.kOffer2SetKit.kSet2User.kMerchantOffice.sOfficeAddress,
fOfficeGeoCode_Longitude=(
offer.kOffer2SetKit.kSet2User.kMerchantOffice.fOfficeGeoCode_Longitude or 0
),
fOfficeGeoCode_Latitude=(
offer.kOffer2SetKit.kSet2User.kMerchantOffice.fOfficeGeoCode_Latitude or 0
),
sGlazingBriefDescription=offer.kOffer2SetKit.kSet2Glazing.sGlazingBriefDescription,
sGlazingMark=offer.kOffer2SetKit.kSet2Glazing.sGlazingMark,
sGlazingToning=offer.kOffer2SetKit.kSet2Glazing.sGlazingToning,
pwc_id=offer.kOffer2SetKit.kSet2PVCprofiles.id,
sProfileName=offer.kOffer2SetKit.kSet2PVCprofiles.sProfileName,
sProfileManufacturer=offer.kOffer2SetKit.kSet2PVCprofiles.sProfileManufacturer or "",
sProfileSealDescription=offer.kOffer2SetKit.kSet2PVCprofiles.sProfileSealDescription,
sMerchantName=offer.kOffer2SetKit.kSet2User.kMerchantOffice.kMerchantName.sMerchantName,
pMerchantLogo=(
str(offer.kOffer2SetKit.kSet2User.kMerchantOffice.kMerchantName.pMerchantLogo)
if offer.kOffer2SetKit.kSet2User.kMerchantOffice.kMerchantName.pMerchantLogo
else ""
),
sMerchantMainURL=(
offer.kOffer2SetKit.kSet2User.kMerchantOffice.kMerchantName.sMerchantMainURL or ""
),
)
for offer in q_price_offer[frame_begin_n:frame_begin_n + 10000]
]
price_frame = [] price_frame = []
count_mount_dim_in_offer = 0 count_mount_dim_in_offer = 0
dim_in_offer = [] dim_in_offer = []
@@ -440,9 +533,9 @@ def report_price_frame(apartment_id: int, mount_dim_per_offer: int, address_long
'GLAZING_TONING': i2.sGlazingToning, 'GLAZING_TONING': i2.sGlazingToning,
'PVC_ID': i2.pwc_id, 'PVC_ID': i2.pwc_id,
'PVC_NAME': i2.sProfileName, 'PVC_NAME': i2.sProfileName,
'PVC_NAME_T': pytils.translit.slugify(i2.sProfileName).lower(), 'PVC_NAME_T': _slugify_lower(i2.sProfileName),
'PVC_MANUFACTURER': i2.sProfileManufacturer, 'PVC_MANUFACTURER': i2.sProfileManufacturer,
'PVC_MANUFACTURER_T': pytils.translit.slugify(i2.sProfileManufacturer).lower(), 'PVC_MANUFACTURER_T': _slugify_lower(i2.sProfileManufacturer),
'PVC_SEAL': i2.sProfileSealDescription, 'PVC_SEAL': i2.sProfileSealDescription,
'SETS_CLIMATE_CONTROL': i2.sSetClimateControl, 'SETS_CLIMATE_CONTROL': i2.sSetClimateControl,
'SETS_SILL': i2.sSetSill, 'SETS_SILL': i2.sSetSill,
@@ -620,7 +713,7 @@ def report_one_win_price(request: HttpRequest,
list_seria_for_win.append(SimpleNamespace( list_seria_for_win.append(SimpleNamespace(
id=seria_item['kApartment__kSeria__id'], id=seria_item['kApartment__kSeria__id'],
sName=seria_name, sName=seria_name,
sNameLat=pytils.translit.slugify(seria_name), sNameLat=_slugify_lower(seria_name),
num_variation_of_apartment=pytils.numeral.sum_string( num_variation_of_apartment=pytils.numeral.sum_string(
seria_item['num_variation_of_apartment'], seria_item['num_variation_of_apartment'],
pytils.numeral.MALE, pytils.numeral.MALE,
@@ -642,13 +735,8 @@ def report_one_win_price(request: HttpRequest,
'SERIA_FOR_WIN': list_seria_for_win, 'SERIA_FOR_WIN': list_seria_for_win,
'WIN_ID': int(win_id), 'WIN_ID': int(win_id),
'MOUNT_DIM_PER_OFFER': 1, '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.perf_counter() - time_start)
}) })
_append_visit_context(to_template=to_template, request=request, time_start=time_start)
return render(request, "price/price_offers_for_one_window.html", to_template) return render(request, "price/price_offers_for_one_window.html", to_template)
@@ -696,102 +784,81 @@ def report_price(request: HttpRequest, build_id: str = "22427", apart_id: str =
except ValueError: except ValueError:
return redirect("/") return redirect("/")
try: try:
# получаем все типы квартир для данного адреса (а заодно и попутную информацию о площади дома и пр.) building = Building_Info.objects.select_related('kSeria_Link__kRoot').get(id=build_id)
q_apart = Apartment_Type.objects.raw( if not building.kSeria_Link_id or not getattr(building.kSeria_Link, 'kRoot_id', None):
f'SELECT' return redirect("/")
f' oknardia_apartment_type.sNameApartment, oknardia_apartment_type.id,'
f' oknardia_apartment_type.iSort, oknardia_seria_info.kRoot_id,' list_apart = list(
f' oknardia_building_info.kSeria_Link_id, oknardia_building_info.sAddress,' Apartment_Type.objects.filter(kSeria_id=building.kSeria_Link.kRoot_id).order_by('iSort')
f' oknardia_building_info.fGeoCode_Latitude, oknardia_building_info.fGeoCode_Longitude,' )
f' oknardia_building_info.fTotal_Area, oknardia_building_info.sCadastre_Num_Area,' if not list_apart:
f' oknardia_building_info.fLand_Area, oknardia_building_info.sInventory_Num,' return redirect("/")
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 квартиры не для этого дома, то сделаем так, что он будет от этого дома! # если кто-то нахимичит ID квартиры не для этого дома, то сделаем так, что он будет от этого дома!
apart_inside = False apart_inside = any(ap.id == apart_id for ap in list_apart)
for i in q_apart: address_slug = _slugify_lower(building.sAddress)
if i.id == apart_id: if not apart_inside or slug != address_slug:
apart_inside = True
break
if not apart_inside or slug != pytils.translit.slugify(list_apart[0].sAddress):
# Переадресация 302, если с apart_id (ID-квартиры нахимичили) или slug-ом. # Переадресация 302, если с apart_id (ID-квартиры нахимичили) или slug-ом.
# Нужно для склейки парных URL в поисковиках # Нужно для склейки парных URL в поисковиках
# При переходе с карты apart_id выставляем в 0. Из-за этого тоже нужно 302-переадресация. # При переходе с карты apart_id выставляем в 0. Из-за этого тоже нужно 302-переадресация.
return redirect(f"/{build_id}/{list_apart[0].id}/{pytils.translit.slugify(list_apart[0].sAddress)}") return redirect(f"/{build_id}/{list_apart[0].id}/{address_slug}")
address_latitude = list_apart[0].fGeoCode_Latitude address_latitude = building.fGeoCode_Latitude
address_longitude = list_apart[0].fGeoCode_Longitude address_longitude = building.fGeoCode_Longitude
to_template.update({'BUILD_ID': build_id}) to_template.update({'BUILD_ID': build_id})
to_template.update({'APPARTMENT_ID': apart_id}) to_template.update({'APPARTMENT_ID': apart_id})
to_template.update({'ADDRESS_LAT': address_latitude}) to_template.update({'ADDRESS_LAT': address_latitude})
to_template.update({'ADDRESS_LON': address_longitude}) to_template.update({'ADDRESS_LON': address_longitude})
to_template.update({'ADDRESS': list_apart[0].sAddress}) to_template.update({'ADDRESS': building.sAddress})
to_template.update({'ADDRESS_T': pytils.translit.slugify(list_apart[0].sAddress)}) to_template.update({'ADDRESS_T': address_slug})
to_template.update({'SERIA': list_apart[0].sSerias_Project}) to_template.update({'SERIA': building.sSerias_Project})
# данные нужные для отображения информации о доме (метраж, число подъездов и пр.) # данные нужные для отображения информации о доме (метраж, число подъездов и пр.)
to_template.update({'CADASTRE_NUM': list_apart[0].sCadastre_Num_Area}) to_template.update({'CADASTRE_NUM': building.sCadastre_Num_Area})
to_template.update({'INVENTORY_NUM': list_apart[0].sInventory_Num}) to_template.update({'INVENTORY_NUM': building.sInventory_Num})
to_template.update({'TYPE_BUILDING': list_apart[0].sType}) to_template.update({'TYPE_BUILDING': building.sType})
to_template.update({'ENERGY_EFFICIENCY': list_apart[0].sEnergy_Efficiency}) to_template.update({'ENERGY_EFFICIENCY': building.sEnergy_Efficiency})
if list_apart[0].fTotal_Area != -1.0: if building.fTotal_Area != -1.0:
to_template.update({'TOTAL_AREA': f"{list_apart[0].fTotal_Area:.1f}"}) to_template.update({'TOTAL_AREA': f"{building.fTotal_Area:.1f}"})
if list_apart[0].fLand_Area != -1.0: if building.fLand_Area != -1.0:
to_template.update({'LAND': f"{list_apart[0].fLand_Area:.1f}"}) to_template.update({'LAND': f"{building.fLand_Area:.1f}"})
if list_apart[0].iNum_Apartments != -1: if building.iNum_Apartments != -1:
to_template.update({'NUM_APARTMENTS': list_apart[0].iNum_Apartments}) to_template.update({'NUM_APARTMENTS': building.iNum_Apartments})
if list_apart[0].iStoreys != -1: if building.iStoreys != -1:
to_template.update({'STOREYS': list_apart[0].iStoreys}) to_template.update({'STOREYS': building.iStoreys})
if list_apart[0].fCommon_Area != -1.0: if building.fCommon_Area != -1.0:
to_template.update({'COMMON_AREA': f"{list_apart[0].fCommon_Area:.1f}"}) to_template.update({'COMMON_AREA': f"{building.fCommon_Area:.1f}"})
if list_apart[0].iEntrances_Porchs != -1: if building.iEntrances_Porchs != -1:
to_template.update({'NUM_ENTERANCES': list_apart[0].iEntrances_Porchs}) to_template.update({'NUM_ENTERANCES': building.iEntrances_Porchs})
if list_apart[0].fUninhabited_Area != -1.0: if building.fUninhabited_Area != -1.0:
to_template.update({'UNINHABITED_AREA': f"{list_apart[0].fUninhabited_Area:.1f}"}) to_template.update({'UNINHABITED_AREA': f"{building.fUninhabited_Area:.1f}"})
if list_apart[0].sManagement_Co != u"N/A": if building.sManagement_Co != u"N/A":
to_template.update({'MANAGEMENT_CO': list_apart[0].sManagement_Co}) to_template.update({'MANAGEMENT_CO': building.sManagement_Co})
if list_apart[0].iElevators != -1: if building.iElevators != -1:
to_template.update({'NUM_ELEVATORS': list_apart[0].iElevators}) to_template.update({'NUM_ELEVATORS': building.iElevators})
if list_apart[0].fResidential_Area != -1.0: if building.fResidential_Area != -1.0:
to_template.update({'RESIDENTIAL_AREA': f"{list_apart[0].fResidential_Area:.1f}"}) to_template.update({'RESIDENTIAL_AREA': f"{building.fResidential_Area:.1f}"})
if list_apart[0].iNum_Residents != -1: if building.iNum_Residents != -1:
to_template.update({'NUM_RESIDENTS': list_apart[0].iNum_Residents}) to_template.update({'NUM_RESIDENTS': building.iNum_Residents})
if list_apart[0].fPrivate_Area != -1.0: if building.fPrivate_Area != -1.0:
to_template.update({'PRIVATE_AREA': f"{list_apart[0].fPrivate_Area:.1f}"}) to_template.update({'PRIVATE_AREA': f"{building.fPrivate_Area:.1f}"})
if list_apart[0].iNum_Accounts != -1: if building.iNum_Accounts != -1:
to_template.update({'NUM_ACCOUNTS': list_apart[0].iNum_Accounts}) to_template.update({'NUM_ACCOUNTS': building.iNum_Accounts})
if list_apart[0].iCommissioning_year != "N/A": if building.iCommissioning_year != "N/A":
to_template.update({'COMMISSIONING_YEAR': list_apart[0].iCommissioning_year}) to_template.update({'COMMISSIONING_YEAR': building.iCommissioning_year})
if list_apart[0].fGovernment_Area != -1.0: if building.fGovernment_Area != -1.0:
to_template.update({'GOVERNMENT_AREA': f"{list_apart[0].fGovernment_Area:.1f}"}) to_template.update({'GOVERNMENT_AREA': f"{building.fGovernment_Area:.1f}"})
if list_apart[0].fCondition_House != -1.0: if building.fCondition_House != -1.0:
to_template.update({'CONDITION_HOUSE': f"{list_apart[0].fCondition_House:.0f}%"}) to_template.update({'CONDITION_HOUSE': f"{building.fCondition_House:.0f}%"})
if list_apart[0].fCondition_Foundation != -1.0: if building.fCondition_Foundation != -1.0:
to_template.update({'CONDITION_FOUNDATION': f"{list_apart[0].fCondition_Foundation:.0f}%"}) to_template.update({'CONDITION_FOUNDATION': f"{building.fCondition_Foundation:.0f}%"})
if list_apart[0].fCondition_Walls != -1.0: if building.fCondition_Walls != -1.0:
to_template.update({'CONDITION_WALL': f"{list_apart[0].fCondition_Walls:.0f}%"}) to_template.update({'CONDITION_WALL': f"{building.fCondition_Walls:.0f}%"})
if list_apart[0].fCondition_Overlap != -1.0: if building.fCondition_Overlap != -1.0:
to_template.update({'CONDITION_OVERLAP': f"{list_apart[0].fCondition_Overlap:.0f}%"}) to_template.update({'CONDITION_OVERLAP': f"{building.fCondition_Overlap:.0f}%"})
if list_apart[0].fMunicipal_Area != -1.0: if building.fMunicipal_Area != -1.0:
to_template.update({'MUNICIPAL_AREA': f"{list_apart[0].fMunicipal_Area:.1f}"}) to_template.update({'MUNICIPAL_AREA': f"{building.fMunicipal_Area:.1f}"})
# заполняем массив квартир для отправки в шаблон # заполняем массив квартир для отправки в шаблон
apart_in_building = [] apart_in_building = []
for apartment_count in q_apart: for apartment_count in list_apart:
apartment_in = {} apartment_in = {}
if apartment_count.id != apart_id: if apartment_count.id != apart_id:
apartment_in.update({'APT_ID': apartment_count.id}) apartment_in.update({'APT_ID': apartment_count.id})
@@ -802,8 +869,8 @@ def report_price(request: HttpRequest, build_id: str = "22427", apart_id: str =
to_template.update({'APARTMENT_IN_BUILDING': apart_in_building}) to_template.update({'APARTMENT_IN_BUILDING': apart_in_building})
# узнаем базовую серию дома # узнаем базовую серию дома
q_base_seria = Seria_Info.objects.get(id=list_apart[0].kRoot_id) q_base_seria = building.kSeria_Link.kRoot
base_seria_slug = pytils.translit.slugify(q_base_seria.sName) base_seria_slug = _slugify_lower(q_base_seria.sName)
to_template.update({'BASE_SERIA': q_base_seria.sName, to_template.update({'BASE_SERIA': q_base_seria.sName,
'BASE_SERIA_LAT': base_seria_slug, 'BASE_SERIA_LAT': base_seria_slug,
'BASE_SERIA_ID': q_base_seria.id}) 'BASE_SERIA_ID': q_base_seria.id})
@@ -813,38 +880,36 @@ def report_price(request: HttpRequest, build_id: str = "22427", apart_id: str =
############################################### ###############################################
# получаем массив окон для данной квартиры... # получаем массив окон для данной квартиры...
try: try:
q_md = Win_MountDim.objects.raw( list_mount_dim_per_offer = [
f'SELECT' SimpleNamespace(
f' oknardia_apartment_type.sNameApartment, oknardia_win_mountdim.iWinWidth,' sNameApartment=row.kApartment.sNameApartment,
f' oknardia_win_mountdim.iWinHight, oknardia_win_mountdim.iWinDepth,' iWinWidth=row.kMountDim.iWinWidth,
f' oknardia_win_mountdim.sFlapConfig, oknardia_win_mountdim.bIsNearDoor,' iWinHight=row.kMountDim.iWinHight,
f' oknardia_win_mountdim.bIsDoor, oknardia_win_mountdim.sDescripion,' iWinDepth=row.kMountDim.iWinDepth,
f' oknardia_win_mountdim.id, oknardia_mountdim2apartment.iQuantity ' sFlapConfig=row.kMountDim.sFlapConfig,
f'FROM oknardia_mountdim2apartment ' bIsNearDoor=row.kMountDim.bIsNearDoor,
f'INNER JOIN oknardia_apartment_type' bIsDoor=row.kMountDim.bIsDoor,
f' ON oknardia_mountdim2apartment.kApartment_id = oknardia_apartment_type.id' sDescripion=row.kMountDim.sDescripion,
f' INNER JOIN oknardia_win_mountdim' id=row.kMountDim.id,
f' ON oknardia_mountdim2apartment.kMountDim_id = oknardia_win_mountdim.id ' iQuantity=row.iQuantity,
f'WHERE oknardia_mountdim2apartment.kApartment_id = {apart_id} ' )
f'GROUP BY' for row in MountDim2Apartment.objects.filter(kApartment_id=apart_id)
f' oknardia_apartment_type.sNameApartment, oknardia_win_mountdim.iWinWidth,' .select_related('kApartment', 'kMountDim')
f' oknardia_win_mountdim.iWinHight, oknardia_win_mountdim.iWinDepth,' .order_by(
f' oknardia_win_mountdim.sFlapConfig, oknardia_win_mountdim.bIsNearDoor,' '-kMountDim__bIsNearDoor',
f' oknardia_win_mountdim.bIsDoor, oknardia_win_mountdim.sDescripion,' '-kMountDim__bIsDoor',
f' oknardia_apartment_type.bApartmentCheck, oknardia_win_mountdim.dMountXYZModify,' 'kMountDim__iWinWidth',
f' oknardia_apartment_type.dApartmentModify, oknardia_win_mountdim.iWinLimit,' '-kMountDim__iWinHight',
f' oknardia_win_mountdim.id, oknardia_mountdim2apartment.iQuantity ' )
f'ORDER BY' ]
f' oknardia_win_mountdim.bIsNearDoor DESC,' if not list_mount_dim_per_offer:
f' oknardia_win_mountdim.bIsDoor DESC,' return redirect("/")
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) mount_dim_per_offer = len(list_mount_dim_per_offer)
to_template.update({'APART': list_mount_dim_per_offer[0].sNameApartment}) to_template.update({'APART': list_mount_dim_per_offer[0].sNameApartment})
# получаем данные для отрисовки больших картинок с проемами. # получаем данные для отрисовки больших картинок с проемами.
to_template.update(get_flaps_for_big_pictures(q_md)) to_template.update(get_flaps_for_big_pictures(list_mount_dim_per_offer))
# <--- # <---
except (ValueError, IndexError, TypeError, ObjectDoesNotExist): except (ValueError, IndexError, TypeError, ObjectDoesNotExist):
return redirect("/") return redirect("/")
@@ -865,12 +930,11 @@ def report_price(request: HttpRequest, build_id: str = "22427", apart_id: str =
# получаем последние визиты всех посетителей из базы # получаем последние визиты всех посетителей из базы
log_visit = get_last_all_user_visit_list() log_visit = get_last_all_user_visit_list()
if log_visit[0]['id'] is not None: if log_visit and log_visit[0].get('id') is not None:
id_last_visit_log = log_visit[0]['id'] + 1 id_last_visit_log = log_visit[0]['id'] + 1
else: else:
id_last_visit_log = 1 id_last_visit_log = 1
# print("id_last_visit_log:", id_last_visit_log) # 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: # максимальный размер циклического буфера if id_last_visit_log > MAX_LEN_RING_LOG_BUFFER: # максимальный размер циклического буфера
id_last_visit_log = 1 # ставим в начало буфера id_last_visit_log = 1 # ставим в начало буфера
try: try:
@@ -891,7 +955,8 @@ def report_price(request: HttpRequest, build_id: str = "22427", apart_id: str =
# получаем последние визиты клиента через куки # получаем последние визиты клиента через куки
last_visit = get_last_user_visit_cookies(request) last_visit = get_last_user_visit_cookies(request)
to_template.update({'LAST_VISIT': get_last_user_visit_list(last_visit)}) # Для блока LAST_VISIT показываем историю до текущего захода.
last_visit_for_context = list(last_visit)
# подготавливаем данные о текущем посещении для помещения в cookie # подготавливаем данные о текущем посещении для помещения в cookie
Item = { Item = {
"LastURL": f"/{build_id}/{apart_id}/{to_template['ADDRESS_T']}", "LastURL": f"/{build_id}/{apart_id}/{to_template['ADDRESS_T']}",
@@ -901,7 +966,13 @@ def report_price(request: HttpRequest, build_id: str = "22427", apart_id: str =
last_visit.insert(0, Item) # Добавляем текущий Item в начало last_visit.insert(0, Item) # Добавляем текущий Item в начало
last_visit = json.dumps(last_visit[:3]) # упаковываем json без пробелов (три записи) last_visit = json.dumps(last_visit[:3]) # упаковываем json без пробелов (три записи)
# print u"сейчас запишем вот эту куку:", LastVisit # print u"сейчас запишем вот эту куку:", LastVisit
to_template.update({'ticks': float(time.perf_counter() - time_start)}) _append_visit_context(
to_template=to_template,
request=request,
time_start=time_start,
log_visit=log_visit,
last_visit_cookie=last_visit_for_context,
)
response = render(request, "price/price_list.html", to_template) response = render(request, "price/price_list.html", to_template)
response.set_cookie("LastVisit", last_visit, max_age=7862400) # ставим или перезаписываем куки (91 день) response.set_cookie("LastVisit", last_visit, max_age=7862400) # ставим или перезаписываем куки (91 день)
return response return response

View File

@@ -20,7 +20,7 @@ from oknardia.models import (
Seria_Info, Seria_Info,
SetKit, SetKit,
) )
from web.prices import redirect_one_win_price_legacy, report_one_win_price from web.prices import redirect_one_win_price_legacy, report_one_win_price, report_price_frame
class ReportOneWinPriceTests(TestCase): class ReportOneWinPriceTests(TestCase):
@@ -248,3 +248,26 @@ class ReportOneWinPriceTests(TestCase):
f"/catalog/standard_opening/price-670x2160mm-tip{self.window_id}/", f"/catalog/standard_opening/price-670x2160mm-tip{self.window_id}/",
) )
def test_report_price_frame_for_apartment_keeps_template_contract(self):
"""ORM-ветка для квартир должна сохранять ключи контекста для price_list*."""
frame = report_price_frame(
apartment_id=self.apartment.id,
mount_dim_per_offer=1,
address_longitude=0,
address_latitude=0,
frame_begin_n=0,
brand_id=0,
win_id=0,
)
self.assertIn("META_DATA_PUBLISH", frame)
self.assertIn("PRICE_FRAME", frame)
self.assertIn("N", frame)
self.assertEqual(len(frame["PRICE_FRAME"]), 1)
offer = frame["PRICE_FRAME"][0]
self.assertEqual(offer["SETS_ID"], self.set_kit.id)
self.assertEqual(offer["MERCHANT"], self.brand.sMerchantName)
self.assertEqual(offer["FIN_PRICE"], self.active_offer.fOfferPrice)
self.assertEqual(len(offer["DIM"]), 1)
self.assertEqual(offer["DIM"][0]["QUANTITY"], 1)