# -*- coding: utf-8 -*- from django.core.exceptions import ObjectDoesNotExist from django.db.models import Count from django.shortcuts import render, redirect from django.http import HttpRequest, HttpResponse from django.utils import timezone from oknardia.models import Win_MountDim, PriceOffer, Apartment_Type, Seria_Info, LogVisitPriceReport, MountDim2Apartment from oknardia.settings import * from web.report1 import get_last_all_user_visit_list, get_last_user_visit_cookies, get_last_user_visit_list from web.add_func import normalize, get_rating_set_for_stars, get_flaps_for_big_pictures, get_flaps_for_mini_pictures, \ get_geo_distance import django.utils.dateformat import time import os import re import json from types import SimpleNamespace import pytils def _one_win_price_canonical_path(win_width_mm: int | str, win_height_mm: int | str, win_id: int | str) -> str: """Возвращает канонический путь страницы цен для одного типового окна.""" return f"/catalog/standard_opening/price-{int(win_width_mm)}x{int(win_height_mm)}mm-tip{int(win_id)}/" def redirect_one_win_price_legacy(request: HttpRequest, win_width_mm: str | int = DEFAULT_WIN_WIDTH_MM, win_height_mm: str | int = DEFAULT_WIN_HEIGHT_MM, win_id: str | int = DEFAULT_WIN_ID) -> HttpResponse: """301-редирект со старого URL /tsena-odnogo-okna/... на канонический URL.""" return redirect( _one_win_price_canonical_path(win_width_mm=win_width_mm, win_height_mm=win_height_mm, win_id=win_id), permanent=True, ) def report_price_frame(apartment_id: int, mount_dim_per_offer: int, address_longitude: float, address_latitude: float, frame_begin_n: int = 0, brand_id: int = 0, win_id: int = 0) -> dict: """ Формируем выдачу цен для фрейма :param apartment_id: int -- ID типа квартиры, для которой получаем ценовые предложения :param mount_dim_per_offer: int -- число различных оконых проемов в этой квартире (чтобы отсеять предложения, в которых не представлены все проемы) :param address_longitude: float -- долгота адреса (геокоордината), чтобы рассчитать удаленность компании предоставившей коммерческие предложения :param address_latitude: float -- широта адреса (геокоордината), чтобы рассчитать удаленность компании предоставившей коммерческие предложения :param frame_begin_n: int -- Номер записи с которой начинается фрейм с ценами (с какого предложения начинать) :param brand_id: int -- ID бренда, если выбран бренд, то отображаем только предложения этого бренда (нужно для виджета, где отображаются предложения только от одной компании) если 0, то отображаем все предложения :param win_id: int -- ID окна, если выбрано окно, то отображаем только предложения этого окна :return: dict -- словарь данных для отображения в фрейме (цены предложений и их характеристики) """ # ценовая выдача time_for_meta = 0 # время для мета-данных в HTML-кода apartment_id = int(apartment_id) mount_dim_per_offer = int(mount_dim_per_offer) address_longitude = float(address_longitude) address_latitude = float(address_latitude) frame_begin_n = int(frame_begin_n) brand_id = int(brand_id) win_id = int(win_id) add_to_sql_for_widget = "" offer_per_frame = OFFER_PER_FRAME if brand_id != 0: # Это вывод для выджета. Нужны цены только по определенному поставщику add_to_sql_for_widget = f" AND oknardia_merchantbrand.id = {brand_id} " offer_per_frame = 1000 # Фреймовый вывод не нужен... фигачим сразу целую 1000 предложений. if int(apartment_id) == 0 and int(win_id) != 0: # ORM-ветка для одиночного типового окна. # Здесь можно полностью уйти от raw SQL, потому что все связи линейные и хорошо покрываются select_related. # Контракт ответа сохраняем прежним: META_DATA_PUBLISH, PRICE_FRAME, N и все вложенные ключи оферов. offer_per_frame = OFFER_PER_FRAME_FOR_ONE_FLAP if brand_id != 0: offer_per_frame = 1000 q_price_offer = ( PriceOffer.objects.filter( sOfferActive=True, kOffer2MountDim_id=win_id, kOffer2SetKit__sSetActive=True, kOffer2SetKit__kSet2User__kMerchantOffice__isnull=False, kOffer2SetKit__kSet2User__kMerchantOffice__kMerchantName__isnull=False, kOffer2SetKit__kSet2Glazing__isnull=False, kOffer2SetKit__kSet2PVCprofiles__isnull=False, ) .values( 'id', 'fOfferPrice', 'dOfferModify', 'sOfferFlapConfig', 'kOffer2MountDim__sDescripion', 'kOffer2MountDim__iWinWidth', 'kOffer2MountDim__iWinHight', 'kOffer2SetKit__id', 'kOffer2SetKit__sSetName', 'kOffer2SetKit__dSetModify', 'kOffer2SetKit__dSetCommercialUntil', 'kOffer2SetKit__sSetClimateControl', 'kOffer2SetKit__sSetSill', 'kOffer2SetKit__sSetImplementAll', 'kOffer2SetKit__sSetImplementHandles', 'kOffer2SetKit__sSetImplementHinges', 'kOffer2SetKit__sSetImplementLatch', 'kOffer2SetKit__sSetImplementLimiter', 'kOffer2SetKit__sSetImplementCatch', 'kOffer2SetKit__sSetPanes', 'kOffer2SetKit__sSetSlope', 'kOffer2SetKit__sSetOtherConditions', 'kOffer2SetKit__sSetDelivery', 'kOffer2SetKit__bSetDelivery', 'kOffer2SetKit__sSetUninstallInstall', 'kOffer2SetKit__bSetUninstallInstall', 'kOffer2SetKit__fSetRating', 'kOffer2SetKit__kSet2User__kMerchantOffice__sOfficePhones', 'kOffer2SetKit__kSet2User__kMerchantOffice__sOfficeDiscountMetaFormula', 'kOffer2SetKit__kSet2User__kMerchantOffice__sOfficeName', 'kOffer2SetKit__kSet2User__kMerchantOffice__sOfficeAddress', 'kOffer2SetKit__kSet2Glazing__sGlazingBriefDescription', 'kOffer2SetKit__kSet2Glazing__sGlazingMark', 'kOffer2SetKit__kSet2Glazing__sGlazingToning', 'kOffer2SetKit__kSet2PVCprofiles__id', 'kOffer2SetKit__kSet2PVCprofiles__sProfileName', 'kOffer2SetKit__kSet2PVCprofiles__sProfileManufacturer', 'kOffer2SetKit__kSet2PVCprofiles__sProfileSealDescription', 'kOffer2SetKit__kSet2User__kMerchantOffice__kMerchantName__sMerchantName', 'kOffer2SetKit__kSet2User__kMerchantOffice__kMerchantName__pMerchantLogo', 'kOffer2SetKit__kSet2User__kMerchantOffice__kMerchantName__sMerchantMainURL', ) .order_by("-dOfferModify") ) if brand_id != 0: q_price_offer = q_price_offer.filter( kOffer2SetKit__kSet2User__kMerchantOffice__kMerchantName_id=brand_id, ) q_price_offer = [ SimpleNamespace( id=offer['id'], fOfferPrice=offer['fOfferPrice'], dOfferModify=offer['dOfferModify'], sOfferFlapConfig=offer['sOfferFlapConfig'], sDescripion=offer['kOffer2MountDim__sDescripion'], iWinWidth=offer['kOffer2MountDim__iWinWidth'], iWinHight=offer['kOffer2MountDim__iWinHight'], setID=offer['kOffer2SetKit__id'], sSetName=offer['kOffer2SetKit__sSetName'], dSetModify=offer['kOffer2SetKit__dSetModify'], dSetCommercialUntil=offer['kOffer2SetKit__dSetCommercialUntil'], sSetClimateControl=offer['kOffer2SetKit__sSetClimateControl'], sSetSill=offer['kOffer2SetKit__sSetSill'], sSetImplementAll=offer['kOffer2SetKit__sSetImplementAll'], sSetImplementHandles=offer['kOffer2SetKit__sSetImplementHandles'], sSetImplementHinges=offer['kOffer2SetKit__sSetImplementHinges'], sSetImplementLatch=offer['kOffer2SetKit__sSetImplementLatch'], sSetImplementLimiter=offer['kOffer2SetKit__sSetImplementLimiter'], sSetImplementCatch=offer['kOffer2SetKit__sSetImplementCatch'], sSetPanes=offer['kOffer2SetKit__sSetPanes'], sSetSlope=offer['kOffer2SetKit__sSetSlope'], sSetOtherConditions=offer['kOffer2SetKit__sSetOtherConditions'], sSetDelivery=offer['kOffer2SetKit__sSetDelivery'], bSetDelivery=offer['kOffer2SetKit__bSetDelivery'], sSetUninstallInstall=offer['kOffer2SetKit__sSetUninstallInstall'], bSetUninstallInstall=offer['kOffer2SetKit__bSetUninstallInstall'], fSetRating=offer['kOffer2SetKit__fSetRating'], sOfficePhones=offer['kOffer2SetKit__kSet2User__kMerchantOffice__sOfficePhones'], sOfficeDiscountMetaFormula=offer['kOffer2SetKit__kSet2User__kMerchantOffice__sOfficeDiscountMetaFormula'], sOfficeName=offer['kOffer2SetKit__kSet2User__kMerchantOffice__sOfficeName'], sOfficeAddress=offer['kOffer2SetKit__kSet2User__kMerchantOffice__sOfficeAddress'], sGlazingBriefDescription=offer['kOffer2SetKit__kSet2Glazing__sGlazingBriefDescription'], sGlazingMark=offer['kOffer2SetKit__kSet2Glazing__sGlazingMark'], sGlazingToning=offer['kOffer2SetKit__kSet2Glazing__sGlazingToning'], pwc_id=offer['kOffer2SetKit__kSet2PVCprofiles__id'], sProfileName=offer['kOffer2SetKit__kSet2PVCprofiles__sProfileName'], sProfileManufacturer=offer['kOffer2SetKit__kSet2PVCprofiles__sProfileManufacturer'], sProfileSealDescription=offer['kOffer2SetKit__kSet2PVCprofiles__sProfileSealDescription'], sMerchantName=offer['kOffer2SetKit__kSet2User__kMerchantOffice__kMerchantName__sMerchantName'], pMerchantLogo=offer['kOffer2SetKit__kSet2User__kMerchantOffice__kMerchantName__pMerchantLogo'], sMerchantMainURL=offer['kOffer2SetKit__kSet2User__kMerchantOffice__kMerchantName__sMerchantMainURL'], iQuantity=1, ) for offer in q_price_offer[frame_begin_n:frame_begin_n + 10000] ] price_frame = [] n_begin = int(frame_begin_n) for offer in q_price_offer: n_begin += 1 total = offer.fOfferPrice image_file = get_flaps_for_mini_pictures(offer.sOfferFlapConfig) dim_in_offer = [{ 'PRICE': offer.fOfferPrice, 'FLAP': offer.sOfferFlapConfig, 'DESCRIPTION': offer.sDescripion, 'WIDTH': offer.iWinWidth, 'HIGHT': offer.iWinHight, 'ID': offer.id, 'IMG_MINI': image_file, 'QUANTITY': 1, 'BULLET': ['A'], 'SUBTOTAL': offer.fOfferPrice, }] discount = 0 try: meta_keys = eval(offer.sOfficeDiscountMetaFormula) if KEY_DICSOUNT in meta_keys: for CountVal in sorted(meta_keys[KEY_DICSOUNT]): if float(total) > float(CountVal): discount = meta_keys[KEY_DICSOUNT][CountVal] except (ValueError, TypeError): pass fin_price = total * (100 - discount) / 100 if discount > 99 or discount < 0.1: discount_color1 = "" discount_color2 = "" else: color_ratio = (discount + 0.) / 100 discount_color1 = f"#{255 - int(color_ratio * 128):02x}ff{255 - int(color_ratio * 128):02x}" discount_color2 = f"#{255 - int(color_ratio * 255):02x}ff{255 - int(color_ratio * 255):02x}" price_frame.append({ 'DISTANCE': -1, 'DIM': dim_in_offer, 'TOTAL': total, 'DISCOUNT': discount, 'DISCOUNT_COLOR1': discount_color1, 'DISCOUNT_COLOR2': discount_color2, 'FIN_PRICE': fin_price, 'OFFICE_NAME': offer.sOfficeName, 'OFFICE_ADDRESS': offer.sOfficeAddress, 'OFFICE_PHONES': offer.sOfficePhones, 'MERCHANT': offer.sMerchantName, 'MERCHANT_LOGO': offer.pMerchantLogo, 'MERCHANT_URL': offer.sMerchantMainURL, 'MERCHANT_URL_SHOT': re.sub(r"(^http://|^https://|/$|www\.)", "", offer.sMerchantMainURL), 'SETS_NAME': offer.sSetName, 'GLAZING_NAME_B': offer.sGlazingBriefDescription, 'GLAZING_MARK': offer.sGlazingMark, 'GLAZING_TONING': offer.sGlazingToning, 'PVC_ID': offer.pwc_id, 'PVC_NAME': offer.sProfileName, 'PVC_NAME_T': pytils.translit.slugify(offer.sProfileName).lower(), 'PVC_MANUFACTURER': offer.sProfileManufacturer, 'PVC_MANUFACTURER_T': pytils.translit.slugify(offer.sProfileManufacturer).lower(), 'PVC_SEAL': offer.sProfileSealDescription, 'SETS_CLIMATE_CONTROL': offer.sSetClimateControl, 'SETS_SILL': offer.sSetSill, 'SETS_IMPLEMENT': offer.sSetImplementAll, 'SETS_IMPLEMENT_R': offer.sSetImplementHandles, 'SETS_IMPLEMENT_P': offer.sSetImplementHinges, 'SETS_IMPLEMENT_Z': offer.sSetImplementLatch, 'SETS_IMPLEMENT_O': offer.sSetImplementLimiter, 'SETS_IMPLEMENT_F': offer.sSetImplementCatch, 'SETS_PANES': offer.sSetPanes, 'SETS_SLOPE': offer.sSetSlope, 'SETS_DELIVERY': offer.sSetDelivery, 'SETS_DELIVERY_B': offer.bSetDelivery, 'SETS_OTHER': offer.sSetOtherConditions, 'SETS_ID': offer.setID, 'SETS_UNINSTALL_INSTALL': offer.sSetUninstallInstall, 'SETS_UNINSTALL_INSTALL_B': offer.bSetUninstallInstall, 'SETS_RATING': offer.fSetRating, 'SETS_RATING_STARTS': get_rating_set_for_stars(offer.fSetRating), 'SETS_DATA_MODIFY': offer.dOfferModify, 'IS_COMMERCIAL': offer.dSetCommercialUntil > timezone.now(), }) if time_for_meta == 0 or django.utils.dateformat.format(time_for_meta, 'U') < \ django.utils.dateformat.format(offer.dOfferModify, 'U'): time_for_meta = offer.dOfferModify if time_for_meta == 0 or django.utils.dateformat.format(time_for_meta, 'U') < \ django.utils.dateformat.format(offer.dSetModify, 'U'): time_for_meta = offer.dSetModify if len(price_frame) == offer_per_frame: break price_frame = sorted(price_frame, key=lambda item: item['DISTANCE']) if len(price_frame) < offer_per_frame: n_begin = '-1' return {'META_DATA_PUBLISH': time_for_meta, 'PRICE_FRAME': price_frame, 'N': n_begin} else: # если выводим цены для типовой квартиры # print("Нужно несколько окон для квартиры") q_price_offer = PriceOffer.objects.raw( f"SELECT" f" oknardia_priceoffer.*," f" oknardia_win_mountdim.*," f" oknardia_setkit.*," f" oknardia_merchantoffice.*," f" oknardia_glazing.*," f" oknardia_pvcprofiles.*," f" oknardia_merchantbrand.*," f" oknardia_mountdim2apartment.iQuantity," f" oknardia_win_mountdim.id AS mID, " f" oknardia_setkit.id AS setID," f" (oknardia_setkit.dSetCommercialUntil > CURRENT_TIMESTAMP) AS bCommercial," f" oknardia_pvcprofiles.id AS pwc_id," f" oknardia_merchantbrand.id AS brand_id " f"FROM oknardia_priceoffer" f" INNER JOIN oknardia_win_mountdim" f" ON oknardia_priceoffer.kOffer2MountDim_id = oknardia_win_mountdim.id" f" INNER JOIN oknardia_setkit" f" ON oknardia_priceoffer.kOffer2SetKit_id = oknardia_setkit.id" f" INNER JOIN oknardia_ouruser" f" ON oknardia_setkit.kSet2User_id = oknardia_ouruser.id" f" INNER JOIN oknardia_merchantoffice" f" ON oknardia_ouruser.kMerchantOffice_id = oknardia_merchantoffice.id" f" INNER JOIN oknardia_glazing" f" ON oknardia_setkit.kSet2Glazing_id = oknardia_glazing.id" f" INNER JOIN oknardia_pvcprofiles" f" ON oknardia_setkit.kSet2PVCprofiles_id = oknardia_pvcprofiles.id" f" INNER JOIN oknardia_mountdim2apartment" f" ON oknardia_mountdim2apartment.kMountDim_id = oknardia_win_mountdim.id" f" INNER JOIN oknardia_merchantbrand" f" ON oknardia_merchantoffice.kMerchantName_id = oknardia_merchantbrand.id " f"WHERE oknardia_priceoffer.sOfferActive IS TRUE" f" AND oknardia_mountdim2apartment.kApartment_id = {int(apartment_id)}" f" AND oknardia_setkit.sSetActive IS TRUE {add_to_sql_for_widget} " f"ORDER BY" f" oknardia_setkit.dSetCreate DESC, " # Сейчас окна в наборе собираются через это f" oknardia_win_mountdim.bIsNearDoor DESC," f" oknardia_win_mountdim.bIsDoor DESC," f" oknardia_win_mountdim.iWinWidth," f" oknardia_win_mountdim.iWinHight DESC " f"LIMIT {int(frame_begin_n)} , 10000;") # print list(qPO) price_frame = [] count_mount_dim_in_offer = 0 dim_in_offer = [] total = 0 cur_bullet = 0 previous_set_id = 0 count_mount_dim_in_frame_page = 0 # Так как получен QuerySet начиная с frame_begin_n, то для того чтобы получить frame_begin_n следующего фрйма # считаем не от нуля, а от старого frame_begin_n n_begin = int(frame_begin_n) # Проверяем есть ли папка для хранения мини-картинки конфигурации схемы открывания. if not os.path.exists(f"{STATIC_BASE_PATH}/{PATH_FOR_IMG}/{PATH_FOR_IMGFLAPCONFIG}"): # создаем такую папку если её нет os.makedirs(f"{STATIC_BASE_PATH}/{PATH_FOR_IMG}/{PATH_FOR_IMGFLAPCONFIG}") # print(">>>>>>>>>>>>>", apartment_id) for i2 in q_price_offer: n_begin += 1 count_mount_dim_in_frame_page += 1 # Случается, что в том или ином наборе поставщиком просчитаны не все проёмы. # Чтобы не происходило формирование предложения (офера) из окон разных наборов делаем проверку # является ли текущий проём в предложении из того же набора, что и предыдущий. # Если он из другого набора, то удаляем предыдущий проём из предложения и начинаем # формирование предложения сначала. if count_mount_dim_in_offer == 0: previous_set_id = i2.setID else: if previous_set_id != i2.setID: # print("Сбой в наборе. Обнуляем набор") previous_set_id = i2.setID count_mount_dim_in_offer = 0 total = 0 cur_bullet = 0 dim_in_offer.pop() dim_in_offer = [] # print("mID:", i2.mID, " || pID:", i2.id, " || price:", i2.fOfferPrice, " || N:", i2.iQuantity, # " || set:", i2.sSetName, " || merchant:", i2.sOfficeName) total += i2.fOfferPrice * i2.iQuantity image_file = get_flaps_for_mini_pictures(i2.sOfferFlapConfig) dim_in_offer.append({ 'PRICE': i2.fOfferPrice, 'FLAP': i2.sOfferFlapConfig, 'DESCRIPTION': i2.sDescripion, 'WIDTH': i2.iWinWidth, 'HIGHT': i2.iWinHight, 'ID': i2.id, 'IMG_MINI': image_file, 'QUANTITY': i2.iQuantity, 'BULLET': [chr(65 + cur_bullet + i) for i in range(i2.iQuantity)], # 'BULLET': range(CurBullet, CurBullet+i2.iQuantity), 'SUBTOTAL': i2.fOfferPrice * i2.iQuantity, }) cur_bullet += i2.iQuantity count_mount_dim_in_offer += 1 if count_mount_dim_in_offer == mount_dim_per_offer: # print("-----------------") # узнаем скидку через разбор формулы на метаязыке discount = 0 try: meta_keys = eval(i2.sOfficeDiscountMetaFormula) if KEY_DICSOUNT in meta_keys: # скидки рассчитываются исходя из общей суммы for CountVal in sorted(meta_keys[KEY_DICSOUNT]): # print(CountVal, "::", meta_keys[KEY_DICSOUNT][CountVal]) if float(total) > float(CountVal): discount = meta_keys[KEY_DICSOUNT][CountVal] # # DiscountTXT += u"!!%d!!" % Discount # print("Значит DISCOUNT: ", Discount) except (ValueError, TypeError): pass fin_price = total * (100 - discount) / 100 # уточняем, есть ли в принципе геокоординаты? if int(i2.fOfficeGeoCode_Longitude) != 0 and int(i2.fOfficeGeoCode_Latitude) != 0 and \ int(address_longitude) != 0 and int(address_latitude) != 0: # рассчитываем дистанцию между адресом дома и офиса. distance = get_geo_distance(i2.fOfficeGeoCode_Longitude, i2.fOfficeGeoCode_Latitude, address_longitude, address_latitude) # т.к. из-за изменений в api яндекс карт поменялась местами широта-долгота и вообще, то # порядок переменных строчной выше... На самом деле должно быть как в закоментированной # строке ниже # distance = get_geo_distance(i2.fOfficeGeoCode_Longitude, i2.fOfficeGeoCode_Latitude, # address_longitude, address_latitude) else: distance = -1 # print(discount) if discount > 99 or discount < 0.1: discount_color1 = "" discount_color2 = "" else: color_ratio = (discount + 0.) / 100 discount_color1 = f"#{255 - int(color_ratio * 128):02x}ff{255 - int(color_ratio * 128):02x}" discount_color2 = f"#{255 - int(color_ratio * 255):02x}ff{255 - int(color_ratio * 255):02x}" # print(discount_color1, discount_color2) price_frame.append({ 'DISTANCE': distance, 'DIM': dim_in_offer, 'TOTAL': total, 'DISCOUNT': discount, 'DISCOUNT_COLOR1': discount_color1, 'DISCOUNT_COLOR2': discount_color2, 'FIN_PRICE': fin_price, 'OFFICE_NAME': i2.sOfficeName, 'OFFICE_ADDRESS': i2.sOfficeAddress, 'OFFICE_PHONES': i2.sOfficePhones, 'MERCHANT': i2.sMerchantName, 'MERCHANT_LOGO': i2.pMerchantLogo, 'MERCHANT_URL': i2.sMerchantMainURL, 'MERCHANT_URL_SHOT': re.sub(r"(?:^http://|^https://|/$|www\.)", "", i2.sMerchantMainURL), 'SETS_NAME': i2.sSetName, 'GLAZING_NAME_B': i2.sGlazingBriefDescription, 'GLAZING_MARK': i2.sGlazingMark, 'GLAZING_TONING': i2.sGlazingToning, 'PVC_ID': i2.pwc_id, 'PVC_NAME': i2.sProfileName, 'PVC_NAME_T': pytils.translit.slugify(i2.sProfileName).lower(), 'PVC_MANUFACTURER': i2.sProfileManufacturer, 'PVC_MANUFACTURER_T': pytils.translit.slugify(i2.sProfileManufacturer).lower(), 'PVC_SEAL': i2.sProfileSealDescription, 'SETS_CLIMATE_CONTROL': i2.sSetClimateControl, 'SETS_SILL': i2.sSetSill, 'SETS_IMPLEMENT': i2.sSetImplementAll, 'SETS_IMPLEMENT_R': i2.sSetImplementHandles, 'SETS_IMPLEMENT_P': i2.sSetImplementHinges, 'SETS_IMPLEMENT_Z': i2.sSetImplementLatch, 'SETS_IMPLEMENT_O': i2.sSetImplementLimiter, 'SETS_IMPLEMENT_F': i2.sSetImplementCatch, 'SETS_PANES': i2.sSetPanes, 'SETS_SLOPE': i2.sSetSlope, 'SETS_DELIVERY': i2.sSetDelivery, 'SETS_DELIVERY_B': i2.bSetDelivery, 'SETS_OTHER': i2.sSetOtherConditions, 'SETS_ID': i2.setID, 'SETS_UNINSTALL_INSTALL': i2.sSetUninstallInstall, 'SETS_UNINSTALL_INSTALL_B': i2.bSetUninstallInstall, 'SETS_RATING': i2.fSetRating, 'SETS_RATING_STARTS': get_rating_set_for_stars(i2.fSetRating), 'SETS_DATA_MODIFY': i2.dOfferModify, 'IS_COMMERCIAL': i2.bCommercial, }) if len(price_frame) == offer_per_frame: break count_mount_dim_in_offer = 0 dim_in_offer = [] total = 0 cur_bullet = 0 # узнаем дату-время самого свежего ценового предложения для размещения в META-тега if time_for_meta == 0 or django.utils.dateformat.format(time_for_meta, 'U') < \ django.utils.dateformat.format(i2.dOfferModify, 'U'): time_for_meta = i2.dOfferModify if time_for_meta == 0 or django.utils.dateformat.format(time_for_meta, 'U') < \ django.utils.dateformat.format(i2.dSetModify, 'U'): time_for_meta = i2.dSetModify # массив ценовых предложений (что бы предложения на одной дистанции были в случайном порядке) # random.shuffle(PriceFrame) # сортируем по удаленности price_frame = sorted(price_frame, key=lambda item: item['DISTANCE']) if len(price_frame) < offer_per_frame: n_begin = '-1' return {'META_DATA_PUBLISH': time_for_meta, 'PRICE_FRAME': price_frame, 'N': n_begin} def report_one_win_price(request: HttpRequest, win_width_mm: str | int = DEFAULT_WIN_WIDTH_MM, win_height_mm: str | int = DEFAULT_WIN_HEIGHT_MM, win_id: str | int = DEFAULT_WIN_ID) -> HttpResponse: """ Формируем выдачу цен для единичного ТИПОВОГО окна (т.е. проема из серийного дома). :param request: HttpRequest -- входящий http-запрос :param win_width_mm: str -- Ширина проема в миллиметрах (это SEO-параметр, в реальности он будет получен из базы) :param win_height_mm: str -- Высота проема в миллиметрах (это SEO-параметр, в реальности он будет получен из базы) :param win_id: str -- ID проема (см. таблицу oknardia_win_mountdim) :return response: HttpResponse -- исходящий http-ответ """ time_start = time.perf_counter() to_template: dict[str, object] = {} try: win_info_rows = ( Win_MountDim.objects .filter(id=int(win_id)) .values( 'id', 'iWinWidth', 'iWinHight', 'iWinDepth', 'sFlapConfig', 'bIsNearDoor', 'bIsDoor', 'sDescripion', ) ) list_win_info = [ SimpleNamespace( id=item['id'], iWinWidth=item['iWinWidth'], iWinHight=item['iWinHight'], iWinDepth=item['iWinDepth'], sFlapConfig=item['sFlapConfig'], bIsNearDoor=item['bIsNearDoor'], bIsDoor=item['bIsDoor'], sDescripion=item['sDescripion'], iQuantity=0, ) for item in win_info_rows ] # Если размеры типового проема не совпадают с размерами из базы, то подменяем # на правильные и перевызываем страницу canonical_width_mm = int(list_win_info[0].iWinWidth * 10) canonical_height_mm = int(list_win_info[0].iWinHight * 10) if (list_win_info[0].iWinWidth * 10 != int(win_width_mm)) or \ (list_win_info[0].iWinHight * 10 != int(win_height_mm)): return redirect( _one_win_price_canonical_path( win_width_mm=canonical_width_mm, win_height_mm=canonical_height_mm, win_id=win_id, ), permanent=True, ) except (ObjectDoesNotExist, ValueError, IndexError, TypeError): return redirect( _one_win_price_canonical_path( win_width_mm=DEFAULT_WIN_WIDTH_MM, win_height_mm=DEFAULT_WIN_HEIGHT_MM, win_id=DEFAULT_WIN_ID, ), permanent=True, ) # все хорошо, засылаем картинку в шаблон to_template.update(get_flaps_for_big_pictures(list_win_info)) # получаем варианты схемы открывания (для графиков) flap_variations = ( PriceOffer.objects.filter( sOfferActive=True, kOffer2MountDim_id=int(win_id), ) .values('sOfferFlapConfig') .annotate(id=Count('sOfferFlapConfig')) .order_by('-id') ) list_offer_flap_variation = [ SimpleNamespace( id=item['id'], sOfferFlapConfig=item['sOfferFlapConfig'], IMG_MINI='', STR_NUM='', ) for item in flap_variations ] for i in range(0, len(list_offer_flap_variation)): if i < 3: list_offer_flap_variation[i].STR_NUM = "вариант " + pytils.numeral.in_words(i + 1) elif i == 3: list_offer_flap_variation[i].STR_NUM = "остальные варианты" continue else: list_offer_flap_variation[3].id += list_offer_flap_variation[i].id continue list_offer_flap_variation[i].IMG_MINI = get_flaps_for_mini_pictures( list_offer_flap_variation[i].sOfferFlapConfig ) to_template.update({'LIST_FLAP_VARIATION': list_offer_flap_variation[:4]}) to_template.update({'NUM_FLAP_VARIATION_IN_WORD': pytils.numeral.sum_string(len(list_offer_flap_variation), pytils.numeral.MALE, ("вариант схемы", "варианта схем", "вариантов схем"))}) # firms_count = ( PriceOffer.objects.filter(kOffer2MountDim_id=int(win_id)) .values('kOfferFromUser_id') .distinct() .count() ) to_template.update({'NUM_TOTAL_FIRM_N_WORD': pytils.numeral.get_plural(firms_count, ("компании", "компаний", "компаний"))}) q = PriceOffer.objects.filter(kOffer2MountDim_id=int(win_id)) to_template.update({'NUM_TOTAL_OFFER_N_WORD': pytils.numeral.get_plural(q.count(), ("готовый расчёт", "готовых расчёта", "готовых расчётов"))}) to_template.update({'NUM_ARCHIVE_OFFER': q.filter(sOfferActive=0).count()}) # seria_for_win = ( MountDim2Apartment.objects.filter(kMountDim_id=int(win_id), kApartment__kSeria__isnull=False) .values('kApartment__kSeria__id', 'kApartment__kSeria__sName') .annotate(num_variation_of_apartment=Count('id')) .order_by('kApartment__kSeria__sName') ) list_seria_for_win = [] for seria_item in seria_for_win: seria_name = seria_item['kApartment__kSeria__sName'] list_seria_for_win.append(SimpleNamespace( id=seria_item['kApartment__kSeria__id'], sName=seria_name, sNameLat=pytils.translit.slugify(seria_name), num_variation_of_apartment=pytils.numeral.sum_string( seria_item['num_variation_of_apartment'], pytils.numeral.MALE, ("типовую планировку квартиры", "типовые планировки квартир", "типовых планировок квартир"), ), )) to_template.update(report_price_frame(apartment_id=0, mount_dim_per_offer= 1, address_longitude= 0, address_latitude= 0, frame_begin_n= 0, brand_id= 0, win_id=int(win_id) ) ) to_template.update({ 'SERIA_FOR_WIN': list_seria_for_win, 'WIN_ID': int(win_id), 'MOUNT_DIM_PER_OFFER': 1, # получаем последние визиты клиента через куки 'LAST_VISIT': get_last_user_visit_list(get_last_user_visit_cookies(request)[:3]), # получаем последние визиты всех посетителей из базы # id2log, log_visit = get_last_all_user_visit_list() 'LOG_VISIT': get_last_all_user_visit_list(), 'ticks': float(time.perf_counter() - time_start) }) return render(request, "price/price_offers_for_one_window.html", to_template) def next_one_win_price(request: HttpRequest, win_id: str | int = DEFAULT_WIN_ID, frame_begin_n: str | int = 0): """ Возвращает очередной фреймом ценовых предложений для выдачи с одиночным окном. :param request: HttpRequest -- входящий http-запрос :param win_id: str -- id типового окна :param frame_begin_n: str -- Номер записи, с которой начинается фрейм с ценами :return: HttpResponse -- """ time_start = time.perf_counter() to_template: dict[str, object] = report_price_frame(apartment_id=0, mount_dim_per_offer=1, address_longitude=0, address_latitude=0, frame_begin_n=int(frame_begin_n), brand_id=0, win_id=int(win_id) ) to_template.update({'MOUNT_DIM_PER_OFFER': 1, 'WIN_ID': int(win_id), 'ticks': float(time.perf_counter() - time_start)}) return render(request, "price/price_offers_for_one_window_frame.html", to_template) def report_price(request: HttpRequest, build_id: str = "22427", apart_id: str = "61", slug: str = "g-moskva-ul-novorossijskaya-d-16") -> HttpResponse: """ Страница с расчетом стоимости окон :param request: HttpRequest -- входящий http-запрос :param build_id: str - id здания (адрес в таблице oknardia_building_info.id) :param apart_id: str - id типовой планировки квартиры (в таблице oknardia_apartment_type.id) :param slug: str - slug адреса здания :return: response: HttpResponse """ time_start = time.perf_counter() msg = "" to_template: dict[str, object] = {} try: build_id = int(build_id) apart_id = int(apart_id) except ValueError: return redirect("/") try: # получаем все типы квартир для данного адреса (а заодно и попутную информацию о площади дома и пр.) q_apart = Apartment_Type.objects.raw( f'SELECT' f' oknardia_apartment_type.sNameApartment, oknardia_apartment_type.id,' f' oknardia_apartment_type.iSort, oknardia_seria_info.kRoot_id,' f' oknardia_building_info.kSeria_Link_id, oknardia_building_info.sAddress,' f' oknardia_building_info.fGeoCode_Latitude, oknardia_building_info.fGeoCode_Longitude,' f' oknardia_building_info.fTotal_Area, oknardia_building_info.sCadastre_Num_Area,' f' oknardia_building_info.fLand_Area, oknardia_building_info.sInventory_Num,' f' oknardia_building_info.iNum_Apartments, oknardia_building_info.sType,' f' oknardia_building_info.iStoreys, oknardia_building_info.fCommon_Area,' f' oknardia_building_info.sEnergy_Efficiency, oknardia_building_info.iEntrances_Porchs,' f' oknardia_building_info.fUninhabited_Area, oknardia_building_info.sManagement_Co,' f' oknardia_building_info.iElevators, oknardia_building_info.fResidential_Area,' f' oknardia_building_info.iNum_Residents, oknardia_building_info.fPrivate_Area,' f' oknardia_building_info.iNum_Accounts, oknardia_building_info.iCommissioning_year,' f' oknardia_building_info.fGovernment_Area, oknardia_building_info.fCondition_House,' f' oknardia_building_info.fCondition_Foundation, oknardia_building_info.fCondition_Walls,' f' oknardia_building_info.fCondition_Overlap, oknardia_building_info.fMunicipal_Area,' f' oknardia_building_info.sSerias_Project ' f'FROM oknardia_seria_info ' f'INNER JOIN oknardia_apartment_type' f' ON oknardia_seria_info.kRoot_id = oknardia_apartment_type.kSeria_id ' f' INNER JOIN oknardia_building_info' f' ON oknardia_building_info.kSeria_Link_id = oknardia_seria_info.id ' f'WHERE oknardia_building_info.id = {build_id} ' f'ORDER BY oknardia_apartment_type.iSort;') list_apart = list(q_apart) # если кто-то нахимичит ID квартиры не для этого дома, то сделаем так, что он будет от этого дома! apart_inside = False for i in q_apart: if i.id == apart_id: apart_inside = True break if not apart_inside or slug != pytils.translit.slugify(list_apart[0].sAddress): # Переадресация 302, если с apart_id (ID-квартиры нахимичили) или slug-ом. # Нужно для склейки парных URL в поисковиках # При переходе с карты apart_id выставляем в 0. Из-за этого тоже нужно 302-переадресация. return redirect(f"/{build_id}/{list_apart[0].id}/{pytils.translit.slugify(list_apart[0].sAddress)}") address_latitude = list_apart[0].fGeoCode_Latitude address_longitude = list_apart[0].fGeoCode_Longitude to_template.update({'BUILD_ID': build_id}) to_template.update({'APPARTMENT_ID': apart_id}) to_template.update({'ADDRESS_LAT': address_latitude}) to_template.update({'ADDRESS_LON': address_longitude}) to_template.update({'ADDRESS': list_apart[0].sAddress}) to_template.update({'ADDRESS_T': pytils.translit.slugify(list_apart[0].sAddress)}) to_template.update({'SERIA': list_apart[0].sSerias_Project}) # данные нужные для отображения информации о доме (метраж, число подъездов и пр.) to_template.update({'CADASTRE_NUM': list_apart[0].sCadastre_Num_Area}) to_template.update({'INVENTORY_NUM': list_apart[0].sInventory_Num}) to_template.update({'TYPE_BUILDING': list_apart[0].sType}) to_template.update({'ENERGY_EFFICIENCY': list_apart[0].sEnergy_Efficiency}) if list_apart[0].fTotal_Area != -1.0: to_template.update({'TOTAL_AREA': f"{list_apart[0].fTotal_Area:.1f}"}) if list_apart[0].fLand_Area != -1.0: to_template.update({'LAND': f"{list_apart[0].fLand_Area:.1f}"}) if list_apart[0].iNum_Apartments != -1: to_template.update({'NUM_APARTMENTS': list_apart[0].iNum_Apartments}) if list_apart[0].iStoreys != -1: to_template.update({'STOREYS': list_apart[0].iStoreys}) if list_apart[0].fCommon_Area != -1.0: to_template.update({'COMMON_AREA': f"{list_apart[0].fCommon_Area:.1f}"}) if list_apart[0].iEntrances_Porchs != -1: to_template.update({'NUM_ENTERANCES': list_apart[0].iEntrances_Porchs}) if list_apart[0].fUninhabited_Area != -1.0: to_template.update({'UNINHABITED_AREA': f"{list_apart[0].fUninhabited_Area:.1f}"}) if list_apart[0].sManagement_Co != u"N/A": to_template.update({'MANAGEMENT_CO': list_apart[0].sManagement_Co}) if list_apart[0].iElevators != -1: to_template.update({'NUM_ELEVATORS': list_apart[0].iElevators}) if list_apart[0].fResidential_Area != -1.0: to_template.update({'RESIDENTIAL_AREA': f"{list_apart[0].fResidential_Area:.1f}"}) if list_apart[0].iNum_Residents != -1: to_template.update({'NUM_RESIDENTS': list_apart[0].iNum_Residents}) if list_apart[0].fPrivate_Area != -1.0: to_template.update({'PRIVATE_AREA': f"{list_apart[0].fPrivate_Area:.1f}"}) if list_apart[0].iNum_Accounts != -1: to_template.update({'NUM_ACCOUNTS': list_apart[0].iNum_Accounts}) if list_apart[0].iCommissioning_year != "N/A": to_template.update({'COMMISSIONING_YEAR': list_apart[0].iCommissioning_year}) if list_apart[0].fGovernment_Area != -1.0: to_template.update({'GOVERNMENT_AREA': f"{list_apart[0].fGovernment_Area:.1f}"}) if list_apart[0].fCondition_House != -1.0: to_template.update({'CONDITION_HOUSE': f"{list_apart[0].fCondition_House:.0f}%"}) if list_apart[0].fCondition_Foundation != -1.0: to_template.update({'CONDITION_FOUNDATION': f"{list_apart[0].fCondition_Foundation:.0f}%"}) if list_apart[0].fCondition_Walls != -1.0: to_template.update({'CONDITION_WALL': f"{list_apart[0].fCondition_Walls:.0f}%"}) if list_apart[0].fCondition_Overlap != -1.0: to_template.update({'CONDITION_OVERLAP': f"{list_apart[0].fCondition_Overlap:.0f}%"}) if list_apart[0].fMunicipal_Area != -1.0: to_template.update({'MUNICIPAL_AREA': f"{list_apart[0].fMunicipal_Area:.1f}"}) # заполняем массив квартир для отправки в шаблон apart_in_building = [] for apartment_count in q_apart: apartment_in = {} if apartment_count.id != apart_id: apartment_in.update({'APT_ID': apartment_count.id}) else: apartment_in.update({'APT_ID': "!"}) apartment_in.update({'APT_NAME': apartment_count.sNameApartment}) apart_in_building.append(apartment_in) to_template.update({'APARTMENT_IN_BUILDING': apart_in_building}) # узнаем базовую серию дома q_base_seria = Seria_Info.objects.get(id=list_apart[0].kRoot_id) base_seria_slug = pytils.translit.slugify(q_base_seria.sName) to_template.update({'BASE_SERIA': q_base_seria.sName, 'BASE_SERIA_LAT': base_seria_slug, 'BASE_SERIA_ID': q_base_seria.id}) # except (ValueError, IndexError, TypeError, ObjectDoesNotExist): except ObjectDoesNotExist: return redirect("/") ############################################### # получаем массив окон для данной квартиры... try: q_md = Win_MountDim.objects.raw( f'SELECT' f' oknardia_apartment_type.sNameApartment, oknardia_win_mountdim.iWinWidth,' f' oknardia_win_mountdim.iWinHight, oknardia_win_mountdim.iWinDepth,' f' oknardia_win_mountdim.sFlapConfig, oknardia_win_mountdim.bIsNearDoor,' f' oknardia_win_mountdim.bIsDoor, oknardia_win_mountdim.sDescripion,' f' oknardia_win_mountdim.id, oknardia_mountdim2apartment.iQuantity ' f'FROM oknardia_mountdim2apartment ' f'INNER JOIN oknardia_apartment_type' f' ON oknardia_mountdim2apartment.kApartment_id = oknardia_apartment_type.id' f' INNER JOIN oknardia_win_mountdim' f' ON oknardia_mountdim2apartment.kMountDim_id = oknardia_win_mountdim.id ' f'WHERE oknardia_mountdim2apartment.kApartment_id = {apart_id} ' f'GROUP BY' f' oknardia_apartment_type.sNameApartment, oknardia_win_mountdim.iWinWidth,' f' oknardia_win_mountdim.iWinHight, oknardia_win_mountdim.iWinDepth,' f' oknardia_win_mountdim.sFlapConfig, oknardia_win_mountdim.bIsNearDoor,' f' oknardia_win_mountdim.bIsDoor, oknardia_win_mountdim.sDescripion,' f' oknardia_apartment_type.bApartmentCheck, oknardia_win_mountdim.dMountXYZModify,' f' oknardia_apartment_type.dApartmentModify, oknardia_win_mountdim.iWinLimit,' f' oknardia_win_mountdim.id, oknardia_mountdim2apartment.iQuantity ' f'ORDER BY' f' oknardia_win_mountdim.bIsNearDoor DESC,' f' oknardia_win_mountdim.bIsDoor DESC,' f' oknardia_win_mountdim.iWinWidth,' f' oknardia_win_mountdim.iWinHight DESC;') list_mount_dim_per_offer = list(q_md) mount_dim_per_offer = len(list_mount_dim_per_offer) to_template.update({'APART': list_mount_dim_per_offer[0].sNameApartment}) # получаем данные для отрисовки больших картинок с проемами. to_template.update(get_flaps_for_big_pictures(q_md)) # <--- except (ValueError, IndexError, TypeError, ObjectDoesNotExist): return redirect("/") # получаем данные для фрейма ценовых предложений price_frame = report_price_frame(apartment_id=apart_id, mount_dim_per_offer=mount_dim_per_offer, address_longitude=address_longitude, address_latitude=address_latitude ) to_template.update(price_frame) # print u"строк в querySet:", CountMountDimInFramePage # dimension_to_template.update({'DISCOUNT_TXT': DiscountTXT}) to_template.update({'MOUNT_DIM_PER_OFFER': mount_dim_per_offer, 'META_DESCRIPTION': "Окнардия ", 'META_KEYWORDS': "Окнардия+ ", 'MSG': msg}) # получаем последние визиты всех посетителей из базы log_visit = get_last_all_user_visit_list() if log_visit[0]['id'] is not None: id_last_visit_log = log_visit[0]['id'] + 1 else: id_last_visit_log = 1 # print("id_last_visit_log:", id_last_visit_log) to_template.update({'LOG_VISIT': log_visit}) if id_last_visit_log > MAX_LEN_RING_LOG_BUFFER: # максимальный размер циклического буфера id_last_visit_log = 1 # ставим в начало буфера try: log_entry = LogVisitPriceReport.objects.get(id=id_last_visit_log) log_entry.sLogAddress = to_template["ADDRESS"] log_entry.sLogNameApartment = to_template["APART"] log_entry.sLogURL = f"/{build_id}/{apart_id}/{to_template['ADDRESS_T']}" log_entry.dLogVisitTime = time.perf_counter() log_entry.save() # UPDATE except ObjectDoesNotExist: log_entry = LogVisitPriceReport( sLogAddress=to_template["ADDRESS"], sLogNameApartment=to_template["APART"], sLogURL=f"/{build_id}/{apart_id}/{to_template['ADDRESS_T']}", dLogVisitTime=time.perf_counter() ) log_entry.save() # INSERT # получаем последние визиты клиента через куки last_visit = get_last_user_visit_cookies(request) to_template.update({'LAST_VISIT': get_last_user_visit_list(last_visit)}) # подготавливаем данные о текущем посещении для помещения в cookie Item = { "LastURL": f"/{build_id}/{apart_id}/{to_template['ADDRESS_T']}", "LastAddress": to_template["ADDRESS"], "LastApart": to_template["APART"], "Time": time.perf_counter()} last_visit.insert(0, Item) # Добавляем текущий Item в начало last_visit = json.dumps(last_visit[:3]) # упаковываем json без пробелов (три записи) # print u"сейчас запишем вот эту куку:", LastVisit to_template.update({'ticks': float(time.perf_counter() - time_start)}) response = render(request, "price/price_list.html", to_template) response.set_cookie("LastVisit", last_visit, max_age=7862400) # ставим или перезаписываем куки (91 день) return response def next_price_frame(request: HttpRequest, apart_id: str = "1", mount_dim_per_offer: str = "1", address_longitude: str = "0.", address_latitude: str = "0.", frame_begin_n: str = "0") -> HttpResponse: """ Возвращает очередным фреймом ценовых предложений. :param request: HttpRequest -- входящий HTTP-запрос :param apart_id: str -- ID типовой квартиры, для которой получаем ценовые предложения :param mount_dim_per_offer: str -- число различных оконных проемов в этой квартире (чтобы отсеять предложения, в которых не представлены все проемы) :param address_longitude: str -- долгота адреса (геокоордината), для которого получаем ценовые предложения, чтобы рассчитать удаленность компании предоставившей коммерческие предложения :param address_latitude: str -- широта адреса (геокоордината), для которого получаем ценовые предложения, чтобы рассчитать удаленность компании предоставившей коммерческие предложения :param frame_begin_n: str -- Номер записи с которой начинается фрейм с ценами :return: HttpResponse -- HTTP-ответ """ time_start = time.perf_counter() # получаем данные для фрейма ценовых предложений price_frame = report_price_frame(apartment_id=int(apart_id), mount_dim_per_offer=int(mount_dim_per_offer), address_longitude=float(address_longitude), address_latitude=float(address_latitude), frame_begin_n=int(frame_begin_n) ) to_template: dict[str, object] = price_frame to_template.update({'APPARTMENT_ID': apart_id, 'MOUNT_DIM_PER_OFFER': mount_dim_per_offer, 'ADDRESS_LAT': address_latitude, 'ADDRESS_LON': address_longitude, 'ticks': float(time.perf_counter() - time_start)}) return render(request, "price/price_list_frame.html", to_template)