mod: изменение роутинга для страницы ценовых предложений

This commit is contained in:
2026-04-30 23:20:34 +03:00
parent a84e780710
commit 61f69c21f7
7 changed files with 114 additions and 37 deletions

View File

@@ -84,11 +84,14 @@ urlpatterns = [
prices.redirect_one_win_price_legacy), # LEGACY-URL: 301 -> КАНОНИЧЕСКИЙ ПУТЬ
re_path(r'^next_price_one_flap_frame/idW(?P<win_id>\d+)N(?P<frame_begin_n>\d+)\S*$',
prices.next_one_win_price), # ПОДГРУЖАЕМЫЙ ФРЕЙМ С ЦЕНОВЫМИ ПРЕДЛОЖЕНИЯМИ ДЛЯ ОДНОГО ПРОЕМА
# --- Ценовая выдача
re_path(r'^(?P<build_id>\d+)/(?P<apart_id>\d+)/(?P<slug>[\s\S]*)$', prices.report_price),
# --- Подгружаемый фрейм ценовая выдачи
# --- Ценовая выдача (НОВЫЙ РОУТИНГ)
# Новый красивый URL с префиксами seriaID, appartAD, addressID
re_path(r'^price/seriaID(?P<seria_id>\d+)--(?P<seria_slug>[^/]+)/appartID(?P<apart_id>\d+)/addressID(?P<address_id>\d+)--(?P<address_slug>[^/]+)/?$', prices.report_price_new),
# --- Подгружаемый фрейм ценовой выдачи (оставляем старый)
re_path(r'^next_price_frame/idA(?P<apart_id>\d+)MDPO(?P<mount_dim_per_offer>\d+)LON(?P<address_longitude>\d+)'
r'LAT(?P<address_latitude>\d+\.*\d*)N(?P<frame_begin_n>\d+\.*\d*)\S*[/*]$', prices.next_price_frame),
# --- Старый URL ценовой выдачи (добавим редирект)
re_path(r'^(?P<build_id>\d+)/(?P<apart_id>\d+)/(?P<slug>[\s\S]*)$', prices.report_price_legacy_redirect),
# СРАВНЕНИЕ ОКОННЫХ НАБОРОВ
re_path(r'^compare_sets/(?P<to_compare>[\s\S]+|.*)$', report1.compare_offers), # дубль для старых ссылок
re_path(r'^compare_offers/(?P<to_compare>[\s\S]+|.*)$', report1.compare_offers),
@@ -116,4 +119,3 @@ if DEBUG:
urlpatterns = [path('__debug__/', include('debug_toolbar.urls')), *urlpatterns]
# Медиа-файлы
urlpatterns += static(MEDIA_URL, document_root=MEDIA_ROOT)

View File

@@ -224,7 +224,10 @@ $(function () { // инициализация и обработка попове
<div class="col-sm-3 visible-md visible-lg ap_list">
<h6>Другие типовые квартиры в&nbsp;этом доме:</h6>
<ul>{% for I_APART in APARTMENT_IN_BUILDING %}
{% if I_APART.APT_ID == '!' %}<li>{{ I_APART.APT_NAME|safe }}</li>{% else %}<li><a href="/{{ BUILD_ID }}/{{ I_APART.APT_ID }}/{{ ADDRESS_T }}">{{ I_APART.APT_NAME|safe }}</a></li>{% endif %}{% endfor %}
{% if I_APART.APT_ID == '!' %}<li>{{ I_APART.APT_NAME|safe }}</li>{% else %}
{# Новый формат роутинга для перехода между квартирами #}
<li><a href="/price/seriaID{{ BASE_SERIA_ID }}--{{ BASE_SERIA_LAT }}/appartID{{ I_APART.APT_ID }}/addressID{{ BUILD_ID }}--{{ ADDRESS_T }}/">{{ I_APART.APT_NAME|safe }}</a></li>
{% endif %}{% endfor %}
</ul>
<a href="/catalog/seria/{{ BASE_SERIA_LAT }}/all{{ BASE_SERIA_ID }}">Информация по серии {{ BASE_SERIA }}</a>
</div>

View File

@@ -149,19 +149,15 @@ TechArticle: описывает страницу как технический
<h3 class="header">Оконные проёмы в&nbsp;типовых квартирах <nobr>серии {{ THIS_SERIA_NAME }}</nobr></h3>
</div>
<div class="col-lg-8 col-xs-12 col-md-offset-1">
<!--- прешаблон начало ---><table style="padding:2px;">{% templatetag openblock %} for row in TABLE_OF_WINDOWS {% templatetag closeblock %}
<tr class="tr2">
<td>{% templatetag openvariable %} row.APART_NAME|safe {% templatetag closevariable %}</td>{% templatetag openblock %} for col in row.WIN_IN_APART {% templatetag closeblock %}
<td class="cntr">{% templatetag openblock %} if col.WIN_ID {% templatetag closeblock %}<nobr title="{% templatetag openvariable %} col.WIN_Q {% templatetag closevariable %} × {% templatetag openvariable %} col.WIN_DESCRIPTION {% templatetag closevariable %}: {% templatetag openvariable %} col.WIN_WIDTH {% templatetag closevariable %}шт.: {% templatetag openvariable %} col.WIN_HEIGHT {% templatetag closevariable %} (Ш×В, см.). Схема открывания: {% templatetag openvariable %} col.WIN_FLAPCFG {% templatetag closevariable %}">{% templatetag openblock %} for I_II in col.WIN_NUM {% templatetag closeblock %}<span style="background-image:url('{% static 'img/svg/mark' %}{% templatetag openvariable %} I_II {% templatetag closevariable %}.svg');">&nbsp;</span>{% templatetag openblock %} endfor {% templatetag closeblock %}</nobr>{% templatetag openblock %} else {% templatetag closeblock %}—{% templatetag openblock %} endif {% templatetag closeblock %}</td>{% templatetag openblock %} endfor {% templatetag closeblock %}
<td style="background:#f9f9f9;"><a href="#{% templatetag openvariable %} row.APART_ID {% templatetag closevariable %}" class="badge" title="Оконных предложений для квартиры: {% templatetag openvariable %} row.NUM_OFFERS {% templatetag closevariable %}"><small class="glyphicon glyphicon-tags" aria-hidden="true"></small>&nbsp;{% templatetag openvariable %} row.NUM_OFFERS {% templatetag closevariable %}</a></td>
</tr>{% templatetag openblock %} endfor {% templatetag closeblock %}
<tr class="trZ">
<td style="font-size: xx-small;vertical-align:text-top">© 2015-{% now "Y" %}, данные: oknardia.ru</td>{% templatetag openblock %} for i in WIN_OFFER_AND_MERCHANT {% templatetag closeblock %}
<td class="cntr" style="background:#f9f9f9;"><a href="/catalog/standard_opening/price-{% templatetag openvariable %} i.WIN_W|floatformat:0 {% templatetag closevariable %}0x{% templatetag openvariable %} i.WIN_H|floatformat:0 {% templatetag closevariable %}0mm-tip{% templatetag openvariable %} i.WIN_ID {% templatetag closevariable %}" class="badge" title="Ценовых предложений для окна: {% templatetag openvariable %} i.WIN_OFFER {% templatetag closevariable %}"><small class="glyphicon glyphicon-tags" aria-hidden="true"></small>&nbsp;{% templatetag openvariable %} i.WIN_OFFER {% templatetag closevariable %}</a></td>{% templatetag openblock %} endfor {% templatetag closeblock %}
<td></td>
</tr>
</table>
<!--- прешаблон конец ---></div>
{# --- ОСНОВНОЙ БЛОК С ТАБЛИЦЕЙ --- #}
{# Если есть кешированный файл, включаем его. Иначе - рендерим блок на лету. #}
{% if PRE_RENDERED_INCLUDE_PATH %}
{% include PRE_RENDERED_INCLUDE_PATH %}
{% else %}
{% include "seria_info/all_seria_info_pre_light_include.html" %}
{% endif %}
{# --- КОНЕЦ ОСНОВНОГО БЛОКА --- #}
</div>
</div>
<div class="row">
@@ -182,7 +178,9 @@ TechArticle: описывает страницу как технический
</div>
<div class="col-md-7 col-lg-offset-1">
<p><small>Чтобы посмотреть цены на&nbsp;установку и&nbsp;замену окон от&nbsp;партнёров &laquo;Окнардия&raquo; в&nbsp;своей квартире: найдите дом на&nbsp;карте; кликните на&nbsp;него; перейдите по&nbsp;ссылке &laquo;Смотреть коммерческие предложения&raquo;. При необходимости смените типовую планировку квартиры (на&nbsp;странице ценовой выдачи, справа от&nbsp;изображения типовых проёмов и&nbsp;схем открывания).</small></p>
<div style="height:350px;">{% include 'seria_info/geo_map.html' %}</div>
<div style="height:350px;">
{% include 'seria_info/geo_map.html' with first_apart_id=TABLE_OF_WINDOWS.0.APART_ID %}
</div>
<div style="font-size: xx-small;float: right">© 2015-{% now "Y" %}, данные: oknardia.ru</div>
</div>
@@ -208,8 +206,8 @@ TechArticle: описывает страницу как технический
</div>
</div>
<div class="row">
{% templatetag openblock %}include "report/report_last_user_visit.html" {% templatetag closeblock %}
{% templatetag openblock %} include "report/report_log_user_visit.html" {% templatetag closeblock %}
{% include "report/report_last_user_visit.html" %}
{% include "report/report_log_user_visit.html" %}
</div>
</div>
{% endblock %}
{% endblock %}

View File

@@ -2,8 +2,7 @@
{% block Top_JS5 %}<script src="https://api-maps.yandex.ru/2.1/?lang=ru_RU" type="text/javascript"></script>
{% if MAP_JS %}<script src="{% static '' %}{{ MAP_JS }}" charset="utf-8" type="text/javascript"></script>{% else %}<script type="text/javascript">
let points = [{% for count in DATA4GEO %}{% if forloop.last %}[{{ count.LONGITUDE|stringformat:"f" }},{{ count.LATITUDE|stringformat:"f" }}]{% else %}[{{ count.LONGITUDE|stringformat:"f" }},{{ count.LATITUDE|stringformat:"f" }}],{% endif %}{% endfor %}];
let forURL = [{% for count in DATA4GEO %}{{ count.ADDR_ID }}{# ,rus: '{{ count.ADDR_RUS }}',lat: '{{ count.ADDR_LAT }}' #}{% if not forloop.last %},{% endif %}{% endfor %}];
let forURL = [{% for count in DATA4GEO %}{ id: {{ count.ADDR_ID }} }{% if not forloop.last %},{% endif %}{% endfor %}];
ymaps.ready(function () {
let myMap = new ymaps.Map('SeriaMap', {
@@ -23,15 +22,23 @@ ymaps.ready(function () {
gridSize: 80
});
geoObjects = [];
add_str1 = '<a href="/';
add_str2 = '/0/">Смотреть коммерческие предложения</a>';
add_str3 = '<b>Здание серии {{ THIS_SERIA_NAME }}</b>';
const linkText = 'Смотреть коммерческие предложения</a>';
const hintText = '<b>Здание серии {{ THIS_SERIA_NAME }}</b>';
const apartmentId = {{ first_apart_id|default:0 }};
const seriaId = {{ THIS_SERIA_ID }};
const seriaSlug = '{{ THIS_SERIA_NAME_T }}';
// Данные передаются в конструктор метки.
for(var i = 0, len = points.length; i < len; i++) {
const buildingId = forURL[i].id;
// Формируем SEO-URL для каждой метки
const balloonLink = `<a href="/price/seriaID${seriaId}--${seriaSlug}/appartID${apartmentId}/addressID${buildingId}--null">`;
geoObjects[i] = new ymaps.Placemark( points[i],
{ // Содержимое иконки, балуна и хинта.
balloonContent: add_str1 + forURL[i] + add_str2,
hintContent: add_str3
balloonContent: balloonLink + linkText,
hintContent: hintText
},
{ preset:'islands#circleIcon',iconColor: 'silver'} );
geoObjects[i].events

View File

@@ -101,7 +101,7 @@ def catalog_seria_info(
is_hard_template = True
else:
# В PROD используем существующий pre-render include при наличии на диске.
light_template = f"{PATH_FOR_SERIA_INFO_HTML_INCLUDE}{seria_id}_id.html"
light_template = f"seria_info/prepared/{seria_id}_id.html"
light_template_w_path = f"{TEMPLATES[0]['DIRS'][0]}/{light_template}"
is_hard_template = not os.path.isfile(light_template_w_path)
@@ -125,10 +125,6 @@ def catalog_seria_info(
.distinct()
)
if is_hard_template:
# Для "тяжелого" шаблона нужны большие картинки схем окон.
to_template.update(get_flaps_for_big_pictures(list_win_in_seria))
window_ids = [win.id for win in list_win_in_seria]
apartments_in_seria = list(
Apartment_Type.objects.filter(kSeria_id=seria_id)
@@ -209,16 +205,26 @@ def catalog_seria_info(
# Для "тяжелого" шаблона получаем навигацию, карту и график, затем кэшируем pre-render.
if is_hard_template:
to_template.update(get_flaps_for_big_pictures(list_win_in_seria))
seria_id, for_seria_nav = seria_nav(seria_id)
to_template.update(for_seria_nav)
to_template.update(seria_info_year(seria_id))
to_template.update(seria_info_geo_code(seria_id))
if not DEBUG:
string_prerender = render_to_string("seria_info/all_seria_info_pre_light.html", to_template)
# Пре-рендер происходит только для "включаемого" шаблона,
# чтобы избежать дублирования базовой разметки.
string_prerender = render_to_string("seria_info/all_seria_info_pre_light_include.html", to_template)
with open(light_template_w_path, "w", encoding="utf-8") as file:
file.write(string_prerender)
# Основной шаблон будет просто включать в себя уже готовый HTML
light_template = "seria_info/all_seria_info_pre_light.html"
else:
to_template.update({"THIS_SERIA_NAME": q_seria.sName})
# Указываем путь к кешированному файлу для include
to_template.update({"PRE_RENDERED_INCLUDE_PATH": light_template})
# Основной шаблон должен быть один и тот же
light_template = "seria_info/all_seria_info_pre_light.html"
_append_visit_context(to_template, request, time_start)
return render(request, light_template, to_template)

View File

@@ -141,8 +141,18 @@ class BuildingOffersSitemap(Sitemap):
yield (building.id, apart_id, pytils.translit.slugify(building.sAddress))
def location(self, item: tuple[int, int, str]) -> str:
build_id, apart_id, slug = item
return f"/{build_id}/{apart_id}/{slug}"
build_id, apart_id, address_slug = item
# Получаем объект здания и серию для формирования нового роутинга
try:
building = Building_Info.objects.select_related('kSeria_Link__kRoot').get(id=build_id)
seria = building.kSeria_Link.kRoot
seria_id = seria.id
seria_slug = pytils.translit.slugify((seria.sName or "").strip()).lower()
except Exception:
# fallback на старый роутинг, если что-то пошло не так
return f"/{build_id}/{apart_id}/{address_slug}"
# Новый формат: /price/seriaID<seria_id>--<seria_slug>/appartID<apart_id>/addressID<address_id>--<address_slug>/
return f"/price/seriaID{seria_id}--{seria_slug}/appartID{apart_id}/addressID{build_id}--{address_slug}/"
def lastmod(self, item: tuple[int, int, str]) -> datetime:
return self.lastmod_value

View File

@@ -1009,3 +1009,54 @@ def next_price_frame(request: HttpRequest, apart_id: str = "1", mount_dim_per_o
'ADDRESS_LON': address_longitude,
'ticks': float(time.perf_counter() - time_start)})
return render(request, "price/price_list_frame.html", to_template)
def report_price_new(request, seria_id, seria_slug, apart_id, address_id, address_slug):
"""
Новый view для ценовой выдачи по новому роутингу.
:param seria_id: ID серии (Seria_Info)
:param seria_slug: slug серии (транслит)
:param apart_id: ID типа квартиры (Apartment_Type)
:param address_id: ID адреса (Building_Info)
:param address_slug: slug адреса (транслит)
"""
from oknardia.models import Building_Info, Apartment_Type, Seria_Info
from django.shortcuts import redirect
# Проверяем, что все объекты существуют
try:
print(f"seria_id: {seria_id}, seria_slug: {seria_slug}, apart_id: {apart_id}, address_id: {address_id}, address_slug: {address_slug}")
seria = Seria_Info.objects.get(id=seria_id)
building = Building_Info.objects.get(id=address_id)
# apartment = Apartment_Type.objects.get(id=apart_id)
except Exception:
return redirect("/")
# Проверяем slug'и, если не совпадает — делаем 301 на канонический URL (новый формат)
seria_slug_real = pytils.translit.slugify((seria.sName or "").strip()).lower()
address_slug_real = pytils.translit.slugify((building.sAddress or "").strip()).lower()
if seria_slug != seria_slug_real or address_slug != address_slug_real:
# Новый формат: /price/seriaID<seria_id>--<seria_slug>/appartAD<apart_id>/addressID<address_id>--<address_slug>/
return redirect(f"/price/seriaID{seria_id}--{seria_slug_real}/appartID{apart_id}/addressID{address_id}--{address_slug_real}/", permanent=True)
# Вызываем старую логику выдачи (используем report_price)
# В старом view: build_id = address_id, apart_id = apart_id, slug = address_slug
return report_price(request, build_id=address_id, apart_id=apart_id, slug=address_slug)
def report_price_legacy_redirect(request, build_id, apart_id, slug):
try:
building = Building_Info.objects.select_related('kSeria_Link__kRoot').get(id=build_id)
seria = building.kSeria_Link.kRoot
# Если apart_id == 0, ищем минимальный валидный ID квартиры для этой серии
if int(apart_id) == 0:
min_apart = Apartment_Type.objects.filter(kSeria_id=seria.id).order_by('id').first()
if min_apart:
apart_id = min_apart.id
except Exception:
return redirect("/")
import pytils
seria_slug = pytils.translit.slugify((seria.sName or "").strip()).lower()
address_slug = pytils.translit.slugify((building.sAddress or "").strip()).lower()
# Новый формат: /price/seriaID<seria_id>--<seria_slug>/appartID<apart_id>/addressID<build_id>--<address_slug>/
return redirect(f"/price/seriaID{seria.id}--{seria_slug}/appartID{apart_id}/addressID{build_id}--{address_slug}/", permanent=True)
seria_slug = pytils.translit.slugify((seria.sName or "").strip()).lower()
address_slug = pytils.translit.slugify((building.sAddress or "").strip()).lower()
return redirect(f"/price/seriaID{seria.id}--{seria_slug}/appartID{apart_id}/addressID{build_id}--{address_slug}/", permanent=True)