From 4bb77a989200c406b6e0586eb66ccad8d09b6c1a Mon Sep 17 00:00:00 2001 From: erjemin Date: Fri, 24 Apr 2026 17:31:56 +0300 Subject: [PATCH] =?UTF-8?q?=20mod:=20=D1=80=D0=B5=D1=84=D0=B0=D0=BA=D1=82?= =?UTF-8?q?=D0=BE=D1=80=D0=B8=D0=BD=D0=B3=20"=D0=BA=D0=B0=D1=82=D0=B0?= =?UTF-8?q?=D0=BB=D0=BE=D0=B3=20=D1=82=D0=B8=D0=BF=D0=BE=D0=B2=D1=8B=D1=85?= =?UTF-8?q?=20=D0=BF=D1=80=D0=BE=D1=91=D0=BC=D0=BE=D0=B2"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 3 +- .../catalog/catalog_standard_opening.html | 27 ++-- oknardia/web/catalog_openings.py | 151 ++++++++++-------- 3 files changed, 94 insertions(+), 87 deletions(-) diff --git a/README.md b/README.md index 3e3686f..5807fbb 100644 --- a/README.md +++ b/README.md @@ -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, которые могут быть полезны для понимания устройства проекта и его администрирования, а также для будущей реорганизации документации. diff --git a/oknardia/templates/catalog/catalog_standard_opening.html b/oknardia/templates/catalog/catalog_standard_opening.html index 65f0314..8d65fc5 100755 --- a/oknardia/templates/catalog/catalog_standard_opening.html +++ b/oknardia/templates/catalog/catalog_standard_opening.html @@ -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 @@ {# #}
{# ПЕРВЫЙ РАЗДЕЛ #}
-

Стандартные оконные проёмы и балконные блоки

-

Ценовая выдача «Окнардии» основана на базе стандартных оконных проёмов в типовых сериях домов. Для каждого проёма существуют рекомендованные организациями-проектировщиками схемы открывание, но партнёры «Окнардии» могут предложить свои, более расширенные или наоборот сокращенные. В таблице приведены параметры стандартных проёмов базы.

+

Стандартные оконные проёмы и балконные блоки

+

Ценовая выдача «Окнардии» основана на базе стандартных оконных проёмов в типовых сериях домов. Для каждого проёма существуют рекоме­ндованные органи­зациями-проекти­ровщиками схемы открывание, но партнёры «Окнардии» могут предложить свои, более расширенные или наоборот сокращенные. В таблице приведены параметры стандартных проёмов базы.

{# реклама Oknardia 250x250 СБОКУ #}
{% include "ad/bannet-250x250.html" %}
- +
@@ -98,24 +98,15 @@ -{# {% for j in SERIAS %}#} -{# {% endfor %}#} {% endfor %}
Размеры (мм){{ i.DESCRIPTION }} {% for j in i.INCLUDING_IN_SERIA %}{{ j.NAME }}{% if not forloop.last %}, {% endif %}{% endfor %} цены{% if j.id in i.INCLUDING_IN_SERIA %}#{% endif %}
- -
- - {# --- Баннер: НАЧАЛО --- #} -

{% include "ad/bannet-wide.html" %}
- {# --- Баннер: конец --- #} + {# --- Баннер: НАЧАЛО --- #}

{% include "ad/bannet-wide.html" %}
{# --- Баннер: конец --- #}
{% include "report/report_last_user_visit.html" %} {% include "report/report_log_user_visit.html" %}
-{% endblock %} - - +{% endblock %} \ No newline at end of file diff --git a/oknardia/web/catalog_openings.py b/oknardia/web/catalog_openings.py index 01c05c9..5c9eefd 100644 --- a/oknardia/web/catalog_openings.py +++ b/oknardia/web/catalog_openings.py @@ -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)