fix: Не хватало серии дома в контекст шаблона + raw-SQL заменен на ORM
This commit is contained in:
@@ -2,6 +2,8 @@
|
|||||||
from django.shortcuts import render, redirect
|
from django.shortcuts import render, redirect
|
||||||
from django.http import HttpRequest, HttpResponse
|
from django.http import HttpRequest, HttpResponse
|
||||||
from django.core.mail import send_mail
|
from django.core.mail import send_mail
|
||||||
|
from django.db.models import ExpressionWrapper, FloatField, F, Count
|
||||||
|
from django.db.models.functions import Abs
|
||||||
from smtplib import SMTPException
|
from smtplib import SMTPException
|
||||||
from oknardia.models import Seria_Info, Building_Info, Apartment_Type
|
from oknardia.models import Seria_Info, Building_Info, Apartment_Type
|
||||||
from web.add_func import get_yandex_geocode_by_address, get_geo_distance
|
from web.add_func import get_yandex_geocode_by_address, get_geo_distance
|
||||||
@@ -105,6 +107,22 @@ def contact(request: HttpRequest) -> HttpResponse:
|
|||||||
return render(request, "contact.html", {})
|
return render(request, "contact.html", {})
|
||||||
|
|
||||||
|
|
||||||
|
def _fmt(value: object, fmt: str = ".1f", threshold: float = 0, default: str = "Нет данных") -> str:
|
||||||
|
"""Вспомогательная функция: форматирует числовое поле здания или возвращает заглушку.
|
||||||
|
|
||||||
|
:param value: значение поля модели (числовое)
|
||||||
|
:param fmt: строка формата для f-string, например '.1f' или '.0f'
|
||||||
|
:param threshold: значения < threshold считаются «нет данных» (обычно 0 или -1)
|
||||||
|
:param default: строка-заглушка при отсутствии данных
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if float(value) < threshold:
|
||||||
|
return default
|
||||||
|
return f"{value:{fmt}}"
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return default
|
||||||
|
|
||||||
|
|
||||||
def get_address(request: HttpRequest) -> HttpResponse:
|
def get_address(request: HttpRequest) -> HttpResponse:
|
||||||
""" Вызывается после ввода пользователем адреса. Получает строку с адресом методом POST
|
""" Вызывается после ввода пользователем адреса. Получает строку с адресом методом POST
|
||||||
|
|
||||||
@@ -141,112 +159,59 @@ def get_address(request: HttpRequest) -> HttpResponse:
|
|||||||
# print(geocode)
|
# print(geocode)
|
||||||
to_template.update({'LATITUDE': str(geocode[0]).replace(",", ".")})
|
to_template.update({'LATITUDE': str(geocode[0]).replace(",", ".")})
|
||||||
to_template.update({'LONGITUDE': str(geocode[1]).replace(",", ".")})
|
to_template.update({'LONGITUDE': str(geocode[1]).replace(",", ".")})
|
||||||
q = Building_Info.objects.raw(
|
# Ищем ближайшее здание по манхэттенскому расстоянию (lat/lon в градусах, ~0.01 ≈ 1 км)
|
||||||
f"SELECT oknardia_building_info.*, "
|
q = (Building_Info.objects
|
||||||
f"ABS({geocode[0]} - oknardia_building_info.fGeoCode_Latitude) + "
|
.annotate(
|
||||||
f"ABS({geocode[1]} - oknardia_building_info.fGeoCode_Longitude) AS R2 "
|
R2=ExpressionWrapper(
|
||||||
f"FROM oknardia_building_info "
|
Abs(float(geocode[0]) - F('fGeoCode_Latitude'))
|
||||||
f"ORDER BY R2 "
|
+ Abs(float(geocode[1]) - F('fGeoCode_Longitude')),
|
||||||
f"LIMIT 1;")[0]
|
output_field=FloatField()
|
||||||
if q.R2 > 0.67: # Если расстояние между точками больше 670 метров, то не показываем результат
|
)
|
||||||
|
)
|
||||||
|
.order_by('R2')
|
||||||
|
.first())
|
||||||
|
if q is None or q.R2 > 0.67: # Если расстояние > ~670 метров или ничего нет — не показываем
|
||||||
to_template.update({'ticks': float(time.perf_counter()-time_start)})
|
to_template.update({'ticks': float(time.perf_counter()-time_start)})
|
||||||
to_template.update({'addr': addr})
|
to_template.update({'addr': addr})
|
||||||
return render(request, "popup/popup_incorrect_address.html", to_template)
|
return render(request, "popup/popup_incorrect_address.html", to_template)
|
||||||
addr = q.sAddress
|
addr = q.sAddress
|
||||||
# print("addr", addr)
|
# print("addr", addr)
|
||||||
to_template.update({'ADDRESS_ID': q.id,
|
to_template.update({
|
||||||
'SERIA': q.sSerias_Project})
|
'ADDRESS_ID': q.id,
|
||||||
if q.fTotal_Area < 0:
|
'SERIA': q.sSerias_Project,
|
||||||
to_template.update({'TOTAL_AREA': "Нет данных"})
|
'TOTAL_AREA': _fmt(q.fTotal_Area),
|
||||||
else:
|
'CADASTRE_NUM': q.sCadastre_Num_Area,
|
||||||
to_template.update({'TOTAL_AREA': f"{q.fTotal_Area: .1f}"})
|
'LAND': _fmt(q.fLand_Area),
|
||||||
to_template.update({'CADASTRE_NUM': q.sCadastre_Num_Area})
|
'INVENTORY_NUM': q.sInventory_Num,
|
||||||
if q.fLand_Area < 0:
|
'NUM_APARTMENTS': q.iNum_Apartments if q.iNum_Apartments >= 0 else "Нет данных",
|
||||||
to_template.update({'LAND': "Нет данных"})
|
'TYPE_BUILDING': q.sType,
|
||||||
else:
|
'STOREYS': q.iStoreys if q.iNum_Apartments >= 0 else "Нет данных",
|
||||||
to_template.update({'LAND': f"{q.fLand_Area: .1f}"})
|
'COMMON_AREA': _fmt(q.fCommon_Area),
|
||||||
to_template.update({'INVENTORY_NUM': q.sInventory_Num})
|
'ENERGY_EFFICIENCY': q.sEnergy_Efficiency,
|
||||||
if q.iNum_Apartments < 0:
|
'NUM_ENTERANCES': q.iEntrances_Porchs if q.iEntrances_Porchs >= 0 else "Нет",
|
||||||
to_template.update({'NUM_APARTMENTS': "Нет данных"})
|
'UNINHABITED_AREA': _fmt(q.fUninhabited_Area),
|
||||||
else:
|
'MANAGEMENT_CO': q.sManagement_Co if q.sManagement_Co != "N/A" else "Нет данных",
|
||||||
to_template.update({'NUM_APARTMENTS': q.iNum_Apartments})
|
'NUM_ELEVATORS': q.iElevators if q.iElevators >= 0 else "Нет данных",
|
||||||
to_template.update({'TYPE_BUILDING': q.sType})
|
'RESIDENTIAL_AREA': _fmt(q.fResidential_Area),
|
||||||
if q.iNum_Apartments < 0:
|
'NUM_RESIDENTS': q.iNum_Residents if q.iNum_Residents >= 0 else "Нет данных",
|
||||||
to_template.update({'STOREYS': "Нет данных"})
|
'PRIVATE_AREA': _fmt(q.fPrivate_Area),
|
||||||
else:
|
'NUM_ACCOUNTS': q.iNum_Accounts if q.iNum_Accounts >= 0 else "Нет данных",
|
||||||
to_template.update({'STOREYS': q.iStoreys})
|
'COMMISSIONING_YEAR': q.iCommissioning_year if q.iCommissioning_year != "N/A" else "Нет данных",
|
||||||
if q.fCommon_Area < 0:
|
'GOVERNMENT_AREA': _fmt(q.fGovernment_Area),
|
||||||
to_template.update({'COMMON_AREA': "Нет данных"})
|
'CONDITION_HOUSE': _fmt(q.fCondition_House, fmt=".0f", default="Нет данных") + "%" if q.fCondition_House >= 0 else "Нет данных",
|
||||||
else:
|
'CONDITION_FOUNDATION': _fmt(q.fCondition_Foundation, fmt=".0f", default="Нет данных") + "%" if q.fCondition_Foundation >= 0 else "Нет данных",
|
||||||
to_template.update({'COMMON_AREA': f"{q.fCommon_Area: .1f}"})
|
'CONDITION_WALL': _fmt(q.fCondition_Walls, fmt=".0f", default="Нет данных") + "%" if q.fCondition_Walls >= 0 else "Нет данных",
|
||||||
to_template.update({'ENERGY_EFFICIENCY': q.sEnergy_Efficiency})
|
'CONDITION_OVERLAP': _fmt(q.fCondition_Overlap, fmt=".0f", default="Нет данных") + "%" if q.fCondition_Overlap >= 0 else "Нет данных",
|
||||||
if q.iEntrances_Porchs < 0:
|
'MUNICIPAL_AREA': _fmt(q.fMunicipal_Area),
|
||||||
to_template.update({'NUM_ENTERANCES': "Нет"})
|
'URL2REFOEMAGKH': q.sURL,
|
||||||
else:
|
})
|
||||||
to_template.update({'NUM_ENTERANCES': q.iEntrances_Porchs})
|
|
||||||
if q.fUninhabited_Area < 0:
|
|
||||||
to_template.update({'UNINHABITED_AREA': "Нет данных"})
|
|
||||||
else:
|
|
||||||
to_template.update({'UNINHABITED_AREA': f"{q.fUninhabited_Area: .1f}"})
|
|
||||||
if q.sManagement_Co == u"N/A":
|
|
||||||
to_template.update({'MANAGEMENT_CO': "Нет данных"})
|
|
||||||
else:
|
|
||||||
to_template.update({'MANAGEMENT_CO': q.sManagement_Co})
|
|
||||||
if q.iElevators < 0:
|
|
||||||
to_template.update({'NUM_ELEVATORS': "Нет данных"})
|
|
||||||
else:
|
|
||||||
to_template.update({'NUM_ELEVATORS': q.iElevators})
|
|
||||||
if q.fResidential_Area < 0:
|
|
||||||
to_template.update({'RESIDENTIAL_AREA': "Нет данных"})
|
|
||||||
else:
|
|
||||||
to_template.update({'RESIDENTIAL_AREA': f"{q.fResidential_Area: .1f}"})
|
|
||||||
if q.iNum_Residents < 0:
|
|
||||||
to_template.update({'NUM_RESIDENTS': "Нет данных"})
|
|
||||||
else:
|
|
||||||
to_template.update({'NUM_RESIDENTS': q.iNum_Residents})
|
|
||||||
if q.fPrivate_Area < 0:
|
|
||||||
to_template.update({'PRIVATE_AREA': "Нет данных"})
|
|
||||||
else:
|
|
||||||
to_template.update({'PRIVATE_AREA': f"{q.fPrivate_Area:.1f}"})
|
|
||||||
if q.iNum_Accounts < 0:
|
|
||||||
to_template.update({'NUM_ACCOUNTS': "Нет данных"})
|
|
||||||
else:
|
|
||||||
to_template.update({'NUM_ACCOUNTS': q.iNum_Accounts})
|
|
||||||
if q.iCommissioning_year == "N/A":
|
|
||||||
to_template.update({'COMMISSIONING_YEAR': "Нет данных"})
|
|
||||||
else:
|
|
||||||
to_template.update({'COMMISSIONING_YEAR': q.iCommissioning_year})
|
|
||||||
if q.fGovernment_Area < 0:
|
|
||||||
to_template.update({'GOVERNMENT_AREA': "Нет данных"})
|
|
||||||
else:
|
|
||||||
to_template.update({'GOVERNMENT_AREA': f"{q.fGovernment_Area: .1f}"})
|
|
||||||
if q.fCondition_House < 0:
|
|
||||||
to_template.update({'CONDITION_HOUSE': "Нет данных"})
|
|
||||||
else:
|
|
||||||
to_template.update({'CONDITION_HOUSE': f"{q.fCondition_House: .0f}%"})
|
|
||||||
if q.fCondition_Foundation < 0:
|
|
||||||
to_template.update({'CONDITION_FOUNDATION': "Нет данных"})
|
|
||||||
else:
|
|
||||||
to_template.update({'CONDITION_FOUNDATION': f"{q.fCondition_Foundation: .0f}%"})
|
|
||||||
if q.fCondition_Walls < 0:
|
|
||||||
to_template.update({'CONDITION_WALL': u"Нет данных"})
|
|
||||||
else:
|
|
||||||
to_template.update({'CONDITION_WALL': f"{q.fCondition_Walls: .0f}%"})
|
|
||||||
if q.fCondition_Overlap < 0:
|
|
||||||
to_template.update({'CONDITION_OVERLAP': "Нет данных"})
|
|
||||||
else:
|
|
||||||
to_template.update({'CONDITION_OVERLAP': f"{q.fCondition_Overlap: .0f}%"})
|
|
||||||
if q.fMunicipal_Area < 0:
|
|
||||||
to_template.update({'MUNICIPAL_AREA': "Нет данных"})
|
|
||||||
else:
|
|
||||||
to_template.update({'MUNICIPAL_AREA': f"{q.fMunicipal_Area: .1f}"})
|
|
||||||
to_template.update({'URL2REFOEMAGKH': q.sURL})
|
|
||||||
# Пробуем получить базовую серию дома. Для этого рекурсивно раскручиваем записи в таблице Seria_Info
|
# Пробуем получить базовую серию дома. Для этого рекурсивно раскручиваем записи в таблице Seria_Info
|
||||||
idd = q.kSeria_Link_id
|
idd = q.kSeria_Link_id
|
||||||
all_apartment_in_seria = False
|
all_apartment_in_seria = False
|
||||||
|
q1 = None # страховка: если у здания нет серии, q1 остаётся None
|
||||||
while idd is not None:
|
while idd is not None:
|
||||||
# рекурсивно движемся по дерву потомок→предок серий домов.
|
# рекурсивно движемся по дерву потомок→предок серий домов.
|
||||||
q1 = Seria_Info.objects.get(id=idd)
|
q1 = Seria_Info.objects.select_related('kRoot').get(id=idd)
|
||||||
# получаем список типовых квартир для серии дома с id == idd
|
# получаем список типовых квартир для серии дома с id == idd
|
||||||
all_apartment_in_seria = Apartment_Type.objects.filter(kSeria_id=idd).order_by("iSort")
|
all_apartment_in_seria = Apartment_Type.objects.filter(kSeria_id=idd).order_by("iSort")
|
||||||
# проверяем есть-ли что-то в списке типовых квартир.
|
# проверяем есть-ли что-то в списке типовых квартир.
|
||||||
@@ -258,39 +223,42 @@ def get_address(request: HttpRequest) -> HttpResponse:
|
|||||||
# проверяем, был ли получен список квартир
|
# проверяем, был ли получен список квартир
|
||||||
if not bool(all_apartment_in_seria):
|
if not bool(all_apartment_in_seria):
|
||||||
# Если списка квартир нет, нужно получить список ближайших адресов, для которых есть цены.
|
# Если списка квартир нет, нужно получить список ближайших адресов, для которых есть цены.
|
||||||
q = Building_Info.objects.raw(
|
# Ищем здания с ненулевыми координатами и у которых через серию есть типовые квартиры.
|
||||||
f"SELECT"
|
q = list(
|
||||||
f" oknardia_building_info.sAddress, oknardia_building_info.id,"
|
Building_Info.objects
|
||||||
f" oknardia_building_info.fGeoCode_Longitude, oknardia_building_info.fGeoCode_Latitude,"
|
.exclude(fGeoCode_Longitude=0.0)
|
||||||
f" oknardia_seria_info.kRoot_id, oknardia_seria_info.sName,"
|
.exclude(fGeoCode_Latitude=0.0)
|
||||||
f" COUNT(oknardia_apartment_type.sNameApartment) AS NumApart,"
|
.filter(kSeria_Link__kRoot__apartment_type__isnull=False)
|
||||||
f" ABS({geocode[0]} - oknardia_building_info.fGeoCode_Latitude)"
|
.select_related('kSeria_Link', 'kSeria_Link__kRoot')
|
||||||
f" + ABS({geocode[1]} - oknardia_building_info.fGeoCode_Longitude) AS R2 "
|
.annotate(
|
||||||
f"FROM oknardia_building_info"
|
R2=ExpressionWrapper(
|
||||||
f" INNER JOIN oknardia_seria_info"
|
Abs(float(geocode[0]) - F('fGeoCode_Latitude'))
|
||||||
f" ON oknardia_building_info.kSeria_Link_id = oknardia_seria_info.id"
|
+ Abs(float(geocode[1]) - F('fGeoCode_Longitude')),
|
||||||
f" INNER JOIN oknardia_apartment_type"
|
output_field=FloatField()
|
||||||
f" ON oknardia_seria_info.kRoot_id = oknardia_apartment_type.kSeria_id "
|
),
|
||||||
f"WHERE oknardia_building_info.fGeoCode_Longitude <> 0.0"
|
NumApart=Count('kSeria_Link__kRoot__apartment_type', distinct=True),
|
||||||
f" AND oknardia_building_info.fGeoCode_Latitude <> 0.0 "
|
sName=F('kSeria_Link__sName'),
|
||||||
f"GROUP BY oknardia_seria_info.sName,"
|
kRoot_id=F('kSeria_Link__kRoot_id'),
|
||||||
f" oknardia_seria_info.kRoot_id,"
|
)
|
||||||
f" oknardia_building_info.id,"
|
.order_by('R2')[:5]
|
||||||
f" oknardia_building_info.sAddress,"
|
)
|
||||||
f" oknardia_building_info.fGeoCode_Longitude,"
|
|
||||||
f" oknardia_building_info.fGeoCode_Latitude "
|
|
||||||
f"ORDER BY R2 "
|
|
||||||
f"LIMIT 5;")
|
|
||||||
q = list(q)
|
|
||||||
for i in q:
|
for i in q:
|
||||||
|
# Пересчитываем на реальное геодезическое расстояние (км)
|
||||||
i.R2 = get_geo_distance(i.fGeoCode_Longitude, i.fGeoCode_Latitude, geocode[0], geocode[1])
|
i.R2 = get_geo_distance(i.fGeoCode_Longitude, i.fGeoCode_Latitude, geocode[0], geocode[1])
|
||||||
# print i.id, i.sAddress, i.sName, i.R2
|
# print i.id, i.sAddress, i.sName, i.R2
|
||||||
# сортируем список по R2 (дистанция от текущего адреса, до домов по которым данные известны)
|
# сортируем список по R2 (дистанция от текущего адреса, до домов по которым данные известны)
|
||||||
sorted(q, key=lambda item: item.R2)
|
q = sorted(q, key=lambda item: item.R2) # NOTE: sorted() возвращает новый список
|
||||||
to_template.update({'NEAR_KNOWN_ADDRESS': q})
|
to_template.update({'NEAR_KNOWN_ADDRESS': q})
|
||||||
# print q
|
# print q
|
||||||
to_template.update({'SERIA_BASE': q1.sName,
|
# Определяем корневую серию для формирования канонического URL
|
||||||
'addr': addr,
|
# Если у серии есть kRoot — берём его, иначе сама q1 является корневой
|
||||||
'addr_T': pytils.translit.slugify(addr),
|
seria_root = (q1.kRoot if (q1 and q1.kRoot_id) else q1)
|
||||||
'ticks': float(time.perf_counter()-time_start)})
|
to_template.update({
|
||||||
|
'SERIA_BASE': q1.sName if q1 else "",
|
||||||
|
'BASE_SERIA_ID': seria_root.id if seria_root else "",
|
||||||
|
'BASE_SERIA_LAT': pytils.translit.slugify((seria_root.sName or "").strip()).lower() if seria_root else "",
|
||||||
|
'addr': addr,
|
||||||
|
'addr_T': pytils.translit.slugify(addr),
|
||||||
|
'ticks': float(time.perf_counter() - time_start),
|
||||||
|
})
|
||||||
return render(request, "popup/popup_show_apartment_variants.html", to_template)
|
return render(request, "popup/popup_show_apartment_variants.html", to_template)
|
||||||
|
|||||||
Reference in New Issue
Block a user