mod: рефакторинг "каталога компаний" (вьюшки и шаблоны)
This commit is contained in:
@@ -1,58 +1,81 @@
|
||||
{% extends "base.html" %}
|
||||
{% load static %}{% load filters %}
|
||||
|
||||
{% block Title %}Каталог изготовителей и поставщиков окон{% endblock %}
|
||||
{% block Title %}Каталог оконных компаний: производители и поставщики окон, рейтинг и цены{% 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 Author4Meta %}: Каталог изготовителей окон{% endblock %}
|
||||
|
||||
{% block CopyrightAuthor4Meta %}: Каталог изготовителей окон{% endblock %}
|
||||
{% block CopyrightAuthor4Meta %}: Каталог «Окнардия»{% endblock %}
|
||||
|
||||
{% block Top_Meta1 %}{# <!-- BEGIN Дополнительные Metatags --> #}
|
||||
<meta itemprop="author" content="Каталог «Окнардия»" />{% if IMG_FOR_BLOG %}
|
||||
<meta itemprop="image" content="https://oknardia.ru/media/{{ IMG_FOR_BLOG }}" />{% else %}
|
||||
<meta itemprop="image" content="https://oknardia.ru/static/img/MerDY3gpU0w.jpg" />{% endif %}
|
||||
<meta itemprop="datePublished" content="{% if PUB_DAT %}{{ PUB_DAT|date:"c" }}{% else %}{% now "c" %}{% endif %}" />
|
||||
<span itemprop="publisher" itemscope itemtype="http://schema.org/Organization"><meta itemprop="name" content="Каталог «Окнардия»" /></span>
|
||||
<span itemprop="author" itemscope itemtype="http://schema.org/Person"><meta itemprop="name" content="Каталог «Окнардия»" /></span>
|
||||
<meta itemprop="articleSection" content="Каталог производителей окон" />
|
||||
<meta itemprop="headline" content="Компании-партнёры «Окнардии», их рейтинг, число оконных наборов и вариантов расчёта цен для типовых проёмов, средняя цена окна..." />
|
||||
<meta name="news_keywords" content="{{ HEADER }}" />
|
||||
<link rel="canonical" href="https://oknardia.ru/catalog/company/" />
|
||||
<link rel="standout" href="https://oknardia.ru/catalog/company/" />
|
||||
<!-- Разметка для соц-сетей Facebook Open Graph -->
|
||||
<meta property="fb:admins" name="admins" content="100000084781830" />
|
||||
<meta property="fb:pages" content="276108456054163" />
|
||||
<meta property="fb:app_id" content="258354027974262" />
|
||||
<meta property="fb:profile_id" name="profile_id" content="https://www.facebook.com/oknardia/" />
|
||||
<meta property="og:locale" content="ru_RU" />
|
||||
<meta property="og:site_name" content="oknardia.ru" />
|
||||
<meta property="og:url" content="https://oknardia.ru//catalog/company/" />
|
||||
<meta property="og:url" content="https://oknardia.ru/catalog/company/" />
|
||||
<meta property="og:type" content="article" />
|
||||
<meta property="og:title" content="Каталог производителей окон | oknardia.ru" />
|
||||
<meta property="og:description" content="Компании-партнеры «Окнардии», их средний рейтинг, число оконных наборов и вариантов расчета цен для типовых проёмов, средняя цена окна..." />
|
||||
<meta property="og:image" content="{% if IMG_FOR_BLOG %}https://oknardia.ru/media/{% endif %}{{ IMG_FOR_BLOG|default:'https://oknardia.ru/static/img/MerDY3gpU0w.jpg' }}" />
|
||||
<link rel="image_src" href="{% if IMG_FOR_BLOG %}https://oknardia.ru/media/{% endif %}{{ IMG_FOR_BLOG|default:'https://oknardia.ru/static/img/MerDY3gpU0w.jpg' }}" />
|
||||
<meta property="og:title" content="Каталог оконных компаний: производители и поставщики окон, рейтинг и цены | oknardia.ru" />
|
||||
<meta property="og:description" content="Актуальный каталог оконных компаний России. Сравните производителей и поставщиков пластиковых окон по рейтингу, ассортименту, средней цене и дате последнего обновления." />
|
||||
<meta property="og:image" content="https://oknardia.ru/static/img/MerDY3gpU0w.jpg" />
|
||||
<link rel="image_src" href="https://oknardia.ru/static/img/MerDY3gpU0w.jpg" />
|
||||
<!-- Разметка для соц-сетей Twitter Card -->
|
||||
<meta name="twitter:title" content="Каталог производителей окон | oknardia.ru" />
|
||||
<meta name="twitter:description" content="Компании-партнеры «Окнардии», их средний рейтинг, число оконных наборов и вариантов расчета цен для типовых проёмов, средняя цена окна..." />
|
||||
<meta name="twitter:title" content="Каталог оконных компаний: производители и поставщики окон, рейтинг и цены | oknardia.ru" />
|
||||
<meta name="twitter:description" content="Актуальный каталог оконных компаний России. Сравните производителей и поставщиков пластиковых окон по рейтингу, ассортименту, средней цене и дате последнего обновления." />
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="twitter:site" content="@oknardia" />
|
||||
<meta name="twitter:domain" content="oknardia.ru" />
|
||||
<meta property="twitter:url" content="{% if IMG_FOR_BLOG %}https://oknardia.ru/media/{% endif %}{{ IMG_FOR_BLOG|default:'https://oknardia.ru/static/img/MerDY3gpU0w.jpg' }}" />
|
||||
<meta name="relap-image" content="{% if IMG_FOR_BLOG %}https://oknardia.ru/media/{% endif %}{{ IMG_FOR_BLOG|default:'https://oknardia.ru/static/img/MerDY3gpU0w.jpg' }}">
|
||||
{# Удалить: <meta name="twitter:domain"> — устаревший тег #}
|
||||
<meta name="twitter:image" content="https://oknardia.ru/static/img/MerDY3gpU0w.jpg" />
|
||||
<meta name="relap-image" content="https://oknardia.ru/static/img/MerDY3gpU0w.jpg">
|
||||
{# <!-- 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 %}
|
||||
<div class="container-fluid">
|
||||
{# <!--- Хлебные крошки: НАЧАЛО --> #}<div class="row">
|
||||
@@ -90,7 +113,3 @@
|
||||
{% include "report/report_log_user_visit.html" %}
|
||||
</div>
|
||||
</div>{% endblock %}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -5,30 +5,20 @@
|
||||
|
||||
{% 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 --> #}
|
||||
<meta itemprop="author" content="Каталог «Окнардия»" />{% if IMG_FOR_BLOG %}
|
||||
<meta itemprop="image" content="https://oknardia.ru/media/{{ IMG_FOR_BLOG }}" />{% else %}
|
||||
<meta itemprop="image" content="https://oknardia.ru/static/img/MerDY3gpU0w.jpg" />{% endif %}
|
||||
<meta itemprop="datePublished" content="{% if PUB_DAT %}{{ PUB_DAT|date:"c" }}{% else %}{% now "c" %}{% endif %}" />
|
||||
<span itemprop="publisher" itemscope itemtype="http://schema.org/Organization"><meta itemprop="name" content="Каталог «Окнардия»" /></span>
|
||||
<span itemprop="author" itemscope itemtype="http://schema.org/Person"><meta itemprop="name" content="Каталог «Окнардия»" /></span>
|
||||
<meta itemprop="articleSection" content="Каталог производителей окон" />
|
||||
<meta itemprop="headline" content="Изготовитель окон «{{ COMPANY }}», описание, производимые им оконные наборы и их состав, характеристики, рейтинг, средние цены и отклонение цен." />
|
||||
{# Microdata (itemprop) убрана — заменена на JSON-LD в блоке ADD_TO_HEAD ниже (чище, надёжнее) #}
|
||||
<meta name="news_keywords" content="{{ HEADER|striptags }}" />
|
||||
<link rel="canonical" href="https://oknardia.ru/catalog/company/{{ COMPANY_ID }}-{{ COMPANY_T }}" />
|
||||
<link rel="standout" href="https://oknardia.ru/catalog/company/{{ COMPANY_ID }}-{{ COMPANY_T }}" />
|
||||
{# Удалить: <link rel="standout"> — тег Google News 2011 г., отменён в 2014, поисковики игнорируют #}
|
||||
<!-- Разметка для соц-сетей Facebook Open Graph -->
|
||||
<meta property="fb:admins" name="admins" content="100000084781830" />
|
||||
<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="og:locale" content="ru_RU" />
|
||||
<meta property="og:site_name" content="oknardia.ru" />
|
||||
<meta property="og:url" content="https://oknardia.ru//catalog/company/{{ COMPANY_ID }}-{{ COMPANY_T }}" />
|
||||
<meta property="og:url" content="https://oknardia.ru/catalog/company/{{ COMPANY_ID }}-{{ COMPANY_T }}" />
|
||||
<meta property="og:type" content="article" />
|
||||
<meta property="og:title" content="Окна «{{ COMPANY }}» | oknardia.ru" />
|
||||
<meta property="og:description" content="Окна «{{ COMPANY }}», описание окон «{{ COMPANY }}», производимые им оконные наборы и их состав, характеристики, рейтинг, средние цены и отклонение цен." />
|
||||
<meta property="og: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' }}" />
|
||||
<link rel="image_src" href="{% if IMG_FOR_BLOG %}https://oknardia.ru/media/{% endif %}{{ IMG_FOR_BLOG|default:'https://oknardia.ru/static/img/MerDY3gpU0w.jpg' }}" />
|
||||
<!-- Разметка для соц-сетей Twitter Card -->
|
||||
<meta name="twitter:title" content="Производителей окон «{{ COMPANY }}» | oknardia.ru" />
|
||||
<meta name="twitter:description" content="Изготовитель окон «{{ COMPANY }}», описание, производимые им оконные наборы и их состав, характеристики, рейтинг, средние цены и отклонение цен." />
|
||||
<meta name="twitter:title" content="Производитель окон «{{ COMPANY }}» | oknardia.ru" />
|
||||
<meta name="twitter:description" content="«{{ COMPANY }}» в каталоге Окнардии: наборы, характеристики, рейтинг и цены на установку окон в типовых домах." />
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="twitter:site" content="@oknardia" />
|
||||
<meta name="twitter:domain" content="oknardia.ru" />
|
||||
{# Удалить: <meta 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 name="relap-image" content="{% if IMG_FOR_BLOG %}https://oknardia.ru/media/{% endif %}{{ IMG_FOR_BLOG|default:'https://oknardia.ru/static/img/MerDY3gpU0w.jpg' }}">
|
||||
{# <!-- END Дополнительные Metatags --> #}{% endblock %}
|
||||
|
||||
{% block Top_JS5 %}
|
||||
<script src="https://api-maps.yandex.ru/2.1/?lang=ru_RU" type="text/javascript"></script>{% endblock %}
|
||||
|
||||
{% block 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 %}
|
||||
|
||||
@@ -1,11 +1,27 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Каталог производителей и компаний.
|
||||
|
||||
Модуль предоставляет views для отображения:
|
||||
1. Списка всех производителей с их ключевыми показателями (рейтинг, количество
|
||||
предложений, среднюю цену и т.п.)
|
||||
2. Детальную информацию о конкретном производителе со всеми его оконными наборами
|
||||
|
||||
Все запросы переведены на Django ORM для лучшей производительности и чистоты кода.
|
||||
"""
|
||||
from django.shortcuts import render, redirect
|
||||
from django.http import HttpRequest, HttpResponse
|
||||
from django.utils import timezone
|
||||
from django.http import HttpRequest, HttpResponse, Http404
|
||||
from django.db.models import Count, Avg, Max, Min, DecimalField
|
||||
from oknardia.models import (
|
||||
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
|
||||
import django.utils.dateformat
|
||||
import time
|
||||
@@ -14,148 +30,500 @@ import re
|
||||
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:
|
||||
time_start = time.perf_counter()
|
||||
to_template: dict[str, object] = {} # словарь, для передачи шаблону
|
||||
q_company = MerchantBrand.objects.raw('SELECT'
|
||||
' oknardia_merchantbrand.id,'
|
||||
' oknardia_merchantbrand.sMerchantName,'
|
||||
' oknardia_merchantbrand.pMerchantLogo,'
|
||||
' oknardia_merchantbrand.sMerchantMainURL,'
|
||||
' COUNT(oknardia_priceoffer.id) AS NumOffers,'
|
||||
' AVG(oknardia_priceoffer.fOfferPrice) AS PriceAVG,'
|
||||
' MAX(oknardia_priceoffer.dOfferModify) AS lastUpdate,'
|
||||
' Q.NumSets,'
|
||||
' Q.RatingAVG,'
|
||||
' 1 AS STARS '
|
||||
'FROM (SELECT'
|
||||
' COUNT(oknardia_setkit.sSetName) AS NumSets,'
|
||||
' oknardia_merchantoffice.kMerchantName_id AS Q_ID,'
|
||||
' AVG(oknardia_setkit.fSetRating) AS RatingAVG'
|
||||
' FROM oknardia_merchantoffice'
|
||||
' INNER JOIN oknardia_ouruser'
|
||||
' ON oknardia_ouruser.kMerchantOffice_id = oknardia_merchantoffice.id'
|
||||
' INNER JOIN oknardia_setkit'
|
||||
' ON oknardia_setkit.kSet2User_id = oknardia_ouruser.id'
|
||||
' GROUP BY oknardia_merchantoffice.id,'
|
||||
' oknardia_merchantoffice.kMerchantName_id) AS Q,'
|
||||
' oknardia_ouruser'
|
||||
' INNER JOIN oknardia_merchantoffice'
|
||||
' ON oknardia_ouruser.kMerchantOffice_id = oknardia_merchantoffice.id'
|
||||
' INNER JOIN oknardia_priceoffer'
|
||||
' ON oknardia_priceoffer.kOfferFromUser_id = oknardia_ouruser.id'
|
||||
' INNER JOIN oknardia_merchantbrand'
|
||||
' ON oknardia_merchantoffice.kMerchantName_id = oknardia_merchantbrand.id'
|
||||
' WHERE Q_ID = oknardia_merchantoffice.kMerchantName_id '
|
||||
'GROUP BY oknardia_merchantoffice.kMerchantName_id '
|
||||
'ORDER BY Q.RatingAVG DESC;')
|
||||
list_company = list(q_company)
|
||||
for i in list_company:
|
||||
i.STARS = get_rating_set_for_stars(i.RatingAVG)
|
||||
i.NumSets = pytils.numeral.get_plural(i.NumSets, u"оконный набор, оконных набора, оконных наборов")
|
||||
i.NumOffers = pytils.numeral.get_plural(i.NumOffers, u"вариант, варианта, вариантов")
|
||||
i.lastUpdate = pytils.dt.distance_of_time_in_words(int(django.utils.dateformat.format(i.lastUpdate, 'U')))
|
||||
i.sMerchantMainURL = pytils.translit.slugify(i.sMerchantName)
|
||||
# print("NAME:", i.sMerchantName, "\tNumSets:", i.NumSets, "\tNumOffers:", i.NumOffers,
|
||||
# "\t:AverageRating:", i.RatingAVG, "\tAveragePrice:", i.PriceAVG, "\tSTARS:", i.STARS)
|
||||
to_template.update({
|
||||
'COMPANIES': list_company,
|
||||
# получаем последние визиты клиента через куки
|
||||
'LAST_VISIT': get_last_user_visit_list(get_last_user_visit_cookies(request)[:3]),
|
||||
# получаем последние визиты всех посетителей из базы
|
||||
# id2log, log_visit = get_last_all_user_visit_list()
|
||||
"""
|
||||
Показывает список всех производителей с ключевыми показателями.
|
||||
|
||||
GET параметры: опционально могут использоваться для фильтрации
|
||||
|
||||
Контекст шаблона:
|
||||
- COMPANIES (list): Список компаний с статистикой
|
||||
- LAST_VISIT (list): Последние визиты текущего пользователя
|
||||
- LOG_VISIT (list): Последние визиты всех пользователей
|
||||
|
||||
Args:
|
||||
request (HttpRequest): HTTP запрос от клиента
|
||||
|
||||
Returns:
|
||||
HttpResponse: Отрендеренная HTML страница со списком компаний
|
||||
"""
|
||||
# Получаем статистику по компаниям с использованием ORM
|
||||
companies_list = _get_company_statistics()
|
||||
|
||||
# Форматируем каждую компанию для вывода в шаблон
|
||||
formatted_companies = [
|
||||
_format_company_for_template(company)
|
||||
for company in companies_list
|
||||
]
|
||||
|
||||
# Получаем информацию о посещениях для персонализации
|
||||
to_template: dict[str, object] = {
|
||||
'COMPANIES': formatted_companies,
|
||||
'LAST_VISIT': get_last_user_visit_list(
|
||||
get_last_user_visit_cookies(request)[:3]
|
||||
),
|
||||
'LOG_VISIT': get_last_all_user_visit_list(),
|
||||
'ticks': float(time.perf_counter() - time_start)
|
||||
})
|
||||
}
|
||||
|
||||
return render(request, "catalog/catalog_company.html", to_template)
|
||||
|
||||
|
||||
def catalog_company_detail(request: HttpRequest, company_id: str, company_name_slug: str) -> HttpResponse:
|
||||
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()
|
||||
to_template: dict[str, object] = {} # словарь, для передачи шаблону
|
||||
company_id = int(company_id)
|
||||
q_by_id = MerchantBrand.objects.get(id=company_id)
|
||||
if pytils.translit.slugify(q_by_id.sMerchantName) != company_name_slug:
|
||||
return redirect('/catalog/company/%d-%s' % (company_id, pytils.translit.slugify(q_by_id.sMerchantName)))
|
||||
to_template.update({'COMPANY': q_by_id.sMerchantName})
|
||||
to_template.update({'COMPANY_ID': company_id})
|
||||
to_template.update({'COMPANY_T': company_name_slug})
|
||||
list_not = [u"нет", u"—", ""]
|
||||
to_template.update({'LIST_NOT': list_not})
|
||||
q_sets = MerchantBrand.objects.raw(f"SELECT"
|
||||
f" COUNT(oknardia_priceoffer.id) AS NumOffers,"
|
||||
f" AVG(oknardia_priceoffer.fOfferPrice) AS priceAVG,"
|
||||
f" MAX(oknardia_priceoffer.dOfferModify) AS lastUpdate,"
|
||||
f" MIN(oknardia_priceoffer.dOfferCreate) AS earlyCreation,"
|
||||
f" oknardia_merchantbrand.*,"
|
||||
f" oknardia_merchantoffice.*,"
|
||||
f" oknardia_merchantoffice.id AS idMERCH,"
|
||||
f" oknardia_setkit.*,"
|
||||
f" oknardia_setkit.id AS idSET,"
|
||||
f" oknardia_pvcprofiles.*,"
|
||||
f" oknardia_pvcprofiles.id AS idPVC,"
|
||||
f" oknardia_glazing.*, "
|
||||
f" oknardia_glazing.id AS idGLAZ "
|
||||
f"FROM oknardia_ouruser"
|
||||
f" INNER JOIN oknardia_merchantoffice"
|
||||
f" ON oknardia_ouruser.kMerchantOffice_id = oknardia_merchantoffice.id"
|
||||
f" INNER JOIN oknardia_merchantbrand"
|
||||
f" ON oknardia_merchantoffice.kMerchantName_id = oknardia_merchantbrand.id"
|
||||
f" INNER JOIN oknardia_setkit"
|
||||
f" ON oknardia_setkit.kSet2User_id = oknardia_ouruser.id"
|
||||
f" INNER JOIN oknardia_priceoffer"
|
||||
f" ON oknardia_priceoffer.kOffer2SetKit_id = oknardia_setkit.id"
|
||||
f" INNER JOIN oknardia_pvcprofiles"
|
||||
f" ON oknardia_setkit.kSet2PVCprofiles_id = oknardia_pvcprofiles.id"
|
||||
f" INNER JOIN oknardia_glazing"
|
||||
f" ON oknardia_setkit.kSet2Glazing_id = oknardia_glazing.id "
|
||||
f"WHERE oknardia_merchantbrand.id = {company_id} "
|
||||
f"AND oknardia_priceoffer.sOfferActive = TRUE "
|
||||
f"GROUP BY oknardia_merchantoffice.id,"
|
||||
f" oknardia_setkit.id,"
|
||||
f" oknardia_setkit.fSetRating "
|
||||
f"ORDER BY oknardia_setkit.fSetRating DESC;")
|
||||
list_sets = list(q_sets)
|
||||
for i in list_sets:
|
||||
i.sMerchantMainURL = {"URL": i.sMerchantMainURL,
|
||||
"URL_VIEW": re.sub(r"(?:^http://|^https://|/$|www\.)", "", i.sMerchantMainURL)}
|
||||
k = random.randint(1, int(len(i.sOfficeEmails)/2) - 1)
|
||||
i.sOfficeEmails = [i.sOfficeEmails[0:k], i.sOfficeEmails[k:-k], i.sOfficeEmails[-k:]]
|
||||
to_template.update({'IMG_FOR_BLOG': i.pMerchantLogo})
|
||||
i.fSetRating = {"RATING": i.fSetRating,
|
||||
"STARS": get_rating_set_for_stars(i.fSetRating)}
|
||||
i.lastUpdate = pytils.dt.distance_of_time_in_words(int(django.utils.dateformat.format(i.lastUpdate, 'U')))
|
||||
i.earlyCreation = pytils.dt.distance_of_time_in_words(int(django.utils.dateformat.format(i.earlyCreation, 'U')))
|
||||
i.sProfileName = {"NAME": i.sProfileName,
|
||||
"NAME_T": pytils.translit.slugify(i.sProfileName)}
|
||||
i.sProfileManufacturer = {"NAME": i.sProfileManufacturer,
|
||||
"NAME_T": pytils.translit.slugify(i.sProfileManufacturer)}
|
||||
i.fProfileSeals = pytils.numeral.sum_string(i.fProfileSeals, pytils.numeral.MALE, u"контур, контура, контуров")
|
||||
if i.sSetImplementCatch.lower() in list_not:
|
||||
i.sSetImplementCatch = ""
|
||||
if i.sSetClimateControl.lower() in list_not:
|
||||
i.sSetClimateControl = ""
|
||||
if len(i.sProfileReinforcement) > 0:
|
||||
i.sProfileReinforcement = i.sProfileReinforcement[0].lower()+i.sProfileReinforcement[1:]
|
||||
if len(i.sSetSill) > 0:
|
||||
i.sSetSill = i.sSetSill[0].lower()+i.sSetSill[1:]
|
||||
if len(i.sSetPanes) > 0:
|
||||
i.sSetPanes = i.sSetPanes[0].lower()+i.sSetPanes[1:]
|
||||
if len(i.sSetSlope) > 0:
|
||||
i.sSetSlope = i.sSetSlope[0].lower()+i.sSetSlope[1:]
|
||||
if len(i.sSetUninstallInstall) > 0:
|
||||
i.sSetUninstallInstall = i.sSetUninstallInstall[0].lower()+i.sSetUninstallInstall[1:]
|
||||
if len(i.sSetDelivery) > 0:
|
||||
i.sSetDelivery = i.sSetDelivery[0].lower()+i.sSetDelivery[1:]
|
||||
if len(i.sSetOtherConditions) > 0:
|
||||
i.sSetOtherConditions = i.sSetOtherConditions[0].lower()+i.sSetOtherConditions[1:]
|
||||
to_template.update({
|
||||
'SETS': list_sets,
|
||||
# получаем последние визиты клиента через куки
|
||||
'LAST_VISIT': get_last_user_visit_list(get_last_user_visit_cookies(request)[:3]),
|
||||
# получаем последние визиты всех посетителей из базы
|
||||
# id2log, log_visit = get_last_all_user_visit_list()
|
||||
company_id_int = int(company_id)
|
||||
|
||||
# Получаем компанию или возвращаем 404
|
||||
try:
|
||||
company = MerchantBrand.objects.get(id=company_id_int)
|
||||
except MerchantBrand.DoesNotExist:
|
||||
raise Http404("Компания не найдена")
|
||||
|
||||
# Проверяем что slug совпадает (для SEO и красивых URL)
|
||||
actual_slug = pytils.translit.slugify(company.sMerchantName)
|
||||
if actual_slug != company_name_slug:
|
||||
return redirect(
|
||||
f'/catalog/company/{company_id_int}-{actual_slug}'
|
||||
)
|
||||
|
||||
# Типичные маркеры, которые означают что поле пусто
|
||||
empty_values = ["нет", "—", ""]
|
||||
|
||||
# Получаем все наборы компании с ценовой статистикой
|
||||
sets_list = _get_company_sets_detail(company_id_int)
|
||||
|
||||
# Форматируем каждый набор для вывода в шаблон
|
||||
formatted_sets = [
|
||||
_format_set_for_template(set_data, empty_values)
|
||||
for set_data in sets_list
|
||||
]
|
||||
|
||||
to_template: dict[str, object] = {
|
||||
'COMPANY': company.sMerchantName,
|
||||
'COMPANY_ID': company_id_int,
|
||||
'COMPANY_T': company_name_slug,
|
||||
'SETS': formatted_sets,
|
||||
'HEADER': f'Изготовитель окон «{company.sMerchantName}»',
|
||||
'META_KEYWORDS': company.sMerchantName,
|
||||
'IMG_FOR_BLOG': company.pMerchantLogo,
|
||||
'LIST_NOT': empty_values,
|
||||
'LAST_VISIT': get_last_user_visit_list(
|
||||
get_last_user_visit_cookies(request)[:3]
|
||||
),
|
||||
'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)
|
||||
|
||||
Reference in New Issue
Block a user