mod: рефакторинг "каталог типовых проёмов"
This commit is contained in:
@@ -16,7 +16,7 @@
|
|||||||
* Рефакторинг `catalog_seria` (`/catalog/seria/`): raw SQL ⟶ ORM для списка корневых серий, подготовка данных упрощена, хвост контекста с визитами и `ticks` вынесен в общий helper внутри `catalog_series.py`.
|
* Рефакторинг `catalog_seria` (`/catalog/seria/`): raw SQL ⟶ ORM для списка корневых серий, подготовка данных упрощена, хвост контекста с визитами и `ticks` вынесен в общий helper внутри `catalog_series.py`.
|
||||||
* Рефакторинг `catalog_seria_info` и связанных функций в `catalog_series.py`: raw SQL ⟶ ORM (`catalog_seria_info`, `seria_nav`, `seria_info_year`, `seria_info_geo_code`), снижена нагрузка на БД за счёт предвыборки и переиспользования агрегатов (`quantities_by_pair`, `offers_by_window`), добавлены безопасные fallback-значения для пустых выборок, включена потоковая обработка `iterator(chunk_size=500)` для гео-данных, обновлены комментарии и docstring под фактическую логику (таблица окон, pre-render light/heavy шаблонов, гео+статистика серии).
|
* Рефакторинг `catalog_seria_info` и связанных функций в `catalog_series.py`: raw SQL ⟶ ORM (`catalog_seria_info`, `seria_nav`, `seria_info_year`, `seria_info_geo_code`), снижена нагрузка на БД за счёт предвыборки и переиспользования агрегатов (`quantities_by_pair`, `offers_by_window`), добавлены безопасные fallback-значения для пустых выборок, включена потоковая обработка `iterator(chunk_size=500)` для гео-данных, обновлены комментарии и docstring под фактическую логику (таблица окон, pre-render light/heavy шаблонов, гео+статистика серии).
|
||||||
* Добавлена management-команда `regenerate_seria_prerender` для оффлайн-пересборки pre-render шаблонов `catalog_seria_info` (все или выбранные root-серии), с режимами `--dry-run` и `--force`; серверный reload (Gunicon? uWSGI или что там еще будет) должен быть вынесен из кода приложения в оркестрацию (cron/systemd/deploy step).
|
* Добавлена management-команда `regenerate_seria_prerender` для оффлайн-пересборки pre-render шаблонов `catalog_seria_info` (все или выбранные root-серии), с режимами `--dry-run` и `--force`; серверный reload (Gunicon? uWSGI или что там еще будет) должен быть вынесен из кода приложения в оркестрацию (cron/systemd/deploy step).
|
||||||
*
|
* Рефакторинг `standard_opening`: raw SQL -> ORM, упрощена дедублекация, убраны лишние запросы и переменные контекста, добавлены комментарии, SEO-описание и keywords, стандартизирован хвост контекста с визитами и `ticks` через общий helper внутри `catalog_openings.py`.
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
@@ -38,7 +38,6 @@
|
|||||||
* [`MANAGEMENT_RUNBOOK.md`](MANAGEMENT_RUNBOOK.md) – единый runbook по management-командам и batch-операциям.
|
* [`MANAGEMENT_RUNBOOK.md`](MANAGEMENT_RUNBOOK.md) – единый runbook по management-командам и batch-операциям.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
Легаси-материалы старого README, которые могут быть полезны для понимания устройства проекта и его
|
Легаси-материалы старого README, которые могут быть полезны для понимания устройства проекта и его
|
||||||
администрирования, а также для будущей реорганизации документации.
|
администрирования, а также для будущей реорганизации документации.
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
{% extends "base.html" %}{% load static %}
|
{% extends "base.html" %}{% load static %}
|
||||||
|
|
||||||
{% block Title %} Стандартные оконные проёмы типовых серий домов :: каталог{% endblock %}
|
{% block Title %}Стандартные оконные проёмы и балконные блоки для типовых серий домов: размеры, схемы, каталог{% endblock %}
|
||||||
|
|
||||||
{% block Add_Body_Attribute %} style="padding-top:70px;"{% endblock %}
|
{% block Add_Body_Attribute %} style="padding-top:70px;"{% endblock %}
|
||||||
|
|
||||||
{% block Description %}Каталог «Окнардия»: стандартные оконные проёмы типовых серий домов...{% endblock %}
|
{% block Description %}Найдите точные размеры (ширину и высоту) и схемы стандартных оконных проёмов и балконных блоков для самых распространённых типовых серий домов в России. Удобный каталог для подбора окон.{% endblock %}
|
||||||
|
|
||||||
{% block Keywords %}оконные проёмы, стандартные окна, стандартные оконные проемы, каталог, каталог оконных проёмов, oknardia, окнардия {{ META_KEYWORDS|default:"" }} {% endblock %}
|
{% block Keywords %}типовые окна, размеры окон, оконные проемы, балконный блок, стандартные окна, размеры окон в панельном доме, серия дома, каталог окон, схемы открывания окон, П-44, II-49, 1-515, oknardia, окнардия{% endblock %}
|
||||||
|
|
||||||
{% block Date4Meta %}{{ PUB_DAT|date:"c" }}{% endblock %}
|
{% block Date4Meta %}{{ PUB_DAT|date:"c" }}{% endblock %}
|
||||||
|
|
||||||
@@ -58,18 +58,18 @@
|
|||||||
<ol class="breadcrumb">
|
<ol class="breadcrumb">
|
||||||
<li><a href="/">Главная</a></li>
|
<li><a href="/">Главная</a></li>
|
||||||
<li><a href="/catalog">Каталог</a></li>
|
<li><a href="/catalog">Каталог</a></li>
|
||||||
<li>Оконные проёмы и балконные блоки</li>
|
<li>Оконные проёмы и балконные блоки</li>
|
||||||
</ol>
|
</ol>
|
||||||
</div>
|
</div>
|
||||||
</div>{# <!--- Хлебные крошки: КОНЕЦ ---> #}
|
</div>{# <!--- Хлебные крошки: КОНЕЦ ---> #}
|
||||||
<dIv class="row">
|
<dIv class="row">
|
||||||
{# ПЕРВЫЙ РАЗДЕЛ #}<div class="col-md-9 col-xs-8">
|
{# ПЕРВЫЙ РАЗДЕЛ #}<div class="col-md-9 col-xs-8">
|
||||||
<h1>Стандартные оконные проёмы и балконные блоки</h1>
|
<h1>Стандартные оконные проёмы и балконные блоки</h1>
|
||||||
<p>Ценовая выдача «Окнардии» основана на базе стандартных оконных проёмов в типовых сериях домов. Для каждого проёма существуют рекомендованные организациями-проектировщиками схемы открывание, но партнёры «Окнардии» могут предложить свои, более расширенные или наоборот сокращенные. В таблице приведены параметры стандартных проёмов базы.</p>
|
<p>Ценовая выдача «Окнардии» основана на базе стандартных оконных проёмов в типовых сериях домов. Для каждого проёма существуют рекоме­ндованные органи­зациями-проекти­ровщиками схемы открывание, но партнёры «Окнардии» могут предложить свои, более расширенные или наоборот сокращенные. В таблице приведены параметры стандартных проёмов базы.</p>
|
||||||
</div>
|
</div>
|
||||||
{# реклама Oknardia 250x250 СБОКУ #}<div class="col-md-3 col-xs-4 float-right">{% include "ad/bannet-250x250.html" %}</div>
|
{# реклама Oknardia 250x250 СБОКУ #}<div class="col-md-3 col-xs-4 float-right">{% include "ad/bannet-250x250.html" %}</div>
|
||||||
<div class="col-md-11 col-xs-12 catalog scrolled">
|
<div class="col-md-11 col-xs-12 catalog scrolled">
|
||||||
<table class="table-striped table-hover table-responsive flap-catalog">
|
<table class="table-striped table-hover table-responsive flap-catalog" style="margin-top: 1em">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th colspan="3" class="c">Размеры (мм)</th>
|
<th colspan="3" class="c">Размеры (мм)</th>
|
||||||
@@ -98,24 +98,15 @@
|
|||||||
<td>{{ i.DESCRIPTION }}</td>
|
<td>{{ i.DESCRIPTION }}</td>
|
||||||
<td>{% for j in i.INCLUDING_IN_SERIA %}<a href="/catalog/seria/{{ j.NAME_T }}/all{{ j.ID }}">{{ j.NAME }}</a>{% if not forloop.last %}, {% endif %}{% endfor %}</td>
|
<td>{% for j in i.INCLUDING_IN_SERIA %}<a href="/catalog/seria/{{ j.NAME_T }}/all{{ j.ID }}">{{ j.NAME }}</a>{% if not forloop.last %}, {% endif %}{% endfor %}</td>
|
||||||
<td><a class="btn btn-default btn-xs" href="/tsena-odnogo-okna/{{ i.W|stringformat:".0f" }}x{{ i.H|stringformat:".0f" }}mm/tip{{ i.ID }}">цены</a></td>
|
<td><a class="btn btn-default btn-xs" href="/tsena-odnogo-okna/{{ i.W|stringformat:".0f" }}x{{ i.H|stringformat:".0f" }}mm/tip{{ i.ID }}">цены</a></td>
|
||||||
{# {% for j in SERIAS %}#}
|
|
||||||
{# <td>{% if j.id in i.INCLUDING_IN_SERIA %}#{% endif %}</td>{% endfor %}#}
|
|
||||||
</tr>{% endfor %}
|
</tr>{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<DIV class="col-xs-12" style="height:6em;"></DIV>
|
<DIV class="col-xs-12" style="height:6em;"></DIV>
|
||||||
</dIv>
|
</dIv>
|
||||||
|
{# --- Баннер: НАЧАЛО --- #}<div class="row"><div class="col-md-12 col-xs-12"><hr class="dotted-black" />{% include "ad/bannet-wide.html" %}</div></div>{# --- Баннер: конец --- #}
|
||||||
{# --- Баннер: НАЧАЛО --- #}
|
|
||||||
<div class="row"><div class="col-md-12 col-xs-12"><hr class="dotted-black" />{% include "ad/bannet-wide.html" %}</div></div>
|
|
||||||
{# --- Баннер: конец --- #}
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
{% include "report/report_last_user_visit.html" %}
|
{% include "report/report_last_user_visit.html" %}
|
||||||
{% include "report/report_log_user_visit.html" %}
|
{% include "report/report_log_user_visit.html" %}
|
||||||
</div>
|
</div>
|
||||||
</div>{% endblock %}
|
</div>{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,82 +1,99 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
from django.db.models import F
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
from django.http import HttpRequest, HttpResponse
|
from django.http import HttpRequest, HttpResponse
|
||||||
from oknardia.models import (
|
from oknardia.models import MountDim2Apartment
|
||||||
Seria_Info,
|
|
||||||
Win_MountDim,
|
|
||||||
)
|
|
||||||
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_flaps_for_mini_pictures
|
from web.add_func import get_flaps_for_mini_pictures
|
||||||
import time
|
import time
|
||||||
import pytils
|
import pytils
|
||||||
|
from typing import Any
|
||||||
|
from itertools import groupby
|
||||||
|
from operator import itemgetter
|
||||||
|
|
||||||
def standard_opening(request: HttpRequest) -> HttpResponse:
|
|
||||||
time_start = time.perf_counter()
|
def _make_slug(value: str) -> str:
|
||||||
to_template: dict[str, object] = {} # словарь, для передачи шаблону
|
"""Транслитерирует строку в slug (pytils)."""
|
||||||
q_seria = Seria_Info.objects.raw('SELECT oknardia_seria_info.id, oknardia_seria_info.sName '
|
return pytils.translit.slugify(value)
|
||||||
'FROM oknardia_seria_info '
|
|
||||||
'WHERE oknardia_seria_info.id = oknardia_seria_info.kRoot_id '
|
|
||||||
'ORDER BY oknardia_seria_info.sName;')
|
def _append_visit_context(to_template: dict, request: HttpRequest, time_start: float) -> None:
|
||||||
to_template.update({'SERIAS': list(q_seria)})
|
"""Дописывает в контекст стандартный хвост: визиты и время выполнения."""
|
||||||
q_win_opening = Win_MountDim.objects.raw(
|
|
||||||
'SELECT oknardia_win_mountdim.*,'
|
|
||||||
' oknardia_seria_info.sName,'
|
|
||||||
' oknardia_seria_info.id AS ID_Seria '
|
|
||||||
'FROM oknardia_win_mountdim'
|
|
||||||
' INNER JOIN oknardia_mountdim2apartment'
|
|
||||||
' ON oknardia_win_mountdim.id = oknardia_mountdim2apartment.kMountDim_id'
|
|
||||||
' RIGHT OUTER JOIN oknardia_apartment_type'
|
|
||||||
' ON oknardia_apartment_type.id = oknardia_mountdim2apartment.kApartment_id'
|
|
||||||
' RIGHT OUTER JOIN oknardia_seria_info'
|
|
||||||
' ON oknardia_apartment_type.kSeria_id = oknardia_seria_info.id '
|
|
||||||
'WHERE oknardia_seria_info.id = oknardia_seria_info.kRoot_id '
|
|
||||||
'GROUP BY oknardia_win_mountdim.iWinWidth, oknardia_win_mountdim.iWinHight,'
|
|
||||||
' oknardia_win_mountdim.bIsDoor, oknardia_win_mountdim.bIsNearDoor,'
|
|
||||||
' oknardia_win_mountdim.sFlapConfig, oknardia_win_mountdim.id,'
|
|
||||||
' oknardia_seria_info.sName, oknardia_seria_info.id '
|
|
||||||
'ORDER BY oknardia_win_mountdim.iWinWidth DESC,'
|
|
||||||
' oknardia_win_mountdim.iWinHight DESC,'
|
|
||||||
' oknardia_win_mountdim.bIsNearDoor,'
|
|
||||||
' oknardia_win_mountdim.bIsDoor,'
|
|
||||||
' oknardia_win_mountdim.id,'
|
|
||||||
' oknardia_seria_info.sName;')
|
|
||||||
list_windows_opening = []
|
|
||||||
tmp_id = 0
|
|
||||||
for i in q_win_opening:
|
|
||||||
if tmp_id != i.id:
|
|
||||||
tmp_id = i.id
|
|
||||||
image_file_name = get_flaps_for_mini_pictures(i.sFlapConfig)
|
|
||||||
list_windows_opening.append({
|
|
||||||
"ID": i.id,
|
|
||||||
"INCLUDING_IN_SERIA": [{
|
|
||||||
"ID": i.ID_Seria,
|
|
||||||
"NAME_T": pytils.translit.slugify(i.sName),
|
|
||||||
"NAME": i.sName
|
|
||||||
}],
|
|
||||||
"INCLUDING_IN_SERIA_ID": [],
|
|
||||||
"URL2IMG": image_file_name,
|
|
||||||
"FLAP_CONFIG": i.sFlapConfig,
|
|
||||||
"DESCRIPTION": i.sDescripion.split(" для")[0].split(" (")[0],
|
|
||||||
"DESCRIPTION_L": i.sDescripion,
|
|
||||||
"IS_DOOR": i.bIsDoor,
|
|
||||||
"IS_NEAR_DOOR": i.bIsNearDoor,
|
|
||||||
"H": i.iWinHight * 10,
|
|
||||||
"W": i.iWinWidth * 10
|
|
||||||
})
|
|
||||||
else:
|
|
||||||
list_windows_opening[-1]["INCLUDING_IN_SERIA"].append({
|
|
||||||
"ID": i.ID_Seria,
|
|
||||||
"NAME_T": pytils.translit.slugify(i.sName),
|
|
||||||
"NAME": i.sName
|
|
||||||
})
|
|
||||||
to_template.update({
|
to_template.update({
|
||||||
'LIST_WIN_OPENING': list_windows_opening,
|
|
||||||
# получаем последние визиты клиента через куки
|
# получаем последние визиты клиента через куки
|
||||||
'LAST_VISIT': get_last_user_visit_list(get_last_user_visit_cookies(request)[:3]),
|
'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(),
|
'LOG_VISIT': get_last_all_user_visit_list(),
|
||||||
'ticks': float(time.perf_counter() - time_start)
|
'ticks': float(time.perf_counter() - time_start),
|
||||||
})
|
})
|
||||||
return render(request, "catalog/catalog_standard_opening.html", to_template)
|
|
||||||
|
|
||||||
|
|
||||||
|
def standard_opening(request: HttpRequest) -> HttpResponse:
|
||||||
|
"""
|
||||||
|
Каталог стандартных оконных проёмов и балконных блоков.
|
||||||
|
|
||||||
|
Что делает вьюха:
|
||||||
|
- Собирает уникальные пары «проём ↔ серия» через ORM.
|
||||||
|
- Агрегирует данные для шаблона в структуру LIST_WIN_OPENING с помощью groupby.
|
||||||
|
- Добавляет в контекст последние визиты и время выполнения.
|
||||||
|
"""
|
||||||
|
time_start = time.perf_counter()
|
||||||
|
|
||||||
|
q_win_opening = (
|
||||||
|
MountDim2Apartment.objects.filter(kApartment__kSeria_id=F('kApartment__kSeria__kRoot_id'))
|
||||||
|
.values(
|
||||||
|
'kMountDim_id',
|
||||||
|
'kMountDim__sFlapConfig',
|
||||||
|
'kMountDim__sDescripion',
|
||||||
|
'kMountDim__bIsDoor',
|
||||||
|
'kMountDim__bIsNearDoor',
|
||||||
|
'kMountDim__iWinHight',
|
||||||
|
'kMountDim__iWinWidth',
|
||||||
|
'kApartment__kSeria_id',
|
||||||
|
'kApartment__kSeria__sName',
|
||||||
|
)
|
||||||
|
.distinct()
|
||||||
|
.order_by(
|
||||||
|
'-kMountDim__iWinWidth',
|
||||||
|
'-kMountDim__iWinHight',
|
||||||
|
'kMountDim__bIsNearDoor',
|
||||||
|
'kMountDim__bIsDoor',
|
||||||
|
'kMountDim_id',
|
||||||
|
'kApartment__kSeria__sName',
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
list_windows_opening: list[dict[str, Any]] = []
|
||||||
|
# Группируем результаты по ID проёма, чтобы собрать все серии, в которые он входит.
|
||||||
|
# `order_by` в запросе гарантирует, что все записи для одного проёма идут подряд.
|
||||||
|
for mount_dim_id, group in groupby(q_win_opening, key=itemgetter('kMountDim_id')):
|
||||||
|
rows_for_opening = list(group)
|
||||||
|
first_row = rows_for_opening[0]
|
||||||
|
description_full = first_row['kMountDim__sDescripion'] or ''
|
||||||
|
|
||||||
|
# Собираем список серий для текущего проёма.
|
||||||
|
serias_for_opening = [
|
||||||
|
{
|
||||||
|
'ID': row['kApartment__kSeria_id'],
|
||||||
|
'NAME_T': _make_slug(row['kApartment__kSeria__sName']),
|
||||||
|
'NAME': row['kApartment__kSeria__sName'],
|
||||||
|
}
|
||||||
|
for row in rows_for_opening
|
||||||
|
]
|
||||||
|
# Формируем данные для строки таблиц (типовой проем)
|
||||||
|
list_windows_opening.append({
|
||||||
|
'ID': mount_dim_id,
|
||||||
|
'INCLUDING_IN_SERIA': serias_for_opening,
|
||||||
|
'URL2IMG': get_flaps_for_mini_pictures(first_row['kMountDim__sFlapConfig']),
|
||||||
|
'FLAP_CONFIG': first_row['kMountDim__sFlapConfig'],
|
||||||
|
'DESCRIPTION': description_full.split(' для')[0].split(' (')[0],
|
||||||
|
'DESCRIPTION_L': description_full,
|
||||||
|
'IS_DOOR': first_row['kMountDim__bIsDoor'],
|
||||||
|
'IS_NEAR_DOOR': first_row['kMountDim__bIsNearDoor'],
|
||||||
|
'H': first_row['kMountDim__iWinHight'] * 10, # см -> мм
|
||||||
|
'W': first_row['kMountDim__iWinWidth'] * 10, # см -> мм
|
||||||
|
})
|
||||||
|
|
||||||
|
to_template = {'LIST_WIN_OPENING': list_windows_opening}
|
||||||
|
_append_visit_context(to_template, request, time_start)
|
||||||
|
return render(request, 'catalog/catalog_standard_opening.html', to_template)
|
||||||
|
|||||||
Reference in New Issue
Block a user