mod: SEO-теги и JSON-LD (chema.org)

This commit is contained in:
2026-05-11 18:29:10 +03:00
parent c62fbceaaf
commit 0efc12fe7a
2 changed files with 157 additions and 24 deletions

View File

@@ -1,20 +1,121 @@
{% extends "base.html" %}{% load static %} {% extends "base.html" %}{% load static %}
{% block Title %}Блоги: Стр.{{ PAGE_BACK|add:"1" }}{% endblock %} {% block Title %}Блог Окнардии для компаний-поставщиков окон и их клиентов — Страница {{ PAGE_BACK|add:"1" }}{% endblock %}
{% block Add_Body_Attribute %} style="padding-top:70px;"{% endblock %} {% block Add_Body_Attribute %} style="padding-top:70px;"{% endblock %}
{% block Description %}Блоги «Окнардия» :: {% for i1 in DIM_BLOGPOST %}{{ i1.HEADER_D }}{% if not forloop.last %}, {% endif %}{% endfor %}{% endblock %} {% block Description %}Блог Окнардии для компаний-поставщиков окон и их клиентов: публикации о пластиковых окнах, продвижении услуг замены окон, ценах и трендах — Страница {{ PAGE_BACK|add:"1" }}{% endblock %}
{% block Keywords %}oknardia, окнардия, blogs, блоги, публикации, цены пластиковых окон, стоимость пластиковых окон, скидки на пластиковые окна, предложения пластиковых окон, {{ META_KEYWORDS|default:"" }} {% endblock %} {% block Keywords %}{{ META_KEYWORDS }}{% endblock %}
{% block Date4Meta %}{% if PUB_DAT %}{{ PUB_DAT|date:"Y-m-d" }}{% else %}{% now "c" %}{% endif %}{% endblock %} {% block Date4Meta %}{% if META_DATA_PUB %}{{ META_DATA_PUB|date:"Y-m-d" }}{% else %}{% now "c" %}{% endif %}{% endblock %}
{% block Last4Meta %}{% if PUB_DAT %}{{ PUB_DAT|date:"Y-m-d" }}{% else %}{% now "c" %}{% endif %}{% endblock %} {% block Last4Meta %}{% if META_DATA_MODIFY %}{{ META_DATA_MODIFY|date:"Y-m-d" }}{% else %}{% now "c" %}{% endif %}{% endblock %}
{% block Author4Meta %}: Блоги{% endblock %} {% block Author4Meta %}: Блоги Окнардия{% endblock %}
{% block CopyrightAuthor4Meta %}: Блоги{% endblock %} {% block CopyrightAuthor4Meta %}: Блоги Окнардия{% endblock %}
{% block Top_Meta1 %}{# <!-- Canonical (текущая страница) и pagination разметка --> #}
<link rel="canonical" href="{{ request.scheme }}://{{ request.get_host }}/blog/P{{ PAGE_BACK }}" />
{% if PAGE_BACK > 0 %}<link rel="prev" href="{{ request.scheme }}://{{ request.get_host }}/blog/P{{ PAGE_BACK|add:'-1' }}" />{% endif %}
{% if FORW_BUTTON %}<link rel="next" href="{{ request.scheme }}://{{ request.get_host }}/blog/P{{ PAGE_BACK|add:'1' }}" />{% endif %}
{# <!-- Meta-теги для улучшения индексирования в социальных сетях (B2B) --> #}
<meta property="og:locale" content="ru_RU" />
<meta property="og:type" content="website" />
<meta property="og:title" content="Блог Окнардии для компаний-поставщиков окон и их клиентов" />
<meta property="og:description" content="Публикации о пластиковых окнах, продвижении услуг замены окон, ценах и трендах — Страница {{ PAGE_BACK|add:"1" }}" />
<meta property="og:url" content="{{ request.scheme }}://{{ request.get_host }}/blog/P{{ PAGE_BACK }}" />
<meta property="og:site_name" content="oknardia.ru" />
{% if META_IMAGE %}<meta property="og:image" content="{{ request.scheme }}://{{ request.get_host }}{{ META_IMAGE }}" />{% else %}<meta property="og:image" content="{{ request.scheme }}://{{ request.get_host }}/static/img/oknardia_logo.svg" />{% endif %}
<meta name="twitter:card" content="summary" />
<meta name="twitter:site" content="@oknardia" />
<meta name="twitter:title" content="Блог Окнардии — для компаний и их клиентов" />
<meta name="twitter:description" content="Статьи о продвижении услуг замены окон, ценах и трендах в оконной индустрии — Страница {{ PAGE_BACK|add:"1" }}" />
{% if META_IMAGE %}<meta name="twitter:image" content="{{ request.scheme }}://{{ request.get_host }}{{ META_IMAGE }}" />{% else %}<meta name="twitter:image" content="{{ request.scheme }}://{{ request.get_host }}/static/img/oknardia_logo.svg" />{% endif %}
{# <!-- /Meta-теги --> #}{% endblock %}
{% block ADD_TO_HEAD %}{# <!-- Schema.org JSON-LD разметка для списка блога (B2B для компаний и их клиентов) --> #}
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "CollectionPage",
"name": "Блог Окнардии — для компаний-поставщиков окон и их клиентов",
"description": "Блог Окнардии для компаний-поставщиков окон и их клиентов: публикации о пластиковых окнах, продвижении услуг замены окон, ценах и трендах. Ресурс для расширения продаж и улучшения видимости в выдаче поисковиков.",
"url": "{{ request.scheme }}://{{ request.get_host }}/blog/P{{ PAGE_BACK }}",
"image": "{% if META_IMAGE %}{{ request.scheme }}://{{ request.get_host }}{{ META_IMAGE }}{% else %}{{ request.scheme }}://{{ request.get_host }}/static/img/oknardia_logo.svg{% endif %}",
"audience": [
{
"@type": "AudienceType",
"name": "B2B: Компании-поставщики и производители оконных конструкций"
},
{
"@type": "AudienceType",
"name": "B2C: Клиенты компаний, ищущие информацию о заменке окон"
}
],
"breadcrumb": {
"@type": "BreadcrumbList",
"itemListElement": [
{
"@type": "ListItem",
"position": 1,
"name": "Главная",
"item": "{{ request.scheme }}://{{ request.get_host }}/"
},
{
"@type": "ListItem",
"position": 2,
"name": "Блог",
"item": "{{ request.scheme }}://{{ request.get_host }}/blog/"
},
{
"@type": "ListItem",
"position": 3,
"name": "Страница {{ PAGE_BACK|add:'1' }}",
"item": "{{ request.scheme }}://{{ request.get_host }}/blog/P{{ PAGE_BACK }}"
}
]
},
"mainEntity": {
"@type": "Blog",
"name": "Блог Окнардии",
"alternateName": "Блог для компаний-поставщиков окон и их клиентов",
"description": "Профессиональный блог для компаний, занимающихся производством, поставкой и установкой пластиковых окон, а также их клиентов. Публикации об оконных конструкциях, продвижении услуг замены окон, ценах, трендах и инновациях в оконной индустрии.",
"url": "{{ request.scheme }}://{{ request.get_host }}/blog/",
"image": "{{ request.scheme }}://{{ request.get_host }}/static/img/oknardia_logo.svg",
"audience": [
{
"@type": "AudienceType",
"name": "Компании-поставщики, производители, установщики окон"
},
{
"@type": "AudienceType",
"name": "Конечные клиенты, ищущие информацию об окнах и услугах"
}
],
"blogPosts": [
{% for POST in DIM_BLOGPOST %}
{
"@type": "BlogPosting",
"headline": "{{ POST.HEADER|escapejs }}",
"description": "{% if POST.META_DESC %}{{ POST.META_DESC|escapejs }}{% else %}Публикация в блоге Окнардии{% endif %}",
"image": "{% if POST.IMG_BLOG %}{{ request.scheme }}://{{ request.get_host }}/media/{{ POST.IMG_BLOG|escapejs }}{% else %}{{ request.scheme }}://{{ request.get_host }}/static/img/oknardia_logo.svg{% endif %}",
"datePublished": "{{ POST.PUB_DAT|date:'Y-m-d' }}T{{ POST.PUB_DAT|date:'H:i:s' }}Z",
"dateModified": "{{ POST.MOD_DAT|date:'Y-m-d' }}T{{ POST.MOD_DAT|date:'H:i:s' }}Z",
"author": {
"@type": "Person",
"name": "{{ POST.NAME1 }}{% if POST.NAME2 %} {{ POST.NAME2 }}{% endif %}"
},
"url": "{{ request.scheme }}://{{ request.get_host }}/blogpost/{{ POST.POST_ID }}/{{ POST.HEADER_T }}",
"keywords": "{% if POST.META_KW %}{{ POST.META_KW|escapejs }}{% else %}блог, публикация, окна, поставщики{% endif %}"
}{% if not forloop.last %},{% endif %}
{% endfor %}
]
}
}
</script>
{# <!-- /Schema.org JSON-LD --> #}{% endblock %}
{% block Top_JS3%} {% block Top_JS3%}
<script> <script>
@@ -24,29 +125,37 @@ $(window).load(function(){var images = $('.half');images.each(function(i){$(this
{% block Main_Content %} {% block Main_Content %}
<div class="container-fluid"> <div class="container-fluid">
{# <!--- Хлебные крошки --> #}<div class="row"> {# Хлебные крошки #}
<div class="row">
<div class="col-md-11 col-xs-12"> <div class="col-md-11 col-xs-12">
<ol class="breadcrumb"> <ol class="breadcrumb">
<li><a href="/">Главная</a></li> <li><a href="/">Главная</a></li>
<li><a href="/blog/">Блог</a></li> <li><a href="/blog/">Блог</a></li>
<li>Стр.{{ PAGE_BACK|add:"1" }}</li> <li>Страница {{ PAGE_BACK|add:"1" }}</li>
</ol> </ol>
<h1>Блог</h1> <h1>Блог</h1>
</div> </div>
</div>{# <!--- /Хлебные крошки ---> #} </div>
{# Список постов #}
{% for POST in DIM_BLOGPOST %}<div class="row"> {% for POST in DIM_BLOGPOST %}<div class="row">
<div class="col-md-11 col-xs-12 blog-list-header"> <div class="col-md-11 col-xs-12 blog-list-header">
<hr class="dotted-black" /> <hr class="dotted-black" />
<p>{{ POST.PUB_DAT|date:"d.F.Y (l) H:i" }}</p> <p><time datetime="{{ POST.PUB_DAT|date:'Y-m-d\TH:i:s\Z' }}">{{ POST.PUB_DAT|date:"d.F.Y (l) H:i" }}</time></p>
<p><img src="/media/{{ POST.USER_AVATAR }}" /> {% if POST.NAME1 != "" or POST.NAME2 != "" %} <i>{{ POST.NAME1 }}{% if POST.NAME2 != "" %}&nbsp;{{ POST.NAME2 }}{% endif %}</i>{% endif %}</p> <p><img src="/media/{{ POST.USER_AVATAR }}" alt="{{ POST.NAME1 }}{% if POST.NAME2 %} {{ POST.NAME2 }}{% endif %}" />
{% if POST.NAME1 != "" or POST.NAME2 != "" %}<i>{{ POST.NAME1 }}{% if POST.NAME2 != "" %}&nbsp;{{ POST.NAME2 }}{% endif %}</i>{% endif %}
</p>
<h2>{{ POST.HEADER|safe }}</h2> <h2>{{ POST.HEADER|safe }}</h2>
</div> </div>
<div class="col-md-11 blog-list-tizer"> <div class="col-md-11 blog-list-tizer">
{# <!--- Тизер поста в блоге ---> #}{{ POST.CONTENT_CUT|safe|truncatechars:4096 }}{# <!--- /Тизер поста в блоге ---> #} {# Тизер поста в блоге #}{{ POST.CONTENT_CUT|safe|truncatechars:4096 }}{# /Тизер поста в блоге #}
{% if POST.CUT_TEXT != "NONE" %}<p><a href="/blogpost/{{ POST.POST_ID }}/{{ POST.HEADER_T }}?page-back={{ PAGE_BACK }}" class="btn btn-default">{{ POST.CUT_TEXT|safe }}</a></p>{% endif %} {% if POST.CUT_TEXT != "NONE" %}<p><a href="/blogpost/{{ POST.POST_ID }}/{{ POST.HEADER_T }}?page-back={{ PAGE_BACK }}" class="btn btn-default">{{ POST.CUT_TEXT|safe }}</a></p>{% endif %}
</div> </div>
</div>{% endfor %} </div>{% endfor %}
{# <!--- Листалка ---> #}<div class="row">
{# Листалка пагинации #}
<div class="row">
<div class="col-md-11 col-xs-12"> <div class="col-md-11 col-xs-12">
<hr class="dotted-black" /> <hr class="dotted-black" />
<nav aria-label="переходы на страницы"> <nav aria-label="переходы на страницы">
@@ -66,13 +175,10 @@ $(window).load(function(){var images = $('.half');images.each(function(i){$(this
</ul> </ul>
</nav> </nav>
</div> </div>
</div>{# <!--- /Листалка: ---> #} </div>
{# <!--- Баннер ---> #}<div class="row"><div class="col-md-12 col-xs-12">{% include "ad/bannet-wide.html" %}</div></div>{# <!--- Баннер: конец --- #}
{# Баннер #}
<div class="row"><div class="col-md-12 col-xs-12">{% include "ad/bannet-wide.html" %}</div></div>
</div>{% endblock %} </div>{% endblock %}
{% comment %}
{% block Top_Nav_Bar %}
{# ОТЛАДКА, ГАСИМ ВЕРХНЕЕ МЕНЮ #}
{% endblock %}
{% endcomment %}

View File

@@ -85,15 +85,19 @@ def blog_list_posts(request: HttpRequest, page: str = "0") -> HttpResponse:
'NAME1': post.kBlogAuthorUser.kDjangoUser.first_name, 'NAME1': post.kBlogAuthorUser.kDjangoUser.first_name,
'NAME2': post.kBlogAuthorUser.kDjangoUser.last_name, 'NAME2': post.kBlogAuthorUser.kDjangoUser.last_name,
'PUB_DAT': post.dPostDataBegin, 'PUB_DAT': post.dPostDataBegin,
'MOD_DAT': post.dPostDataModify,
'HEADER': post.sPostHeader, 'HEADER': post.sPostHeader,
'HEADER_D': safe_html_spec_symbols(post.sPostHeader), 'HEADER_D': safe_html_spec_symbols(post.sPostHeader),
'HEADER_T': sanitize_slug(post.sPostHeader).lower(), 'HEADER_T': sanitize_slug(post.sPostHeader),
'POST_ID': post.id, 'POST_ID': post.id,
'USER_STATUS': post.kBlogAuthorUser.get_sUserStatus_display(), 'USER_STATUS': post.kBlogAuthorUser.get_sUserStatus_display(),
'USER_AVATAR': post.kBlogAuthorUser.sUserAvatarImg, 'USER_AVATAR': post.kBlogAuthorUser.sUserAvatarImg,
'USER_TITLE': post.kBlogAuthorUser.sUserJobTitle, 'USER_TITLE': post.kBlogAuthorUser.sUserJobTitle,
'USER_FROM_ID_OFFICE': post.kBlogAuthorUser.kMerchantOffice, 'USER_FROM_ID_OFFICE': post.kBlogAuthorUser.kMerchantOffice,
'CONTENT_CUT': post.sPostContent}) 'CONTENT_CUT': post.sPostContent,
'META_DESC': post.sMetaDescription,
'META_KW': post.sMetaKeywords,
'IMG_BLOG': post.sImgForBlogSocial})
# ищем CUT в тексте блога # ищем CUT в тексте блога
i_cut1 = post.sPostContent.lower().find(u"<cut") i_cut1 = post.sPostContent.lower().find(u"<cut")
if i_cut1 != -1: if i_cut1 != -1:
@@ -107,14 +111,37 @@ def blog_list_posts(request: HttpRequest, page: str = "0") -> HttpResponse:
dim_blogposts[i].update({'CUT_TEXT': u"Читать дальше →"}) dim_blogposts[i].update({'CUT_TEXT': u"Читать дальше →"})
else: else:
# Проверка на случай если нет "cut" и текст не длинный... нужна ли кнопка "читать дальше"? # Проверка на случай если нет "cut" и текст не длинный... нужна ли кнопка "читать дальше"?
if len(post.sPostContent) < 4096: if len(post.sPostContent) < 2048:
dim_blogposts[i].update({'CUT_TEXT': u"NONE"}) dim_blogposts[i].update({'CUT_TEXT': u"NONE"})
else: else:
dim_blogposts[i].update({'CUT_TEXT': u"Читать дальше →"}) dim_blogposts[i].update({'CUT_TEXT': u"Читать дальше →"})
i += 1 i += 1
# Формируем SEO-данные для мета-тегов страницы
# Ключевые слова для B2B блога (компании-поставщик и их клиенты)
combined_keywords = u"oknardia, окнардия, блог, поставщики окон, производители, установщики, компании"
first_post_image = ""
if dim_blogposts:
# Объединяем META_KW из нескольких первых постов
collected_keywords = []
for post_dict in dim_blogposts[:3]: # из первых 3 постов
if post_dict.get('META_KW'):
# Берем только часть keywords без фиксированного префикса (чтобы не повторять)
kw_parts = post_dict['META_KW'].split(", ")
if len(kw_parts) > 4: # пропускаем первые 4 (фиксированный префикс)
collected_keywords.extend(kw_parts[4:])
if collected_keywords:
combined_keywords = u"oknardia, окнардия, блог, поставщики окон, производители, установщики, " + ", ".join(collected_keywords[:5])
# Берем изображение первого поста для og:image
if dim_blogposts[0].get('IMG_BLOG'):
first_post_image = f"/media/{dim_blogposts[0]['IMG_BLOG']}"
to_template.update({'DIM_BLOGPOST': dim_blogposts, to_template.update({'DIM_BLOGPOST': dim_blogposts,
'META_DATA_PUB': q[0].dPostDataBegin, 'META_DATA_PUB': q[0].dPostDataBegin,
'META_DATA_MODIFY': q[0].dPostDataModify, 'META_DATA_MODIFY': q[0].dPostDataModify,
'META_KEYWORDS': combined_keywords,
'META_IMAGE': first_post_image,
'PAGE_BACK': page, 'PAGE_BACK': page,
'ticks': float(time()-time_start)}) 'ticks': float(time()-time_start)})
return render(request, template, to_template) return render(request, template, to_template)