mod: Рефакторинг страницы цен одного окна (вьюшки, шаблоны, тесты, новый canonical-роутинг)

This commit is contained in:
2026-04-26 14:53:49 +03:00
parent 21501799ca
commit 3479b31f0e
10 changed files with 777 additions and 164 deletions

250
oknardia/web/test_prices.py Normal file
View File

@@ -0,0 +1,250 @@
from datetime import timedelta
from decimal import Decimal
from unittest.mock import patch
from django.contrib.auth.models import User
from django.db import connection
from django.http import HttpResponse
from django.test import RequestFactory, TestCase
from django.utils import timezone
from oknardia.models import (
Apartment_Type,
Glazing,
MerchantBrand,
MerchantOffice,
MountDim2Apartment,
OurUser,
PVCprofiles,
PriceOffer,
Seria_Info,
SetKit,
)
from web.prices import redirect_one_win_price_legacy, report_one_win_price
class ReportOneWinPriceTests(TestCase):
"""Регрессионные тесты для ORM-версии report_one_win_price."""
def setUp(self) -> None:
self.factory = RequestFactory()
django_user = User.objects.create_user(username="price-tester", password="secret")
self.our_user = OurUser.objects.create(kDjangoUser=django_user)
# Тестовая SQLite-схема в проекте может быть legacy-вариантом с flap_config вместо sFlapConfig.
# Для тестов report_one_win_price явно добавляем sFlapConfig, чтобы код проверялся в целевом режиме.
with connection.cursor() as cursor:
cursor.execute("PRAGMA table_info(oknardia_win_mountdim)")
existing_columns = {row[1] for row in cursor.fetchall()}
if "sFlapConfig" not in existing_columns:
cursor.execute("ALTER TABLE oknardia_win_mountdim ADD COLUMN sFlapConfig varchar(32)")
# if "flap_config" in existing_columns:
# cursor.execute(
# "UPDATE oknardia_win_mountdim SET sFlapConfig = flap_config "
# "WHERE sFlapConfig IS NULL"
# )
self.brand = MerchantBrand.objects.create(
sMerchantName="Оконный бренд",
sMerchantMainURL="https://example.com",
)
self.office = MerchantOffice.objects.create(
sOfficeName="Оконный бренд — офис",
kMerchantName=self.brand,
sOfficePhones="+7(495)123-45-67",
sOfficeAddress="Москва, Тестовая улица, 1",
sOfficeDiscountMetaFormula="{'discount': {'10000': 5}}",
)
self.our_user.kMerchantOffice = self.office
self.our_user.save(update_fields=["kMerchantOffice"])
with connection.cursor() as cursor:
insert_columns = [
"iWinWidth",
"iWinHight",
"iWinDepth",
"sFlapConfig",
"sDescripion",
"bIsDoor",
"bIsNearDoor",
"iWinLimit",
"dMountXYZDataCreate",
"dMountXYZModify",
]
insert_values = [
Decimal("67.0"),
Decimal("216.0"),
Decimal("15.0"),
"[>]",
"Тестовый проём",
0,
0,
Decimal("5.0"),
]
if "flap_config" in existing_columns:
insert_columns.insert(3, "flap_config")
insert_values.insert(3, "[>]")
columns_sql = ", ".join(insert_columns)
placeholders_sql = ", ".join(["?"] * len(insert_values)) + ", CURRENT_TIMESTAMP, CURRENT_TIMESTAMP"
cursor.execute(
f"INSERT INTO oknardia_win_mountdim ({columns_sql}) VALUES ({placeholders_sql})",
insert_values,
)
self.window_id = cursor.lastrowid
self.seria = Seria_Info.objects.create(sName="П-44")
self.apartment = Apartment_Type.objects.create(
sNameApartment="1-комнатная",
kSeria=self.seria,
)
MountDim2Apartment.objects.create(
kApartment=self.apartment,
kMountDim_id=self.window_id,
iQuantity=1,
)
self.glazing = Glazing.objects.create(
sGlazingName="Тестовый стеклопакет",
sGlazingBriefDescription="Двухкамерный стеклопакет",
sGlazingMark="4-10-4-10-4",
sGlazingToning="нет",
kGlazing2User=self.our_user,
)
self.profile = PVCprofiles.objects.create(
sProfileName="Profile Test",
sProfileBriefDescription="Профиль для теста",
sProfileManufacturer="Test Manufacturer",
sProfileSealDescription="чёрный",
sProfileReinforcement="сталь",
kProfile2User=self.our_user,
fProfileRating=4.2,
)
self.set_kit = SetKit.objects.create(
sSetName="Тестовый набор",
kSet2User=self.our_user,
kSet2PVCprofiles=self.profile,
kSet2Glazing=self.glazing,
sSetImplementAll="Фурнитура",
sSetImplementHandles="Ручки",
sSetImplementHinges="Петли",
sSetImplementLatch="Запоры",
sSetImplementLimiter="Ограничитель",
sSetImplementCatch="Фиксатор",
sSetSill="Подоконник",
sSetSlope="Откос",
sSetPanes="Отлив",
sSetDelivery="Доставка",
bSetDelivery=True,
sSetUninstallInstall="Монтаж",
bSetUninstallInstall=True,
sSetOtherConditions="Прочие условия",
sSetClimateControl="Климат",
fSetRating=4.5,
dSetCommercialUntil=timezone.now() + timedelta(days=30),
)
self.active_offer = PriceOffer.objects.create(
kOffer2MountDim_id=self.window_id,
kOfferFromUser=self.our_user,
kOffer2SetKit=self.set_kit,
sOfferFlapConfig="[>]",
fOfferPrice=Decimal("12345.00"),
sOfferActive=True,
)
self.archived_offer = PriceOffer.objects.create(
kOffer2MountDim_id=self.window_id,
kOfferFromUser=self.our_user,
kOffer2SetKit=self.set_kit,
sOfferFlapConfig="[<]",
fOfferPrice=Decimal("11111.00"),
sOfferActive=False,
)
@patch("web.prices.get_last_all_user_visit_list", return_value=[])
@patch("web.prices.get_last_user_visit_list", return_value=[])
@patch("web.prices.get_last_user_visit_cookies", return_value=[])
@patch("web.prices.get_flaps_for_mini_pictures", return_value="img/test-mini.png")
@patch(
"web.prices.get_flaps_for_big_pictures",
return_value={
"FLAP_DIM": [{
"iWinWidth": Decimal("67.0"),
"iWinHight": Decimal("216.0"),
"iWinWidth_mm": 670,
"iWinHight_mm": 2160,
}],
"WIN_DIM": [],
},
)
def test_report_one_win_price_renders_expected_context(
self,
mocked_big_pictures,
mocked_mini_pictures,
mocked_cookies,
mocked_last_visits,
mocked_all_visits,
):
"""Вьюха должна собирать тот же ключевой контекст, но уже без raw SQL."""
request = self.factory.get(
f"/catalog/standard_opening/price-670x2160mm-tip{self.window_id}",
)
captured = {}
def fake_render(_request, template_name, context):
captured["template_name"] = template_name
captured["context"] = context
return HttpResponse("ok")
with patch("web.prices.render", side_effect=fake_render):
response = report_one_win_price(request, "670", "2160", str(self.window_id))
context = captured["context"]
self.assertEqual(response.status_code, 200)
self.assertEqual(captured["template_name"], "price/price_offers_for_one_window.html")
self.assertEqual(context["WIN_ID"], self.window_id)
self.assertEqual(context["MOUNT_DIM_PER_OFFER"], 1)
self.assertEqual(context["NUM_ARCHIVE_OFFER"], 1)
self.assertIn("2", context["NUM_TOTAL_OFFER_N_WORD"])
self.assertEqual(len(context["LIST_FLAP_VARIATION"]), 1)
self.assertEqual(context["LIST_FLAP_VARIATION"][0].sOfferFlapConfig, "[>]")
self.assertTrue(context["LIST_FLAP_VARIATION"][0].STR_NUM.startswith("вариант"))
self.assertEqual(context["LIST_FLAP_VARIATION"][0].IMG_MINI, "img/test-mini.png")
self.assertEqual(len(context["SERIA_FOR_WIN"]), 1)
self.assertEqual(context["SERIA_FOR_WIN"][0].sName, self.seria.sName)
self.assertEqual(len(context["PRICE_FRAME"]), 1)
self.assertEqual(context["PRICE_FRAME"][0]["SETS_NAME"], self.set_kit.sSetName)
self.assertEqual(context["PRICE_FRAME"][0]["MERCHANT"], self.brand.sMerchantName)
self.assertEqual(context["PRICE_FRAME"][0]["DIM"][0]["IMG_MINI"], "img/test-mini.png")
self.assertIn("META_DATA_PUBLISH", context)
self.assertTrue(mocked_big_pictures.called)
self.assertTrue(mocked_mini_pictures.called)
self.assertTrue(mocked_cookies.called)
self.assertTrue(mocked_last_visits.called)
self.assertTrue(mocked_all_visits.called)
def test_report_one_win_price_redirects_to_canonical_dimensions(self):
"""Если SEO-размеры в URL неверные, вьюха должна редиректить на канонический URL."""
request = self.factory.get(
f"/catalog/standard_opening/price-999x999mm-tip{self.window_id}",
)
response = report_one_win_price(request, "999", "999", str(self.window_id))
self.assertEqual(response.status_code, 301)
self.assertEqual(
response["Location"],
f"/catalog/standard_opening/price-670x2160mm-tip{self.window_id}/",
)
def test_legacy_one_win_url_redirects_to_canonical_url(self):
"""Старый URL страницы одного окна должен отдавать 301 на новый канонический путь."""
request = self.factory.get(
f"/tsena-odnogo-okna/670x2160mm/tip{self.window_id}",
)
response = redirect_one_win_price_legacy(request, "670", "2160", str(self.window_id))
self.assertEqual(response.status_code, 301)
self.assertEqual(
response["Location"],
f"/catalog/standard_opening/price-670x2160mm-tip{self.window_id}/",
)