mod: рефакторинг "каталога компаний" (вьюшки и шаблоны)

This commit is contained in:
2026-04-25 08:06:15 +03:00
parent 509bce5111
commit 74220ab3a3
4 changed files with 612 additions and 201 deletions

View File

@@ -17,8 +17,7 @@
* Рефакторинг `catalog_seria_info` и связанных функций в `catalog_series.py`: raw SQL ⟶ ORM (`catalog_seria_info`, `seria_nav`, `seria_info_year`, `seria_info_geo_code`), снижена нагрузка на БД за счёт предвыборки и переиспользования агрегатов (`quantities_by_pair`, `offers_by_window`), добавлены безопасные fallback-значения для пустых выборок, включена потоковая обработка `iterator(chunk_size=500)` для гео-данных, обновлены комментарии и docstring под фактическую логику (таблица окон, pre-render light/heavy шаблонов, гео+статистика серии). * Рефакторинг `catalog_seria_info` и связанных функций в `catalog_series.py`: raw SQL ⟶ ORM (`catalog_seria_info`, `seria_nav`, `seria_info_year`, `seria_info_geo_code`), снижена нагрузка на БД за счёт предвыборки и переиспользования агрегатов (`quantities_by_pair`, `offers_by_window`), добавлены безопасные fallback-значения для пустых выборок, включена потоковая обработка `iterator(chunk_size=500)` для гео-данных, обновлены комментарии и docstring под фактическую логику (таблица окон, pre-render light/heavy шаблонов, гео+статистика серии).
* Добавлена management-команда `regenerate_seria_prerender` для оффлайн-пересборки pre-render шаблонов `catalog_seria_info` (все или выбранные root-серии), с режимами `--dry-run` и `--force`; серверный reload (Gunicon? uWSGI или что там еще будет) должен быть вынесен из кода приложения в оркестрацию (cron/systemd/deploy step). * Добавлена management-команда `regenerate_seria_prerender` для оффлайн-пересборки pre-render шаблонов `catalog_seria_info` (все или выбранные root-серии), с режимами `--dry-run` и `--force`; серверный reload (Gunicon? uWSGI или что там еще будет) должен быть вынесен из кода приложения в оркестрацию (cron/systemd/deploy step).
* Рефакторинг `standard_opening`: raw SQL -> ORM, упрощена дедублекация, убраны лишние запросы и переменные контекста, добавлены комментарии, SEO-описание и keywords, стандартизирован хвост контекста с визитами и `ticks` через общий helper внутри `catalog_openings.py`. * Рефакторинг `standard_opening`: raw SQL -> ORM, упрощена дедублекация, убраны лишние запросы и переменные контекста, добавлены комментарии, SEO-описание и keywords, стандартизирован хвост контекста с визитами и `ticks` через общий helper внутри `catalog_openings.py`.
* * Рефакторинг `catalog_company` и `catalog_company_detail` (`/catalog/company`): raw SQL → ORM для получения списка компаний и их наборов, вынесены вспомогательные функции (`_get_company_statistics`, `_get_company_sets_detail`, `_format_company_for_template`, `_format_set_for_template`, `_clean_text_field`, `_lowercase_first_char`), упрощена логика форматирования данных, добавлены подробные комментарии и docstring для каждой функции, использованы `select_related` и `annotate` для оптимизации запросов, добавлена защита от `Http404` при неправильных slugs. Улучшены SEO-атрибуты, и добавлена разметка shema.org.
*
* *
* *

View File

@@ -1,58 +1,81 @@
{% extends "base.html" %} {% extends "base.html" %}
{% load static %}{% load filters %} {% load static %}{% load filters %}
{% block Title %}Каталог изготовителей и поставщиков окон{% endblock %} {% block Title %}Каталог оконных компаний: производители и поставщики окон, рейтинг и цены{% endblock %}
{% block Add_Body_Attribute %} style="padding-top:70px;"{% endblock %} {% block Add_Body_Attribute %} style="padding-top:70px;"{% endblock %}
{% block Description %}Каталог изготовителей окон, партнёры «Окнардия», рейтинг, {% for i in COMPANIES %}{{ i.sMerchantName }}, {% endfor %} средняя цена окна{% endblock %} {% block Description %}Актуальный каталог оконных компаний России. Сравните производителей и поставщиков пластиковых окон по рейтингу, ассортименту, средней цене и дате последнего обновления.{% endblock %}
{% block Keywords %}Оконные компании, {% for i in COMPANIES %}{{ i.sMerchantName }}, {% endfor %} изготовители окон, производители окон, постащики окон, партнёры, каталог компаний, каталог оконных компаний, oknardia, окнардия {{ META_KEYWORDS|default:"" }} {% endblock %} {% block Keywords %}оконные компании, каталог компаний, производители окон, поставщики окон, рейтинг оконных компаний, сравнить цены на окна, oknardia, окнардия{% endblock %}
{% block Date4Meta %}{% if PUB_DAT %}{{ PUB_DAT|date:"c" }}{% else %}{% now "c" %}{% endif %}{% endblock %} {% block Author4Meta %}: Каталог «Окнардия»{% endblock %}
{% block Last4Meta %}{% if PUB_DAT %}{{ PUB_DAT|date:"c" }}{% else %}{% now "c" %}{% endif %}{% endblock %} {% block CopyrightAuthor4Meta %}: Каталог «Окнардия»{% endblock %}
{% block Author4Meta %}: Каталог изготовителей окон{% endblock %}
{% block CopyrightAuthor4Meta %}: Каталог изготовителей окон{% endblock %}
{% block Top_Meta1 %}{# <!-- BEGIN Дополнительные Metatags --> #} {% block Top_Meta1 %}{# <!-- BEGIN Дополнительные Metatags --> #}
<meta itemprop="author" content="Каталог «Окнардия»" />{% if IMG_FOR_BLOG %}
<meta itemprop="image" content="https://oknardia.ru/media/{{ IMG_FOR_BLOG }}" />{% else %}
<meta itemprop="image" content="https://oknardia.ru/static/img/MerDY3gpU0w.jpg" />{% endif %}
<meta itemprop="datePublished" content="{% if PUB_DAT %}{{ PUB_DAT|date:"c" }}{% else %}{% now "c" %}{% endif %}" />
<span itemprop="publisher" itemscope itemtype="http://schema.org/Organization"><meta itemprop="name" content="Каталог «Окнардия»" /></span>
<span itemprop="author" itemscope itemtype="http://schema.org/Person"><meta itemprop="name" content="Каталог «Окнардия»" /></span>
<meta itemprop="articleSection" content="Каталог производителей окон" />
<meta itemprop="headline" content="Компании-партнёры «Окнардии», их рейтинг, число оконных наборов и вариантов расчёта цен для типовых проёмов, средняя цена окна..." />
<meta name="news_keywords" content="{{ HEADER }}" />
<link rel="canonical" href="https://oknardia.ru/catalog/company/" /> <link rel="canonical" href="https://oknardia.ru/catalog/company/" />
<link rel="standout" href="https://oknardia.ru/catalog/company/" />
<!-- Разметка для соц-сетей Facebook Open Graph --> <!-- Разметка для соц-сетей Facebook Open Graph -->
<meta property="fb:admins" name="admins" content="100000084781830" />
<meta property="fb:pages" content="276108456054163" />
<meta property="fb:app_id" content="258354027974262" />
<meta property="fb:profile_id" name="profile_id" content="https://www.facebook.com/oknardia/" />
<meta property="og:locale" content="ru_RU" /> <meta property="og:locale" content="ru_RU" />
<meta property="og:site_name" content="oknardia.ru" /> <meta property="og:site_name" content="oknardia.ru" />
<meta property="og:url" content="https://oknardia.ru//catalog/company/" /> <meta property="og:url" content="https://oknardia.ru/catalog/company/" />
<meta property="og:type" content="article" /> <meta property="og:type" content="article" />
<meta property="og:title" content="Каталог производителей окон | oknardia.ru" /> <meta property="og:title" content="Каталог оконных компаний: производители и поставщики окон, рейтинг и цены | oknardia.ru" />
<meta property="og:description" content="Компании-партнеры «Окнардии», их средний рейтинг, число оконных наборов и вариантов расчета цен для типовых проёмов, средняя цена окна..." /> <meta property="og:description" content="Актуальный каталог оконных компаний России. Сравните производителей и поставщиков пластиковых окон по рейтингу, ассортименту, средней цене и дате последнего обновления." />
<meta property="og:image" content="{% if IMG_FOR_BLOG %}https://oknardia.ru/media/{% endif %}{{ IMG_FOR_BLOG|default:'https://oknardia.ru/static/img/MerDY3gpU0w.jpg' }}" /> <meta property="og:image" content="https://oknardia.ru/static/img/MerDY3gpU0w.jpg" />
<link rel="image_src" href="{% if IMG_FOR_BLOG %}https://oknardia.ru/media/{% endif %}{{ IMG_FOR_BLOG|default:'https://oknardia.ru/static/img/MerDY3gpU0w.jpg' }}" /> <link rel="image_src" href="https://oknardia.ru/static/img/MerDY3gpU0w.jpg" />
<!-- Разметка для соц-сетей Twitter Card --> <!-- Разметка для соц-сетей Twitter Card -->
<meta name="twitter:title" content="Каталог производителей окон | oknardia.ru" /> <meta name="twitter:title" content="Каталог оконных компаний: производители и поставщики окон, рейтинг и цены | oknardia.ru" />
<meta name="twitter:description" content="Компании-партнеры «Окнардии», их средний рейтинг, число оконных наборов и вариантов расчета цен для типовых проёмов, средняя цена окна..." /> <meta name="twitter:description" content="Актуальный каталог оконных компаний России. Сравните производителей и поставщиков пластиковых окон по рейтингу, ассортименту, средней цене и дате последнего обновления." />
<meta name="twitter:card" content="summary"> <meta name="twitter:card" content="summary">
<meta name="twitter:site" content="@oknardia" /> <meta name="twitter:site" content="@oknardia" />
<meta name="twitter:domain" content="oknardia.ru" /> {# Удалить: <meta name="twitter:domain"> — устаревший тег #}
<meta property="twitter:url" content="{% if IMG_FOR_BLOG %}https://oknardia.ru/media/{% endif %}{{ IMG_FOR_BLOG|default:'https://oknardia.ru/static/img/MerDY3gpU0w.jpg' }}" /> <meta name="twitter:image" content="https://oknardia.ru/static/img/MerDY3gpU0w.jpg" />
<meta name="relap-image" content="{% if IMG_FOR_BLOG %}https://oknardia.ru/media/{% endif %}{{ IMG_FOR_BLOG|default:'https://oknardia.ru/static/img/MerDY3gpU0w.jpg' }}"> <meta name="relap-image" content="https://oknardia.ru/static/img/MerDY3gpU0w.jpg">
{# <!-- END Дополнительные Metatags --> #}{% endblock %} {# <!-- END Дополнительные Metatags --> #}{% endblock %}
{% block ADD_TO_HEAD %}{% comment %}
JSON-LD для страницы-списка компаний: CollectionPage + ItemList с элементами Organization.
Это понятнее для поисковиков, чем legacy microdata на метатегах.
{% endcomment %}
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "CollectionPage",
"name": "Каталог оконных компаний: производители и поставщики окон",
"description": "Актуальный каталог оконных компаний России с рейтингами, средней ценой и составом наборов.",
"url": "https://oknardia.ru/catalog/company/",
"inLanguage": "ru-RU",
"isPartOf": {
"@type": "WebSite",
"name": "Окнардия",
"url": "https://oknardia.ru"
},
"mainEntity": {
"@type": "ItemList",
"name": "Производители и поставщики окон",
"numberOfItems": {{ COMPANIES|length }},
"itemListElement": [
{% for i in COMPANIES %}
{
"@type": "ListItem",
"position": {{ forloop.counter }},
"item": {
"@type": "Organization",
"name": "{{ i.sMerchantName|escapejs }}",
"url": "https://oknardia.ru/catalog/company/{{ i.id }}-{{ i.sMerchantMainURL }}",
"logo": "https://oknardia.ru/media/{{ i.pMerchantLogo }}"
}
}{% if not forloop.last %},{% endif %}
{% endfor %}
]
}
}
</script>
{% endblock %}
{% block Main_Content %} {% block Main_Content %}
<div class="container-fluid"> <div class="container-fluid">
{# <!--- Хлебные крошки: НАЧАЛО --> #}<div class="row"> {# <!--- Хлебные крошки: НАЧАЛО --> #}<div class="row">
@@ -90,7 +113,3 @@
{% include "report/report_log_user_visit.html" %} {% include "report/report_log_user_visit.html" %}
</div> </div>
</div>{% endblock %} </div>{% endblock %}

View File

@@ -5,30 +5,20 @@
{% block Add_Body_Attribute %} style="padding-top:70px;"{% endblock %} {% block Add_Body_Attribute %} style="padding-top:70px;"{% endblock %}
{% block Description %}«{{ COMPANY }}», описание компании «{{ COMPANY }}», оконные наборы от «{{ COMPANY }}» и их состав, характеристики «{{ COMPANY }}», рейтинг «{{ COMPANY }}», средние цены и отклонение цен «{{ COMPANY }}».{% endblock %} {% block Description %}Производитель окон «{{ COMPANY }}» в каталоге Окнардии: оконные наборы, их состав и характеристики, независимый рейтинг качества, средние цены на замену оконных конструкций в типовых домах.{% endblock %}
{% block Keywords %}{{ COMPANY }}, компания {{ COMPANY }}, окна {{ COMPANY }}, изготовитель окон {{ COMPANY }}, производитель окон {{ COMPANY }}, поставщик окон {{ COMPANY }}, партнёр, каталог компаний, каталог оконных компаний, oknardia, окнардия {{ META_KEYWORDS|default:"" }} {% endblock %} {% block Keywords %}{{ COMPANY }}, компания {{ COMPANY }}, окна {{ COMPANY }}, изготовитель окон {{ COMPANY }}, производитель окон {{ COMPANY }}, поставщик окон {{ COMPANY }}, партнёр, каталог компаний, каталог оконных компаний, oknardia, окнардия{% endblock %}
{% block Date4Meta %}{% if PUB_DAT %}{{ PUB_DAT|date:"c" }}{% else %}{% now "c" %}{% endif %}{% endblock %}
{% block Last4Meta %}{% if PUB_DAT %}{{ PUB_DAT|date:"c" }}{% else %}{% now "c" %}{% endif %}{% endblock %} {% block Author4Meta %}Каталог изготовителей окон{% endblock %}
{% block Author4Meta %}: Каталог изготовителей окон{% endblock %} {% block CopyrightAuthor4Meta %}Каталог изготовителей окон{% endblock %}
{% block CopyrightAuthor4Meta %}: Каталог изготовителей окон{% endblock %}
{% block Top_Meta1 %}{# <!-- BEGIN Дополнительные Metatags --> #} {% block Top_Meta1 %}{# <!-- BEGIN Дополнительные Metatags --> #}
<meta itemprop="author" content="Каталог «Окнардия»" />{% if IMG_FOR_BLOG %} {# Microdata (itemprop) убрана — заменена на JSON-LD в блоке ADD_TO_HEAD ниже (чище, надёжнее) #}
<meta itemprop="image" content="https://oknardia.ru/media/{{ IMG_FOR_BLOG }}" />{% else %}
<meta itemprop="image" content="https://oknardia.ru/static/img/MerDY3gpU0w.jpg" />{% endif %}
<meta itemprop="datePublished" content="{% if PUB_DAT %}{{ PUB_DAT|date:"c" }}{% else %}{% now "c" %}{% endif %}" />
<span itemprop="publisher" itemscope itemtype="http://schema.org/Organization"><meta itemprop="name" content="Каталог «Окнардия»" /></span>
<span itemprop="author" itemscope itemtype="http://schema.org/Person"><meta itemprop="name" content="Каталог «Окнардия»" /></span>
<meta itemprop="articleSection" content="Каталог производителей окон" />
<meta itemprop="headline" content="Изготовитель окон «{{ COMPANY }}», описание, производимые им оконные наборы и их состав, характеристики, рейтинг, средние цены и отклонение цен." />
<meta name="news_keywords" content="{{ HEADER|striptags }}" /> <meta name="news_keywords" content="{{ HEADER|striptags }}" />
<link rel="canonical" href="https://oknardia.ru/catalog/company/{{ COMPANY_ID }}-{{ COMPANY_T }}" /> <link rel="canonical" href="https://oknardia.ru/catalog/company/{{ COMPANY_ID }}-{{ COMPANY_T }}" />
<link rel="standout" href="https://oknardia.ru/catalog/company/{{ COMPANY_ID }}-{{ COMPANY_T }}" /> {# Удалить: <link rel="standout"> — тег Google News 2011 г., отменён в 2014, поисковики игнорируют #}
<!-- Разметка для соц-сетей Facebook Open Graph --> <!-- Разметка для соц-сетей Facebook Open Graph -->
<meta property="fb:admins" name="admins" content="100000084781830" /> <meta property="fb:admins" name="admins" content="100000084781830" />
<meta property="fb:pages" content="276108456054163" /> <meta property="fb:pages" content="276108456054163" />
@@ -36,24 +26,59 @@
<meta property="fb:profile_id" name="profile_id" content="https://www.facebook.com/oknardia/" /> <meta property="fb:profile_id" name="profile_id" content="https://www.facebook.com/oknardia/" />
<meta property="og:locale" content="ru_RU" /> <meta property="og:locale" content="ru_RU" />
<meta property="og:site_name" content="oknardia.ru" /> <meta property="og:site_name" content="oknardia.ru" />
<meta property="og:url" content="https://oknardia.ru//catalog/company/{{ COMPANY_ID }}-{{ COMPANY_T }}" /> <meta property="og:url" content="https://oknardia.ru/catalog/company/{{ COMPANY_ID }}-{{ COMPANY_T }}" />
<meta property="og:type" content="article" /> <meta property="og:type" content="article" />
<meta property="og:title" content="Окна «{{ COMPANY }}» | oknardia.ru" /> <meta property="og:title" content="Окна «{{ COMPANY }}» | oknardia.ru" />
<meta property="og:description" content="Окна «{{ COMPANY }}», описание окон «{{ COMPANY }}», производимые им оконные наборы и их состав, характеристики, рейтинг, средние цены и отклонение цен." /> <meta property="og:description" content="«{{ COMPANY }}» оконные наборы, состав и характеристики, независимый рейтинг качества, средние цены на установку. Агрегатор Окнардия." />
<meta property="og:image" content="{% if IMG_FOR_BLOG %}https://oknardia.ru/media/{% endif %}{{ IMG_FOR_BLOG|default:'https://oknardia.ru/static/img/MerDY3gpU0w.jpg' }}" /> <meta property="og:image" content="{% if IMG_FOR_BLOG %}https://oknardia.ru/media/{% endif %}{{ IMG_FOR_BLOG|default:'https://oknardia.ru/static/img/MerDY3gpU0w.jpg' }}" />
<link rel="image_src" href="{% if IMG_FOR_BLOG %}https://oknardia.ru/media/{% endif %}{{ IMG_FOR_BLOG|default:'https://oknardia.ru/static/img/MerDY3gpU0w.jpg' }}" /> <link rel="image_src" href="{% if IMG_FOR_BLOG %}https://oknardia.ru/media/{% endif %}{{ IMG_FOR_BLOG|default:'https://oknardia.ru/static/img/MerDY3gpU0w.jpg' }}" />
<!-- Разметка для соц-сетей Twitter Card --> <!-- Разметка для соц-сетей Twitter Card -->
<meta name="twitter:title" content="Производителей окон «{{ COMPANY }}» | oknardia.ru" /> <meta name="twitter:title" content="Производитель окон «{{ COMPANY }}» | oknardia.ru" />
<meta name="twitter:description" content="Изготовитель окон «{{ COMPANY }}», описание, производимые им оконные наборы и их состав, характеристики, рейтинг, средние цены и отклонение цен." /> <meta name="twitter:description" content="«{{ COMPANY }}» в каталоге Окнардии: наборы, характеристики, рейтинг и цены на установку окон в типовых домах." />
<meta name="twitter:card" content="summary"> <meta name="twitter:card" content="summary">
<meta name="twitter:site" content="@oknardia" /> <meta name="twitter:site" content="@oknardia" />
<meta name="twitter:domain" content="oknardia.ru" /> {# Удалить: <meta name="twitter:domain"> — устарело с 2015, Twitter его не использует #}
<meta property="twitter:url" content="{% if IMG_FOR_BLOG %}https://oknardia.ru/media/{% endif %}{{ IMG_FOR_BLOG|default:'https://oknardia.ru/static/img/MerDY3gpU0w.jpg' }}" /> <meta property="twitter:url" content="{% if IMG_FOR_BLOG %}https://oknardia.ru/media/{% endif %}{{ IMG_FOR_BLOG|default:'https://oknardia.ru/static/img/MerDY3gpU0w.jpg' }}" />
<meta name="relap-image" content="{% if IMG_FOR_BLOG %}https://oknardia.ru/media/{% endif %}{{ IMG_FOR_BLOG|default:'https://oknardia.ru/static/img/MerDY3gpU0w.jpg' }}"> <meta name="relap-image" content="{% if IMG_FOR_BLOG %}https://oknardia.ru/media/{% endif %}{{ IMG_FOR_BLOG|default:'https://oknardia.ru/static/img/MerDY3gpU0w.jpg' }}">
{# <!-- END Дополнительные Metatags --> #}{% endblock %} {# <!-- END Дополнительные Metatags --> #}{% endblock %}
{% block Top_JS5 %}
<script src="https://api-maps.yandex.ru/2.1/?lang=ru_RU" type="text/javascript"></script>{% endblock %}
{% block Top_JS5 %}<script src="https://api-maps.yandex.ru/2.1/?lang=ru_RU" type="text/javascript"></script>{% endblock %} {% block ADD_TO_HEAD %}{% comment %}
JSON-LD разметка Schema.org для страницы производителя окон.
Тип LocalBusiness описывает компанию-поставщика окон: название, контакты, адрес, геокоординаты,
логотип и ссылку на официальный сайт производителя.
Данные берутся из первого набора в SETS (все наборы принадлежат одному офису/бренду),
поэтому достаточно SETS.0 для контактной информации.
Документация: https://schema.org/LocalBusiness #}{% endcomment %}
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "LocalBusiness",
"name": "{{ COMPANY|escapejs }}",
"description": "Производитель окон «{{ COMPANY|escapejs }}»: оконные наборы, характеристики профилей и стеклопакетов, цены на установку в типовых домах.",
"url": "https://oknardia.ru/catalog/company/{{ COMPANY_ID }}-{{ COMPANY_T }}",
"image": "{% if IMG_FOR_BLOG %}https://oknardia.ru/media/{{ IMG_FOR_BLOG }}{% else %}https://oknardia.ru/static/img/MerDY3gpU0w.jpg{% endif %}",
"logo": {
"@type": "ImageObject",
"url": "{% if IMG_FOR_BLOG %}https://oknardia.ru/media/{{ IMG_FOR_BLOG }}{% else %}https://oknardia.ru/static/img/MerDY3gpU0w.jpg{% endif %}"
}{% if SETS %},
"telephone": "{{ SETS.0.sOfficePhones|striptags|escapejs }}",
"address": {
"@type": "PostalAddress",
"streetAddress": "{{ SETS.0.sOfficeAddress|escapejs }}",
"addressCountry": "RU"
},
"geo": {
"@type": "GeoCoordinates",
"latitude": {{ SETS.0.fOfficeGeoCode_Latitude|stringformat:".7f" }},
"longitude": {{ SETS.0.fOfficeGeoCode_Longitude|stringformat:".7f" }}
},
"sameAs": "{{ SETS.0.sMerchantMainURL.URL|escapejs }}"{% endif %}
}
</script>
{% endblock %}
{% block Main_Content %} {% block Main_Content %}

View File

@@ -1,11 +1,27 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""
Каталог производителей и компаний.
Модуль предоставляет views для отображения:
1. Списка всех производителей с их ключевыми показателями (рейтинг, количество
предложений, среднюю цену и т.п.)
2. Детальную информацию о конкретном производителе со всеми его оконными наборами
Все запросы переведены на Django ORM для лучшей производительности и чистоты кода.
"""
from django.shortcuts import render, redirect from django.shortcuts import render, redirect
from django.http import HttpRequest, HttpResponse from django.http import HttpRequest, HttpResponse, Http404
from django.utils import timezone from django.db.models import Count, Avg, Max, Min, DecimalField
from oknardia.models import ( from oknardia.models import (
MerchantBrand, MerchantBrand,
SetKit,
PriceOffer,
)
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 get_rating_set_for_stars from web.add_func import get_rating_set_for_stars
import django.utils.dateformat import django.utils.dateformat
import time import time
@@ -14,148 +30,500 @@ import re
import pytils import pytils
def _get_company_statistics() -> list:
"""
Получает список компаний (MerchantBrand) с агрегированной статистикой.
Статистика включает:
- Количество оконных наборов от компании
- Средний рейтинг наборов
- Количество ценовых предложений
- Среднюю цену предложений
- Дату последнего обновления цены
Оптимизировано для минимизации запросов к БД.
Returns:
list: Список словарей с данными компаний
"""
# 1. Статистика по наборам (SetKit) для каждой компании
set_stats = (
SetKit.objects
.filter(kSet2User__kMerchantOffice__kMerchantName__isnull=False)
.values('kSet2User__kMerchantOffice__kMerchantName_id')
.annotate(
num_sets=Count('id', distinct=True),
avg_rating=Avg('fSetRating')
)
)
set_stats_dict = {
stat['kSet2User__kMerchantOffice__kMerchantName_id']: {
'num_sets': stat['num_sets'],
'avg_rating': stat['avg_rating'] or 0
}
for stat in set_stats
}
# 2. Статистика по ценовым предложениям (PriceOffer)
companies_data = (
PriceOffer.objects
.filter(
sOfferActive=True,
kOfferFromUser__kMerchantOffice__kMerchantName__isnull=False
)
.values('kOfferFromUser__kMerchantOffice__kMerchantName_id')
.annotate(
num_offers=Count('id', distinct=True),
price_avg=Avg('fOfferPrice', output_field=DecimalField()),
last_update=Max('dOfferModify')
)
.order_by('-last_update')
)
# 3. Получаем все объекты MerchantBrand одним запросом (решение проблемы N+1)
company_ids = [
offer['kOfferFromUser__kMerchantOffice__kMerchantName_id']
for offer in companies_data
]
merchants = MerchantBrand.objects.in_bulk(company_ids)
# 4. Собираем финальный результат
result = []
for offer in companies_data:
company_id = offer['kOfferFromUser__kMerchantOffice__kMerchantName_id']
merchant = merchants.get(company_id)
if not merchant:
continue
set_stat = set_stats_dict.get(company_id, {
'num_sets': 0,
'avg_rating': 0
})
result.append({
'id': merchant.id,
'sMerchantName': merchant.sMerchantName,
'pMerchantLogo': merchant.pMerchantLogo,
'NumSets': set_stat['num_sets'],
'RatingAVG': set_stat['avg_rating'],
'NumOffers': offer['num_offers'],
'PriceAVG': offer['price_avg'],
'lastUpdate': offer['last_update']
})
# Сортируем по среднему рейтингу (убывание)
result.sort(key=lambda x: x['RatingAVG'], reverse=True)
return result
def _format_company_for_template(company_data: dict) -> dict:
"""
Форматирует данные компании для вывода в шаблон.
Применяет:
- Конвертацию времени в читаемый формат (e.g., "3 дня назад")
- Склонение существительных (plural forms)
- Вычисление звёзд рейтинга
- Скатывание имени в slug для URL
Args:
company_data (dict): Словарь с данными компании
Returns:
dict: Отформатированные данные компании
"""
formatted = company_data.copy()
# Вычисляем звёзды на основе рейтинга
formatted['STARS'] = get_rating_set_for_stars(
formatted['RatingAVG']
)
# Применяем правильные формы множественного числа
formatted['NumSets'] = pytils.numeral.get_plural(
formatted['NumSets'],
"оконный набор, оконных набора, оконных наборов"
)
formatted['NumOffers'] = pytils.numeral.get_plural(
formatted['NumOffers'],
"вариант, варианта, вариантов"
)
# Конвертируем время последнего обновления в читаемый формат
if formatted['lastUpdate']:
timestamp = int(
django.utils.dateformat.format(
formatted['lastUpdate'],
'U'
)
)
formatted['lastUpdate'] = pytils.dt.distance_of_time_in_words(
timestamp
)
# Генерируем slug из имени компании для URL
formatted['sMerchantMainURL'] = pytils.translit.slugify(
formatted['sMerchantName']
)
return formatted
def catalog_company(request: HttpRequest) -> HttpResponse: def catalog_company(request: HttpRequest) -> HttpResponse:
time_start = time.perf_counter() """
to_template: dict[str, object] = {} # словарь, для передачи шаблону Показывает список всех производителей с ключевыми показателями.
q_company = MerchantBrand.objects.raw('SELECT'
' oknardia_merchantbrand.id,' GET параметры: опционально могут использоваться для фильтрации
' oknardia_merchantbrand.sMerchantName,'
' oknardia_merchantbrand.pMerchantLogo,' Контекст шаблона:
' oknardia_merchantbrand.sMerchantMainURL,' - COMPANIES (list): Список компаний с статистикой
' COUNT(oknardia_priceoffer.id) AS NumOffers,' - LAST_VISIT (list): Последние визиты текущего пользователя
' AVG(oknardia_priceoffer.fOfferPrice) AS PriceAVG,' - LOG_VISIT (list): Последние визиты всех пользователей
' MAX(oknardia_priceoffer.dOfferModify) AS lastUpdate,'
' Q.NumSets,' Args:
' Q.RatingAVG,' request (HttpRequest): HTTP запрос от клиента
' 1 AS STARS '
'FROM (SELECT' Returns:
' COUNT(oknardia_setkit.sSetName) AS NumSets,' HttpResponse: Отрендеренная HTML страница со списком компаний
' oknardia_merchantoffice.kMerchantName_id AS Q_ID,' """
' AVG(oknardia_setkit.fSetRating) AS RatingAVG' # Получаем статистику по компаниям с использованием ORM
' FROM oknardia_merchantoffice' companies_list = _get_company_statistics()
' INNER JOIN oknardia_ouruser'
' ON oknardia_ouruser.kMerchantOffice_id = oknardia_merchantoffice.id' # Форматируем каждую компанию для вывода в шаблон
' INNER JOIN oknardia_setkit' formatted_companies = [
' ON oknardia_setkit.kSet2User_id = oknardia_ouruser.id' _format_company_for_template(company)
' GROUP BY oknardia_merchantoffice.id,' for company in companies_list
' oknardia_merchantoffice.kMerchantName_id) AS Q,' ]
' oknardia_ouruser'
' INNER JOIN oknardia_merchantoffice' # Получаем информацию о посещениях для персонализации
' ON oknardia_ouruser.kMerchantOffice_id = oknardia_merchantoffice.id' to_template: dict[str, object] = {
' INNER JOIN oknardia_priceoffer' 'COMPANIES': formatted_companies,
' ON oknardia_priceoffer.kOfferFromUser_id = oknardia_ouruser.id' 'LAST_VISIT': get_last_user_visit_list(
' INNER JOIN oknardia_merchantbrand' get_last_user_visit_cookies(request)[:3]
' ON oknardia_merchantoffice.kMerchantName_id = oknardia_merchantbrand.id' ),
' WHERE Q_ID = oknardia_merchantoffice.kMerchantName_id '
'GROUP BY oknardia_merchantoffice.kMerchantName_id '
'ORDER BY Q.RatingAVG DESC;')
list_company = list(q_company)
for i in list_company:
i.STARS = get_rating_set_for_stars(i.RatingAVG)
i.NumSets = pytils.numeral.get_plural(i.NumSets, u"оконный набор, оконных набора, оконных наборов")
i.NumOffers = pytils.numeral.get_plural(i.NumOffers, u"вариант, варианта, вариантов")
i.lastUpdate = pytils.dt.distance_of_time_in_words(int(django.utils.dateformat.format(i.lastUpdate, 'U')))
i.sMerchantMainURL = pytils.translit.slugify(i.sMerchantName)
# print("NAME:", i.sMerchantName, "\tNumSets:", i.NumSets, "\tNumOffers:", i.NumOffers,
# "\t:AverageRating:", i.RatingAVG, "\tAveragePrice:", i.PriceAVG, "\tSTARS:", i.STARS)
to_template.update({
'COMPANIES': list_company,
# получаем последние визиты клиента через куки
'LAST_VISIT': get_last_user_visit_list(get_last_user_visit_cookies(request)[:3]),
# получаем последние визиты всех посетителей из базы
# id2log, log_visit = get_last_all_user_visit_list()
'LOG_VISIT': get_last_all_user_visit_list(), 'LOG_VISIT': get_last_all_user_visit_list(),
'ticks': float(time.perf_counter() - time_start) }
})
return render(request, "catalog/catalog_company.html", to_template) return render(request, "catalog/catalog_company.html", to_template)
def catalog_company_detail(request: HttpRequest, company_id: str, company_name_slug: str) -> HttpResponse: def _lowercase_first_char(text: str) -> str:
"""
Преобразует первый символ строки в нижний регистр.
Args:
text (str): Исходная строка
Returns:
str: Строка с строчным первым символом (если длина > 0)
"""
return text[0].lower() + text[1:] if len(text) > 0 else text
def _clean_text_field(text: str, empty_values: list) -> str:
"""
Очищает текстовое поле, удаляя типичные маркеры "пусто" и преобразуя
первый символ в нижний регистр.
Args:
text (str): Исходный текст
empty_values (list): Список значений, которые считаются "пустыми"
Returns:
str: Очищенный текст или пустая строка если значение в empty_values
"""
if text.lower() in empty_values:
return ""
return _lowercase_first_char(text)
def _get_company_sets_detail(company_id: int) -> list:
"""
Получает все оконные наборы для компании с полной статистикой по ценам.
Использует оптимизированные select_related и prefetch_related для минимизации
запросов к БД. Группирует данные по наборам (SetKit) с уникальностью.
Args:
company_id (int): ID компании (MerchantBrand)
Returns:
list: Список словарей с данными наборов, отсортированные по рейтингу
"""
# Получаем активные ценовые предложения для компаний с агрегацией по наборам
price_stats = (
PriceOffer.objects
.filter(
sOfferActive=True,
kOfferFromUser__kMerchantOffice__kMerchantName_id=company_id
)
.values('kOffer2SetKit_id')
.annotate(
num_offers=Count('id'),
price_avg=Avg('fOfferPrice', output_field=DecimalField()),
last_update=Max('dOfferModify'),
early_creation=Min('dOfferCreate')
)
)
# Преобразуем в словарь для быстрого доступа по ID набора
price_stats_dict = {
stat['kOffer2SetKit_id']: {
'num_offers': stat['num_offers'],
'price_avg': stat['price_avg'],
'last_update': stat['last_update'],
'early_creation': stat['early_creation']
}
for stat in price_stats
}
# Получаем все наборы компании с их зависимостями
# select_related оптимизирует ForeignKey запросы (профиль, стеклопакет)
sets_queryset = (
SetKit.objects
.filter(
kSet2User__kMerchantOffice__kMerchantName_id=company_id
)
.select_related(
'kSet2User',
'kSet2User__kMerchantOffice',
'kSet2User__kMerchantOffice__kMerchantName',
'kSet2PVCprofiles',
'kSet2Glazing'
)
.order_by('-fSetRating')
)
# Собираем результат, комбинируя данные SetKit с агрегированной статистикой
result = []
seen_set_ids = set()
for setkit in sets_queryset:
# Пропускаем дубликаты наборов (может быть несколько ценовых предложений
# для одного набора)
if setkit.id in seen_set_ids:
continue
seen_set_ids.add(setkit.id)
# Получаем статистику по ценам для этого набора
price_stat = price_stats_dict.get(setkit.id, {
'num_offers': 0,
'price_avg': None,
'last_update': None,
'early_creation': None
})
# Собираем все данные в один объект
result.append({
'setkit': setkit,
'num_offers': price_stat['num_offers'],
'price_avg': price_stat['price_avg'],
'last_update': price_stat['last_update'],
'early_creation': price_stat['early_creation'],
'merchant_office': setkit.kSet2User.kMerchantOffice,
'merchant_brand': setkit.kSet2User.kMerchantOffice.kMerchantName,
'profile': setkit.kSet2PVCprofiles,
'glazing': setkit.kSet2Glazing
})
return result
def _format_set_for_template(set_data: dict, empty_values: list) -> dict:
"""
Форматирует данные оконного набора для вывода в шаблон.
Применяет:
- Преобразование URL в удобный для отображения формат
- Разделение email адресов на части (для обфускации)
- Вычисление звёзд рейтинга
- Конвертация времени в читаемый формат
- Создание slugs для названий и производителей
- Склонение числительных(контуры, швы и т.п.)
- Очистку пустых полей от стандартных маркеров ("нет", "" и т.п.)
Args:
set_data (dict): Данные набора с объектами моделей
empty_values (list): Список значений, считаемых "пустыми"
Returns:
dict: Отформатированные данные для шаблона
"""
set_kit = set_data['setkit']
merchant_office = set_data['merchant_office']
merchant_brand = set_data['merchant_brand']
profile = set_data['profile']
glazing = set_data['glazing']
formatted = {
# Ключи ниже оставлены в legacy-формате, т.к. шаблон использует именно их имена.
'sSetName': set_kit.sSetName,
'sMerchantName': merchant_brand.sMerchantName,
'sMerchantDescription': merchant_brand.sMerchantDescription,
'fSetRating': {
'RATING': set_kit.fSetRating,
'STARS': get_rating_set_for_stars(set_kit.fSetRating)
},
'num_offers': set_data['num_offers'],
'price_avg': set_data['price_avg'],
'bSetDelivery': set_kit.bSetDelivery,
'bSetUninstallInstall': set_kit.bSetUninstallInstall,
'sSetImplementAll': set_kit.sSetImplementAll,
'sSetImplementHandles': set_kit.sSetImplementHandles,
'sMerchantMainURL': {
'URL': merchant_office.kMerchantName.sMerchantMainURL,
'URL_VIEW': re.sub(
r"^https?://|/$|www\.",
"",
merchant_office.kMerchantName.sMerchantMainURL
)
},
'sOfficePhones': merchant_office.sOfficePhones,
'sOfficeDescription': merchant_office.sOfficeDescription,
'sOfficeEmails': merchant_office.sOfficeEmails,
'sOfficeName': merchant_office.sOfficeName,
'sOfficeAddress': merchant_office.sOfficeAddress,
'fOfficeGeoCode_Latitude': merchant_office.fOfficeGeoCode_Latitude,
'fOfficeGeoCode_Longitude': merchant_office.fOfficeGeoCode_Longitude,
'sOfficeDiscountMetaFormula': merchant_office.sOfficeDiscountMetaFormula,
'pMerchantLogo': merchant_office.kMerchantName.pMerchantLogo,
'idPVC': profile.id,
'sProfileBriefDescription': profile.sProfileBriefDescription,
'iProfileCameras': profile.iProfileCameras,
'sProfileName': {
'NAME': profile.sProfileName,
'NAME_T': pytils.translit.slugify(profile.sProfileName)
},
'sProfileManufacturer': {
'NAME': profile.sProfileManufacturer,
'NAME_T': pytils.translit.slugify(profile.sProfileManufacturer)
},
'sProfileColor': profile.sProfileColor,
'sProfileSealDescription': profile.sProfileSealDescription,
'fProfileSeals': pytils.numeral.sum_string(
profile.fProfileSeals,
pytils.numeral.MALE,
"контур, контура, контуров"
),
'sGlazingBriefDescription': glazing.sGlazingBriefDescription,
'sGlazingManufacturer': glazing.sGlazingManufacturer,
'sGlazingMark': glazing.sGlazingMark,
'sGlazingToning': glazing.sGlazingToning,
'sSetImplementCatch': _clean_text_field(set_kit.sSetImplementCatch, empty_values),
'sSetClimateControl': _clean_text_field(set_kit.sSetClimateControl, empty_values),
'sProfileReinforcement': _lowercase_first_char(profile.sProfileReinforcement),
'sSetSill': _lowercase_first_char(set_kit.sSetSill),
'sSetPanes': _lowercase_first_char(set_kit.sSetPanes),
'sSetSlope': _lowercase_first_char(set_kit.sSetSlope),
'sSetUninstallInstall': _lowercase_first_char(set_kit.sSetUninstallInstall),
'sSetDelivery': _lowercase_first_char(set_kit.sSetDelivery),
'sSetOtherConditions': _lowercase_first_char(set_kit.sSetOtherConditions),
}
# Конвертируем даты в читаемый формат
if set_data['last_update']:
timestamp = int(django.utils.dateformat.format(set_data['last_update'], 'U'))
formatted['lastUpdate'] = pytils.dt.distance_of_time_in_words(timestamp)
if set_data['early_creation']:
timestamp = int(django.utils.dateformat.format(set_data['early_creation'],'U'))
formatted['earlyCreation'] = pytils.dt.distance_of_time_in_words(timestamp)
# Разделяем email на части для обфускации (показываем середину отдельно)
# На фронтенде JS собирает все обратно в валидный e-mail
if formatted['sOfficeEmails']:
try:
email_len = len(formatted['sOfficeEmails'])
k = random.randint(1, max(1, int(email_len / 2) - 1))
formatted['sOfficeEmails'] = [
formatted['sOfficeEmails'][0:k],
formatted['sOfficeEmails'][k:-k],
formatted['sOfficeEmails'][-k:]
]
except (ValueError, ZeroDivisionError):
# Если ошибка при случайном разделении, оставляем как есть
pass
return formatted
def catalog_company_detail(
request: HttpRequest,
company_id: str,
company_name_slug: str
) -> HttpResponse:
"""
Показывает детальную информацию о компании и все её оконные наборы.
Производит редирект если slug в URL не совпадает с актуальным.
GET параметры: опционально могут использоваться для фильтрации
Контекст шаблона:
- COMPANY (str): Название компании
- COMPANY_ID (int): ID компании
- COMPANY_T (str): Slug компании
- SETS (list): Список оконных наборов с их полной информацией
- IMG_FOR_BLOG (str): Логотип компании
- LIST_NOT (list): Стандартные маркеры "пусто"
- LAST_VISIT (list): Последние визиты текущего пользователя
- LOG_VISIT (list): Последние визиты всех пользователей
- ticks (float): Время выполнения представления (в секундах)
Args:
request (HttpRequest): HTTP запрос от клиента
company_id (str): ID компании в виде строки
company_name_slug (str): Slug названия компании из URL
Returns:
HttpResponse: Отрендеренная HTML страница с деталью компании или редирект
"""
time_start = time.perf_counter() time_start = time.perf_counter()
to_template: dict[str, object] = {} # словарь, для передачи шаблону company_id_int = int(company_id)
company_id = int(company_id)
q_by_id = MerchantBrand.objects.get(id=company_id) # Получаем компанию или возвращаем 404
if pytils.translit.slugify(q_by_id.sMerchantName) != company_name_slug: try:
return redirect('/catalog/company/%d-%s' % (company_id, pytils.translit.slugify(q_by_id.sMerchantName))) company = MerchantBrand.objects.get(id=company_id_int)
to_template.update({'COMPANY': q_by_id.sMerchantName}) except MerchantBrand.DoesNotExist:
to_template.update({'COMPANY_ID': company_id}) raise Http404("Компания не найдена")
to_template.update({'COMPANY_T': company_name_slug})
list_not = [u"нет", u"", ""] # Проверяем что slug совпадает (для SEO и красивых URL)
to_template.update({'LIST_NOT': list_not}) actual_slug = pytils.translit.slugify(company.sMerchantName)
q_sets = MerchantBrand.objects.raw(f"SELECT" if actual_slug != company_name_slug:
f" COUNT(oknardia_priceoffer.id) AS NumOffers," return redirect(
f" AVG(oknardia_priceoffer.fOfferPrice) AS priceAVG," f'/catalog/company/{company_id_int}-{actual_slug}'
f" MAX(oknardia_priceoffer.dOfferModify) AS lastUpdate," )
f" MIN(oknardia_priceoffer.dOfferCreate) AS earlyCreation,"
f" oknardia_merchantbrand.*," # Типичные маркеры, которые означают что поле пусто
f" oknardia_merchantoffice.*," empty_values = ["нет", "", ""]
f" oknardia_merchantoffice.id AS idMERCH,"
f" oknardia_setkit.*," # Получаем все наборы компании с ценовой статистикой
f" oknardia_setkit.id AS idSET," sets_list = _get_company_sets_detail(company_id_int)
f" oknardia_pvcprofiles.*,"
f" oknardia_pvcprofiles.id AS idPVC," # Форматируем каждый набор для вывода в шаблон
f" oknardia_glazing.*, " formatted_sets = [
f" oknardia_glazing.id AS idGLAZ " _format_set_for_template(set_data, empty_values)
f"FROM oknardia_ouruser" for set_data in sets_list
f" INNER JOIN oknardia_merchantoffice" ]
f" ON oknardia_ouruser.kMerchantOffice_id = oknardia_merchantoffice.id"
f" INNER JOIN oknardia_merchantbrand" to_template: dict[str, object] = {
f" ON oknardia_merchantoffice.kMerchantName_id = oknardia_merchantbrand.id" 'COMPANY': company.sMerchantName,
f" INNER JOIN oknardia_setkit" 'COMPANY_ID': company_id_int,
f" ON oknardia_setkit.kSet2User_id = oknardia_ouruser.id" 'COMPANY_T': company_name_slug,
f" INNER JOIN oknardia_priceoffer" 'SETS': formatted_sets,
f" ON oknardia_priceoffer.kOffer2SetKit_id = oknardia_setkit.id" 'HEADER': f'Изготовитель окон «{company.sMerchantName}»',
f" INNER JOIN oknardia_pvcprofiles" 'META_KEYWORDS': company.sMerchantName,
f" ON oknardia_setkit.kSet2PVCprofiles_id = oknardia_pvcprofiles.id" 'IMG_FOR_BLOG': company.pMerchantLogo,
f" INNER JOIN oknardia_glazing" 'LIST_NOT': empty_values,
f" ON oknardia_setkit.kSet2Glazing_id = oknardia_glazing.id " 'LAST_VISIT': get_last_user_visit_list(
f"WHERE oknardia_merchantbrand.id = {company_id} " get_last_user_visit_cookies(request)[:3]
f"AND oknardia_priceoffer.sOfferActive = TRUE " ),
f"GROUP BY oknardia_merchantoffice.id,"
f" oknardia_setkit.id,"
f" oknardia_setkit.fSetRating "
f"ORDER BY oknardia_setkit.fSetRating DESC;")
list_sets = list(q_sets)
for i in list_sets:
i.sMerchantMainURL = {"URL": i.sMerchantMainURL,
"URL_VIEW": re.sub(r"(?:^http://|^https://|/$|www\.)", "", i.sMerchantMainURL)}
k = random.randint(1, int(len(i.sOfficeEmails)/2) - 1)
i.sOfficeEmails = [i.sOfficeEmails[0:k], i.sOfficeEmails[k:-k], i.sOfficeEmails[-k:]]
to_template.update({'IMG_FOR_BLOG': i.pMerchantLogo})
i.fSetRating = {"RATING": i.fSetRating,
"STARS": get_rating_set_for_stars(i.fSetRating)}
i.lastUpdate = pytils.dt.distance_of_time_in_words(int(django.utils.dateformat.format(i.lastUpdate, 'U')))
i.earlyCreation = pytils.dt.distance_of_time_in_words(int(django.utils.dateformat.format(i.earlyCreation, 'U')))
i.sProfileName = {"NAME": i.sProfileName,
"NAME_T": pytils.translit.slugify(i.sProfileName)}
i.sProfileManufacturer = {"NAME": i.sProfileManufacturer,
"NAME_T": pytils.translit.slugify(i.sProfileManufacturer)}
i.fProfileSeals = pytils.numeral.sum_string(i.fProfileSeals, pytils.numeral.MALE, u"контур, контура, контуров")
if i.sSetImplementCatch.lower() in list_not:
i.sSetImplementCatch = ""
if i.sSetClimateControl.lower() in list_not:
i.sSetClimateControl = ""
if len(i.sProfileReinforcement) > 0:
i.sProfileReinforcement = i.sProfileReinforcement[0].lower()+i.sProfileReinforcement[1:]
if len(i.sSetSill) > 0:
i.sSetSill = i.sSetSill[0].lower()+i.sSetSill[1:]
if len(i.sSetPanes) > 0:
i.sSetPanes = i.sSetPanes[0].lower()+i.sSetPanes[1:]
if len(i.sSetSlope) > 0:
i.sSetSlope = i.sSetSlope[0].lower()+i.sSetSlope[1:]
if len(i.sSetUninstallInstall) > 0:
i.sSetUninstallInstall = i.sSetUninstallInstall[0].lower()+i.sSetUninstallInstall[1:]
if len(i.sSetDelivery) > 0:
i.sSetDelivery = i.sSetDelivery[0].lower()+i.sSetDelivery[1:]
if len(i.sSetOtherConditions) > 0:
i.sSetOtherConditions = i.sSetOtherConditions[0].lower()+i.sSetOtherConditions[1:]
to_template.update({
'SETS': list_sets,
# получаем последние визиты клиента через куки
'LAST_VISIT': get_last_user_visit_list(get_last_user_visit_cookies(request)[:3]),
# получаем последние визиты всех посетителей из базы
# id2log, log_visit = get_last_all_user_visit_list()
'LOG_VISIT': get_last_all_user_visit_list(), 'LOG_VISIT': get_last_all_user_visit_list(),
'ticks': float(time.perf_counter() - time_start) }
})
# Добавляем метрику выполнения представления
to_template['ticks'] = float(time.perf_counter() - time_start)
return render(request, "catalog/catalog_company_detail.html", to_template) return render(request, "catalog/catalog_company_detail.html", to_template)