mod: унифицированная slug-офикация
This commit is contained in:
@@ -3,42 +3,117 @@ __author__ = 'Sergei Erjemin'
|
||||
|
||||
from PIL import Image, ImageDraw
|
||||
from oknardia.settings import *
|
||||
from pytils.translit import slugify
|
||||
import os
|
||||
import math
|
||||
import re
|
||||
import html
|
||||
import urllib3
|
||||
import xml.dom.minidom
|
||||
|
||||
|
||||
def safe_html_spec_symbols(s: str) -> str:
|
||||
""" Очистка строки от HTML-разметки типографа
|
||||
""" Очистка строки от HTML-разметки и получение чистого текста.
|
||||
|
||||
Функция удаляет HTML-теги, содержимое исключённых тегов (script, style, object, embed, applet,
|
||||
iframe, svg, canvas, code, kbd, pre, var, samp, output, noscript, link, meta, form, input,
|
||||
button, textarea, select, base, title, head, body, track, source, picture), заменяет HTML-мнемоники
|
||||
на Unicode-символы и убирает лишние пробелы.
|
||||
|
||||
:param s: str -- строка которую надо очистить
|
||||
:return: str: str -- очищенная строка
|
||||
:return: str -- очищенная строка с чистым текстом
|
||||
"""
|
||||
# очистка строки от некоторых спец-символов HTML
|
||||
result = s.replace('­', '')
|
||||
result = result.replace('<span class="laquo">', '')
|
||||
result = result.replace('<span style="margin-right:0.44em;">', '')
|
||||
result = result.replace('<span style="margin-left:-0.44em;">', '')
|
||||
result = result.replace('<span class="raquo">', '')
|
||||
result = result.replace('<span class="point">', '')
|
||||
result = result.replace('<span class="thinsp">', ' ')
|
||||
result = result.replace('<span class="ensp">', '')
|
||||
result = result.replace('</span>', '')
|
||||
result = result.replace(' ', ' ')
|
||||
result = result.replace('«', '«')
|
||||
result = result.replace('»', '»')
|
||||
result = result.replace('…', '…')
|
||||
result = result.replace('<nobr>', '')
|
||||
result = result.replace('</nobr>', '')
|
||||
result = result.replace('—', '—')
|
||||
result = result.replace('№', '№')
|
||||
result = result.replace('<br />', ' ')
|
||||
result = result.replace('<br>', ' ')
|
||||
# Шаг 1: Удаляем содержимое "опасных" и невидимых тегов
|
||||
# Опасные: script, object, embed, applet, iframe, svg, canvas
|
||||
# Техническое содержимое: style, code, kbd, pre, var, samp, output, noscript
|
||||
# Формы: form, input, button, textarea, select
|
||||
# Служебные: meta, link, base, title, head, body, track, source, picture
|
||||
# Используем флаг IGNORECASE и DOTALL для работы с многострочным контентом
|
||||
result = re.sub(
|
||||
r'<(script|style|code|kbd|pre|var|samp|output|noscript|link|meta|iframe|object|embed|applet|form|input|button|textarea|select|svg|canvas|base|title|head|body|track|source|picture)(?:\s[^>]*)?>.*?</\1>',
|
||||
'',
|
||||
s,
|
||||
flags=re.IGNORECASE | re.DOTALL
|
||||
)
|
||||
|
||||
# Удаляем самозакрывающиеся теги (что-то типа <input/>, <embed/>, и т.д.)
|
||||
result = re.sub(
|
||||
r'<(input|embed|meta|link|base|track|source|img)(?:\s[^>]*)?/>',
|
||||
'',
|
||||
result,
|
||||
flags=re.IGNORECASE
|
||||
)
|
||||
|
||||
# Шаг 2: Удаляем все остальные HTML-теги (в т.ч. самозакрывающиеся)
|
||||
result = re.sub(r'<[^>]+>', '', result)
|
||||
|
||||
# Шаг 3: Заменяем HTML-мнемоники на Unicode-символы (включая числовые и именованные)
|
||||
# html.unescape() обрабатывает: , <, №, € и т.д.
|
||||
result = html.unescape(result)
|
||||
|
||||
# Шаг 4: Очищаем множественные пробелы (в т.ч. табуляцию и переводы строк)
|
||||
result = re.sub(r'\s+', ' ', result)
|
||||
|
||||
# Шаг 5: Убираем пробелы в начале и конце строки
|
||||
result = result.strip()
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def sanitize_slug(text: str, separator: str = '-', max_length: int = 200) -> str:
|
||||
""" Преобразует текст в URL-безопасный слаг (slug).
|
||||
|
||||
Функция очищает текст от HTML-разметки, выполняет транслитерацию русского текста в
|
||||
латиницу, заменяет пробелы и недопустимые символы на разделитель (по умолчанию дефис),
|
||||
и возвращает готовый к использованию в URL слаг.
|
||||
|
||||
Этапы обработки:
|
||||
1. Очистка от HTML-разметки через safe_html_spec_symbols()
|
||||
2. Транслитерация русского текста в латиницу через pytils.translit.slugify()
|
||||
3. Замена множественных разделителей на один
|
||||
4. Удаление разделителя в начале и конце
|
||||
5. Прерывание на max_length символов
|
||||
|
||||
:param text: str -- исходный текст, может содержать HTML и русский текст
|
||||
:param separator: str -- разделитель для слага (по умолчанию дефис '-')
|
||||
pytils.slugify() всегда использует дефис, этот параметр
|
||||
конвертирует результат в нужный разделитель
|
||||
:param max_length: int -- максимальная длина слага в символах (по умолчанию 200)
|
||||
:return: str -- очищенный и готовый к использованию слаг
|
||||
|
||||
Примеры:
|
||||
>>> sanitize_slug(' Тест — HTML <b>текст</b> ')
|
||||
'test-html-tekst'
|
||||
>>> sanitize_slug('Привет мир!!! @#$')
|
||||
'privet-mir'
|
||||
>>> sanitize_slug('<p>Русский текст в слаге</p>')
|
||||
'russkii-tekst-v-slage'
|
||||
>>> sanitize_slug('Проверка_слага', separator='_')
|
||||
'proverka_slaga'
|
||||
"""
|
||||
# Шаг 1: Очищаем от HTML и мнемоник, убираем лишние пробелы
|
||||
cleaned = safe_html_spec_symbols(text)
|
||||
|
||||
# Шаг 2: Транслитерируем русский текст в латиницу (pytils.slugify использует дефис)
|
||||
slug = slugify(cleaned)
|
||||
|
||||
# Шаг 3: Конвертируем разделитель если нужен другой (не дефис)
|
||||
if separator != '-':
|
||||
slug = slug.replace('-', separator)
|
||||
|
||||
# Шаг 4: Убираем множественные разделители (например, '---' -> '-')
|
||||
slug = re.sub(f'{re.escape(separator)}+', separator, slug)
|
||||
|
||||
# Шаг 5: Убираем разделитель в начале и конце если он есть
|
||||
slug = slug.strip(separator)
|
||||
|
||||
# Шаг 6: Обрезаем по max_length если нужно (и убираем разделитель в конце)
|
||||
if max_length and len(slug) > max_length:
|
||||
slug = slug[:max_length].rstrip(separator)
|
||||
|
||||
return slug.lower()
|
||||
|
||||
|
||||
# def Rus2Lat(RusString):
|
||||
# return translit(re.sub(
|
||||
# r'<[\s\S]*?>', '', re.sub(r'&[\S]*?;', '-', RusString)
|
||||
|
||||
@@ -6,10 +6,9 @@ from django.core.exceptions import ObjectDoesNotExist
|
||||
from oknardia.models import BlogPosts
|
||||
from oknardia.settings import *
|
||||
from django.utils import timezone
|
||||
from web.add_func import safe_html_spec_symbols
|
||||
from web.add_func import safe_html_spec_symbols, sanitize_slug
|
||||
from time import time
|
||||
import re
|
||||
import pytils
|
||||
from oknardia.settings import *
|
||||
|
||||
|
||||
@@ -88,7 +87,7 @@ def blog_list_posts(request: HttpRequest, page: str = "0") -> HttpResponse:
|
||||
'PUB_DAT': post.dPostDataBegin,
|
||||
'HEADER': post.sPostHeader,
|
||||
'HEADER_D': safe_html_spec_symbols(post.sPostHeader),
|
||||
'HEADER_T': pytils.translit.slugify(safe_html_spec_symbols(post.sPostHeader)).lower(),
|
||||
'HEADER_T': sanitize_slug(post.sPostHeader).lower(),
|
||||
'POST_ID': post.id,
|
||||
'USER_STATUS': post.kBlogAuthorUser.get_sUserStatus_display(),
|
||||
'USER_AVATAR': post.kBlogAuthorUser.sUserAvatarImg,
|
||||
@@ -160,20 +159,19 @@ def blog_post(request: HttpRequest, post_id: str = "0", page_back: str = None) -
|
||||
to_template.update({'PUB_DAT': q.dPostDataBegin,
|
||||
'PUB_MODIFY': q.dPostDataModify,
|
||||
'HEADER': q.sPostHeader,
|
||||
'HEADER_T': pytils.translit.slugify(safe_html_spec_symbols(q.sPostHeader)).lower(),
|
||||
'HEADER_T': sanitize_slug(q.sPostHeader).lower(),
|
||||
'USER_STATUS': q.kBlogAuthorUser.get_sUserStatus_display(),
|
||||
'USER_AVATAR': q.kBlogAuthorUser.sUserAvatarImg,
|
||||
'USER_TITLE': q.kBlogAuthorUser.sUserJobTitle,
|
||||
'USER_FROM_ID_OFFICE': q.kBlogAuthorUser.kMerchantOffice,
|
||||
'CONTENT': re.sub(r'<cut[\s\S]*?>', '', q.sPostContent, 0, re.IGNORECASE)})
|
||||
to_template.update({'TIZER': safe_html_spec_symbols(
|
||||
re.sub('<script[\s\S]*?</script>|<style[\s\S]*?</style>|<iframe[\s\S]*?</iframe>',
|
||||
'', to_template["CONTENT"], 0, re.IGNORECASE))})
|
||||
content = to_template.get('CONTENT', '')
|
||||
to_template.update({'TIZER': sanitize_slug(str(content))})
|
||||
# получаем следующую по дате запись
|
||||
try:
|
||||
q1 = BlogPosts.objects.filter(dPostDataBegin__gt=q.dPostDataBegin, dPostDataBegin__lt=timezone.now(),
|
||||
bPublished=True, bArchive=False).order_by('dPostDataBegin')[0]
|
||||
to_template.update({'FORW_HEADER_T': pytils.translit.slugify(safe_html_spec_symbols(q1.sPostHeader)).lower(),
|
||||
to_template.update({'FORW_HEADER_T': sanitize_slug(q1.sPostHeader).lower(),
|
||||
'FORW_ID': q1.id})
|
||||
except(IndexError, ObjectDoesNotExist, BlogPosts.DoesNotExist):
|
||||
to_template.update({'FORW_DISABLE': True})
|
||||
@@ -181,7 +179,7 @@ def blog_post(request: HttpRequest, post_id: str = "0", page_back: str = None) -
|
||||
try:
|
||||
q1 = BlogPosts.objects.filter(dPostDataBegin__lt=q.dPostDataBegin, bPublished=True,
|
||||
bArchive=False).order_by('-dPostDataBegin')[0]
|
||||
to_template.update({'BACK_HEADER_T': pytils.translit.slugify(safe_html_spec_symbols(q1.sPostHeader)).lower(),
|
||||
to_template.update({'BACK_HEADER_T': sanitize_slug(q1.sPostHeader).lower(),
|
||||
'BACK_ID': q1.id})
|
||||
except(IndexError, ObjectDoesNotExist, BlogPosts.DoesNotExist):
|
||||
to_template.update({'BACK_DISABLE': True})
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import time
|
||||
|
||||
import pytils.translit
|
||||
from django.http import HttpRequest, HttpResponse
|
||||
from django.shortcuts import render, redirect
|
||||
|
||||
from oknardia.models import Seria_Info, SetKit
|
||||
from web.add_func import get_rating_set_for_stars
|
||||
from web.add_func import get_rating_set_for_stars, sanitize_slug
|
||||
from web.report1 import get_last_all_user_visit_list, get_last_user_visit_list
|
||||
|
||||
|
||||
@@ -32,7 +31,7 @@ def catalog_sets(request: HttpRequest) -> HttpResponse:
|
||||
|
||||
Для каждого набора собирается dict с полями набора, профиля, стеклопакета и компании-установщика.
|
||||
Цепочка FK: SetKit.kSet2User → OurUser.kMerchantOffice → MerchantOffice.kMerchantName (MerchantBrand).
|
||||
Слаги URL формируются через pytils.translit.slugify.
|
||||
Слаги URL формируются через sanitize_slug.
|
||||
|
||||
:param request: HttpRequest -- входящий http-запрос
|
||||
:return response: HttpResponse -- исходящий http-ответ
|
||||
@@ -69,15 +68,13 @@ def catalog_sets(request: HttpRequest) -> HttpResponse:
|
||||
'glazing': glazing,
|
||||
# компания-установщик
|
||||
'merchant_id': brand.id if brand else None,
|
||||
'merchant_slug': pytils.translit.slugify(brand.sMerchantName) if brand else "",
|
||||
'merchant_slug': sanitize_slug(brand.sMerchantName) if brand else "",
|
||||
'merchant_name': brand.sMerchantName if brand else "",
|
||||
'merchant_logo': str(brand.pMerchantLogo) if brand and brand.pMerchantLogo else "",
|
||||
'merchant_url': brand.sMerchantMainURL if brand else "",
|
||||
# слаги для ссылок на профиль в каталоге профилей
|
||||
'profile_manufacturer_slug': pytils.translit.slugify(
|
||||
profile.sProfileManufacturer) if profile else "",
|
||||
'profile_slug': pytils.translit.slugify(
|
||||
profile.sProfileName) if profile else "",
|
||||
'profile_manufacturer_slug': sanitize_slug(profile.sProfileManufacturer) if profile else "",
|
||||
'profile_slug': sanitize_slug(profile.sProfileName) if profile else "",
|
||||
})
|
||||
|
||||
to_template: dict[str, object] = {
|
||||
@@ -99,7 +96,7 @@ def report_all_info_seria_redirect(request: HttpRequest, seria_id: str = "12") -
|
||||
seria_id = int(seria_id)
|
||||
q_seria = Seria_Info.objects.get(id=seria_id)
|
||||
if q_seria.id == q_seria.kRoot_id:
|
||||
return redirect("f/catalog/seria/{pytils.translit.slugify(q_seria.sName)}/all{seria_id}")
|
||||
return redirect(f"/catalog/seria/{sanitize_slug(q_seria.sName)}/all{seria_id}")
|
||||
except (Seria_Info.DoesNotExist, ValueError):
|
||||
return redirect("/catalog/seria")
|
||||
return redirect("/catalog/seria")
|
||||
|
||||
@@ -17,8 +17,8 @@ from oknardia.models import (
|
||||
SetKit,
|
||||
PriceOffer,
|
||||
)
|
||||
from web.report1 import get_last_all_user_visit_list, get_last_user_visit_list
|
||||
from web.add_func import get_rating_set_for_stars
|
||||
from web.report1 import get_last_all_user_visit_list
|
||||
from web.add_func import get_rating_set_for_stars, sanitize_slug
|
||||
import django.utils.dateformat
|
||||
import time
|
||||
import random
|
||||
@@ -131,12 +131,10 @@ def _format_company_for_template(company_data: dict) -> dict:
|
||||
dict: Отформатированные данные компании
|
||||
"""
|
||||
formatted = company_data.copy()
|
||||
|
||||
# Вычисляем звёзды на основе рейтинга
|
||||
formatted['STARS'] = get_rating_set_for_stars(
|
||||
formatted['RatingAVG']
|
||||
)
|
||||
|
||||
# Применяем правильные формы множественного числа
|
||||
formatted['NumSets'] = pytils.numeral.get_plural(
|
||||
formatted['NumSets'],
|
||||
@@ -146,7 +144,6 @@ def _format_company_for_template(company_data: dict) -> dict:
|
||||
formatted['NumOffers'],
|
||||
"вариант, варианта, вариантов"
|
||||
)
|
||||
|
||||
# Конвертируем время последнего обновления в читаемый формат
|
||||
if formatted['lastUpdate']:
|
||||
timestamp = int(
|
||||
@@ -158,12 +155,8 @@ def _format_company_for_template(company_data: dict) -> dict:
|
||||
formatted['lastUpdate'] = pytils.dt.distance_of_time_in_words(
|
||||
timestamp
|
||||
)
|
||||
|
||||
# Генерируем slug из имени компании для URL
|
||||
formatted['sMerchantMainURL'] = pytils.translit.slugify(
|
||||
formatted['sMerchantName']
|
||||
)
|
||||
|
||||
formatted['sMerchantMainURL'] = sanitize_slug(formatted['sMerchantName'])
|
||||
return formatted
|
||||
|
||||
|
||||
@@ -387,11 +380,11 @@ def _format_set_for_template(set_data: dict, empty_values: list) -> dict:
|
||||
'iProfileCameras': profile.iProfileCameras,
|
||||
'sProfileName': {
|
||||
'NAME': profile.sProfileName,
|
||||
'NAME_T': pytils.translit.slugify(profile.sProfileName)
|
||||
'NAME_T': sanitize_slug(profile.sProfileName)
|
||||
},
|
||||
'sProfileManufacturer': {
|
||||
'NAME': profile.sProfileManufacturer,
|
||||
'NAME_T': pytils.translit.slugify(profile.sProfileManufacturer)
|
||||
'NAME_T': sanitize_slug(profile.sProfileManufacturer)
|
||||
},
|
||||
'sProfileColor': profile.sProfileColor,
|
||||
'sProfileSealDescription': profile.sProfileSealDescription,
|
||||
@@ -482,7 +475,7 @@ def catalog_company_detail(
|
||||
raise Http404("Компания не найдена")
|
||||
|
||||
# Проверяем что slug совпадает (для SEO и красивых URL)
|
||||
actual_slug = pytils.translit.slugify(company.sMerchantName)
|
||||
actual_slug = sanitize_slug(company.sMerchantName)
|
||||
if actual_slug != company_name_slug:
|
||||
return redirect(
|
||||
f'/catalog/company/{company_id_int}-{actual_slug}'
|
||||
|
||||
@@ -3,20 +3,14 @@ from django.db.models import F
|
||||
from django.shortcuts import render
|
||||
from django.http import HttpRequest, HttpResponse
|
||||
from oknardia.models import MountDim2Apartment
|
||||
from web.report1 import get_last_all_user_visit_list, get_last_user_visit_list
|
||||
from web.add_func import get_flaps_for_mini_pictures
|
||||
from web.report1 import get_last_all_user_visit_list
|
||||
from web.add_func import get_flaps_for_mini_pictures, sanitize_slug
|
||||
import time
|
||||
import pytils
|
||||
from typing import Any
|
||||
from itertools import groupby
|
||||
from operator import itemgetter
|
||||
|
||||
|
||||
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({
|
||||
@@ -73,7 +67,7 @@ def standard_opening(request: HttpRequest) -> HttpResponse:
|
||||
serias_for_opening = [
|
||||
{
|
||||
'ID': row['kApartment__kSeria_id'],
|
||||
'NAME_T': _make_slug(row['kApartment__kSeria__sName']),
|
||||
'NAME_T': sanitize_slug(row['kApartment__kSeria__sName']),
|
||||
'NAME': row['kApartment__kSeria__sName'],
|
||||
}
|
||||
for row in rows_for_opening
|
||||
|
||||
@@ -7,8 +7,8 @@ from django.shortcuts import render, redirect
|
||||
from django.http import HttpRequest, HttpResponse
|
||||
from oknardia.settings import *
|
||||
from oknardia.models import Catalog2Profile, PVCprofiles, PriceOffer
|
||||
from web.report1 import get_last_all_user_visit_list, get_last_user_visit_list
|
||||
from web.add_func import normalize, get_rating_set_for_stars
|
||||
from web.report1 import get_last_all_user_visit_list
|
||||
from web.add_func import normalize, get_rating_set_for_stars, sanitize_slug
|
||||
import time
|
||||
import json
|
||||
import re
|
||||
@@ -17,19 +17,13 @@ import pytils
|
||||
# ---------------------------------------------------------------------------
|
||||
# Модульные хелперы, общие для всех вьюх этого файла
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def make_slug(value: str) -> str:
|
||||
"""Транслитерирует строку в slug (pytils)."""
|
||||
return pytils.translit.slugify(value).lower()
|
||||
|
||||
|
||||
def _merchant_row_to_dict(row: dict) -> dict:
|
||||
"""Преобразует ORM-строку с данными партнёра в словарь для шаблона."""
|
||||
merchant_name = row["kOffer2SetKit__kSet2User__kMerchantOffice__kMerchantName__sMerchantName"]
|
||||
return {
|
||||
"MERCHANT_ID": row["kOffer2SetKit__kSet2User__kMerchantOffice__kMerchantName__id"],
|
||||
"MERCHANT_NAME": merchant_name,
|
||||
"MERCHANT_NAME_T": make_slug(merchant_name),
|
||||
"MERCHANT_NAME_T": sanitize_slug(merchant_name),
|
||||
"MERCHANT_LOGO_URL": row["kOffer2SetKit__kSet2User__kMerchantOffice__kMerchantName__pMerchantLogo"],
|
||||
"MERCHANT_OFFERS": row["offers_by_merchant"],
|
||||
}
|
||||
@@ -40,7 +34,7 @@ def _profile_row_to_dict(profile: dict) -> dict:
|
||||
return {
|
||||
"PROFILE_NAME": profile["sProfileBriefDescription"],
|
||||
"PROFILE_ID": profile["id"],
|
||||
"PROFILE_URL": make_slug(profile["sProfileName"]),
|
||||
"PROFILE_URL": sanitize_slug(profile["sProfileName"]),
|
||||
"PROFILE_RATING": profile["fProfileRating"],
|
||||
"PROFILE_RATING_STARS": get_rating_set_for_stars(profile["fProfileRating"]),
|
||||
}
|
||||
@@ -94,11 +88,11 @@ def catalog_profile(request: HttpRequest) -> HttpResponse:
|
||||
list_profile_manufactures.append({
|
||||
"PROF_MAN_ID": profile["id"],
|
||||
"PROF_MAN": profile["sProfileManufacturer"],
|
||||
"PROF_MAN_T": make_slug(profile["sProfileManufacturer"]),
|
||||
"PROF_MAN_T": sanitize_slug(profile["sProfileManufacturer"]),
|
||||
"PROF_MAN_LIST": [{
|
||||
"PROF_NAME_ID": profile["id"],
|
||||
"PROF_NAME": profile["sProfileBriefDescription"],
|
||||
"PROF_NAME_T": make_slug(profile["sProfileName"]),
|
||||
"PROF_NAME_T": sanitize_slug(profile["sProfileName"]),
|
||||
}]
|
||||
})
|
||||
else:
|
||||
@@ -106,7 +100,7 @@ def catalog_profile(request: HttpRequest) -> HttpResponse:
|
||||
list_profile_manufactures[-1]["PROF_MAN_LIST"].append({
|
||||
"PROF_NAME_ID": profile["id"],
|
||||
"PROF_NAME": profile["sProfileBriefDescription"],
|
||||
"PROF_NAME_T": make_slug(profile["sProfileName"]),
|
||||
"PROF_NAME_T": sanitize_slug(profile["sProfileName"]),
|
||||
})
|
||||
|
||||
to_template.update({
|
||||
@@ -128,17 +122,17 @@ def catalog_profile_model(request: HttpRequest, manufacture_id: int, manufacture
|
||||
|
||||
:param request: HttpRequest -- входящий http-запрос
|
||||
:param manufacture_id: id профиля. Предполагается, что это первый id при сортировке по sProfileBriefDescription
|
||||
:param manufacture_name: название производителя (транслитерированное pytils.translit.slugify())
|
||||
:param manufacture_name: название производителя (транслитерированное sanitize_slug())
|
||||
:param model_id: id модели (марки) профиля
|
||||
:param model_name: модель (марка) профиля (транслитерированное pytils.translit.slugify(sProfileName))
|
||||
:param model_name: модель (марка) профиля (транслитерированное sanitize_slug(sProfileName))
|
||||
:return response: HttpResponse -- исходящий http-ответ
|
||||
"""
|
||||
time_start = time.perf_counter()
|
||||
manufacture_id = int(manufacture_id)
|
||||
model_id = int(model_id)
|
||||
q_pvc_by_id = PVCprofiles.objects.get(id=model_id)
|
||||
manufacturer_slug = pytils.translit.slugify(q_pvc_by_id.sProfileManufacturer)
|
||||
model_slug = pytils.translit.slugify(q_pvc_by_id.sProfileName)
|
||||
manufacturer_slug = sanitize_slug(q_pvc_by_id.sProfileManufacturer)
|
||||
model_slug = sanitize_slug(q_pvc_by_id.sProfileName)
|
||||
if manufacturer_slug != manufacture_name \
|
||||
or model_slug != model_name \
|
||||
or manufacture_id != model_id:
|
||||
@@ -268,21 +262,21 @@ def catalog_profile_manufacture(request: HttpRequest, manufacture_id: int, manuf
|
||||
|
||||
:param request: HttpRequest -- входящий http-запрос
|
||||
:param manufacture_id: id профиля. Предполагается, что это первый id при сортировке по sProfileBriefDescription
|
||||
:param manufacture_name: название производителя (транслитерированное pytils.translit.slugify())
|
||||
:param manufacture_name: название производителя (транслитерированное sanitize_slug())
|
||||
:return response: HttpResponse -- исходящий http-ответ
|
||||
"""
|
||||
time_start = time.perf_counter()
|
||||
manufacture_id = int(manufacture_id)
|
||||
q_pvc_by_id = PVCprofiles.objects.get(id=manufacture_id)
|
||||
if pytils.translit.slugify(q_pvc_by_id.sProfileManufacturer) != manufacture_name:
|
||||
if sanitize_slug(q_pvc_by_id.sProfileManufacturer) != manufacture_name:
|
||||
return redirect(f'/catalog/profile/{manufacture_id}-'
|
||||
f'{pytils.translit.slugify(q_pvc_by_id.sProfileManufacturer)}')
|
||||
f'{sanitize_slug(q_pvc_by_id.sProfileManufacturer)}')
|
||||
else:
|
||||
q_pvc_by_id = PVCprofiles.objects.order_by('id') \
|
||||
.filter(sProfileManufacturer=q_pvc_by_id.sProfileManufacturer).first()
|
||||
if q_pvc_by_id.id != manufacture_id:
|
||||
return redirect(f'/catalog/profile/{q_pvc_by_id.id}-'
|
||||
f'{pytils.translit.slugify(q_pvc_by_id.sProfileManufacturer)}')
|
||||
f'{sanitize_slug(q_pvc_by_id.sProfileManufacturer)}')
|
||||
to_template: dict[str, object] = {'CATALOG_MANUFACT': q_pvc_by_id.sProfileManufacturer,
|
||||
'CATALOG_MAN2URL': manufacture_name,
|
||||
'CATALOG_URL': f"{manufacture_id}-{manufacture_name}"}
|
||||
|
||||
@@ -14,18 +14,13 @@ from oknardia.models import (
|
||||
Building_Info,
|
||||
)
|
||||
from web.report1 import get_last_all_user_visit_list, get_last_user_visit_list
|
||||
from web.add_func import get_flaps_for_big_pictures
|
||||
from web.add_func import get_flaps_for_big_pictures, sanitize_slug
|
||||
import time
|
||||
import os
|
||||
import math
|
||||
import pytils
|
||||
|
||||
|
||||
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({
|
||||
@@ -54,7 +49,7 @@ def catalog_seria(request: HttpRequest) -> HttpResponse:
|
||||
'ID': row['id'],
|
||||
'URL': row['sURL2IMG'],
|
||||
'NAME': row['sName'],
|
||||
'NAME_T': _make_slug(row['sName']),
|
||||
'NAME_T': sanitize_slug(row['sName']),
|
||||
}
|
||||
for row in q_seria
|
||||
]
|
||||
@@ -87,8 +82,8 @@ def catalog_seria_info(
|
||||
try:
|
||||
seria_id = int(seria_id)
|
||||
q_seria = Seria_Info.objects.only("id", "kRoot_id", "sName").get(id=seria_id)
|
||||
if q_seria.id != q_seria.kRoot_id or seria_name_translit != pytils.translit.slugify(q_seria.sName):
|
||||
return redirect(f"/catalog/seria/{pytils.translit.slugify(q_seria.sName)}/all{seria_id}")
|
||||
if q_seria.id != q_seria.kRoot_id or seria_name_translit != sanitize_slug(q_seria.sName):
|
||||
return redirect(f"/catalog/seria/{sanitize_slug(q_seria.sName)}/all{seria_id}")
|
||||
except (ObjectDoesNotExist, ValueError):
|
||||
return redirect("/catalog/")
|
||||
|
||||
@@ -295,7 +290,7 @@ def all_seria_nav(seria_id: int, q_seria) -> tuple[int, dict]:
|
||||
one_seria = {
|
||||
"SERIA_R": seria_name,
|
||||
"ID2URL": seria_id_value,
|
||||
"SERIA_L": pytils.translit.slugify(seria_name),
|
||||
"SERIA_L": sanitize_slug(seria_name),
|
||||
}
|
||||
if seria_id_value == seria_id:
|
||||
# Изображение серии: используется в OG-image в шаблоне seria_info
|
||||
@@ -309,7 +304,7 @@ def all_seria_nav(seria_id: int, q_seria) -> tuple[int, dict]:
|
||||
"THIS_SERIA_DESCRIPTION": seria_description,
|
||||
# ID и slug серии нужны для canonical URL и JSON-LD в шаблоне
|
||||
"THIS_SERIA_ID": seria_id_value,
|
||||
"THIS_SERIA_NAME_T": pytils.translit.slugify(seria_name),
|
||||
"THIS_SERIA_NAME_T": sanitize_slug(seria_name),
|
||||
# URL изображения серии для OG-тегов (путь относительно /media/)
|
||||
"THIS_SERIA_IMAGE_URL": str(seria_image) if seria_image else "",
|
||||
})
|
||||
@@ -417,7 +412,7 @@ def seria_info_geo_code(seria_id: int | str = DEFAULT_SERIA_ID_FOR_CATALOG) -> d
|
||||
seria_to_geo.append({"LATITUDE": latitude,
|
||||
"LONGITUDE": longitude,
|
||||
"ADDR_ID": count["id"],
|
||||
"ADDR_LAT": pytils.translit.slugify(count["sAddress"]),
|
||||
"ADDR_LAT": sanitize_slug(count["sAddress"]),
|
||||
"ADDR_RUS": count["sAddress"],
|
||||
"SER_ID": count["kSeria_Link__kRoot_id"]
|
||||
})
|
||||
|
||||
@@ -36,8 +36,7 @@ from oknardia.models import (
|
||||
SetKit,
|
||||
Win_MountDim,
|
||||
)
|
||||
import pytils
|
||||
|
||||
from web.add_func import sanitize_slug
|
||||
|
||||
# Namespace схемы sitemap.xml по стандарту sitemaps.org.
|
||||
SITEMAP_XMLNS = "http://www.sitemaps.org/schemas/sitemap/0.9"
|
||||
@@ -138,7 +137,7 @@ class BuildingOffersSitemap(Sitemap):
|
||||
if not root_id:
|
||||
continue
|
||||
for apart_id in apartments_by_root.get(root_id, []):
|
||||
yield (building.id, apart_id, pytils.translit.slugify(building.sAddress))
|
||||
yield (building.id, apart_id, sanitize_slug(building.sAddress))
|
||||
|
||||
def location(self, item: tuple[int, int, str]) -> str:
|
||||
build_id, apart_id, address_slug = item
|
||||
@@ -147,7 +146,7 @@ class BuildingOffersSitemap(Sitemap):
|
||||
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()
|
||||
seria_slug = sanitize_slug((seria.sName or ""))
|
||||
except Exception:
|
||||
# fallback на старый роутинг, если что-то пошло не так
|
||||
return f"/{build_id}/{apart_id}/{address_slug}"
|
||||
@@ -250,7 +249,7 @@ class BlogPostSitemap(Sitemap):
|
||||
).only("id", "sPostHeader", "dPostDataModify")
|
||||
|
||||
def location(self, item: BlogPosts) -> str:
|
||||
return f"/blogpost/{item.id}/{pytils.translit.slugify(item.sPostHeader).lower()}"
|
||||
return f"/blogpost/{item.id}/{sanitize_slug(item.sPostHeader)}"
|
||||
|
||||
def lastmod(self, item: BlogPosts) -> date | datetime | None:
|
||||
return item.dPostDataModify
|
||||
@@ -270,7 +269,7 @@ class ProfileManufactureSitemap(Sitemap):
|
||||
)
|
||||
|
||||
def location(self, item: dict) -> str:
|
||||
manufacturer_slug = pytils.translit.slugify(item["sProfileManufacturer"]).lower()
|
||||
manufacturer_slug = sanitize_slug(item["sProfileManufacturer"])
|
||||
return f"/catalog/profile/{item['first_id']}-{manufacturer_slug}"
|
||||
|
||||
def lastmod(self, item: dict) -> date | datetime | None:
|
||||
@@ -287,8 +286,8 @@ class ProfileModelSitemap(Sitemap):
|
||||
return PVCprofiles.objects.only("id", "sProfileManufacturer", "sProfileName", "dProfileModify")
|
||||
|
||||
def location(self, item: PVCprofiles) -> str:
|
||||
manufacturer_slug = pytils.translit.slugify(item.sProfileManufacturer).lower()
|
||||
model_slug = pytils.translit.slugify(item.sProfileName).lower()
|
||||
manufacturer_slug = sanitize_slug(item.sProfileManufacturer)
|
||||
model_slug = sanitize_slug(item.sProfileName)
|
||||
# Исторически канонический URL использует id модели и в сегменте manufacturer_id, и в segment model_id.
|
||||
return f"/catalog/profile/{item.id}-{manufacturer_slug}/{item.id}-{model_slug}"
|
||||
|
||||
@@ -308,7 +307,7 @@ class SeriaDetailSitemap(Sitemap):
|
||||
)
|
||||
|
||||
def location(self, item: Seria_Info) -> str:
|
||||
return f"/catalog/seria/{pytils.translit.slugify(item.sName).lower()}/all{item.id}"
|
||||
return f"/catalog/seria/{sanitize_slug(item.sName)}/all{item.id}"
|
||||
|
||||
def lastmod(self, item: Seria_Info) -> date | datetime | None:
|
||||
return item.dSeriaInfoModify
|
||||
@@ -329,7 +328,7 @@ class CompanyDetailSitemap(Sitemap):
|
||||
)
|
||||
|
||||
def location(self, item: MerchantBrand) -> str:
|
||||
return f"/catalog/company/{item.id}-{pytils.translit.slugify(item.sMerchantName).lower()}"
|
||||
return f"/catalog/company/{item.id}-{sanitize_slug(item.sMerchantName)}"
|
||||
|
||||
def lastmod(self, item: MerchantBrand) -> date | datetime | None:
|
||||
return getattr(item, "last_offer_modify", None) or getattr(item, "last_office_modify", None)
|
||||
|
||||
@@ -3,7 +3,6 @@ from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pytils
|
||||
from django.conf import settings
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.db.models import F
|
||||
@@ -11,6 +10,7 @@ from django.test import RequestFactory
|
||||
|
||||
from oknardia.models import Seria_Info
|
||||
from web import catalog_series
|
||||
from web.add_func import sanitize_slug
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
@@ -76,7 +76,7 @@ class Command(BaseCommand):
|
||||
if target_file.exists():
|
||||
target_file.unlink()
|
||||
|
||||
slug = pytils.translit.slugify(seria.sName)
|
||||
slug = sanitize_slug(seria.sName)
|
||||
request = request_factory.get(f"/catalog/seria/{slug}/all{seria.id}")
|
||||
|
||||
# В команде принудительно включаем «production-mode» для вьюхи,
|
||||
|
||||
@@ -15,8 +15,8 @@ from oknardia.models import (
|
||||
)
|
||||
from oknardia.settings import *
|
||||
from web.report1 import get_last_all_user_visit_list, get_last_user_visit_list
|
||||
from web.add_func import normalize, get_rating_set_for_stars, get_flaps_for_big_pictures, get_flaps_for_mini_pictures, \
|
||||
get_geo_distance
|
||||
from web.add_func import get_rating_set_for_stars, get_flaps_for_big_pictures, get_flaps_for_mini_pictures, \
|
||||
get_geo_distance, sanitize_slug
|
||||
import django.utils.dateformat
|
||||
import time
|
||||
import os
|
||||
@@ -26,11 +26,6 @@ from types import SimpleNamespace
|
||||
import pytils
|
||||
|
||||
|
||||
def _slugify_lower(value: str | None) -> str:
|
||||
"""Транслитерирует строку в slug и всегда приводит к нижнему регистру."""
|
||||
return pytils.translit.slugify((value or "").strip()).lower()
|
||||
|
||||
|
||||
def _one_win_price_canonical_path(win_width_mm: int | str, win_height_mm: int | str, win_id: int | str) -> str:
|
||||
"""Возвращает канонический путь страницы цен для одного типового окна."""
|
||||
return f"/catalog/standard_opening/price-{int(win_width_mm)}x{int(win_height_mm)}mm-tip{int(win_id)}/"
|
||||
@@ -271,9 +266,9 @@ def report_price_frame(apartment_id: int, mount_dim_per_offer: int, address_long
|
||||
'GLAZING_TONING': offer.sGlazingToning,
|
||||
'PVC_ID': offer.pwc_id,
|
||||
'PVC_NAME': offer.sProfileName,
|
||||
'PVC_NAME_T': _slugify_lower(offer.sProfileName),
|
||||
'PVC_NAME_T': sanitize_slug(offer.sProfileName),
|
||||
'PVC_MANUFACTURER': offer.sProfileManufacturer,
|
||||
'PVC_MANUFACTURER_T': _slugify_lower(offer.sProfileManufacturer),
|
||||
'PVC_MANUFACTURER_T': sanitize_slug(offer.sProfileManufacturer),
|
||||
'PVC_SEAL': offer.sProfileSealDescription,
|
||||
'SETS_CLIMATE_CONTROL': offer.sSetClimateControl,
|
||||
'SETS_SILL': offer.sSetSill,
|
||||
@@ -530,9 +525,9 @@ def report_price_frame(apartment_id: int, mount_dim_per_offer: int, address_long
|
||||
'GLAZING_TONING': i2.sGlazingToning,
|
||||
'PVC_ID': i2.pwc_id,
|
||||
'PVC_NAME': i2.sProfileName,
|
||||
'PVC_NAME_T': _slugify_lower(i2.sProfileName),
|
||||
'PVC_NAME_T': sanitize_slug(i2.sProfileName),
|
||||
'PVC_MANUFACTURER': i2.sProfileManufacturer,
|
||||
'PVC_MANUFACTURER_T': _slugify_lower(i2.sProfileManufacturer),
|
||||
'PVC_MANUFACTURER_T': sanitize_slug(i2.sProfileManufacturer),
|
||||
'PVC_SEAL': i2.sProfileSealDescription,
|
||||
'SETS_CLIMATE_CONTROL': i2.sSetClimateControl,
|
||||
'SETS_SILL': i2.sSetSill,
|
||||
@@ -710,7 +705,7 @@ def report_one_win_price(request: HttpRequest,
|
||||
list_seria_for_win.append(SimpleNamespace(
|
||||
id=seria_item['kApartment__kSeria__id'],
|
||||
sName=seria_name,
|
||||
sNameLat=_slugify_lower(seria_name),
|
||||
sNameLat=sanitize_slug(seria_name),
|
||||
num_variation_of_apartment=pytils.numeral.sum_string(
|
||||
seria_item['num_variation_of_apartment'],
|
||||
pytils.numeral.MALE,
|
||||
@@ -793,7 +788,7 @@ def report_price(request: HttpRequest, build_id: str = "22427", apart_id: str =
|
||||
|
||||
# если кто-то нахимичит ID квартиры не для этого дома, то сделаем так, что он будет от этого дома!
|
||||
apart_inside = any(ap.id == apart_id for ap in list_apart)
|
||||
address_slug = _slugify_lower(building.sAddress)
|
||||
address_slug = sanitize_slug(building.sAddress)
|
||||
if not apart_inside or slug != address_slug:
|
||||
# Переадресация 302, если с apart_id (ID-квартиры нахимичили) или slug-ом.
|
||||
# Нужно для склейки парных URL в поисковиках
|
||||
@@ -867,7 +862,7 @@ def report_price(request: HttpRequest, build_id: str = "22427", apart_id: str =
|
||||
|
||||
# узнаем базовую серию дома
|
||||
q_base_seria = building.kSeria_Link.kRoot
|
||||
base_seria_slug = _slugify_lower(q_base_seria.sName)
|
||||
base_seria_slug = sanitize_slug(q_base_seria.sName)
|
||||
to_template.update({'BASE_SERIA': q_base_seria.sName,
|
||||
'BASE_SERIA_LAT': base_seria_slug,
|
||||
'BASE_SERIA_ID': q_base_seria.id})
|
||||
@@ -1015,8 +1010,8 @@ def report_price_new(request, seria_id, seria_slug, apart_id, address_id, addres
|
||||
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()
|
||||
seria_slug_real = sanitize_slug((seria.sName or "").strip()).lower()
|
||||
address_slug_real = sanitize_slug((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)
|
||||
@@ -1037,10 +1032,7 @@ def report_price_legacy_redirect(request, build_id, apart_id, slug):
|
||||
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()
|
||||
seria_slug = sanitize_slug((seria.sName or "").strip()).lower()
|
||||
address_slug = sanitize_slug((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)
|
||||
|
||||
@@ -7,7 +7,7 @@ from django.utils import timezone
|
||||
from django.db.models import F, Q, ExpressionWrapper, BooleanField, Max, Count, Avg
|
||||
from oknardia.models import LogVisitPriceReport, SetKit
|
||||
from oknardia.settings import *
|
||||
from web.add_func import normalize, get_rating_set_for_stars, sum_through
|
||||
from web.add_func import normalize, get_rating_set_for_stars, sum_through, sanitize_slug
|
||||
# from time import time
|
||||
import django.utils.dateformat
|
||||
import time
|
||||
@@ -290,7 +290,7 @@ def compare_offers(request: HttpRequest, to_compare: str = "1,2") -> HttpRespons
|
||||
"MERCHANT": i.sMerchantName,
|
||||
"MERCHANT_ID": i.MERCHANT_ID,
|
||||
"IS_COMMERCIAL": i.bCommercial,
|
||||
"MERCHANT_T": pytils.translit.slugify(i.sMerchantName),
|
||||
"MERCHANT_T": sanitize_slug(i.sMerchantName),
|
||||
"MERCHANT_URL": i.sMerchantMainURL,
|
||||
"MERCHANT_URL_SHOT": re.sub(r"(?:^https?://|/$|www\.)", "", i.sMerchantMainURL),
|
||||
"SET_NAME": i.sSetName,
|
||||
@@ -300,9 +300,9 @@ def compare_offers(request: HttpRequest, to_compare: str = "1,2") -> HttpRespons
|
||||
"RATING_SET_COLOR": rating_set_color,
|
||||
"PROFILE_ID": i.PROFILE_ID,
|
||||
"PROFILE_NAME": i.sProfileName,
|
||||
"PROFILE_NAME_T": pytils.translit.slugify(i.sProfileName),
|
||||
"PROFILE_NAME_T": sanitize_slug(i.sProfileName),
|
||||
"PROFILE_MANUFACTURER": i.sProfileManufacturer,
|
||||
"PROFILE_MANUFACTURER_T": pytils.translit.slugify(i.sProfileManufacturer),
|
||||
"PROFILE_MANUFACTURER_T": sanitize_slug(i.sProfileManufacturer),
|
||||
"PROFILE_NUM_COLOR": i.sProfileColor,
|
||||
"PROFILE_NUM_CAMERAS": i.iProfileCameras, # Число камер рамы/створки
|
||||
"PROFILE_NUM_CAMERAS_COLOR": _color_hi(profile_num_cameras, min_cameras, max_cameras, threshold=1),
|
||||
|
||||
@@ -3,7 +3,7 @@ from django.shortcuts import render, redirect
|
||||
from django.http import HttpRequest, HttpResponse
|
||||
from oknardia.models import PVCprofiles
|
||||
from oknardia.settings import *
|
||||
from web.add_func import normalize, get_rating_set_for_stars
|
||||
from web.add_func import normalize, get_rating_set_for_stars, sanitize_slug
|
||||
from time import time
|
||||
import json
|
||||
import pytils
|
||||
@@ -73,9 +73,9 @@ def profiles_rating(request: HttpRequest) -> HttpResponse:
|
||||
"ID": profile.id,
|
||||
"R_REAL": rating_real,
|
||||
"BRAND": profile.sProfileManufacturer,
|
||||
"BRAND_URL": pytils.translit.slugify(profile.sProfileManufacturer),
|
||||
"BRAND_URL": sanitize_slug(profile.sProfileManufacturer),
|
||||
"NAME": profile.sProfileName,
|
||||
"NAME_URL": pytils.translit.slugify(profile.sProfileName),
|
||||
"NAME_URL": sanitize_slug(profile.sProfileName),
|
||||
"K_ARR": k_arr,
|
||||
"RATING_STAR": get_rating_set_for_stars(profile.fProfileRating),
|
||||
"RATING_N": profile.fProfileRating,
|
||||
|
||||
@@ -6,12 +6,8 @@ from django.db.models import ExpressionWrapper, FloatField, F, Count
|
||||
from django.db.models.functions import Abs
|
||||
from smtplib import SMTPException
|
||||
from oknardia.models import Seria_Info, Building_Info, Apartment_Type
|
||||
from web.add_func import get_yandex_geocode_by_address, get_geo_distance
|
||||
import json
|
||||
import datetime
|
||||
from web.add_func import get_yandex_geocode_by_address, get_geo_distance, sanitize_slug
|
||||
import time
|
||||
import pytils
|
||||
|
||||
# from django.core.context_processors import csrf
|
||||
|
||||
|
||||
@@ -240,9 +236,9 @@ def get_address(request: HttpRequest) -> HttpResponse:
|
||||
to_template.update({
|
||||
'SERIA_BASE': q1.sName if q1 else "",
|
||||
'BASE_SERIA_ID': seria_root.id if seria_root else "",
|
||||
'BASE_SERIA_LAT': pytils.translit.slugify((seria_root.sName or "").strip()).lower() if seria_root else "",
|
||||
'BASE_SERIA_LAT': sanitize_slug((seria_root.sName or "").strip()) if seria_root else "",
|
||||
'addr': addr,
|
||||
'addr_T': pytils.translit.slugify(addr),
|
||||
'addr_T': sanitize_slug(addr),
|
||||
'ticks': float(time.perf_counter() - time_start),
|
||||
})
|
||||
return render(request, "popup/popup_show_apartment_variants.html", to_template)
|
||||
|
||||
Reference in New Issue
Block a user