minor: в отдельный catalog_profiles.py выделены все вьюхи для каталога профилей
This commit is contained in:
@@ -18,9 +18,8 @@ from django.contrib import admin
|
|||||||
from django.urls import include, path, re_path
|
from django.urls import include, path, re_path
|
||||||
from django.conf.urls.static import static
|
from django.conf.urls.static import static
|
||||||
from oknardia.settings import *
|
from oknardia.settings import *
|
||||||
from web import views, autocomplete_addr, user_manager, blog, diagrams, report1, report2, catalog, prices, service
|
from web import views, autocomplete_addr, user_manager, blog, diagrams, report1, report2, catalog, prices, service, \
|
||||||
|
catalog_profiles
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('admin/', admin.site.urls),
|
path('admin/', admin.site.urls),
|
||||||
|
|
||||||
@@ -57,13 +56,13 @@ urlpatterns = [
|
|||||||
re_path(r'^stat/rating[/*]$', report2.ratings),
|
re_path(r'^stat/rating[/*]$', report2.ratings),
|
||||||
re_path(r'^stat/rating/profiles_rank[/*]$', report2.profiles_rating),
|
re_path(r'^stat/rating/profiles_rank[/*]$', report2.profiles_rating),
|
||||||
# --- Каталог
|
# --- Каталог
|
||||||
# --- --- Каталог профилей
|
|
||||||
re_path(r'^catalog[/*]$', catalog.catalog_root),
|
re_path(r'^catalog[/*]$', catalog.catalog_root),
|
||||||
re_path(r'^catalog/profile[/*]$', catalog.catalog_profile),
|
# --- --- Каталог профилей
|
||||||
|
re_path(r'^catalog/profile[/*]$', catalog_profiles.catalog_profile),
|
||||||
re_path(r'^catalog/profile/(?P<manufacture_id>\d+)-(?P<manufacture_name>\S*)'
|
re_path(r'^catalog/profile/(?P<manufacture_id>\d+)-(?P<manufacture_name>\S*)'
|
||||||
r'/(?P<model_id>\d+)-(?P<model_name>\S*)[/*]$', catalog.catalog_profile_model),
|
r'/(?P<model_id>\d+)-(?P<model_name>\S*)[/*]$', catalog_profiles.catalog_profile_model),
|
||||||
re_path(r'^catalog/profile/(?P<manufacture_id>\d+)-(?P<manufacture_name>\S*)[/*]$',
|
re_path(r'^catalog/profile/(?P<manufacture_id>\d+)-(?P<manufacture_name>\S*)[/*]$',
|
||||||
catalog.catalog_profile_manufacture),
|
catalog_profiles.catalog_profile_manufacture),
|
||||||
# --- --- Каталог серий типового строительства
|
# --- --- Каталог серий типового строительства
|
||||||
re_path(r'^catalog/seria[/*]$', catalog.catalog_seria),
|
re_path(r'^catalog/seria[/*]$', catalog.catalog_seria),
|
||||||
re_path(r'^catalog/seria/(?P<seria_name_translit>[^/]*)/all(?P<seria_id>\d+)[/*]$', catalog.catalog_seria_info),
|
re_path(r'^catalog/seria/(?P<seria_name_translit>[^/]*)/all(?P<seria_id>\d+)[/*]$', catalog.catalog_seria_info),
|
||||||
@@ -100,6 +99,7 @@ urlpatterns = [
|
|||||||
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
# ___ ____ _ _____ _ _ _____ _
|
# ___ ____ _ _____ _ _ _____ _
|
||||||
# | | | | \ ___| |_ _ _ ___ |_ _|___ ___| | |_ ___ ___ | _ |___ ___ ___| |
|
# | | | | \ ___| |_ _ _ ___ |_ _|___ ___| | |_ ___ ___ | _ |___ ___ ___| |
|
||||||
|
|||||||
@@ -46,382 +46,6 @@ def catalog_root(request: HttpRequest) -> HttpResponse:
|
|||||||
response = render(request, "catalog/catalog_root.html", to_template)
|
response = render(request, "catalog/catalog_root.html", to_template)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
def catalog_profile(request: HttpRequest) -> HttpResponse:
|
|
||||||
"""
|
|
||||||
КАТАЛОГ ПРОФИЛЕЙ: страница со списком производителей и моделей (марками) профилей
|
|
||||||
|
|
||||||
:param request: HttpRequest -- входящий http-запрос
|
|
||||||
:return response: HttpResponse -- исходящий http-ответ
|
|
||||||
"""
|
|
||||||
time_start = time.time()
|
|
||||||
# Берём только те поля, которые реально нужны для построения страницы каталога.
|
|
||||||
# Это позволяет не тащить лишние данные из БД и сразу работать с простыми словарями.
|
|
||||||
profile_rows = list(
|
|
||||||
PVCprofiles.objects.values(
|
|
||||||
"id",
|
|
||||||
"sProfileName",
|
|
||||||
"sProfileBriefDescription",
|
|
||||||
"sProfileManufacturer",
|
|
||||||
).order_by("sProfileManufacturer", "sProfileBriefDescription")
|
|
||||||
)
|
|
||||||
profile_count = len(profile_rows)
|
|
||||||
to_template = {
|
|
||||||
'CATALOG_PROFILE_NUM': pytils.numeral.get_plural(profile_count, "профиль,профиля,профилей")
|
|
||||||
}
|
|
||||||
# Локальный помощник: slug нужен несколько раз, а повторять одну и ту же строку не хочется.
|
|
||||||
def make_slug(value: str) -> str:
|
|
||||||
return pytils.translit.slugify(value).lower()
|
|
||||||
|
|
||||||
list_profile_manufactures = []
|
|
||||||
tmp_profile_manufacture = ""
|
|
||||||
for profile in profile_rows:
|
|
||||||
if profile["sProfileManufacturer"] == "":
|
|
||||||
# Пустой производитель в каталоге только мешает: не создаём для него отдельную группу.
|
|
||||||
continue
|
|
||||||
|
|
||||||
if tmp_profile_manufacture != profile["sProfileManufacturer"]:
|
|
||||||
# Новый производитель — открываем новую группу карточек.
|
|
||||||
tmp_profile_manufacture = profile["sProfileManufacturer"]
|
|
||||||
list_profile_manufactures.append({
|
|
||||||
"PROF_MAN_ID": profile["id"],
|
|
||||||
"PROF_MAN": profile["sProfileManufacturer"],
|
|
||||||
"PROF_MAN_T": make_slug(profile["sProfileManufacturer"]),
|
|
||||||
"PROF_MAN_LIST": [{
|
|
||||||
"PROF_NAME_ID": profile["id"],
|
|
||||||
"PROF_NAME": profile["sProfileBriefDescription"],
|
|
||||||
"PROF_NAME_T": make_slug(profile["sProfileName"]),
|
|
||||||
}]
|
|
||||||
})
|
|
||||||
else:
|
|
||||||
# Если производитель уже встречался, просто дописываем новую модель в его список.
|
|
||||||
list_profile_manufactures[-1]["PROF_MAN_LIST"].append({
|
|
||||||
"PROF_NAME_ID": profile["id"],
|
|
||||||
"PROF_NAME": profile["sProfileBriefDescription"],
|
|
||||||
"PROF_NAME_T": make_slug(profile["sProfileName"]),
|
|
||||||
})
|
|
||||||
|
|
||||||
to_template.update({
|
|
||||||
'CATALOG_PROFILE_MAN1_NAME2': list_profile_manufactures,
|
|
||||||
'CATALOG_MANUFACT_NUM': len(list_profile_manufactures),
|
|
||||||
'CATALOG_MANUFACT_NUM_W':
|
|
||||||
pytils.numeral.sum_string(len(list_profile_manufactures), pytils.numeral.MALE, ("производитель",
|
|
||||||
"производителя",
|
|
||||||
"производителей")),
|
|
||||||
'LAST_VISIT': get_last_user_visit_list(get_last_user_visit_cookies(request)[:3]),
|
|
||||||
'LOG_VISIT': get_last_all_user_visit_list(),
|
|
||||||
'ticks': float(time.time() - time_start),
|
|
||||||
})
|
|
||||||
return render(request, "catalog/catalog_of_profiles.html", to_template)
|
|
||||||
|
|
||||||
|
|
||||||
def catalog_profile_model(request: HttpRequest, manufacture_id: int, manufacture_name: str,
|
|
||||||
model_id: int, model_name: str) -> HttpResponse:
|
|
||||||
"""
|
|
||||||
КАТАЛОГ ПРОФИЛЕЙ: страница с описанием марки профиля
|
|
||||||
|
|
||||||
:param request: HttpRequest -- входящий http-запрос
|
|
||||||
:param manufacture_id: id профиля. Предполагается, что это первый id при сортировке по sProfileBriefDescription
|
|
||||||
:param manufacture_name: название производителя (транслитерированное pytils.translit.slugify())
|
|
||||||
:param model_id: id модели (марки) профиля
|
|
||||||
:param model_name: модель (марка) профиля (транслитерированное pytils.translit.slugify(sProfileName))
|
|
||||||
:return response: HttpResponse -- исходящий http-ответ
|
|
||||||
"""
|
|
||||||
time_start = time.time()
|
|
||||||
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)
|
|
||||||
if manufacturer_slug != manufacture_name \
|
|
||||||
or model_slug != model_name \
|
|
||||||
or manufacture_id != model_id:
|
|
||||||
return redirect(f"/catalog/profile/{model_id}-{manufacturer_slug}/"
|
|
||||||
f"{model_id}-{model_slug}")
|
|
||||||
|
|
||||||
# Локальные помощники держат вьюху короче и не размазывают однотипную логику по коду.
|
|
||||||
def make_slug(value: str) -> str:
|
|
||||||
return pytils.translit.slugify(value).lower()
|
|
||||||
|
|
||||||
def build_other_list(value: str) -> list[str]:
|
|
||||||
# Убираем пустые куски, чтобы не плодить «пустые» характеристики в шаблоне.
|
|
||||||
result = []
|
|
||||||
for chunk in (part.strip() for part in value.split(";")):
|
|
||||||
if not chunk:
|
|
||||||
continue
|
|
||||||
if ":" in chunk:
|
|
||||||
head, tail = chunk.split(":", 1)
|
|
||||||
result.append(f"<b>{head.strip()}:</b>{tail.strip()}")
|
|
||||||
else:
|
|
||||||
result.append(f"<b>{chunk}</b>")
|
|
||||||
return result
|
|
||||||
|
|
||||||
def update_pub_dat(current_pub_dat: datetime | None, candidate_pub_dat: datetime | None) -> datetime | None:
|
|
||||||
# На странице оставляем дату публикации/обновления только если она реально новее карточки профиля.
|
|
||||||
if candidate_pub_dat is None:
|
|
||||||
return current_pub_dat
|
|
||||||
if current_pub_dat is None or candidate_pub_dat.replace(tzinfo=None) > current_pub_dat.replace(tzinfo=None):
|
|
||||||
return candidate_pub_dat
|
|
||||||
return current_pub_dat
|
|
||||||
|
|
||||||
def apply_rating_colors(rating: dict, rating_pairs: tuple[tuple[str, str], ...], multiplier: int,
|
|
||||||
gray: bool = False) -> None:
|
|
||||||
# Один маленький helper вместо россыпи почти одинаковых строк: меняется только множитель и формат RGB.
|
|
||||||
for rating_key, template_key in rating_pairs:
|
|
||||||
color = int(255 - rating[rating_key] * multiplier)
|
|
||||||
if gray:
|
|
||||||
to_template[template_key] = f"{color},{color},{color}"
|
|
||||||
else:
|
|
||||||
to_template[template_key] = f"{color},255,{color}"
|
|
||||||
|
|
||||||
def merchant_row_to_dict(row: dict) -> dict:
|
|
||||||
# Один маппер для строки с партнёром: ключи шаблона остаются как были.
|
|
||||||
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_LOGO_URL": row["kOffer2SetKit__kSet2User__kMerchantOffice__kMerchantName__pMerchantLogo"],
|
|
||||||
"MERCHANT_OFFERS": row["offers_by_merchant"],
|
|
||||||
}
|
|
||||||
|
|
||||||
def profile_row_to_dict(profile: dict) -> dict:
|
|
||||||
# И то же самое для списка соседних профилей производителя.
|
|
||||||
return {
|
|
||||||
"PROFILE_NAME": profile["sProfileBriefDescription"],
|
|
||||||
"PROFILE_ID": profile["id"],
|
|
||||||
"PROFILE_URL": make_slug(profile["sProfileName"]),
|
|
||||||
"PROFILE_RATING": profile["fProfileRating"],
|
|
||||||
"PROFILE_RATING_STARS": get_rating_set_for_stars(profile["fProfileRating"]),
|
|
||||||
}
|
|
||||||
|
|
||||||
to_template: dict[str, object] = {"CATALOG_MODEL": q_pvc_by_id,
|
|
||||||
"CATALOG_MAN2URL": manufacture_name,
|
|
||||||
"CATALOG_URL": f"{manufacture_id}-{manufacture_name}",
|
|
||||||
"CATALOG_URL2": f"{manufacture_id}-{manufacture_name}/{model_id}-{model_name}",
|
|
||||||
"PROFILE_RATING_STARS": get_rating_set_for_stars(q_pvc_by_id.fProfileRating)}
|
|
||||||
try:
|
|
||||||
got_json = json.loads(q_pvc_by_id.sProfileDescription)
|
|
||||||
# раскрашиваем кружочки рейтинга напротив характеристик профиля
|
|
||||||
rating_pairs = (
|
|
||||||
(RANK_PVCP_CAMERAS_NUM_NAME, "RANK_PVCP_CAMERAS_COLOR"),
|
|
||||||
(RANK_PVCP_SEALS_NAME, "RANK_PVCP_SEALS_COLOR"),
|
|
||||||
(RANK_PVCP_THICKNESS_NAME, "RANK_PVCP_THICKNESS_COLOR"),
|
|
||||||
(RANK_PVCP_G_THICKNESS_NAME, "RANK_PVCP_G_THICKNESS_COLOR"),
|
|
||||||
(RANK_PVCP_RABBET_NAME, "RANK_PVCP_RABBET_COLOR"),
|
|
||||||
(RANK_PVCP_HEAT_TRANSFER_NAME, "RANK_PVCP_HEAT_TRANSFER_COLOR"),
|
|
||||||
(RANK_PVCP_SOUNDPROOFING_NAME, "RANK_PVCP_SOUNDPROOFING_COLOR"),
|
|
||||||
(RANK_PVCP_HEIGHT_NAME, "RANK_PVCP_HEIGHT_COLOR"),
|
|
||||||
)
|
|
||||||
if KEY_RATING in got_json:
|
|
||||||
# кружочки зелёные
|
|
||||||
apply_rating_colors(got_json[KEY_RATING], rating_pairs, 255)
|
|
||||||
elif KEY_RATING_VIRTUAL in got_json:
|
|
||||||
# кружочки серые
|
|
||||||
apply_rating_colors(got_json[KEY_RATING_VIRTUAL], rating_pairs, 64, gray=True)
|
|
||||||
else:
|
|
||||||
pass
|
|
||||||
if KEY_HTML in got_json:
|
|
||||||
to_template.update({"EXTRA_INFO": got_json[KEY_HTML]})
|
|
||||||
except (TypeError, ValueError, KeyError):
|
|
||||||
pass
|
|
||||||
to_template.update({"LIST_OTHER": build_other_list(q_pvc_by_id.sProfileOther)})
|
|
||||||
# Партнёров считаем через ORM: так код проще читать и легче переносить между СУБД.
|
|
||||||
q_merchant = (
|
|
||||||
PriceOffer.objects.filter(
|
|
||||||
kOffer2SetKit__kSet2PVCprofiles_id=model_id,
|
|
||||||
sOfferActive=True,
|
|
||||||
)
|
|
||||||
.values(
|
|
||||||
"kOffer2SetKit__kSet2User__kMerchantOffice__kMerchantName__id",
|
|
||||||
"kOffer2SetKit__kSet2User__kMerchantOffice__kMerchantName__sMerchantName",
|
|
||||||
"kOffer2SetKit__kSet2User__kMerchantOffice__kMerchantName__pMerchantLogo",
|
|
||||||
)
|
|
||||||
.annotate(offers_by_merchant=Count("id"))
|
|
||||||
.order_by("-offers_by_merchant", "kOffer2SetKit__kSet2User__kMerchantOffice__kMerchantName__sMerchantName")
|
|
||||||
)
|
|
||||||
to_template.update({'MERCHANTS': [merchant_row_to_dict(row) for row in q_merchant]})
|
|
||||||
# Близкие профили этого же производителя нужны для быстрых переходов по карточкам.
|
|
||||||
q_profiles = (
|
|
||||||
PVCprofiles.objects.filter(sProfileManufacturer=q_pvc_by_id.sProfileManufacturer)
|
|
||||||
.exclude(id=model_id)
|
|
||||||
.values("id", "fProfileRating", "sProfileBriefDescription", "sProfileName")
|
|
||||||
.order_by("fProfileRating")
|
|
||||||
)
|
|
||||||
to_template.update({'PROFILES': [profile_row_to_dict(profile) for profile in q_profiles]})
|
|
||||||
# Описание профиля берём через связку каталог -> блог: это один ORM-запрос вместо сырого SQL.
|
|
||||||
q_profiles_detail = (
|
|
||||||
Catalog2Profile.objects.filter(
|
|
||||||
kProfile_id=model_id,
|
|
||||||
sCatalogCardType=CATALOG_RECORD_FOR_PROFILE_MODEL,
|
|
||||||
kBlogCatalog__isnull=False,
|
|
||||||
)
|
|
||||||
.select_related("kBlogCatalog")
|
|
||||||
.order_by("kBlogCatalog__iCatalogSort")
|
|
||||||
)
|
|
||||||
profile_blog_posts = [row.kBlogCatalog for row in q_profiles_detail if row.kBlogCatalog is not None]
|
|
||||||
to_template.update({'PROFILE_DETAIL': profile_blog_posts})
|
|
||||||
# Картинка и дата публикации для meta-тегов берутся из связанного блога, если он есть.
|
|
||||||
if profile_blog_posts:
|
|
||||||
for blog_post in profile_blog_posts:
|
|
||||||
if blog_post.sImgForBlogSocial:
|
|
||||||
to_template['IMG_FOR_BLOG'] = blog_post.sImgForBlogSocial
|
|
||||||
break
|
|
||||||
|
|
||||||
pub_dat: datetime = q_pvc_by_id.dProfileModify
|
|
||||||
if profile_blog_posts:
|
|
||||||
profile_blog_dat: datetime | None = max((post.dPostDataModify for post in profile_blog_posts), default=pub_dat)
|
|
||||||
pub_dat = update_pub_dat(pub_dat, profile_blog_dat) or pub_dat
|
|
||||||
to_template['PUB_DAT'] = pub_dat
|
|
||||||
to_template.update(
|
|
||||||
{
|
|
||||||
'LAST_VISIT': get_last_user_visit_list(get_last_user_visit_cookies(request)[:3]),
|
|
||||||
'LOG_VISIT': get_last_all_user_visit_list(),
|
|
||||||
'ticks': float(time.time() - time_start),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
return render(request, "catalog/catalog_of_profiles_model.html", to_template)
|
|
||||||
|
|
||||||
|
|
||||||
def catalog_profile_manufacture(request: HttpRequest, manufacture_id: int, manufacture_name: str) -> HttpResponse:
|
|
||||||
"""
|
|
||||||
КАТАЛОГ ПРОФИЛЕЙ: страница с описанием производителя профилей и списком марки производимых им профилей
|
|
||||||
|
|
||||||
:param request: HttpRequest -- входящий http-запрос
|
|
||||||
:param manufacture_id: id профиля. Предполагается, что это первый id при сортировке по sProfileBriefDescription
|
|
||||||
:param manufacture_name: название производителя (транслитерированное pytils.translit.slugify())
|
|
||||||
:return response: HttpResponse -- исходящий http-ответ
|
|
||||||
"""
|
|
||||||
time_start = time.time()
|
|
||||||
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:
|
|
||||||
return redirect(f'/catalog/profile/{manufacture_id}-'
|
|
||||||
f'{pytils.translit.slugify(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)}')
|
|
||||||
to_template = {'CATALOG_MANUFACT': q_pvc_by_id.sProfileManufacturer,
|
|
||||||
'CATALOG_MAN2URL': manufacture_name,
|
|
||||||
'CATALOG_URL': f"{manufacture_id}-{manufacture_name}"}
|
|
||||||
try:
|
|
||||||
# получаем информацию о производителе (статью из блога)
|
|
||||||
manufacture_description = list(PVCprofiles.objects.raw(
|
|
||||||
f"SELECT "
|
|
||||||
f" oknardia_blogposts.* "
|
|
||||||
f"FROM oknardia_catalog2profile"
|
|
||||||
f" RIGHT OUTER JOIN oknardia_pvcprofiles"
|
|
||||||
f" ON oknardia_catalog2profile.kProfile_id = oknardia_pvcprofiles.id"
|
|
||||||
f" LEFT OUTER JOIN oknardia_blogposts"
|
|
||||||
f" ON oknardia_catalog2profile.kBlogCatalog_id = oknardia_blogposts.id "
|
|
||||||
f"WHERE oknardia_catalog2profile.sCatalogCardType = {CATALOG_RECORD_FOR_PROFILE_MANUFACTURER} "
|
|
||||||
f" AND oknardia_pvcprofiles.sProfileManufacturer = '{q_pvc_by_id.sProfileManufacturer}'"
|
|
||||||
f" AND oknardia_blogposts.bCatalog IS TRUE "
|
|
||||||
f"GROUP BY oknardia_blogposts.bCatalog "
|
|
||||||
f"LIMIT 1;"
|
|
||||||
))[0]
|
|
||||||
to_template.update({'PUB_DAT': manufacture_description.dPostDataModify})
|
|
||||||
if PATH_FOR_IMG_BLOG in manufacture_description.sImgForBlogSocial:
|
|
||||||
to_template.update({'IMG_FOR_BLOG': manufacture_description.sImgForBlogSocial})
|
|
||||||
to_template.update({'HEADER': manufacture_description.sPostHeader,
|
|
||||||
'CONTENT': re.sub(r'<cut[\s\S]*>', '', manufacture_description.sPostContent,
|
|
||||||
0, re.IGNORECASE)})
|
|
||||||
to_template.update({'TIZER': re.sub(r'<script[\s\S]*?</script>|<style[\s\S]*?</style>|<iframe[\s\S]*?</iframe>',
|
|
||||||
'', to_template["CONTENT"], 0, re.IGNORECASE)})
|
|
||||||
except (ObjectDoesNotExist, IndexError, TypeError, KeyError,):
|
|
||||||
pass
|
|
||||||
q_profiles = PVCprofiles.objects.raw(
|
|
||||||
f"SELECT oknardia_pvcprofiles.id,"
|
|
||||||
f" oknardia_pvcprofiles.fProfileRating,"
|
|
||||||
f" oknardia_pvcprofiles.sProfileBriefDescription,"
|
|
||||||
f" oknardia_pvcprofiles.sProfileName "
|
|
||||||
f"FROM oknardia_pvcprofiles "
|
|
||||||
f"WHERE oknardia_pvcprofiles.sProfileManufacturer = '{q_pvc_by_id.sProfileManufacturer}' "
|
|
||||||
f"ORDER BY oknardia_pvcprofiles.fProfileRating;"
|
|
||||||
)
|
|
||||||
list_profiles = []
|
|
||||||
for i in q_profiles:
|
|
||||||
list_profiles.append({
|
|
||||||
"PROFILE_NAME": i.sProfileBriefDescription,
|
|
||||||
"PROFILE_ID": i.id,
|
|
||||||
"PROFILE_URL": pytils.translit.slugify(i.sProfileName).lower(),
|
|
||||||
"PROFILE_RATING": i.fProfileRating,
|
|
||||||
"PROFILE_RATING_STARS": get_rating_set_for_stars(i.fProfileRating),
|
|
||||||
})
|
|
||||||
to_template.update({'PROFILES': list_profiles})
|
|
||||||
try:
|
|
||||||
q_share_of_offers = list(PVCprofiles.objects.raw(
|
|
||||||
f"SELECT"
|
|
||||||
f" 1 AS id,"
|
|
||||||
f" SUM(Q1.offers_by_model) AS offers_by_maufacture,"
|
|
||||||
f" Q2.tatal_offers-SUM(Q1.offers_by_model) AS offers_other "
|
|
||||||
f"FROM (SELECT COUNT(oknardia_priceoffer.id) AS offers_by_model"
|
|
||||||
f" FROM oknardia_priceoffer"
|
|
||||||
f" LEFT OUTER JOIN oknardia_setkit"
|
|
||||||
f" ON oknardia_priceoffer.kOffer2SetKit_id = oknardia_setkit.id"
|
|
||||||
f" RIGHT OUTER JOIN oknardia_pvcprofiles"
|
|
||||||
f" ON oknardia_setkit.kSet2PVCprofiles_id = oknardia_pvcprofiles.id"
|
|
||||||
f" WHERE oknardia_pvcprofiles.sProfileManufacturer = '{q_pvc_by_id.sProfileManufacturer}') Q1,"
|
|
||||||
f" (SELECT COUNT(oknardia_priceoffer.id) AS tatal_offers"
|
|
||||||
f" FROM oknardia_priceoffer) AS Q2 "
|
|
||||||
f"LIMIT 1;"
|
|
||||||
))[0]
|
|
||||||
to_template.update({
|
|
||||||
'OFFERS_BY_MAUFACTURE': q_share_of_offers.offers_by_maufacture,
|
|
||||||
'OFFERS_OTHER': q_share_of_offers.offers_other,
|
|
||||||
'OFFERS_ANGLE': 90 + 180 * normalize(q_share_of_offers.offers_by_maufacture,
|
|
||||||
q_share_of_offers.offers_other + q_share_of_offers.offers_by_maufacture)
|
|
||||||
})
|
|
||||||
if q_share_of_offers is not None and q_share_of_offers.offers_by_maufacture != 0:
|
|
||||||
q_merchant = PVCprofiles.objects.raw(
|
|
||||||
f"SELECT"
|
|
||||||
f" COUNT(oknardia_priceoffer.id) AS offers_by_merchant,"
|
|
||||||
f" oknardia_merchantbrand.sMerchantName,"
|
|
||||||
f" oknardia_merchantbrand.pMerchantLogo,"
|
|
||||||
f" oknardia_merchantbrand.id "
|
|
||||||
f"FROM oknardia_priceoffer"
|
|
||||||
f" INNER JOIN oknardia_setkit"
|
|
||||||
f" ON oknardia_priceoffer.kOffer2SetKit_id = oknardia_setkit.id"
|
|
||||||
f" INNER JOIN oknardia_pvcprofiles"
|
|
||||||
f" ON oknardia_setkit.kSet2PVCprofiles_id = oknardia_pvcprofiles.id"
|
|
||||||
f" INNER JOIN oknardia_ouruser"
|
|
||||||
f" ON oknardia_setkit.kSet2User_id = oknardia_ouruser.id"
|
|
||||||
f" INNER JOIN oknardia_merchantoffice"
|
|
||||||
f" ON oknardia_ouruser.kMerchantOffice_id = oknardia_merchantoffice.id"
|
|
||||||
f" INNER JOIN oknardia_merchantbrand"
|
|
||||||
f" ON oknardia_merchantoffice.kMerchantName_id = oknardia_merchantbrand.id "
|
|
||||||
f"WHERE oknardia_pvcprofiles.sProfileManufacturer = '{q_pvc_by_id.sProfileManufacturer}' "
|
|
||||||
f"GROUP BY oknardia_merchantbrand.sMerchantName,"
|
|
||||||
f" oknardia_merchantbrand.pMerchantLogo,"
|
|
||||||
f" oknardia_merchantbrand.id "
|
|
||||||
f"ORDER BY offers_by_merchant DESC;"
|
|
||||||
)
|
|
||||||
list_merchant = []
|
|
||||||
for i in q_merchant:
|
|
||||||
list_merchant.append({
|
|
||||||
"MERCHANT_ID": i.id,
|
|
||||||
"MERCHANT_NAME": i.sMerchantName,
|
|
||||||
"MERCHANT_NAME_T": pytils.translit.slugify(i.sMerchantName),
|
|
||||||
"MERCHANT_LOGO_URL": i.pMerchantLogo,
|
|
||||||
"MERCHANT_OFFERS": i.offers_by_merchant
|
|
||||||
})
|
|
||||||
to_template.update({'MERCHANTS': list_merchant})
|
|
||||||
except (ObjectDoesNotExist, IndexError, TypeError): # вообще-то, запрос q_share_of_offers всегда что-то вернёт,
|
|
||||||
pass # но на всякий случай
|
|
||||||
to_template.update({
|
|
||||||
# получаем последние визиты клиента через куки
|
|
||||||
'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.time() - time_start)
|
|
||||||
})
|
|
||||||
return render(request, "catalog/catalog_of_profiles_manufacture.html", to_template)
|
|
||||||
|
|
||||||
|
|
||||||
# Каталог типовый серий зданий (пока переадресация)
|
# Каталог типовый серий зданий (пока переадресация)
|
||||||
def catalog_seria(request: HttpRequest) -> HttpResponse:
|
def catalog_seria(request: HttpRequest) -> HttpResponse:
|
||||||
"""
|
"""
|
||||||
|
|||||||
389
oknardia/web/catalog_profiles.py
Normal file
389
oknardia/web/catalog_profiles.py
Normal file
@@ -0,0 +1,389 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
|
from django.db.models import Count
|
||||||
|
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_cookies, get_last_user_visit_list
|
||||||
|
from web.add_func import normalize, get_rating_set_for_stars
|
||||||
|
import time
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
import pytils
|
||||||
|
|
||||||
|
def catalog_profile(request: HttpRequest) -> HttpResponse:
|
||||||
|
"""
|
||||||
|
КАТАЛОГ ПРОФИЛЕЙ: страница со списком производителей и моделей (марками) профилей
|
||||||
|
|
||||||
|
:param request: HttpRequest -- входящий http-запрос
|
||||||
|
:return response: HttpResponse -- исходящий http-ответ
|
||||||
|
"""
|
||||||
|
time_start = time.time()
|
||||||
|
# Берём только те поля, которые реально нужны для построения страницы каталога.
|
||||||
|
# Это позволяет не тащить лишние данные из БД и сразу работать с простыми словарями.
|
||||||
|
profile_rows = list(
|
||||||
|
PVCprofiles.objects.values(
|
||||||
|
"id",
|
||||||
|
"sProfileName",
|
||||||
|
"sProfileBriefDescription",
|
||||||
|
"sProfileManufacturer",
|
||||||
|
).order_by("sProfileManufacturer", "sProfileBriefDescription")
|
||||||
|
)
|
||||||
|
profile_count = len(profile_rows)
|
||||||
|
to_template = {
|
||||||
|
'CATALOG_PROFILE_NUM': pytils.numeral.get_plural(profile_count, "профиль,профиля,профилей")
|
||||||
|
}
|
||||||
|
# Локальный помощник: slug нужен несколько раз, а повторять одну и ту же строку не хочется.
|
||||||
|
def make_slug(value: str) -> str:
|
||||||
|
return pytils.translit.slugify(value).lower()
|
||||||
|
|
||||||
|
list_profile_manufactures = []
|
||||||
|
tmp_profile_manufacture = ""
|
||||||
|
for profile in profile_rows:
|
||||||
|
if profile["sProfileManufacturer"] == "":
|
||||||
|
# Пустой производитель в каталоге только мешает: не создаём для него отдельную группу.
|
||||||
|
continue
|
||||||
|
|
||||||
|
if tmp_profile_manufacture != profile["sProfileManufacturer"]:
|
||||||
|
# Новый производитель — открываем новую группу карточек.
|
||||||
|
tmp_profile_manufacture = profile["sProfileManufacturer"]
|
||||||
|
list_profile_manufactures.append({
|
||||||
|
"PROF_MAN_ID": profile["id"],
|
||||||
|
"PROF_MAN": profile["sProfileManufacturer"],
|
||||||
|
"PROF_MAN_T": make_slug(profile["sProfileManufacturer"]),
|
||||||
|
"PROF_MAN_LIST": [{
|
||||||
|
"PROF_NAME_ID": profile["id"],
|
||||||
|
"PROF_NAME": profile["sProfileBriefDescription"],
|
||||||
|
"PROF_NAME_T": make_slug(profile["sProfileName"]),
|
||||||
|
}]
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
# Если производитель уже встречался, просто дописываем новую модель в его список.
|
||||||
|
list_profile_manufactures[-1]["PROF_MAN_LIST"].append({
|
||||||
|
"PROF_NAME_ID": profile["id"],
|
||||||
|
"PROF_NAME": profile["sProfileBriefDescription"],
|
||||||
|
"PROF_NAME_T": make_slug(profile["sProfileName"]),
|
||||||
|
})
|
||||||
|
|
||||||
|
to_template.update({
|
||||||
|
'CATALOG_PROFILE_MAN1_NAME2': list_profile_manufactures,
|
||||||
|
'CATALOG_MANUFACT_NUM': len(list_profile_manufactures),
|
||||||
|
'CATALOG_MANUFACT_NUM_W':
|
||||||
|
pytils.numeral.sum_string(len(list_profile_manufactures), pytils.numeral.MALE, ("производитель",
|
||||||
|
"производителя",
|
||||||
|
"производителей")),
|
||||||
|
'LAST_VISIT': get_last_user_visit_list(get_last_user_visit_cookies(request)[:3]),
|
||||||
|
'LOG_VISIT': get_last_all_user_visit_list(),
|
||||||
|
'ticks': float(time.time() - time_start),
|
||||||
|
})
|
||||||
|
return render(request, "catalog/catalog_of_profiles.html", to_template)
|
||||||
|
|
||||||
|
|
||||||
|
def catalog_profile_model(request: HttpRequest, manufacture_id: int, manufacture_name: str,
|
||||||
|
model_id: int, model_name: str) -> HttpResponse:
|
||||||
|
"""
|
||||||
|
КАТАЛОГ ПРОФИЛЕЙ: страница с описанием марки профиля
|
||||||
|
|
||||||
|
:param request: HttpRequest -- входящий http-запрос
|
||||||
|
:param manufacture_id: id профиля. Предполагается, что это первый id при сортировке по sProfileBriefDescription
|
||||||
|
:param manufacture_name: название производителя (транслитерированное pytils.translit.slugify())
|
||||||
|
:param model_id: id модели (марки) профиля
|
||||||
|
:param model_name: модель (марка) профиля (транслитерированное pytils.translit.slugify(sProfileName))
|
||||||
|
:return response: HttpResponse -- исходящий http-ответ
|
||||||
|
"""
|
||||||
|
time_start = time.time()
|
||||||
|
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)
|
||||||
|
if manufacturer_slug != manufacture_name \
|
||||||
|
or model_slug != model_name \
|
||||||
|
or manufacture_id != model_id:
|
||||||
|
return redirect(f"/catalog/profile/{model_id}-{manufacturer_slug}/"
|
||||||
|
f"{model_id}-{model_slug}")
|
||||||
|
|
||||||
|
# Локальные помощники держат вьюху короче и не размазывают однотипную логику по коду.
|
||||||
|
def make_slug(value: str) -> str:
|
||||||
|
return pytils.translit.slugify(value).lower()
|
||||||
|
|
||||||
|
def build_other_list(value: str) -> list[str]:
|
||||||
|
# Убираем пустые куски, чтобы не плодить «пустые» характеристики в шаблоне.
|
||||||
|
result = []
|
||||||
|
for chunk in (part.strip() for part in value.split(";")):
|
||||||
|
if not chunk:
|
||||||
|
continue
|
||||||
|
if ":" in chunk:
|
||||||
|
head, tail = chunk.split(":", 1)
|
||||||
|
result.append(f"<b>{head.strip()}:</b>{tail.strip()}")
|
||||||
|
else:
|
||||||
|
result.append(f"<b>{chunk}</b>")
|
||||||
|
return result
|
||||||
|
|
||||||
|
def update_pub_dat(current_pub_dat: datetime | None, candidate_pub_dat: datetime | None) -> datetime | None:
|
||||||
|
# На странице оставляем дату публикации/обновления только если она реально новее карточки профиля.
|
||||||
|
if candidate_pub_dat is None:
|
||||||
|
return current_pub_dat
|
||||||
|
if current_pub_dat is None or candidate_pub_dat.replace(tzinfo=None) > current_pub_dat.replace(tzinfo=None):
|
||||||
|
return candidate_pub_dat
|
||||||
|
return current_pub_dat
|
||||||
|
|
||||||
|
def apply_rating_colors(rating: dict, rating_pairs: tuple[tuple[str, str], ...], multiplier: int,
|
||||||
|
gray: bool = False) -> None:
|
||||||
|
# Один маленький helper вместо россыпи почти одинаковых строк: меняется только множитель и формат RGB.
|
||||||
|
for rating_key, template_key in rating_pairs:
|
||||||
|
color = int(255 - rating[rating_key] * multiplier)
|
||||||
|
if gray:
|
||||||
|
to_template[template_key] = f"{color},{color},{color}"
|
||||||
|
else:
|
||||||
|
to_template[template_key] = f"{color},255,{color}"
|
||||||
|
|
||||||
|
def merchant_row_to_dict(row: dict) -> dict:
|
||||||
|
# Один маппер для строки с партнёром: ключи шаблона остаются как были.
|
||||||
|
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_LOGO_URL": row["kOffer2SetKit__kSet2User__kMerchantOffice__kMerchantName__pMerchantLogo"],
|
||||||
|
"MERCHANT_OFFERS": row["offers_by_merchant"],
|
||||||
|
}
|
||||||
|
|
||||||
|
def profile_row_to_dict(profile: dict) -> dict:
|
||||||
|
# И то же самое для списка соседних профилей производителя.
|
||||||
|
return {
|
||||||
|
"PROFILE_NAME": profile["sProfileBriefDescription"],
|
||||||
|
"PROFILE_ID": profile["id"],
|
||||||
|
"PROFILE_URL": make_slug(profile["sProfileName"]),
|
||||||
|
"PROFILE_RATING": profile["fProfileRating"],
|
||||||
|
"PROFILE_RATING_STARS": get_rating_set_for_stars(profile["fProfileRating"]),
|
||||||
|
}
|
||||||
|
|
||||||
|
to_template: dict[str, object] = {"CATALOG_MODEL": q_pvc_by_id,
|
||||||
|
"CATALOG_MAN2URL": manufacture_name,
|
||||||
|
"CATALOG_URL": f"{manufacture_id}-{manufacture_name}",
|
||||||
|
"CATALOG_URL2": f"{manufacture_id}-{manufacture_name}/{model_id}-{model_name}",
|
||||||
|
"PROFILE_RATING_STARS": get_rating_set_for_stars(q_pvc_by_id.fProfileRating)}
|
||||||
|
try:
|
||||||
|
got_json = json.loads(q_pvc_by_id.sProfileDescription)
|
||||||
|
# раскрашиваем кружочки рейтинга напротив характеристик профиля
|
||||||
|
rating_pairs = (
|
||||||
|
(RANK_PVCP_CAMERAS_NUM_NAME, "RANK_PVCP_CAMERAS_COLOR"),
|
||||||
|
(RANK_PVCP_SEALS_NAME, "RANK_PVCP_SEALS_COLOR"),
|
||||||
|
(RANK_PVCP_THICKNESS_NAME, "RANK_PVCP_THICKNESS_COLOR"),
|
||||||
|
(RANK_PVCP_G_THICKNESS_NAME, "RANK_PVCP_G_THICKNESS_COLOR"),
|
||||||
|
(RANK_PVCP_RABBET_NAME, "RANK_PVCP_RABBET_COLOR"),
|
||||||
|
(RANK_PVCP_HEAT_TRANSFER_NAME, "RANK_PVCP_HEAT_TRANSFER_COLOR"),
|
||||||
|
(RANK_PVCP_SOUNDPROOFING_NAME, "RANK_PVCP_SOUNDPROOFING_COLOR"),
|
||||||
|
(RANK_PVCP_HEIGHT_NAME, "RANK_PVCP_HEIGHT_COLOR"),
|
||||||
|
)
|
||||||
|
if KEY_RATING in got_json:
|
||||||
|
# кружочки зелёные
|
||||||
|
apply_rating_colors(got_json[KEY_RATING], rating_pairs, 255)
|
||||||
|
elif KEY_RATING_VIRTUAL in got_json:
|
||||||
|
# кружочки серые
|
||||||
|
apply_rating_colors(got_json[KEY_RATING_VIRTUAL], rating_pairs, 64, gray=True)
|
||||||
|
else:
|
||||||
|
pass
|
||||||
|
if KEY_HTML in got_json:
|
||||||
|
to_template.update({"EXTRA_INFO": got_json[KEY_HTML]})
|
||||||
|
except (TypeError, ValueError, KeyError):
|
||||||
|
pass
|
||||||
|
to_template.update({"LIST_OTHER": build_other_list(q_pvc_by_id.sProfileOther)})
|
||||||
|
# Партнёров считаем через ORM: так код проще читать и легче переносить между СУБД.
|
||||||
|
q_merchant = (
|
||||||
|
PriceOffer.objects.filter(
|
||||||
|
kOffer2SetKit__kSet2PVCprofiles_id=model_id,
|
||||||
|
sOfferActive=True,
|
||||||
|
)
|
||||||
|
.values(
|
||||||
|
"kOffer2SetKit__kSet2User__kMerchantOffice__kMerchantName__id",
|
||||||
|
"kOffer2SetKit__kSet2User__kMerchantOffice__kMerchantName__sMerchantName",
|
||||||
|
"kOffer2SetKit__kSet2User__kMerchantOffice__kMerchantName__pMerchantLogo",
|
||||||
|
)
|
||||||
|
.annotate(offers_by_merchant=Count("id"))
|
||||||
|
.order_by("-offers_by_merchant", "kOffer2SetKit__kSet2User__kMerchantOffice__kMerchantName__sMerchantName")
|
||||||
|
)
|
||||||
|
to_template.update({'MERCHANTS': [merchant_row_to_dict(row) for row in q_merchant]})
|
||||||
|
# Близкие профили этого же производителя нужны для быстрых переходов по карточкам.
|
||||||
|
q_profiles = (
|
||||||
|
PVCprofiles.objects.filter(sProfileManufacturer=q_pvc_by_id.sProfileManufacturer)
|
||||||
|
.exclude(id=model_id)
|
||||||
|
.values("id", "fProfileRating", "sProfileBriefDescription", "sProfileName")
|
||||||
|
.order_by("fProfileRating")
|
||||||
|
)
|
||||||
|
to_template.update({'PROFILES': [profile_row_to_dict(profile) for profile in q_profiles]})
|
||||||
|
# Описание профиля берём через связку каталог -> блог: это один ORM-запрос вместо сырого SQL.
|
||||||
|
q_profiles_detail = (
|
||||||
|
Catalog2Profile.objects.filter(
|
||||||
|
kProfile_id=model_id,
|
||||||
|
sCatalogCardType=CATALOG_RECORD_FOR_PROFILE_MODEL,
|
||||||
|
kBlogCatalog__isnull=False,
|
||||||
|
)
|
||||||
|
.select_related("kBlogCatalog")
|
||||||
|
.order_by("kBlogCatalog__iCatalogSort")
|
||||||
|
)
|
||||||
|
profile_blog_posts = [row.kBlogCatalog for row in q_profiles_detail if row.kBlogCatalog is not None]
|
||||||
|
to_template.update({'PROFILE_DETAIL': profile_blog_posts})
|
||||||
|
# Картинка и дата публикации для meta-тегов берутся из связанного блога, если он есть.
|
||||||
|
if profile_blog_posts:
|
||||||
|
for blog_post in profile_blog_posts:
|
||||||
|
if blog_post.sImgForBlogSocial:
|
||||||
|
to_template['IMG_FOR_BLOG'] = blog_post.sImgForBlogSocial
|
||||||
|
break
|
||||||
|
|
||||||
|
pub_dat: datetime = q_pvc_by_id.dProfileModify
|
||||||
|
if profile_blog_posts:
|
||||||
|
profile_blog_dat: datetime | None = max((post.dPostDataModify for post in profile_blog_posts), default=pub_dat)
|
||||||
|
pub_dat = update_pub_dat(pub_dat, profile_blog_dat) or pub_dat
|
||||||
|
to_template['PUB_DAT'] = pub_dat
|
||||||
|
to_template.update(
|
||||||
|
{
|
||||||
|
'LAST_VISIT': get_last_user_visit_list(get_last_user_visit_cookies(request)[:3]),
|
||||||
|
'LOG_VISIT': get_last_all_user_visit_list(),
|
||||||
|
'ticks': float(time.time() - time_start),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return render(request, "catalog/catalog_of_profiles_model.html", to_template)
|
||||||
|
|
||||||
|
|
||||||
|
def catalog_profile_manufacture(request: HttpRequest, manufacture_id: int, manufacture_name: str) -> HttpResponse:
|
||||||
|
"""
|
||||||
|
КАТАЛОГ ПРОФИЛЕЙ: страница с описанием производителя профилей и списком марки производимых им профилей
|
||||||
|
|
||||||
|
:param request: HttpRequest -- входящий http-запрос
|
||||||
|
:param manufacture_id: id профиля. Предполагается, что это первый id при сортировке по sProfileBriefDescription
|
||||||
|
:param manufacture_name: название производителя (транслитерированное pytils.translit.slugify())
|
||||||
|
:return response: HttpResponse -- исходящий http-ответ
|
||||||
|
"""
|
||||||
|
time_start = time.time()
|
||||||
|
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:
|
||||||
|
return redirect(f'/catalog/profile/{manufacture_id}-'
|
||||||
|
f'{pytils.translit.slugify(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)}')
|
||||||
|
to_template = {'CATALOG_MANUFACT': q_pvc_by_id.sProfileManufacturer,
|
||||||
|
'CATALOG_MAN2URL': manufacture_name,
|
||||||
|
'CATALOG_URL': f"{manufacture_id}-{manufacture_name}"}
|
||||||
|
try:
|
||||||
|
# получаем информацию о производителе (статью из блога)
|
||||||
|
manufacture_description = list(PVCprofiles.objects.raw(
|
||||||
|
f"SELECT "
|
||||||
|
f" oknardia_blogposts.* "
|
||||||
|
f"FROM oknardia_catalog2profile"
|
||||||
|
f" RIGHT OUTER JOIN oknardia_pvcprofiles"
|
||||||
|
f" ON oknardia_catalog2profile.kProfile_id = oknardia_pvcprofiles.id"
|
||||||
|
f" LEFT OUTER JOIN oknardia_blogposts"
|
||||||
|
f" ON oknardia_catalog2profile.kBlogCatalog_id = oknardia_blogposts.id "
|
||||||
|
f"WHERE oknardia_catalog2profile.sCatalogCardType = {CATALOG_RECORD_FOR_PROFILE_MANUFACTURER} "
|
||||||
|
f" AND oknardia_pvcprofiles.sProfileManufacturer = '{q_pvc_by_id.sProfileManufacturer}'"
|
||||||
|
f" AND oknardia_blogposts.bCatalog IS TRUE "
|
||||||
|
f"GROUP BY oknardia_blogposts.bCatalog "
|
||||||
|
f"LIMIT 1;"
|
||||||
|
))[0]
|
||||||
|
to_template.update({'PUB_DAT': manufacture_description.dPostDataModify})
|
||||||
|
if PATH_FOR_IMG_BLOG in manufacture_description.sImgForBlogSocial:
|
||||||
|
to_template.update({'IMG_FOR_BLOG': manufacture_description.sImgForBlogSocial})
|
||||||
|
to_template.update({'HEADER': manufacture_description.sPostHeader,
|
||||||
|
'CONTENT': re.sub(r'<cut[\s\S]*>', '', manufacture_description.sPostContent,
|
||||||
|
0, re.IGNORECASE)})
|
||||||
|
to_template.update({'TIZER': re.sub(r'<script[\s\S]*?</script>|<style[\s\S]*?</style>|<iframe[\s\S]*?</iframe>',
|
||||||
|
'', to_template["CONTENT"], 0, re.IGNORECASE)})
|
||||||
|
except (ObjectDoesNotExist, IndexError, TypeError, KeyError,):
|
||||||
|
pass
|
||||||
|
q_profiles = PVCprofiles.objects.raw(
|
||||||
|
f"SELECT oknardia_pvcprofiles.id,"
|
||||||
|
f" oknardia_pvcprofiles.fProfileRating,"
|
||||||
|
f" oknardia_pvcprofiles.sProfileBriefDescription,"
|
||||||
|
f" oknardia_pvcprofiles.sProfileName "
|
||||||
|
f"FROM oknardia_pvcprofiles "
|
||||||
|
f"WHERE oknardia_pvcprofiles.sProfileManufacturer = '{q_pvc_by_id.sProfileManufacturer}' "
|
||||||
|
f"ORDER BY oknardia_pvcprofiles.fProfileRating;"
|
||||||
|
)
|
||||||
|
list_profiles = []
|
||||||
|
for i in q_profiles:
|
||||||
|
list_profiles.append({
|
||||||
|
"PROFILE_NAME": i.sProfileBriefDescription,
|
||||||
|
"PROFILE_ID": i.id,
|
||||||
|
"PROFILE_URL": pytils.translit.slugify(i.sProfileName).lower(),
|
||||||
|
"PROFILE_RATING": i.fProfileRating,
|
||||||
|
"PROFILE_RATING_STARS": get_rating_set_for_stars(i.fProfileRating),
|
||||||
|
})
|
||||||
|
to_template.update({'PROFILES': list_profiles})
|
||||||
|
try:
|
||||||
|
q_share_of_offers = list(PVCprofiles.objects.raw(
|
||||||
|
f"SELECT"
|
||||||
|
f" 1 AS id,"
|
||||||
|
f" SUM(Q1.offers_by_model) AS offers_by_maufacture,"
|
||||||
|
f" Q2.tatal_offers-SUM(Q1.offers_by_model) AS offers_other "
|
||||||
|
f"FROM (SELECT COUNT(oknardia_priceoffer.id) AS offers_by_model"
|
||||||
|
f" FROM oknardia_priceoffer"
|
||||||
|
f" LEFT OUTER JOIN oknardia_setkit"
|
||||||
|
f" ON oknardia_priceoffer.kOffer2SetKit_id = oknardia_setkit.id"
|
||||||
|
f" RIGHT OUTER JOIN oknardia_pvcprofiles"
|
||||||
|
f" ON oknardia_setkit.kSet2PVCprofiles_id = oknardia_pvcprofiles.id"
|
||||||
|
f" WHERE oknardia_pvcprofiles.sProfileManufacturer = '{q_pvc_by_id.sProfileManufacturer}') Q1,"
|
||||||
|
f" (SELECT COUNT(oknardia_priceoffer.id) AS tatal_offers"
|
||||||
|
f" FROM oknardia_priceoffer) AS Q2 "
|
||||||
|
f"LIMIT 1;"
|
||||||
|
))[0]
|
||||||
|
to_template.update({
|
||||||
|
'OFFERS_BY_MAUFACTURE': q_share_of_offers.offers_by_maufacture,
|
||||||
|
'OFFERS_OTHER': q_share_of_offers.offers_other,
|
||||||
|
'OFFERS_ANGLE': 90 + 180 * normalize(q_share_of_offers.offers_by_maufacture,
|
||||||
|
q_share_of_offers.offers_other + q_share_of_offers.offers_by_maufacture)
|
||||||
|
})
|
||||||
|
if q_share_of_offers is not None and q_share_of_offers.offers_by_maufacture != 0:
|
||||||
|
q_merchant = PVCprofiles.objects.raw(
|
||||||
|
f"SELECT"
|
||||||
|
f" COUNT(oknardia_priceoffer.id) AS offers_by_merchant,"
|
||||||
|
f" oknardia_merchantbrand.sMerchantName,"
|
||||||
|
f" oknardia_merchantbrand.pMerchantLogo,"
|
||||||
|
f" oknardia_merchantbrand.id "
|
||||||
|
f"FROM oknardia_priceoffer"
|
||||||
|
f" INNER JOIN oknardia_setkit"
|
||||||
|
f" ON oknardia_priceoffer.kOffer2SetKit_id = oknardia_setkit.id"
|
||||||
|
f" INNER JOIN oknardia_pvcprofiles"
|
||||||
|
f" ON oknardia_setkit.kSet2PVCprofiles_id = oknardia_pvcprofiles.id"
|
||||||
|
f" INNER JOIN oknardia_ouruser"
|
||||||
|
f" ON oknardia_setkit.kSet2User_id = oknardia_ouruser.id"
|
||||||
|
f" INNER JOIN oknardia_merchantoffice"
|
||||||
|
f" ON oknardia_ouruser.kMerchantOffice_id = oknardia_merchantoffice.id"
|
||||||
|
f" INNER JOIN oknardia_merchantbrand"
|
||||||
|
f" ON oknardia_merchantoffice.kMerchantName_id = oknardia_merchantbrand.id "
|
||||||
|
f"WHERE oknardia_pvcprofiles.sProfileManufacturer = '{q_pvc_by_id.sProfileManufacturer}' "
|
||||||
|
f"GROUP BY oknardia_merchantbrand.sMerchantName,"
|
||||||
|
f" oknardia_merchantbrand.pMerchantLogo,"
|
||||||
|
f" oknardia_merchantbrand.id "
|
||||||
|
f"ORDER BY offers_by_merchant DESC;"
|
||||||
|
)
|
||||||
|
list_merchant = []
|
||||||
|
for i in q_merchant:
|
||||||
|
list_merchant.append({
|
||||||
|
"MERCHANT_ID": i.id,
|
||||||
|
"MERCHANT_NAME": i.sMerchantName,
|
||||||
|
"MERCHANT_NAME_T": pytils.translit.slugify(i.sMerchantName),
|
||||||
|
"MERCHANT_LOGO_URL": i.pMerchantLogo,
|
||||||
|
"MERCHANT_OFFERS": i.offers_by_merchant
|
||||||
|
})
|
||||||
|
to_template.update({'MERCHANTS': list_merchant})
|
||||||
|
except (ObjectDoesNotExist, IndexError, TypeError): # вообще-то, запрос q_share_of_offers всегда что-то вернёт,
|
||||||
|
pass # но на всякий случай
|
||||||
|
to_template.update({
|
||||||
|
# получаем последние визиты клиента через куки
|
||||||
|
'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.time() - time_start)
|
||||||
|
})
|
||||||
|
return render(request, "catalog/catalog_of_profiles_manufacture.html", to_template)
|
||||||
Reference in New Issue
Block a user