mod: рефакторинг "каталог типовых проёмов"
This commit is contained in:
@@ -16,7 +16,7 @@
|
||||
* Рефакторинг `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 шаблонов, гео+статистика серии).
|
||||
* Добавлена 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-операциям.
|
||||
|
||||
|
||||
|
||||
---
|
||||
Легаси-материалы старого README, которые могут быть полезны для понимания устройства проекта и его
|
||||
администрирования, а также для будущей реорганизации документации.
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
{% extends "base.html" %}{% load static %}
|
||||
|
||||
{% block Title %} Стандартные оконные проёмы типовых серий домов :: каталог{% endblock %}
|
||||
{% block Title %}Стандартные оконные проёмы и балконные блоки для типовых серий домов: размеры, схемы, каталог{% 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 %}
|
||||
|
||||
@@ -58,18 +58,18 @@
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="/">Главная</a></li>
|
||||
<li><a href="/catalog">Каталог</a></li>
|
||||
<li>Оконные проёмы и балконные блоки</li>
|
||||
<li>Оконные проёмы и балконные блоки</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>{# <!--- Хлебные крошки: КОНЕЦ ---> #}
|
||||
<dIv class="row">
|
||||
{# ПЕРВЫЙ РАЗДЕЛ #}<div class="col-md-9 col-xs-8">
|
||||
<h1>Стандартные оконные проёмы и балконные блоки</h1>
|
||||
<p>Ценовая выдача «Окнардии» основана на базе стандартных оконных проёмов в типовых сериях домов. Для каждого проёма существуют рекомендованные организациями-проектировщиками схемы открывание, но партнёры «Окнардии» могут предложить свои, более расширенные или наоборот сокращенные. В таблице приведены параметры стандартных проёмов базы.</p>
|
||||
<h1>Стандартные оконные проёмы и балконные блоки</h1>
|
||||
<p>Ценовая выдача «Окнардии» основана на базе стандартных оконных проёмов в типовых сериях домов. Для каждого проёма существуют рекоме­ндованные органи­зациями-проекти­ровщиками схемы открывание, но партнёры «Окнардии» могут предложить свои, более расширенные или наоборот сокращенные. В таблице приведены параметры стандартных проёмов базы.</p>
|
||||
</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">
|
||||
<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>
|
||||
<tr>
|
||||
<th colspan="3" class="c">Размеры (мм)</th>
|
||||
@@ -98,24 +98,15 @@
|
||||
<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><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 %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
|
||||
<DIV class="col-xs-12" style="height:6em;"></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">
|
||||
{% include "report/report_last_user_visit.html" %}
|
||||
{% include "report/report_log_user_visit.html" %}
|
||||
</div>
|
||||
</div>{% endblock %}
|
||||
|
||||
|
||||
</div>{% endblock %}
|
||||
@@ -1,82 +1,99 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from django.db.models import F
|
||||
from django.shortcuts import render
|
||||
from django.http import HttpRequest, HttpResponse
|
||||
from oknardia.models import (
|
||||
Seria_Info,
|
||||
Win_MountDim,
|
||||
)
|
||||
from oknardia.models import MountDim2Apartment
|
||||
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
|
||||
import time
|
||||
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()
|
||||
to_template: dict[str, object] = {} # словарь, для передачи шаблону
|
||||
q_seria = Seria_Info.objects.raw('SELECT oknardia_seria_info.id, oknardia_seria_info.sName '
|
||||
'FROM oknardia_seria_info '
|
||||
'WHERE oknardia_seria_info.id = oknardia_seria_info.kRoot_id '
|
||||
'ORDER BY oknardia_seria_info.sName;')
|
||||
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
|
||||
})
|
||||
|
||||
def _make_slug(value: str) -> str:
|
||||
"""Транслитерирует строку в slug (pytils)."""
|
||||
return pytils.translit.slugify(value)
|
||||
|
||||
|
||||
def _append_visit_context(to_template: dict, request: HttpRequest, time_start: float) -> None:
|
||||
"""Дописывает в контекст стандартный хвост: визиты и время выполнения."""
|
||||
to_template.update({
|
||||
'LIST_WIN_OPENING': list_windows_opening,
|
||||
# получаем последние визиты клиента через куки
|
||||
'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)
|
||||
'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