mod: рефакторинг "каталога компаний" (вьюшки и шаблоны)
This commit is contained in:
@@ -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