mod: Рефакторинг страницы цен одного окна (вьюшки, шаблоны, тесты, новый canonical-роутинг)
This commit is contained in:
@@ -205,8 +205,12 @@ DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
|||||||
CAPTCHA_PUBLIC_KEY = env('CAPTCHA_PUBLIC_KEY', default='')
|
CAPTCHA_PUBLIC_KEY = env('CAPTCHA_PUBLIC_KEY', default='')
|
||||||
CAPTCHA_PRIVATE_KEY = env('CAPTCHA_PRIVATE_KEY', default='')
|
CAPTCHA_PRIVATE_KEY = env('CAPTCHA_PRIVATE_KEY', default='')
|
||||||
|
|
||||||
|
# МАГИЧЕСКИЕ ЧИСЛА
|
||||||
# если непонятно какая серия выбрана через каталог (finger fix) выбираем серию типового строения:
|
# если непонятно какая серия выбрана через каталог (finger fix) выбираем серию типового строения:
|
||||||
DEFAULT_SERIA_ID_FOR_CATALOG = 843 # СЕРИЯ 1-515/9 -- дом в котором я живу
|
DEFAULT_SERIA_ID_FOR_CATALOG = 843 # СЕРИЯ 1-515/9 -- дом в котором я живу
|
||||||
|
DEFAULT_WIN_WIDTH_MM = 670 # Ширина типового окна для ID=16 (если не выбрано)
|
||||||
|
DEFAULT_WIN_HEIGHT_MM = 2160 # Высота типового окна для ID=16 (если не выбрано)
|
||||||
|
DEFAULT_WIN_ID = 16 # ID типового окна (если не выбрано)
|
||||||
|
|
||||||
# количество коммерческих предложений во фрейме отчета
|
# количество коммерческих предложений во фрейме отчета
|
||||||
OFFER_PER_FRAME = 5
|
OFFER_PER_FRAME = 5
|
||||||
|
|||||||
@@ -62,9 +62,9 @@ urlpatterns = [
|
|||||||
re_path(r'^catalog/profile[/*]$', catalog_profiles.catalog_profile), # СПИСОК ВСЕХ ПРОФИЛЕЙ И ПРОИЗВОДИТЕЛЕЙ
|
re_path(r'^catalog/profile[/*]$', catalog_profiles.catalog_profile), # СПИСОК ВСЕХ ПРОФИЛЕЙ И ПРОИЗВОДИТЕЛЕЙ
|
||||||
re_path(r'^catalog/profile/(?P<manufacture_id>\d+)-(?P<manufacture_name>\S*)'
|
re_path(r'^catalog/profile/(?P<manufacture_id>\d+)-(?P<manufacture_name>\S*)'
|
||||||
r'/(?P<model_id>\d+)-(?P<model_name>\S*)[/*]$',
|
r'/(?P<model_id>\d+)-(?P<model_name>\S*)[/*]$',
|
||||||
catalog_profiles.catalog_profile_model), # КАРТОЧКА ПРОФИЛЯ (ИЛИ ПРОИЗВОДИТЕЛЯ)
|
catalog_profiles.catalog_profile_model), # СТРАНИЦА ОПИСАНИЯ МОДЕЛИ ПРОФИЛЯ
|
||||||
re_path(r'^catalog/profile/(?P<manufacture_id>\d+)-(?P<manufacture_name>\S*)[/*]$',
|
re_path(r'^catalog/profile/(?P<manufacture_id>\d+)-(?P<manufacture_name>\S*)[/*]$',
|
||||||
catalog_profiles.catalog_profile_manufacture),
|
catalog_profiles.catalog_profile_manufacture), # КАРТОЧКА ОПИСАНИЯ ПРОИЗВОДИТЕЛЯ ПРОФИЛЯ
|
||||||
# --- --- КАТАЛОГ СЕРИЙ ТИПОВОГО СТРОИТЕЛЬСТВА
|
# --- --- КАТАЛОГ СЕРИЙ ТИПОВОГО СТРОИТЕЛЬСТВА
|
||||||
re_path(r'^catalog/seria[/*]$', catalog_series.catalog_seria), # СПИСОК ВСЕХ СЕРИЙ ЗДАНИЙ
|
re_path(r'^catalog/seria[/*]$', catalog_series.catalog_seria), # СПИСОК ВСЕХ СЕРИЙ ЗДАНИЙ
|
||||||
re_path(r'^catalog/seria/(?P<seria_name_translit>[^/]*)/all(?P<seria_id>\d+)[/*]$',
|
re_path(r'^catalog/seria/(?P<seria_name_translit>[^/]*)/all(?P<seria_id>\d+)[/*]$',
|
||||||
@@ -75,12 +75,15 @@ urlpatterns = [
|
|||||||
# --- --- КАТАЛОГ ПРОИЗВОДИТЕЛЕЙ ОКОН
|
# --- --- КАТАЛОГ ПРОИЗВОДИТЕЛЕЙ ОКОН
|
||||||
re_path(r'^catalog/company[/*]$', catalog_companies.catalog_company), # СПИСОК ВСЕХ ПРОИЗВОДИТЕЛЕЙ ОКОН
|
re_path(r'^catalog/company[/*]$', catalog_companies.catalog_company), # СПИСОК ВСЕХ ПРОИЗВОДИТЕЛЕЙ ОКОН
|
||||||
re_path(r'^catalog/company/(?P<company_id>\d+)-(?P<company_name_slug>\S*)[/*]$',
|
re_path(r'^catalog/company/(?P<company_id>\d+)-(?P<company_name_slug>\S*)[/*]$',
|
||||||
catalog_companies.catalog_company_detail), # КАРТОЧКА ПРОИЗВОДИТЕЛЯ ОКОН
|
catalog_companies.catalog_company_detail), # КАРТОЧКА ПРОИЗВОДИТЕЛЯ-УСТНОАЩИКА ОКОН
|
||||||
# ЦЕНОВЫЕ ПРЕДЛОЖЕНИЯ
|
# ЦЕНОВЫЕ ПРЕДЛОЖЕНИЯ
|
||||||
# --- Одиночное окно
|
# --- ОДИНОЧНОЕ ОКНО
|
||||||
|
re_path(r'^catalog/standard_opening/price-(?P<win_width_mm>\d+)x(?P<win_height_mm>\d+)mm-tip(?P<win_id>\d+)[/*]$',
|
||||||
|
prices.report_one_win_price), # КАНОНИЧЕСКИЙ SEO-URL СТРАНИЦЫ ЦЕН ДЛЯ ОДНОГО ПРОЕМА
|
||||||
re_path(r'^tsena-odnogo-okna/(?P<win_width_mm>\d+)x(?P<win_height_mm>\d+)mm/tip(?P<win_id>\d+)[/*]$',
|
re_path(r'^tsena-odnogo-okna/(?P<win_width_mm>\d+)x(?P<win_height_mm>\d+)mm/tip(?P<win_id>\d+)[/*]$',
|
||||||
prices.report_one_win_price),
|
prices.redirect_one_win_price_legacy), # LEGACY-URL: 301 -> КАНОНИЧЕСКИЙ ПУТЬ
|
||||||
re_path(r'^next_price_one_flap_frame/idW(?P<win_id>\d+)N(?P<frame_begin_n>\d+)\S*$', prices.next_one_win_price),
|
re_path(r'^next_price_one_flap_frame/idW(?P<win_id>\d+)N(?P<frame_begin_n>\d+)\S*$',
|
||||||
|
prices.next_one_win_price), # ПОДГРУЖАЕМЫЙ ФРЕЙМ С ЦЕНОВЫМИ ПРЕДЛОЖЕНИЯМИ ДЛЯ ОДНОГО ПРОЕМА
|
||||||
# --- Ценовая выдача
|
# --- Ценовая выдача
|
||||||
re_path(r'^(?P<build_id>\d+)/(?P<apart_id>\d+)/(?P<slug>[\s\S]*)$', prices.report_price),
|
re_path(r'^(?P<build_id>\d+)/(?P<apart_id>\d+)/(?P<slug>[\s\S]*)$', prices.report_price),
|
||||||
# --- Подгружаемый фрейм ценовая выдачи
|
# --- Подгружаемый фрейм ценовая выдачи
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ CollectionPage + ItemList помогают поисковику понять с
|
|||||||
"@type": "Thing",
|
"@type": "Thing",
|
||||||
"name": "{{ i.DESCRIPTION|escapejs }}",
|
"name": "{{ i.DESCRIPTION|escapejs }}",
|
||||||
"description": "{{ i.DESCRIPTION_L|escapejs }}",
|
"description": "{{ i.DESCRIPTION_L|escapejs }}",
|
||||||
"url": "{{ request.scheme }}://{{ request.get_host }}/tsena-odnogo-okna/{{ i.W|stringformat:'.0f' }}x{{ i.H|stringformat:'.0f' }}mm/tip{{ i.ID }}",
|
"url": "{{ request.scheme }}://{{ request.get_host }}/catalog/standard_opening/price-{{ i.W|stringformat:'.0f' }}x{{ i.H|stringformat:'.0f' }}mm-tip{{ i.ID }}",
|
||||||
"image": "{{ request.scheme }}://{{ request.get_host }}{% static i.URL2IMG %}",
|
"image": "{{ request.scheme }}://{{ request.get_host }}{% static i.URL2IMG %}",
|
||||||
"additionalProperty": [
|
"additionalProperty": [
|
||||||
{"@type": "PropertyValue", "name": "Ширина", "value": "{{ i.W|stringformat:'.0f' }} мм"},
|
{"@type": "PropertyValue", "name": "Ширина", "value": "{{ i.W|stringformat:'.0f' }} мм"},
|
||||||
@@ -133,7 +133,7 @@ CollectionPage + ItemList помогают поисковику понять с
|
|||||||
<td data-sort="{% if i.IS_DOOR %}1{% else %}0{% endif %}">{% if i.IS_DOOR %}да{% else %}—{% endif %}</td>
|
<td data-sort="{% if i.IS_DOOR %}1{% else %}0{% endif %}">{% if i.IS_DOOR %}да{% else %}—{% endif %}</td>
|
||||||
<td>{{ i.DESCRIPTION }}</td>
|
<td>{{ i.DESCRIPTION }}</td>
|
||||||
<td>{% for j in i.INCLUDING_IN_SERIA %}<a href="/catalog/seria/{{ j.NAME_T }}/all{{ j.ID }}">{{ j.NAME }}</a>{% if not forloop.last %}, {% endif %}{% endfor %}</td>
|
<td>{% for j in i.INCLUDING_IN_SERIA %}<a href="/catalog/seria/{{ j.NAME_T }}/all{{ j.ID }}">{{ j.NAME }}</a>{% if not forloop.last %}, {% endif %}{% endfor %}</td>
|
||||||
<td><a class="btn btn-default btn-xs" href="/tsena-odnogo-okna/{{ i.W|stringformat:".0f" }}x{{ i.H|stringformat:".0f" }}mm/tip{{ i.ID }}">цены</a></td>
|
<td><a class="btn btn-default btn-xs" href="/catalog/standard_opening/price-{{ i.W|stringformat:".0f" }}x{{ i.H|stringformat:".0f" }}mm-tip{{ i.ID }}">цены</a></td>
|
||||||
</tr>{% endfor %}
|
</tr>{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@@ -5,11 +5,12 @@
|
|||||||
|
|
||||||
{% block Add_Body_Attribute %} style="padding-top:70px;"{% endblock %}
|
{% block Add_Body_Attribute %} style="padding-top:70px;"{% endblock %}
|
||||||
|
|
||||||
|
{# SEO блоки дат:#}
|
||||||
|
{# - Date4Meta: дата публикации (первого появления) — используем дату модификации данных. #}
|
||||||
|
{# - Last4Meta: дата последнего обновления — будет по умолчанию now из base.html. #}
|
||||||
{% block Date4Meta %}{{ META_DATA_PUBLISH|date:"Y-m-d" }}{% endblock %}
|
{% block Date4Meta %}{{ META_DATA_PUBLISH|date:"Y-m-d" }}{% endblock %}
|
||||||
|
|
||||||
{% block Last4Meta %}{{ META_DATA_PUBLISH|date:"Y-m-d" }}{% endblock %}
|
{% block Top_JS4 %}{# Для построения круговой диаграммы #}
|
||||||
|
|
||||||
{% block Top_JS4 %}
|
|
||||||
<script type="text/javascript" src="//www.gstatic.com/charts/loader.js"></script>
|
<script type="text/javascript" src="//www.gstatic.com/charts/loader.js"></script>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
google.charts.load("current", {packages: ["corechart"]});
|
google.charts.load("current", {packages: ["corechart"]});
|
||||||
@@ -35,6 +36,140 @@
|
|||||||
}
|
}
|
||||||
</script>{% endblock %}
|
</script>{% endblock %}
|
||||||
|
|
||||||
|
{% block ADD_TO_HEAD %}{% comment %}
|
||||||
|
JSON-LD микроразметка для поисковых систем (Schema.org):
|
||||||
|
- BreadcrumbList: хлебные крошки для навигации в поиске
|
||||||
|
- Organization: информация о бренде/компании
|
||||||
|
- Product: типовое окно с полной информацией
|
||||||
|
- Рейтинги и цены берутся из таблицы предложений (price_offers_for_one_window_frame.html)
|
||||||
|
{% endcomment %}<script type="application/ld+json">
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"@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 }}/catalog/standard_opening/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"@type": "ListItem",
|
||||||
|
"position": 4,
|
||||||
|
"name": "Окно {% for I_WIN_DIM in FLAP_DIM %}{{ I_WIN_DIM.iWinWidth_mm|floatformat:0 }}×{{ I_WIN_DIM.iWinHight_mm|floatformat:0 }}{% endfor %} мм",
|
||||||
|
"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": "Типовое пластиковое окно {% for I_WIN_DIM in FLAP_DIM %}{{ I_WIN_DIM.iWinWidth|floatformat:0 }}×{{ I_WIN_DIM.iWinHight|floatformat:0 }} см{% endfor %}",
|
||||||
|
"size": "{% for I_WIN_DIM in FLAP_DIM %}{% if not forloop.first %}, {% endif %}{{ I_WIN_DIM.iWinWidth_mm|floatformat:0 }}×{{ I_WIN_DIM.iWinHight_mm|floatformat:0 }} мм{% endfor %}",
|
||||||
|
"description": "Цены на пластиковое окно стандартного размера для типовых жилых домов серий {% for I in SERIA_FOR_WIN %}{% if forloop.last %} и {% elif forloop.first %}{% else %}, {% endif %}{{ I.sName }}{% endfor %}. Сравните предложения различных производителей и установщиков, узнайте актуальные цены, технические характеристики стеклопакетов, профилей, фурнитуры и условия доставки/монтажа.",
|
||||||
|
"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": "{{ NUM_TOTAL_OFFER_N_WORD|safe }}"
|
||||||
|
},
|
||||||
|
"datePublished": "{{ META_DATA_PUBLISH|date:'Y-m-d' }}",
|
||||||
|
"dateModified": "{{ META_DATA_PUBLISH|date:'Y-m-d' }}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"@context": "https://schema.org/",
|
||||||
|
"@type": "ItemList",
|
||||||
|
"name": "Коммерческие предложения для типового окна",
|
||||||
|
"numberOfItems": "{{ PRICE_FRAME|length }}",
|
||||||
|
"itemListElement": [
|
||||||
|
{% for CurOffer in PRICE_FRAME %}
|
||||||
|
{
|
||||||
|
"@type": "ListItem",
|
||||||
|
"position": {{ forloop.counter }},
|
||||||
|
"item": {
|
||||||
|
"@type": "Offer",
|
||||||
|
"name": "{{ CurOffer.SETS_NAME|striptags|escapejs }}",
|
||||||
|
"url": "{{ request.scheme }}://{{ request.get_host }}{{ request.path }}#btn{{ CurOffer.SETS_ID }}",
|
||||||
|
"price": "{{ CurOffer.FIN_PRICE|stringformat:'.2f' }}",
|
||||||
|
"priceCurrency": "RUB",
|
||||||
|
"itemCondition": "https://schema.org/NewCondition",
|
||||||
|
"availability": "https://schema.org/InStock",
|
||||||
|
"seller": {
|
||||||
|
"@type": "Organization",
|
||||||
|
"name": "{{ CurOffer.MERCHANT|striptags|escapejs }}"
|
||||||
|
},
|
||||||
|
"itemOffered": {
|
||||||
|
"@type": "Product",
|
||||||
|
"name": "{{ CurOffer.SETS_NAME|striptags|escapejs }}",
|
||||||
|
"additionalProperty": [
|
||||||
|
{
|
||||||
|
"@type": "PropertyValue",
|
||||||
|
"name": "Оконный профиль",
|
||||||
|
"value": "{{ CurOffer.PVC_NAME|striptags|escapejs }}{% if CurOffer.PVC_MANUFACTURER %} ({{ CurOffer.PVC_MANUFACTURER|striptags|escapejs }}){% endif %}"
|
||||||
|
}{% if CurOffer.PVC_MANUFACTURER %},
|
||||||
|
{
|
||||||
|
"@type": "PropertyValue",
|
||||||
|
"name": "Производитель профиля",
|
||||||
|
"value": "{{ CurOffer.PVC_MANUFACTURER|striptags|escapejs }}"
|
||||||
|
}{% endif %}{% if CurOffer.GLAZING_MARK %},
|
||||||
|
{
|
||||||
|
"@type": "PropertyValue",
|
||||||
|
"name": "Стеклопакет",
|
||||||
|
"value": "{{ CurOffer.GLAZING_MARK|striptags|escapejs }}"
|
||||||
|
}{% endif %}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"dateModified": "{{ CurOffer.SETS_DATA_MODIFY|date:'Y-m-d' }}"{% if CurOffer.SETS_RATING > -0.1 %},
|
||||||
|
"aggregateRating": {
|
||||||
|
"@type": "AggregateRating",
|
||||||
|
"ratingValue": "{{ CurOffer.SETS_RATING|stringformat:'.2f' }}",
|
||||||
|
"bestRating": "5",
|
||||||
|
"worstRating": "0",
|
||||||
|
"ratingCount": "1"
|
||||||
|
}{% endif %}
|
||||||
|
}
|
||||||
|
}{% if not forloop.last %},{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
</script>{% endblock %}
|
||||||
|
|
||||||
{% block Description %}Цены на типовое окно {% for I_WIN_DIM in FLAP_DIM %}{{ I_WIN_DIM.iWinWidth|floatformat:0 }}x{{ I_WIN_DIM.iWinHight|floatformat:0 }} см. для домов серий {% for I in SERIA_FOR_WIN %}{% if forloop.last %} и {% elif forloop.first %}{% else %}, {% endif %}{{ I.sName }}{% endfor %}{% endfor %}.{% endblock %}
|
{% block Description %}Цены на типовое окно {% for I_WIN_DIM in FLAP_DIM %}{{ I_WIN_DIM.iWinWidth|floatformat:0 }}x{{ I_WIN_DIM.iWinHight|floatformat:0 }} см. для домов серий {% for I in SERIA_FOR_WIN %}{% if forloop.last %} и {% elif forloop.first %}{% else %}, {% endif %}{{ I.sName }}{% endfor %}{% endfor %}.{% endblock %}
|
||||||
|
|
||||||
{% comment %}{% block Description %}Цены на пластиковые окна для серии {{ BASE_SERIA }} ({{ APART }} квартира, {{ ADDRESS }}) :: {% for CurOffer in PRICE_FRAME %}Поставщик: {{ CurOffer.MERCHANT }}; Комплектация: {{ CurOffer.SETS_NAME }}; Цена: {{ CurOffer.FIN_PRICE }}₽ :: {% endfor %}{% endblock %}{% endcomment %}
|
{% comment %}{% block Description %}Цены на пластиковые окна для серии {{ BASE_SERIA }} ({{ APART }} квартира, {{ ADDRESS }}) :: {% for CurOffer in PRICE_FRAME %}Поставщик: {{ CurOffer.MERCHANT }}; Комплектация: {{ CurOffer.SETS_NAME }}; Цена: {{ CurOffer.FIN_PRICE }}₽ :: {% endfor %}{% endblock %}{% endcomment %}
|
||||||
@@ -146,12 +281,14 @@ $(function () { // инициализация и обработка попове
|
|||||||
<span itemscope itemtype="http://schema.org/Product">
|
<span itemscope itemtype="http://schema.org/Product">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-9">
|
<div class="col-md-9">
|
||||||
<h1>Цены на окно {% for I_WIN_DIM in FLAP_DIM %}{{ I_WIN_DIM.iWinWidth_mm|floatformat:0 }}×{{ I_WIN_DIM.iWinHight_mm|floatformat:0 }}{% endfor %} мм. <small>(типовое)</small></h1>
|
<h1 itemprop="name">Цены на окно {% for I_WIN_DIM in FLAP_DIM %}{{ I_WIN_DIM.iWinWidth_mm|floatformat:0 }}×{{ I_WIN_DIM.iWinHight_mm|floatformat:0 }}{% endfor %} мм. <small>(типовое)</small></h1>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-9">
|
<div class="col-md-9">
|
||||||
<p>Типовой проём {% for I_WIN_DIM in FLAP_DIM %}{{ I_WIN_DIM.iWinWidth|floatformat:1 }}×{{ I_WIN_DIM.iWinHight|floatformat:1 }}{% endfor %} cм. представлен в домах серий: {% for I in SERIA_FOR_WIN %}{% if forloop.last %} и {% elif forloop.first %}{% else %}, {% endif %}<a href="/catalog/seria/{{ I.sNameLat }}/all{{ I.id }}">{{ I.sName }}</a>{% endfor %}. База «Окнардии» размещено {{ NUM_TOTAL_OFFER_N_WORD }} цен для окон в такой проем (из них в архиве {{ NUM_ARCHIVE_OFFER }}). Предложено {{ NUM_FLAP_VARIATION_IN_WORD }} открывания от {{ NUM_TOTAL_FIRM_N_WORD }}.</p>
|
<p itemprop="description">Типовой проём {% for I_WIN_DIM in FLAP_DIM %}{{ I_WIN_DIM.iWinWidth|floatformat:1 }}×{{ I_WIN_DIM.iWinHight|floatformat:1 }}{% endfor %} cм. представлен в домах серий: {% for I in SERIA_FOR_WIN %}{% if forloop.last %} и {% elif forloop.first %}{% else %}, {% endif %}<a href="/catalog/seria/{{ I.sNameLat }}/all{{ I.id }}">{{ I.sName }}</a>{% endfor %}. База «Окнардии» размещено {{ NUM_TOTAL_OFFER_N_WORD }} цен для окон в такой проем (из них в архиве {{ NUM_ARCHIVE_OFFER }}). Предложено {{ NUM_FLAP_VARIATION_IN_WORD }} открывания от {{ NUM_TOTAL_FIRM_N_WORD }}.</p>
|
||||||
</div>
|
</div>
|
||||||
{# Микроразмектка: названеи продукта #}<meta itemprop="name" content="Окна {{ APART|safe }} ({{ ADDRESS }})" />
|
{# Микроразметка: название продукта и марка #}
|
||||||
|
<meta itemprop="brand" content="ОКНАРДИЯ — агрегатор цен на окна" />
|
||||||
|
<meta itemprop="productionDate" content="{{ META_DATA_PUBLISH|date:'Y-m-d' }}" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row ShowBigFlapPictures">
|
<div class="row ShowBigFlapPictures">
|
||||||
@@ -176,8 +313,6 @@ $(function () { // инициализация и обработка попове
|
|||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
<p id="tab-note">В таблице представлены только цены поставщиков из базы «Окнардия». Клик на названии набора отобразит детальную спецификацию каждого предложения: профиль рамы и створки, схему стеклопакета, фурнитуру, элементы отлива, подоконника, откоса, системы <nobr>климат-контроля</nobr>) и сопутствующие услуги. Предложения выводятся блоками. Очередной блок выводится кнопкой «Ещё коммерческие предложения окон» под таблицей. Детальные технические характеристики стеклопакетов, профилей и описание сопутствующих услуг можно посмотреть и сравнить с помощью кнопки «Сравнить выбранные».</p>
|
<p id="tab-note">В таблице представлены только цены поставщиков из базы «Окнардия». Клик на названии набора отобразит детальную спецификацию каждого предложения: профиль рамы и створки, схему стеклопакета, фурнитуру, элементы отлива, подоконника, откоса, системы <nobr>климат-контроля</nobr>) и сопутствующие услуги. Предложения выводятся блоками. Очередной блок выводится кнопкой «Ещё коммерческие предложения окон» под таблицей. Детальные технические характеристики стеклопакетов, профилей и описание сопутствующих услуг можно посмотреть и сравнить с помощью кнопки «Сравнить выбранные».</p>
|
||||||
</div>
|
</div>
|
||||||
{# Микроразмектка: названеи продукта #}
|
|
||||||
<meta itemprop="name" content="Окна {{ APART|safe }} ({{ ADDRESS }})"/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -6,8 +6,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 }} – {{ 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>)
|
||||||
■ {{ CurOffer.GLAZING_NAME_B|safe }} <nobr>({{ CurOffer.GLAZING_MARK }})</nobr>
|
■ {{ CurOffer.GLAZING_NAME_B|safe }} <nobr>({{ CurOffer.GLAZING_MARK }})</nobr>
|
||||||
@@ -29,17 +29,15 @@
|
|||||||
</DiV>
|
</DiV>
|
||||||
<!-- Дата обновления -->
|
<!-- Дата обновления -->
|
||||||
<nobr class="badge badge4price" title="Дата обновления коммерческого предложения окон — {{ CurOffer.SETS_DATA_MODIFY|date:"d.M.Y" }}"><b class="glyphicon glyphicon-calendar"></b> {{ CurOffer.SETS_DATA_MODIFY|date:"d.M.Y" }}</nobr>
|
<nobr class="badge badge4price" title="Дата обновления коммерческого предложения окон — {{ CurOffer.SETS_DATA_MODIFY|date:"d.M.Y" }}"><b class="glyphicon glyphicon-calendar"></b> {{ CurOffer.SETS_DATA_MODIFY|date:"d.M.Y" }}</nobr>
|
||||||
<!-- Звездочки рейтинга -->
|
<!-- Звездочки рейтинга с микроразметкой Rating -->
|
||||||
<nobr class="badge badge4price" title="Рейтинг «Окнардии»{% if CurOffer.SETS_RATING > -0.1 %} — {{ CurOffer.SETS_RATING|stringformat:".2f" }} баллов{% endif %}"><a
|
<nobr class="badge badge4price" title="Рейтинг «Окнардии»{% if CurOffer.SETS_RATING > -0.1 %} — {{ CurOffer.SETS_RATING|stringformat:".2f" }} баллов{% endif %}">
|
||||||
|
<a
|
||||||
href="javascript://"
|
href="javascript://"
|
||||||
id-set="{{ CurOffer.SETS_ID }}"
|
id-set="{{ CurOffer.SETS_ID }}"
|
||||||
data-trigger="focus" tabindex="0"
|
data-trigger="focus" tabindex="0"
|
||||||
title="{% if CurOffer.SETS_RATING > 0.01 %}<b> Рейтинг {{ CurOffer.SETS_RATING|stringformat:".2f" }}</b> для оконого набора «{{ CurOffer.SETS_NAME }}» компании «{{ CurOffer.MERCHANT }}» состоит из:{% else %}Рейтинг не присвоен{% endif %}"
|
title="{% if CurOffer.SETS_RATING > 0.01 %}<b> Рейтинг {{ CurOffer.SETS_RATING|stringformat:".2f" }}</b> для оконого набора «{{ CurOffer.SETS_NAME }}» компании «{{ CurOffer.MERCHANT }}» состоит из:{% else %}Рейтинг не присвоен{% endif %}"
|
||||||
data-toggle="popover">рейтинг</a>: {% for Star in CurOffer.SETS_RATING_STARTS %}{% if Star == 0 %}<b class="glyphicon glyphicon-star-empty"></b>{% else %}<b class="glyphicon glyphicon-star"></b>{% endif %}{% endfor %} {% if CurOffer.SETS_RATING > -0.1 %} {{ CurOffer.SETS_RATING|stringformat:".2f" }}{% endif %}</nobr>
|
data-toggle="popover">рейтинг</a>: {% for Star in CurOffer.SETS_RATING_STARTS %}{% if Star == 0 %}<b class="glyphicon glyphicon-star-empty"></b>{% else %}<b class="glyphicon glyphicon-star"></b>{% endif %}{% endfor %} {% if CurOffer.SETS_RATING > -0.1 %}{{ CurOffer.SETS_RATING|stringformat:".2f" }}{% endif %}</nobr>
|
||||||
</span>
|
</span>
|
||||||
<span itemprop="brand" itemscope itemtype="http://schema.org/Brand">
|
|
||||||
<meta itemprop="name" content="{{ CurOffer.MERCHANT }}" />
|
|
||||||
<meta itemprop="logo" content="{{ request.scheme }}://{{ request.get_host }}/media/{{ CurOffer.MERCHANT_LOGO }}" />
|
|
||||||
</span></td>
|
</span></td>
|
||||||
<!--- Конец большой ячейки со спецификацией оконного предложения --->
|
<!--- Конец большой ячейки со спецификацией оконного предложения --->
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -50,10 +48,8 @@
|
|||||||
|
|
||||||
<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 %}−{{ 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 %}−{{ 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 }} <small class="glyphicon glyphicon-ruble" aria-label="₽ (руб.)" title="₽ (руб.)"></small>
|
Итого: {{ CurOffer.FIN_PRICE|stringformat:".2f"|price_format }} <small class="glyphicon glyphicon-ruble" aria-label="₽ (руб.)" title="₽ (руб.)"></small>
|
||||||
<meta itemprop="price" content="{{ CurOffer.FIN_PRICE }}" />
|
|
||||||
<meta itemprop="priceCurrency" content="RUB" />
|
|
||||||
</th>
|
</th>
|
||||||
{% if CurOffer.DIM|length == 1 %}
|
{% if CurOffer.DIM|length == 1 %}
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<nobr>{{ I_WIN_DIM.iWinWidth|stringformat:".0f" }}0×{{ I_WIN_DIM.iWinHight|stringformat:".0f" }}0 мм.</nobr><br />{% if not I_WIN_DIM.iQuantity == 0 %}
|
<nobr>{{ I_WIN_DIM.iWinWidth|stringformat:".0f" }}0×{{ I_WIN_DIM.iWinHight|stringformat:".0f" }}0 мм.</nobr><br />{% if not I_WIN_DIM.iQuantity == 0 %}
|
||||||
<nobr><b>{{ I_WIN_DIM.iQuantity }} шт.</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 }} шт.</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="/tsena-odnogo-okna/{{ 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">
|
||||||
|
|||||||
@@ -157,7 +157,7 @@ TechArticle: описывает страницу как технический
|
|||||||
</tr>{% templatetag openblock %} endfor {% templatetag closeblock %}
|
</tr>{% templatetag openblock %} endfor {% templatetag closeblock %}
|
||||||
<tr class="trZ">
|
<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 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> {% templatetag openvariable %} i.WIN_OFFER {% templatetag closevariable %}</a></td>{% templatetag openblock %} endfor {% templatetag closeblock %}
|
<td class="cntr" style="background:#f9f9f9;"><a href="/catalog/standard_opening/price-{% 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> {% templatetag openvariable %} i.WIN_OFFER {% templatetag closevariable %}</a></td>{% templatetag openblock %} endfor {% templatetag closeblock %}
|
||||||
<td></td>
|
<td></td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ def _as_sitemap_date(value: date | datetime | None) -> str:
|
|||||||
|
|
||||||
|
|
||||||
class SingleWindowSitemap(Sitemap):
|
class SingleWindowSitemap(Sitemap):
|
||||||
"""Источник URL для страниц цен одного проёма (/tsena-odnogo-okna/...)."""
|
"""Источник URL для страниц цен одного проёма (/catalog/standard_opening/price-...)."""
|
||||||
|
|
||||||
changefreq = "weekly"
|
changefreq = "weekly"
|
||||||
priority = 0.5
|
priority = 0.5
|
||||||
@@ -98,7 +98,7 @@ class SingleWindowSitemap(Sitemap):
|
|||||||
# поэтому умножаем на 10 и приводим к int.
|
# поэтому умножаем на 10 и приводим к int.
|
||||||
width_mm = int(float(item.iWinWidth) * 10)
|
width_mm = int(float(item.iWinWidth) * 10)
|
||||||
height_mm = int(float(item.iWinHight) * 10)
|
height_mm = int(float(item.iWinHight) * 10)
|
||||||
return f"/tsena-odnogo-okna/{width_mm}x{height_mm}mm/tip{item.id}"
|
return f"/catalog/standard_opening/price-{width_mm}x{height_mm}mm-tip{item.id}"
|
||||||
|
|
||||||
def lastmod(self, item: Win_MountDim) -> datetime:
|
def lastmod(self, item: Win_MountDim) -> datetime:
|
||||||
return self.lastmod_value
|
return self.lastmod_value
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
|
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 oknardia.models import Win_MountDim, PriceOffer, Apartment_Type, Seria_Info, LogVisitPriceReport
|
from django.utils import timezone
|
||||||
|
from oknardia.models import Win_MountDim, PriceOffer, Apartment_Type, Seria_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, \
|
||||||
@@ -12,9 +14,26 @@ import time
|
|||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import json
|
import json
|
||||||
|
from types import SimpleNamespace
|
||||||
import pytils
|
import pytils
|
||||||
|
|
||||||
|
|
||||||
|
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)}/"
|
||||||
|
|
||||||
|
|
||||||
|
def redirect_one_win_price_legacy(request: HttpRequest,
|
||||||
|
win_width_mm: str | int = DEFAULT_WIN_WIDTH_MM,
|
||||||
|
win_height_mm: str | int = DEFAULT_WIN_HEIGHT_MM,
|
||||||
|
win_id: str | int = DEFAULT_WIN_ID) -> HttpResponse:
|
||||||
|
"""301-редирект со старого URL /tsena-odnogo-okna/... на канонический URL."""
|
||||||
|
return redirect(
|
||||||
|
_one_win_price_canonical_path(win_width_mm=win_width_mm, win_height_mm=win_height_mm, win_id=win_id),
|
||||||
|
permanent=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
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:
|
||||||
""" Формируем выдачу цен для фрейма
|
""" Формируем выдачу цен для фрейма
|
||||||
@@ -49,65 +68,219 @@ def report_price_frame(apartment_id: int, mount_dim_per_offer: int, address_long
|
|||||||
add_to_sql_for_widget = f" AND oknardia_merchantbrand.id = {brand_id} "
|
add_to_sql_for_widget = f" AND oknardia_merchantbrand.id = {brand_id} "
|
||||||
offer_per_frame = 1000 # Фреймовый вывод не нужен... фигачим сразу целую 1000 предложений.
|
offer_per_frame = 1000 # Фреймовый вывод не нужен... фигачим сразу целую 1000 предложений.
|
||||||
if int(apartment_id) == 0 and int(win_id) != 0:
|
if int(apartment_id) == 0 and int(win_id) != 0:
|
||||||
# если выводим цены только для одного проема
|
# ORM-ветка для одиночного типового окна.
|
||||||
|
# Здесь можно полностью уйти от raw SQL, потому что все связи линейные и хорошо покрываются select_related.
|
||||||
|
# Контракт ответа сохраняем прежним: META_DATA_PUBLISH, PRICE_FRAME, N и все вложенные ключи оферов.
|
||||||
offer_per_frame = OFFER_PER_FRAME_FOR_ONE_FLAP
|
offer_per_frame = OFFER_PER_FRAME_FOR_ONE_FLAP
|
||||||
q_price_offer = PriceOffer.objects.raw(
|
if brand_id != 0:
|
||||||
f"SELECT"
|
offer_per_frame = 1000
|
||||||
f" oknardia_priceoffer.id, oknardia_priceoffer.iOfferImpressions,"
|
|
||||||
f" oknardia_priceoffer.fOfferPrice, oknardia_priceoffer.dOfferModify,"
|
q_price_offer = (
|
||||||
f" oknardia_priceoffer.fOfferRating, oknardia_priceoffer.sOfferFlapConfig,"
|
PriceOffer.objects.filter(
|
||||||
f" oknardia_priceoffer.iOfferViews, oknardia_priceoffer.sOfferActive,"
|
sOfferActive=True,
|
||||||
f" oknardia_win_mountdim.sDescripion, oknardia_win_mountdim.id AS mID, "
|
kOffer2MountDim_id=win_id,
|
||||||
f" oknardia_win_mountdim.bIsNearDoor, oknardia_win_mountdim.bIsDoor,"
|
kOffer2SetKit__sSetActive=True,
|
||||||
f" oknardia_win_mountdim.iWinWidth, oknardia_win_mountdim.iWinHight,"
|
kOffer2SetKit__kSet2User__kMerchantOffice__isnull=False,
|
||||||
f" oknardia_setkit.id AS setID,"
|
kOffer2SetKit__kSet2User__kMerchantOffice__kMerchantName__isnull=False,
|
||||||
f" oknardia_setkit.sSetName, oknardia_setkit.dSetModify,"
|
kOffer2SetKit__kSet2Glazing__isnull=False,
|
||||||
f" oknardia_setkit.sSetClimateControl, oknardia_setkit.sSetSill,"
|
kOffer2SetKit__kSet2PVCprofiles__isnull=False,
|
||||||
f" oknardia_setkit.sSetImplementAll, oknardia_setkit.sSetImplementHandles,"
|
)
|
||||||
f" oknardia_setkit.sSetImplementHinges, oknardia_setkit.sSetImplementLatch,"
|
.values(
|
||||||
f" oknardia_setkit.sSetImplementLimiter, oknardia_setkit.sSetImplementCatch,"
|
'id',
|
||||||
f" oknardia_setkit.sSetPanes, oknardia_setkit.sSetSlope,"
|
'fOfferPrice',
|
||||||
f" oknardia_setkit.sSetOtherConditions, oknardia_setkit.sSetActive,"
|
'dOfferModify',
|
||||||
f" oknardia_setkit.bSetDelivery, oknardia_setkit.sSetDelivery,"
|
'sOfferFlapConfig',
|
||||||
f" oknardia_setkit.sSetUninstallInstall, oknardia_setkit.bSetUninstallInstall,"
|
'kOffer2MountDim__sDescripion',
|
||||||
f" oknardia_setkit.fSetRating, oknardia_setkit.iSetNumEval,"
|
'kOffer2MountDim__iWinWidth',
|
||||||
f" oknardia_setkit.iSetImpressions, oknardia_setkit.iSetViews,"
|
'kOffer2MountDim__iWinHight',
|
||||||
f" (oknardia_setkit.dSetCommercialUntil > NOW()) AS bCommercial,"
|
'kOffer2SetKit__id',
|
||||||
f" oknardia_merchantoffice.sOfficePhones, "
|
'kOffer2SetKit__sSetName',
|
||||||
f" oknardia_merchantoffice.sOfficeDiscountMetaFormula,"
|
'kOffer2SetKit__dSetModify',
|
||||||
f" oknardia_merchantoffice.sOfficeName, oknardia_merchantoffice.sOfficeAddress,"
|
'kOffer2SetKit__dSetCommercialUntil',
|
||||||
f" oknardia_glazing.fGlazingRating,"
|
'kOffer2SetKit__sSetClimateControl',
|
||||||
f" oknardia_glazing.sGlazingName, oknardia_glazing.sGlazingBriefDescription,"
|
'kOffer2SetKit__sSetSill',
|
||||||
f" oknardia_glazing.sGlazingMark, oknardia_glazing.sGlazingToning,"
|
'kOffer2SetKit__sSetImplementAll',
|
||||||
f" oknardia_pvcprofiles.sProfileBriefDescription, oknardia_pvcprofiles.id AS pwc_id,"
|
'kOffer2SetKit__sSetImplementHandles',
|
||||||
f" oknardia_pvcprofiles.sProfileReinforcement, oknardia_pvcprofiles.sProfileSealDescription,"
|
'kOffer2SetKit__sSetImplementHinges',
|
||||||
f" oknardia_pvcprofiles.sProfileName, oknardia_pvcprofiles.sProfileColor,"
|
'kOffer2SetKit__sSetImplementLatch',
|
||||||
f" oknardia_pvcprofiles.fProfileRating, oknardia_pvcprofiles.sProfileManufacturer,"
|
'kOffer2SetKit__sSetImplementLimiter',
|
||||||
f" oknardia_merchantbrand.sMerchantName, oknardia_merchantbrand.pMerchantLogo,"
|
'kOffer2SetKit__sSetImplementCatch',
|
||||||
f" oknardia_merchantbrand.sMerchantMainURL, oknardia_merchantbrand.id AS brand_id,"
|
'kOffer2SetKit__sSetPanes',
|
||||||
f" 1 AS iQuantity, 0 AS fOfficeGeoCode_Longitude, 0 AS fOfficeGeoCode_Latitude "
|
'kOffer2SetKit__sSetSlope',
|
||||||
f"FROM oknardia_priceoffer"
|
'kOffer2SetKit__sSetOtherConditions',
|
||||||
f" INNER JOIN oknardia_win_mountdim"
|
'kOffer2SetKit__sSetDelivery',
|
||||||
f" ON oknardia_priceoffer.kOffer2MountDim_id = oknardia_win_mountdim.id"
|
'kOffer2SetKit__bSetDelivery',
|
||||||
f" INNER JOIN oknardia_setkit"
|
'kOffer2SetKit__sSetUninstallInstall',
|
||||||
f" ON oknardia_priceoffer.kOffer2SetKit_id = oknardia_setkit.id"
|
'kOffer2SetKit__bSetUninstallInstall',
|
||||||
f" INNER JOIN oknardia_ouruser"
|
'kOffer2SetKit__fSetRating',
|
||||||
f" ON oknardia_setkit.kSet2User_id = oknardia_ouruser.id"
|
'kOffer2SetKit__kSet2User__kMerchantOffice__sOfficePhones',
|
||||||
f" INNER JOIN oknardia_merchantoffice"
|
'kOffer2SetKit__kSet2User__kMerchantOffice__sOfficeDiscountMetaFormula',
|
||||||
f" ON oknardia_ouruser.kMerchantOffice_id = oknardia_merchantoffice.id"
|
'kOffer2SetKit__kSet2User__kMerchantOffice__sOfficeName',
|
||||||
f" INNER JOIN oknardia_glazing"
|
'kOffer2SetKit__kSet2User__kMerchantOffice__sOfficeAddress',
|
||||||
f" ON oknardia_setkit.kSet2Glazing_id = oknardia_glazing.id"
|
'kOffer2SetKit__kSet2Glazing__sGlazingBriefDescription',
|
||||||
f" INNER JOIN oknardia_pvcprofiles"
|
'kOffer2SetKit__kSet2Glazing__sGlazingMark',
|
||||||
f" ON oknardia_setkit.kSet2PVCprofiles_id = oknardia_pvcprofiles.id"
|
'kOffer2SetKit__kSet2Glazing__sGlazingToning',
|
||||||
f" INNER JOIN oknardia_merchantbrand"
|
'kOffer2SetKit__kSet2PVCprofiles__id',
|
||||||
f" ON oknardia_merchantoffice.kMerchantName_id = oknardia_merchantbrand.id "
|
'kOffer2SetKit__kSet2PVCprofiles__sProfileName',
|
||||||
f"WHERE oknardia_priceoffer.sOfferActive IS TRUE"
|
'kOffer2SetKit__kSet2PVCprofiles__sProfileManufacturer',
|
||||||
f" AND oknardia_setkit.sSetActive IS TRUE "
|
'kOffer2SetKit__kSet2PVCprofiles__sProfileSealDescription',
|
||||||
f" AND oknardia_win_mountdim.id = {int(win_id)}"
|
'kOffer2SetKit__kSet2User__kMerchantOffice__kMerchantName__sMerchantName',
|
||||||
f" {add_to_sql_for_widget} "
|
'kOffer2SetKit__kSet2User__kMerchantOffice__kMerchantName__pMerchantLogo',
|
||||||
f"ORDER BY"
|
'kOffer2SetKit__kSet2User__kMerchantOffice__kMerchantName__sMerchantMainURL',
|
||||||
f" oknardia_priceoffer.dOfferModify DESC "
|
)
|
||||||
f"LIMIT {int(frame_begin_n)}, 10000;")
|
.order_by("-dOfferModify")
|
||||||
|
)
|
||||||
|
if brand_id != 0:
|
||||||
|
q_price_offer = q_price_offer.filter(
|
||||||
|
kOffer2SetKit__kSet2User__kMerchantOffice__kMerchantName_id=brand_id,
|
||||||
|
)
|
||||||
|
q_price_offer = [
|
||||||
|
SimpleNamespace(
|
||||||
|
id=offer['id'],
|
||||||
|
fOfferPrice=offer['fOfferPrice'],
|
||||||
|
dOfferModify=offer['dOfferModify'],
|
||||||
|
sOfferFlapConfig=offer['sOfferFlapConfig'],
|
||||||
|
sDescripion=offer['kOffer2MountDim__sDescripion'],
|
||||||
|
iWinWidth=offer['kOffer2MountDim__iWinWidth'],
|
||||||
|
iWinHight=offer['kOffer2MountDim__iWinHight'],
|
||||||
|
setID=offer['kOffer2SetKit__id'],
|
||||||
|
sSetName=offer['kOffer2SetKit__sSetName'],
|
||||||
|
dSetModify=offer['kOffer2SetKit__dSetModify'],
|
||||||
|
dSetCommercialUntil=offer['kOffer2SetKit__dSetCommercialUntil'],
|
||||||
|
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'],
|
||||||
|
sOfficeName=offer['kOffer2SetKit__kSet2User__kMerchantOffice__sOfficeName'],
|
||||||
|
sOfficeAddress=offer['kOffer2SetKit__kSet2User__kMerchantOffice__sOfficeAddress'],
|
||||||
|
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'],
|
||||||
|
sProfileSealDescription=offer['kOffer2SetKit__kSet2PVCprofiles__sProfileSealDescription'],
|
||||||
|
sMerchantName=offer['kOffer2SetKit__kSet2User__kMerchantOffice__kMerchantName__sMerchantName'],
|
||||||
|
pMerchantLogo=offer['kOffer2SetKit__kSet2User__kMerchantOffice__kMerchantName__pMerchantLogo'],
|
||||||
|
sMerchantMainURL=offer['kOffer2SetKit__kSet2User__kMerchantOffice__kMerchantName__sMerchantMainURL'],
|
||||||
|
iQuantity=1,
|
||||||
|
)
|
||||||
|
for offer in q_price_offer[frame_begin_n:frame_begin_n + 10000]
|
||||||
|
]
|
||||||
|
|
||||||
|
price_frame = []
|
||||||
|
n_begin = int(frame_begin_n)
|
||||||
|
for offer in q_price_offer:
|
||||||
|
n_begin += 1
|
||||||
|
total = offer.fOfferPrice
|
||||||
|
image_file = get_flaps_for_mini_pictures(offer.sOfferFlapConfig)
|
||||||
|
dim_in_offer = [{
|
||||||
|
'PRICE': offer.fOfferPrice,
|
||||||
|
'FLAP': offer.sOfferFlapConfig,
|
||||||
|
'DESCRIPTION': offer.sDescripion,
|
||||||
|
'WIDTH': offer.iWinWidth,
|
||||||
|
'HIGHT': offer.iWinHight,
|
||||||
|
'ID': offer.id,
|
||||||
|
'IMG_MINI': image_file,
|
||||||
|
'QUANTITY': 1,
|
||||||
|
'BULLET': ['A'],
|
||||||
|
'SUBTOTAL': offer.fOfferPrice,
|
||||||
|
}]
|
||||||
|
|
||||||
|
discount = 0
|
||||||
|
try:
|
||||||
|
meta_keys = eval(offer.sOfficeDiscountMetaFormula)
|
||||||
|
if KEY_DICSOUNT in meta_keys:
|
||||||
|
for CountVal in sorted(meta_keys[KEY_DICSOUNT]):
|
||||||
|
if float(total) > float(CountVal):
|
||||||
|
discount = meta_keys[KEY_DICSOUNT][CountVal]
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
fin_price = total * (100 - discount) / 100
|
||||||
|
if discount > 99 or discount < 0.1:
|
||||||
|
discount_color1 = ""
|
||||||
|
discount_color2 = ""
|
||||||
|
else:
|
||||||
|
color_ratio = (discount + 0.) / 100
|
||||||
|
discount_color1 = f"#{255 - int(color_ratio * 128):02x}ff{255 - int(color_ratio * 128):02x}"
|
||||||
|
discount_color2 = f"#{255 - int(color_ratio * 255):02x}ff{255 - int(color_ratio * 255):02x}"
|
||||||
|
|
||||||
|
price_frame.append({
|
||||||
|
'DISTANCE': -1,
|
||||||
|
'DIM': dim_in_offer,
|
||||||
|
'TOTAL': total,
|
||||||
|
'DISCOUNT': discount,
|
||||||
|
'DISCOUNT_COLOR1': discount_color1,
|
||||||
|
'DISCOUNT_COLOR2': discount_color2,
|
||||||
|
'FIN_PRICE': fin_price,
|
||||||
|
'OFFICE_NAME': offer.sOfficeName,
|
||||||
|
'OFFICE_ADDRESS': offer.sOfficeAddress,
|
||||||
|
'OFFICE_PHONES': offer.sOfficePhones,
|
||||||
|
'MERCHANT': offer.sMerchantName,
|
||||||
|
'MERCHANT_LOGO': offer.pMerchantLogo,
|
||||||
|
'MERCHANT_URL': offer.sMerchantMainURL,
|
||||||
|
'MERCHANT_URL_SHOT': re.sub(r"(^http://|^https://|/$|www\.)", "", offer.sMerchantMainURL),
|
||||||
|
'SETS_NAME': offer.sSetName,
|
||||||
|
'GLAZING_NAME_B': offer.sGlazingBriefDescription,
|
||||||
|
'GLAZING_MARK': offer.sGlazingMark,
|
||||||
|
'GLAZING_TONING': offer.sGlazingToning,
|
||||||
|
'PVC_ID': offer.pwc_id,
|
||||||
|
'PVC_NAME': offer.sProfileName,
|
||||||
|
'PVC_NAME_T': pytils.translit.slugify(offer.sProfileName).lower(),
|
||||||
|
'PVC_MANUFACTURER': offer.sProfileManufacturer,
|
||||||
|
'PVC_MANUFACTURER_T': pytils.translit.slugify(offer.sProfileManufacturer).lower(),
|
||||||
|
'PVC_SEAL': offer.sProfileSealDescription,
|
||||||
|
'SETS_CLIMATE_CONTROL': offer.sSetClimateControl,
|
||||||
|
'SETS_SILL': offer.sSetSill,
|
||||||
|
'SETS_IMPLEMENT': offer.sSetImplementAll,
|
||||||
|
'SETS_IMPLEMENT_R': offer.sSetImplementHandles,
|
||||||
|
'SETS_IMPLEMENT_P': offer.sSetImplementHinges,
|
||||||
|
'SETS_IMPLEMENT_Z': offer.sSetImplementLatch,
|
||||||
|
'SETS_IMPLEMENT_O': offer.sSetImplementLimiter,
|
||||||
|
'SETS_IMPLEMENT_F': offer.sSetImplementCatch,
|
||||||
|
'SETS_PANES': offer.sSetPanes,
|
||||||
|
'SETS_SLOPE': offer.sSetSlope,
|
||||||
|
'SETS_DELIVERY': offer.sSetDelivery,
|
||||||
|
'SETS_DELIVERY_B': offer.bSetDelivery,
|
||||||
|
'SETS_OTHER': offer.sSetOtherConditions,
|
||||||
|
'SETS_ID': offer.setID,
|
||||||
|
'SETS_UNINSTALL_INSTALL': offer.sSetUninstallInstall,
|
||||||
|
'SETS_UNINSTALL_INSTALL_B': offer.bSetUninstallInstall,
|
||||||
|
'SETS_RATING': offer.fSetRating,
|
||||||
|
'SETS_RATING_STARTS': get_rating_set_for_stars(offer.fSetRating),
|
||||||
|
'SETS_DATA_MODIFY': offer.dOfferModify,
|
||||||
|
'IS_COMMERCIAL': offer.dSetCommercialUntil > timezone.now(),
|
||||||
|
})
|
||||||
|
|
||||||
|
if time_for_meta == 0 or django.utils.dateformat.format(time_for_meta, 'U') < \
|
||||||
|
django.utils.dateformat.format(offer.dOfferModify, 'U'):
|
||||||
|
time_for_meta = offer.dOfferModify
|
||||||
|
if time_for_meta == 0 or django.utils.dateformat.format(time_for_meta, 'U') < \
|
||||||
|
django.utils.dateformat.format(offer.dSetModify, 'U'):
|
||||||
|
time_for_meta = offer.dSetModify
|
||||||
|
|
||||||
|
if len(price_frame) == offer_per_frame:
|
||||||
|
break
|
||||||
|
|
||||||
|
price_frame = sorted(price_frame, key=lambda item: item['DISTANCE'])
|
||||||
|
if len(price_frame) < offer_per_frame:
|
||||||
|
n_begin = '-1'
|
||||||
|
return {'META_DATA_PUBLISH': time_for_meta, 'PRICE_FRAME': price_frame, 'N': n_begin}
|
||||||
else:
|
else:
|
||||||
# если выводим цены для типовой квартиры
|
# если выводим цены для типовой квартиры
|
||||||
# print("Нужно несколько окон для квартиры")
|
# print("Нужно несколько окон для квартиры")
|
||||||
@@ -123,7 +296,7 @@ def report_price_frame(apartment_id: int, mount_dim_per_offer: int, address_long
|
|||||||
f" oknardia_mountdim2apartment.iQuantity,"
|
f" oknardia_mountdim2apartment.iQuantity,"
|
||||||
f" oknardia_win_mountdim.id AS mID, "
|
f" oknardia_win_mountdim.id AS mID, "
|
||||||
f" oknardia_setkit.id AS setID,"
|
f" oknardia_setkit.id AS setID,"
|
||||||
f" (oknardia_setkit.dSetCommercialUntil > NOW()) AS bCommercial,"
|
f" (oknardia_setkit.dSetCommercialUntil > CURRENT_TIMESTAMP) AS bCommercial,"
|
||||||
f" oknardia_pvcprofiles.id AS pwc_id,"
|
f" oknardia_pvcprofiles.id AS pwc_id,"
|
||||||
f" oknardia_merchantbrand.id AS brand_id "
|
f" oknardia_merchantbrand.id AS brand_id "
|
||||||
f"FROM oknardia_priceoffer"
|
f"FROM oknardia_priceoffer"
|
||||||
@@ -314,8 +487,10 @@ 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}
|
||||||
|
|
||||||
|
|
||||||
def report_one_win_price(request: HttpRequest, win_width_mm: str = '670', win_height_mm: str = '2160',
|
def report_one_win_price(request: HttpRequest,
|
||||||
win_id: str = '16') -> HttpResponse:
|
win_width_mm: str | int = DEFAULT_WIN_WIDTH_MM,
|
||||||
|
win_height_mm: str | int = DEFAULT_WIN_HEIGHT_MM,
|
||||||
|
win_id: str | int = DEFAULT_WIN_ID) -> HttpResponse:
|
||||||
""" Формируем выдачу цен для единичного ТИПОВОГО окна (т.е. проема из серийного дома).
|
""" Формируем выдачу цен для единичного ТИПОВОГО окна (т.е. проема из серийного дома).
|
||||||
|
|
||||||
|
|
||||||
@@ -328,46 +503,78 @@ def report_one_win_price(request: HttpRequest, win_width_mm: str = '670', win_he
|
|||||||
time_start = time.perf_counter()
|
time_start = time.perf_counter()
|
||||||
to_template: dict[str, object] = {}
|
to_template: dict[str, object] = {}
|
||||||
try:
|
try:
|
||||||
# т.к. для вызова GetFlapDim4BigPictures нужно иметь внутри queryset поле iQuantity нельзя использовать
|
win_info_rows = (
|
||||||
# простой запрос (см. следующую строку).
|
Win_MountDim.objects
|
||||||
# qWinInfo = Win_MountDim.objects.filter(id=int(win_id))
|
.filter(id=int(win_id))
|
||||||
# Придется сделать запрос немного сложнее:
|
.values(
|
||||||
q_win_info = Win_MountDim.objects.raw(
|
'id',
|
||||||
f'SELECT oknardia_win_mountdim.iWinWidth,'
|
'iWinWidth',
|
||||||
f' oknardia_win_mountdim.iWinHight, oknardia_win_mountdim.iWinDepth,'
|
'iWinHight',
|
||||||
f' oknardia_win_mountdim.sFlapConfig, oknardia_win_mountdim.bIsNearDoor,'
|
'iWinDepth',
|
||||||
f' oknardia_win_mountdim.bIsDoor, oknardia_win_mountdim.sDescripion,'
|
'sFlapConfig',
|
||||||
f' oknardia_win_mountdim.id, 0 as iQuantity '
|
'bIsNearDoor',
|
||||||
f'FROM oknardia_win_mountdim '
|
'bIsDoor',
|
||||||
f'WHERE oknardia_win_mountdim.id = {int(win_id)};'
|
'sDescripion',
|
||||||
)
|
)
|
||||||
list_win_info = list(q_win_info)
|
)
|
||||||
|
list_win_info = [
|
||||||
|
SimpleNamespace(
|
||||||
|
id=item['id'],
|
||||||
|
iWinWidth=item['iWinWidth'],
|
||||||
|
iWinHight=item['iWinHight'],
|
||||||
|
iWinDepth=item['iWinDepth'],
|
||||||
|
sFlapConfig=item['sFlapConfig'],
|
||||||
|
bIsNearDoor=item['bIsNearDoor'],
|
||||||
|
bIsDoor=item['bIsDoor'],
|
||||||
|
sDescripion=item['sDescripion'],
|
||||||
|
iQuantity=0,
|
||||||
|
)
|
||||||
|
for item in win_info_rows
|
||||||
|
]
|
||||||
# Если размеры типового проема не совпадают с размерами из базы, то подменяем
|
# Если размеры типового проема не совпадают с размерами из базы, то подменяем
|
||||||
# на правильные и перевызываем страницу
|
# на правильные и перевызываем страницу
|
||||||
|
canonical_width_mm = int(list_win_info[0].iWinWidth * 10)
|
||||||
|
canonical_height_mm = int(list_win_info[0].iWinHight * 10)
|
||||||
if (list_win_info[0].iWinWidth * 10 != int(win_width_mm)) or \
|
if (list_win_info[0].iWinWidth * 10 != int(win_width_mm)) or \
|
||||||
(list_win_info[0].iWinHight * 10 != int(win_height_mm)):
|
(list_win_info[0].iWinHight * 10 != int(win_height_mm)):
|
||||||
return redirect(f"/tsena-odnogo-okna/{list_win_info[0].iWinWidth * 10}x{list_win_info[0].iWinHight * 10}"
|
return redirect(
|
||||||
f"mm/tip{win_id}")
|
_one_win_price_canonical_path(
|
||||||
|
win_width_mm=canonical_width_mm,
|
||||||
|
win_height_mm=canonical_height_mm,
|
||||||
|
win_id=win_id,
|
||||||
|
),
|
||||||
|
permanent=True,
|
||||||
|
)
|
||||||
except (ObjectDoesNotExist, ValueError, IndexError, TypeError):
|
except (ObjectDoesNotExist, ValueError, IndexError, TypeError):
|
||||||
return redirect("/tsena-odnogo-okna/670x2160mm/tip16")
|
return redirect(
|
||||||
|
_one_win_price_canonical_path(
|
||||||
|
win_width_mm=DEFAULT_WIN_WIDTH_MM,
|
||||||
|
win_height_mm=DEFAULT_WIN_HEIGHT_MM,
|
||||||
|
win_id=DEFAULT_WIN_ID,
|
||||||
|
),
|
||||||
|
permanent=True,
|
||||||
|
)
|
||||||
# все хорошо, засылаем картинку в шаблон
|
# все хорошо, засылаем картинку в шаблон
|
||||||
to_template.update(get_flaps_for_big_pictures(list_win_info))
|
to_template.update(get_flaps_for_big_pictures(list_win_info))
|
||||||
# получаем варианты схемы открывания (для графиков)
|
# получаем варианты схемы открывания (для графиков)
|
||||||
q_offer_flap_variation = PriceOffer.objects.raw(
|
flap_variations = (
|
||||||
f'SELECT'
|
PriceOffer.objects.filter(
|
||||||
f' COUNT(oknardia_priceoffer.sOfferFlapConfig) AS id,'
|
sOfferActive=True,
|
||||||
f' "" AS IMG_MINI,'
|
kOffer2MountDim_id=int(win_id),
|
||||||
f' "" AS STR_NUM,'
|
|
||||||
f' oknardia_priceoffer.sOfferFlapConfig '
|
|
||||||
f'FROM oknardia_priceoffer '
|
|
||||||
f'WHERE oknardia_priceoffer.sOfferActive <> 0'
|
|
||||||
f' AND oknardia_priceoffer.kOffer2MountDim_id = {int(win_id)} '
|
|
||||||
f'GROUP BY oknardia_priceoffer.sOfferFlapConfig,'
|
|
||||||
f' oknardia_priceoffer.sOfferActive,'
|
|
||||||
f' oknardia_priceoffer.kOffer2MountDim_id '
|
|
||||||
f'ORDER BY id DESC;'
|
|
||||||
)
|
)
|
||||||
list_offer_flap_variation = list(q_offer_flap_variation)
|
.values('sOfferFlapConfig')
|
||||||
|
.annotate(id=Count('sOfferFlapConfig'))
|
||||||
|
.order_by('-id')
|
||||||
|
)
|
||||||
|
list_offer_flap_variation = [
|
||||||
|
SimpleNamespace(
|
||||||
|
id=item['id'],
|
||||||
|
sOfferFlapConfig=item['sOfferFlapConfig'],
|
||||||
|
IMG_MINI='',
|
||||||
|
STR_NUM='',
|
||||||
|
)
|
||||||
|
for item in flap_variations
|
||||||
|
]
|
||||||
for i in range(0, len(list_offer_flap_variation)):
|
for i in range(0, len(list_offer_flap_variation)):
|
||||||
if i < 3:
|
if i < 3:
|
||||||
list_offer_flap_variation[i].STR_NUM = "вариант " + pytils.numeral.in_words(i + 1)
|
list_offer_flap_variation[i].STR_NUM = "вариант " + pytils.numeral.in_words(i + 1)
|
||||||
@@ -387,15 +594,13 @@ def report_one_win_price(request: HttpRequest, win_width_mm: str = '670', win_he
|
|||||||
"варианта схем",
|
"варианта схем",
|
||||||
"вариантов схем"))})
|
"вариантов схем"))})
|
||||||
#
|
#
|
||||||
q = PriceOffer.objects.raw(f'SELECT'
|
firms_count = (
|
||||||
f' COUNT(oknardia_priceoffer.kOfferFromUser_id) AS id,'
|
PriceOffer.objects.filter(kOffer2MountDim_id=int(win_id))
|
||||||
f' oknardia_priceoffer.kOfferFromUser_id,'
|
.values('kOfferFromUser_id')
|
||||||
f' oknardia_priceoffer.kOffer2MountDim_id '
|
.distinct()
|
||||||
f'FROM oknardia_priceoffer '
|
.count()
|
||||||
f'WHERE oknardia_priceoffer.kOffer2MountDim_id = {int(win_id)} '
|
)
|
||||||
f'GROUP BY oknardia_priceoffer.kOffer2MountDim_id,'
|
to_template.update({'NUM_TOTAL_FIRM_N_WORD': pytils.numeral.get_plural(firms_count,
|
||||||
f' oknardia_priceoffer.kOfferFromUser_id;')
|
|
||||||
to_template.update({'NUM_TOTAL_FIRM_N_WORD': pytils.numeral.get_plural(len(list(q)),
|
|
||||||
("компании", "компаний", "компаний"))})
|
("компании", "компаний", "компаний"))})
|
||||||
q = PriceOffer.objects.filter(kOffer2MountDim_id=int(win_id))
|
q = PriceOffer.objects.filter(kOffer2MountDim_id=int(win_id))
|
||||||
to_template.update({'NUM_TOTAL_OFFER_N_WORD': pytils.numeral.get_plural(q.count(),
|
to_template.update({'NUM_TOTAL_OFFER_N_WORD': pytils.numeral.get_plural(q.count(),
|
||||||
@@ -403,33 +608,36 @@ def report_one_win_price(request: HttpRequest, win_width_mm: str = '670', win_he
|
|||||||
"готовых расчётов"))})
|
"готовых расчётов"))})
|
||||||
to_template.update({'NUM_ARCHIVE_OFFER': q.filter(sOfferActive=0).count()})
|
to_template.update({'NUM_ARCHIVE_OFFER': q.filter(sOfferActive=0).count()})
|
||||||
#
|
#
|
||||||
q_seria_for_win = PriceOffer.objects.raw(
|
seria_for_win = (
|
||||||
f'SELECT'
|
MountDim2Apartment.objects.filter(kMountDim_id=int(win_id), kApartment__kSeria__isnull=False)
|
||||||
f' oknardia_seria_info.sName, oknardia_seria_info.id AS id,'
|
.values('kApartment__kSeria__id', 'kApartment__kSeria__sName')
|
||||||
f' "" AS sNameLat,'
|
.annotate(num_variation_of_apartment=Count('id'))
|
||||||
f' COUNT(oknardia_mountdim2apartment.id) AS num_variation_of_apartment '
|
.order_by('kApartment__kSeria__sName')
|
||||||
f'FROM oknardia_apartment_type'
|
|
||||||
f' INNER JOIN oknardia_mountdim2apartment'
|
|
||||||
f' ON oknardia_mountdim2apartment.kApartment_id = oknardia_apartment_type.id'
|
|
||||||
f' INNER JOIN oknardia_win_mountdim'
|
|
||||||
f' ON oknardia_mountdim2apartment.kMountDim_id = oknardia_win_mountdim.id'
|
|
||||||
f' INNER JOIN oknardia_seria_info'
|
|
||||||
f' ON oknardia_apartment_type.kSeria_id = oknardia_seria_info.id '
|
|
||||||
f'WHERE oknardia_win_mountdim.id = {int(win_id)} '
|
|
||||||
f'GROUP BY oknardia_win_mountdim.id,'
|
|
||||||
f' oknardia_seria_info.sName,'
|
|
||||||
f' oknardia_seria_info.id '
|
|
||||||
f'ORDER BY oknardia_seria_info.sName;'
|
|
||||||
)
|
)
|
||||||
list_seria_for_win = list(q_seria_for_win)
|
list_seria_for_win = []
|
||||||
for i in list_seria_for_win:
|
for seria_item in seria_for_win:
|
||||||
i.sNameLat = pytils.translit.slugify(i.sName)
|
seria_name = seria_item['kApartment__kSeria__sName']
|
||||||
i.num_variation_of_apartment = pytils.numeral.sum_string(i.num_variation_of_apartment,
|
list_seria_for_win.append(SimpleNamespace(
|
||||||
|
id=seria_item['kApartment__kSeria__id'],
|
||||||
|
sName=seria_name,
|
||||||
|
sNameLat=pytils.translit.slugify(seria_name),
|
||||||
|
num_variation_of_apartment=pytils.numeral.sum_string(
|
||||||
|
seria_item['num_variation_of_apartment'],
|
||||||
pytils.numeral.MALE,
|
pytils.numeral.MALE,
|
||||||
("типовую планировку квартиры",
|
("типовую планировку квартиры",
|
||||||
"типовые планировки квартир",
|
"типовые планировки квартир",
|
||||||
"типовых планировок квартир"))
|
"типовых планировок квартир"),
|
||||||
to_template.update(report_price_frame(0, 1, 0, 0, 0, 0, int(win_id)))
|
),
|
||||||
|
))
|
||||||
|
to_template.update(report_price_frame(apartment_id=0,
|
||||||
|
mount_dim_per_offer= 1,
|
||||||
|
address_longitude= 0,
|
||||||
|
address_latitude= 0,
|
||||||
|
frame_begin_n= 0,
|
||||||
|
brand_id= 0,
|
||||||
|
win_id=int(win_id)
|
||||||
|
)
|
||||||
|
)
|
||||||
to_template.update({
|
to_template.update({
|
||||||
'SERIA_FOR_WIN': list_seria_for_win,
|
'SERIA_FOR_WIN': list_seria_for_win,
|
||||||
'WIN_ID': int(win_id),
|
'WIN_ID': int(win_id),
|
||||||
@@ -444,16 +652,25 @@ def report_one_win_price(request: HttpRequest, win_width_mm: str = '670', win_he
|
|||||||
return render(request, "price/price_offers_for_one_window.html", to_template)
|
return render(request, "price/price_offers_for_one_window.html", to_template)
|
||||||
|
|
||||||
|
|
||||||
def next_one_win_price(request: HttpRequest, win_id='16', frame_begin_n="0"):
|
def next_one_win_price(request: HttpRequest,
|
||||||
|
win_id: str | int = DEFAULT_WIN_ID,
|
||||||
|
frame_begin_n: str | int = 0):
|
||||||
""" Возвращает очередной фреймом ценовых предложений для выдачи с одиночным окном.
|
""" Возвращает очередной фреймом ценовых предложений для выдачи с одиночным окном.
|
||||||
|
|
||||||
:param request: HttpRequest -- входящий http-запрос
|
:param request: HttpRequest -- входящий http-запрос
|
||||||
:param win_id: str -- id типового окна
|
:param win_id: str -- id типового окна
|
||||||
:param frame_begin_n: str -- Номер записи с которой начинается фрейм с ценами
|
:param frame_begin_n: str -- Номер записи, с которой начинается фрейм с ценами
|
||||||
:return: HttpResponse --
|
:return: HttpResponse --
|
||||||
"""
|
"""
|
||||||
time_start = time.perf_counter()
|
time_start = time.perf_counter()
|
||||||
to_template: dict[str, object] = report_price_frame(0, 1, 0, 0, int(frame_begin_n), 0, int(win_id))
|
to_template: dict[str, object] = report_price_frame(apartment_id=0,
|
||||||
|
mount_dim_per_offer=1,
|
||||||
|
address_longitude=0,
|
||||||
|
address_latitude=0,
|
||||||
|
frame_begin_n=int(frame_begin_n),
|
||||||
|
brand_id=0,
|
||||||
|
win_id=int(win_id)
|
||||||
|
)
|
||||||
to_template.update({'MOUNT_DIM_PER_OFFER': 1,
|
to_template.update({'MOUNT_DIM_PER_OFFER': 1,
|
||||||
'WIN_ID': int(win_id),
|
'WIN_ID': int(win_id),
|
||||||
'ticks': float(time.perf_counter() - time_start)})
|
'ticks': float(time.perf_counter() - time_start)})
|
||||||
@@ -633,7 +850,11 @@ def report_price(request: HttpRequest, build_id: str = "22427", apart_id: str =
|
|||||||
return redirect("/")
|
return redirect("/")
|
||||||
|
|
||||||
# получаем данные для фрейма ценовых предложений
|
# получаем данные для фрейма ценовых предложений
|
||||||
price_frame = report_price_frame(apart_id, mount_dim_per_offer, address_longitude, address_latitude)
|
price_frame = report_price_frame(apartment_id=apart_id,
|
||||||
|
mount_dim_per_offer=mount_dim_per_offer,
|
||||||
|
address_longitude=address_longitude,
|
||||||
|
address_latitude=address_latitude
|
||||||
|
)
|
||||||
to_template.update(price_frame)
|
to_template.update(price_frame)
|
||||||
# print u"строк в querySet:", CountMountDimInFramePage
|
# print u"строк в querySet:", CountMountDimInFramePage
|
||||||
# dimension_to_template.update({'DISCOUNT_TXT': DiscountTXT})
|
# dimension_to_template.update({'DISCOUNT_TXT': DiscountTXT})
|
||||||
@@ -704,8 +925,12 @@ def next_price_frame(request: HttpRequest, apart_id: str = "1", mount_dim_per_o
|
|||||||
"""
|
"""
|
||||||
time_start = time.perf_counter()
|
time_start = time.perf_counter()
|
||||||
# получаем данные для фрейма ценовых предложений
|
# получаем данные для фрейма ценовых предложений
|
||||||
price_frame = report_price_frame(int(apart_id), int(mount_dim_per_offer), float(address_longitude),
|
price_frame = report_price_frame(apartment_id=int(apart_id),
|
||||||
float(address_latitude), int(frame_begin_n))
|
mount_dim_per_offer=int(mount_dim_per_offer),
|
||||||
|
address_longitude=float(address_longitude),
|
||||||
|
address_latitude=float(address_latitude),
|
||||||
|
frame_begin_n=int(frame_begin_n)
|
||||||
|
)
|
||||||
to_template: dict[str, object] = price_frame
|
to_template: dict[str, object] = price_frame
|
||||||
to_template.update({'APPARTMENT_ID': apart_id,
|
to_template.update({'APPARTMENT_ID': apart_id,
|
||||||
'MOUNT_DIM_PER_OFFER': mount_dim_per_offer,
|
'MOUNT_DIM_PER_OFFER': mount_dim_per_offer,
|
||||||
|
|||||||
250
oknardia/web/test_prices.py
Normal file
250
oknardia/web/test_prices.py
Normal file
@@ -0,0 +1,250 @@
|
|||||||
|
from datetime import timedelta
|
||||||
|
from decimal import Decimal
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.db import connection
|
||||||
|
from django.http import HttpResponse
|
||||||
|
from django.test import RequestFactory, TestCase
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
from oknardia.models import (
|
||||||
|
Apartment_Type,
|
||||||
|
Glazing,
|
||||||
|
MerchantBrand,
|
||||||
|
MerchantOffice,
|
||||||
|
MountDim2Apartment,
|
||||||
|
OurUser,
|
||||||
|
PVCprofiles,
|
||||||
|
PriceOffer,
|
||||||
|
Seria_Info,
|
||||||
|
SetKit,
|
||||||
|
)
|
||||||
|
from web.prices import redirect_one_win_price_legacy, report_one_win_price
|
||||||
|
|
||||||
|
|
||||||
|
class ReportOneWinPriceTests(TestCase):
|
||||||
|
"""Регрессионные тесты для ORM-версии report_one_win_price."""
|
||||||
|
|
||||||
|
def setUp(self) -> None:
|
||||||
|
self.factory = RequestFactory()
|
||||||
|
django_user = User.objects.create_user(username="price-tester", password="secret")
|
||||||
|
self.our_user = OurUser.objects.create(kDjangoUser=django_user)
|
||||||
|
|
||||||
|
# Тестовая SQLite-схема в проекте может быть legacy-вариантом с flap_config вместо sFlapConfig.
|
||||||
|
# Для тестов report_one_win_price явно добавляем sFlapConfig, чтобы код проверялся в целевом режиме.
|
||||||
|
with connection.cursor() as cursor:
|
||||||
|
cursor.execute("PRAGMA table_info(oknardia_win_mountdim)")
|
||||||
|
existing_columns = {row[1] for row in cursor.fetchall()}
|
||||||
|
if "sFlapConfig" not in existing_columns:
|
||||||
|
cursor.execute("ALTER TABLE oknardia_win_mountdim ADD COLUMN sFlapConfig varchar(32)")
|
||||||
|
# if "flap_config" in existing_columns:
|
||||||
|
# cursor.execute(
|
||||||
|
# "UPDATE oknardia_win_mountdim SET sFlapConfig = flap_config "
|
||||||
|
# "WHERE sFlapConfig IS NULL"
|
||||||
|
# )
|
||||||
|
|
||||||
|
self.brand = MerchantBrand.objects.create(
|
||||||
|
sMerchantName="Оконный бренд",
|
||||||
|
sMerchantMainURL="https://example.com",
|
||||||
|
)
|
||||||
|
self.office = MerchantOffice.objects.create(
|
||||||
|
sOfficeName="Оконный бренд — офис",
|
||||||
|
kMerchantName=self.brand,
|
||||||
|
sOfficePhones="+7(495)123-45-67",
|
||||||
|
sOfficeAddress="Москва, Тестовая улица, 1",
|
||||||
|
sOfficeDiscountMetaFormula="{'discount': {'10000': 5}}",
|
||||||
|
)
|
||||||
|
self.our_user.kMerchantOffice = self.office
|
||||||
|
self.our_user.save(update_fields=["kMerchantOffice"])
|
||||||
|
|
||||||
|
with connection.cursor() as cursor:
|
||||||
|
insert_columns = [
|
||||||
|
"iWinWidth",
|
||||||
|
"iWinHight",
|
||||||
|
"iWinDepth",
|
||||||
|
"sFlapConfig",
|
||||||
|
"sDescripion",
|
||||||
|
"bIsDoor",
|
||||||
|
"bIsNearDoor",
|
||||||
|
"iWinLimit",
|
||||||
|
"dMountXYZDataCreate",
|
||||||
|
"dMountXYZModify",
|
||||||
|
]
|
||||||
|
insert_values = [
|
||||||
|
Decimal("67.0"),
|
||||||
|
Decimal("216.0"),
|
||||||
|
Decimal("15.0"),
|
||||||
|
"[>]",
|
||||||
|
"Тестовый проём",
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
Decimal("5.0"),
|
||||||
|
]
|
||||||
|
if "flap_config" in existing_columns:
|
||||||
|
insert_columns.insert(3, "flap_config")
|
||||||
|
insert_values.insert(3, "[>]")
|
||||||
|
columns_sql = ", ".join(insert_columns)
|
||||||
|
placeholders_sql = ", ".join(["?"] * len(insert_values)) + ", CURRENT_TIMESTAMP, CURRENT_TIMESTAMP"
|
||||||
|
cursor.execute(
|
||||||
|
f"INSERT INTO oknardia_win_mountdim ({columns_sql}) VALUES ({placeholders_sql})",
|
||||||
|
insert_values,
|
||||||
|
)
|
||||||
|
self.window_id = cursor.lastrowid
|
||||||
|
self.seria = Seria_Info.objects.create(sName="П-44")
|
||||||
|
self.apartment = Apartment_Type.objects.create(
|
||||||
|
sNameApartment="1-комнатная",
|
||||||
|
kSeria=self.seria,
|
||||||
|
)
|
||||||
|
MountDim2Apartment.objects.create(
|
||||||
|
kApartment=self.apartment,
|
||||||
|
kMountDim_id=self.window_id,
|
||||||
|
iQuantity=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.glazing = Glazing.objects.create(
|
||||||
|
sGlazingName="Тестовый стеклопакет",
|
||||||
|
sGlazingBriefDescription="Двухкамерный стеклопакет",
|
||||||
|
sGlazingMark="4-10-4-10-4",
|
||||||
|
sGlazingToning="нет",
|
||||||
|
kGlazing2User=self.our_user,
|
||||||
|
)
|
||||||
|
self.profile = PVCprofiles.objects.create(
|
||||||
|
sProfileName="Profile Test",
|
||||||
|
sProfileBriefDescription="Профиль для теста",
|
||||||
|
sProfileManufacturer="Test Manufacturer",
|
||||||
|
sProfileSealDescription="чёрный",
|
||||||
|
sProfileReinforcement="сталь",
|
||||||
|
kProfile2User=self.our_user,
|
||||||
|
fProfileRating=4.2,
|
||||||
|
)
|
||||||
|
self.set_kit = SetKit.objects.create(
|
||||||
|
sSetName="Тестовый набор",
|
||||||
|
kSet2User=self.our_user,
|
||||||
|
kSet2PVCprofiles=self.profile,
|
||||||
|
kSet2Glazing=self.glazing,
|
||||||
|
sSetImplementAll="Фурнитура",
|
||||||
|
sSetImplementHandles="Ручки",
|
||||||
|
sSetImplementHinges="Петли",
|
||||||
|
sSetImplementLatch="Запоры",
|
||||||
|
sSetImplementLimiter="Ограничитель",
|
||||||
|
sSetImplementCatch="Фиксатор",
|
||||||
|
sSetSill="Подоконник",
|
||||||
|
sSetSlope="Откос",
|
||||||
|
sSetPanes="Отлив",
|
||||||
|
sSetDelivery="Доставка",
|
||||||
|
bSetDelivery=True,
|
||||||
|
sSetUninstallInstall="Монтаж",
|
||||||
|
bSetUninstallInstall=True,
|
||||||
|
sSetOtherConditions="Прочие условия",
|
||||||
|
sSetClimateControl="Климат",
|
||||||
|
fSetRating=4.5,
|
||||||
|
dSetCommercialUntil=timezone.now() + timedelta(days=30),
|
||||||
|
)
|
||||||
|
self.active_offer = PriceOffer.objects.create(
|
||||||
|
kOffer2MountDim_id=self.window_id,
|
||||||
|
kOfferFromUser=self.our_user,
|
||||||
|
kOffer2SetKit=self.set_kit,
|
||||||
|
sOfferFlapConfig="[>]",
|
||||||
|
fOfferPrice=Decimal("12345.00"),
|
||||||
|
sOfferActive=True,
|
||||||
|
)
|
||||||
|
self.archived_offer = PriceOffer.objects.create(
|
||||||
|
kOffer2MountDim_id=self.window_id,
|
||||||
|
kOfferFromUser=self.our_user,
|
||||||
|
kOffer2SetKit=self.set_kit,
|
||||||
|
sOfferFlapConfig="[<]",
|
||||||
|
fOfferPrice=Decimal("11111.00"),
|
||||||
|
sOfferActive=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
@patch("web.prices.get_last_all_user_visit_list", return_value=[])
|
||||||
|
@patch("web.prices.get_last_user_visit_list", return_value=[])
|
||||||
|
@patch("web.prices.get_last_user_visit_cookies", return_value=[])
|
||||||
|
@patch("web.prices.get_flaps_for_mini_pictures", return_value="img/test-mini.png")
|
||||||
|
@patch(
|
||||||
|
"web.prices.get_flaps_for_big_pictures",
|
||||||
|
return_value={
|
||||||
|
"FLAP_DIM": [{
|
||||||
|
"iWinWidth": Decimal("67.0"),
|
||||||
|
"iWinHight": Decimal("216.0"),
|
||||||
|
"iWinWidth_mm": 670,
|
||||||
|
"iWinHight_mm": 2160,
|
||||||
|
}],
|
||||||
|
"WIN_DIM": [],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
def test_report_one_win_price_renders_expected_context(
|
||||||
|
self,
|
||||||
|
mocked_big_pictures,
|
||||||
|
mocked_mini_pictures,
|
||||||
|
mocked_cookies,
|
||||||
|
mocked_last_visits,
|
||||||
|
mocked_all_visits,
|
||||||
|
):
|
||||||
|
"""Вьюха должна собирать тот же ключевой контекст, но уже без raw SQL."""
|
||||||
|
request = self.factory.get(
|
||||||
|
f"/catalog/standard_opening/price-670x2160mm-tip{self.window_id}",
|
||||||
|
)
|
||||||
|
captured = {}
|
||||||
|
|
||||||
|
def fake_render(_request, template_name, context):
|
||||||
|
captured["template_name"] = template_name
|
||||||
|
captured["context"] = context
|
||||||
|
return HttpResponse("ok")
|
||||||
|
|
||||||
|
with patch("web.prices.render", side_effect=fake_render):
|
||||||
|
response = report_one_win_price(request, "670", "2160", str(self.window_id))
|
||||||
|
|
||||||
|
context = captured["context"]
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertEqual(captured["template_name"], "price/price_offers_for_one_window.html")
|
||||||
|
self.assertEqual(context["WIN_ID"], self.window_id)
|
||||||
|
self.assertEqual(context["MOUNT_DIM_PER_OFFER"], 1)
|
||||||
|
self.assertEqual(context["NUM_ARCHIVE_OFFER"], 1)
|
||||||
|
self.assertIn("2", context["NUM_TOTAL_OFFER_N_WORD"])
|
||||||
|
self.assertEqual(len(context["LIST_FLAP_VARIATION"]), 1)
|
||||||
|
self.assertEqual(context["LIST_FLAP_VARIATION"][0].sOfferFlapConfig, "[>]")
|
||||||
|
self.assertTrue(context["LIST_FLAP_VARIATION"][0].STR_NUM.startswith("вариант"))
|
||||||
|
self.assertEqual(context["LIST_FLAP_VARIATION"][0].IMG_MINI, "img/test-mini.png")
|
||||||
|
self.assertEqual(len(context["SERIA_FOR_WIN"]), 1)
|
||||||
|
self.assertEqual(context["SERIA_FOR_WIN"][0].sName, self.seria.sName)
|
||||||
|
self.assertEqual(len(context["PRICE_FRAME"]), 1)
|
||||||
|
self.assertEqual(context["PRICE_FRAME"][0]["SETS_NAME"], self.set_kit.sSetName)
|
||||||
|
self.assertEqual(context["PRICE_FRAME"][0]["MERCHANT"], self.brand.sMerchantName)
|
||||||
|
self.assertEqual(context["PRICE_FRAME"][0]["DIM"][0]["IMG_MINI"], "img/test-mini.png")
|
||||||
|
self.assertIn("META_DATA_PUBLISH", context)
|
||||||
|
self.assertTrue(mocked_big_pictures.called)
|
||||||
|
self.assertTrue(mocked_mini_pictures.called)
|
||||||
|
self.assertTrue(mocked_cookies.called)
|
||||||
|
self.assertTrue(mocked_last_visits.called)
|
||||||
|
self.assertTrue(mocked_all_visits.called)
|
||||||
|
|
||||||
|
def test_report_one_win_price_redirects_to_canonical_dimensions(self):
|
||||||
|
"""Если SEO-размеры в URL неверные, вьюха должна редиректить на канонический URL."""
|
||||||
|
request = self.factory.get(
|
||||||
|
f"/catalog/standard_opening/price-999x999mm-tip{self.window_id}",
|
||||||
|
)
|
||||||
|
|
||||||
|
response = report_one_win_price(request, "999", "999", str(self.window_id))
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 301)
|
||||||
|
self.assertEqual(
|
||||||
|
response["Location"],
|
||||||
|
f"/catalog/standard_opening/price-670x2160mm-tip{self.window_id}/",
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_legacy_one_win_url_redirects_to_canonical_url(self):
|
||||||
|
"""Старый URL страницы одного окна должен отдавать 301 на новый канонический путь."""
|
||||||
|
request = self.factory.get(
|
||||||
|
f"/tsena-odnogo-okna/670x2160mm/tip{self.window_id}",
|
||||||
|
)
|
||||||
|
|
||||||
|
response = redirect_one_win_price_legacy(request, "670", "2160", str(self.window_id))
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 301)
|
||||||
|
self.assertEqual(
|
||||||
|
response["Location"],
|
||||||
|
f"/catalog/standard_opening/price-670x2160mm-tip{self.window_id}/",
|
||||||
|
)
|
||||||
|
|
||||||
Reference in New Issue
Block a user