fix: seo и пустое состояние тегов

This commit is contained in:
2026-04-12 15:57:37 +03:00
parent 6d8ccb5ceb
commit 3f72d2e963
12 changed files with 387 additions and 126 deletions

View File

@@ -56,6 +56,7 @@ INSTALLED_APPS = [
'django.contrib.sessions', 'django.contrib.sessions',
'django.contrib.messages', 'django.contrib.messages',
'django.contrib.staticfiles', 'django.contrib.staticfiles',
'django.contrib.sitemaps',
# Панель отладки показываем только в dev-окружении при `DEBUG=True`. # Панель отладки показываем только в dev-окружении при `DEBUG=True`.
'debug_toolbar', 'debug_toolbar',
'django_select2', 'django_select2',

View File

@@ -14,10 +14,12 @@ Including another URLconf
2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
""" """
from django.contrib import admin from django.contrib import admin
from django.contrib.sitemaps.views import sitemap as sitemap_view
from django.conf.urls.static import static from django.conf.urls.static import static
from django.urls import path, include, re_path from django.urls import path, include, re_path
from cadpoint import settings from cadpoint import settings
from web import views from web import views
from web.sitemaps import CadpointSitemap
urlpatterns = [ urlpatterns = [
path( path(
@@ -50,7 +52,7 @@ urlpatterns = [
# re_path(r'^category-table/(?P<content_id>\d*)-\S*$', views.redirect_item), # re_path(r'^category-table/(?P<content_id>\d*)-\S*$', views.redirect_item),
# re_path(r'^aboutcadpoint.html/(?P<content_id>\d*)-\S*$', views.redirect_item), # re_path(r'^aboutcadpoint.html/(?P<content_id>\d*)-\S*$', views.redirect_item),
re_path(r'^sitemap.xml$', views.sitemap), path('sitemap.xml', sitemap_view, {'sitemaps': {'cadpoint': CadpointSitemap}}, name='web_sitemap'),
] ]

View File

@@ -1,8 +1,13 @@
{% extends "base.jinja2" %}{% load slug_ru %} {% extends "base.jinja2" %}{% load slug_ru %}
{% block Title %}Все теги{% endblock %} {% block page_title %}Все теги | CADpoint{% endblock %}
{% block canonical %}https://cadpoint.ru/alltags{% endblock %} {% block meta_title %}Все теги | CADpoint{% endblock %}
{% block og_title %}Все теги | CADpoint{% endblock %}
{% block twitter_title %}Все теги | CADpoint{% endblock %}
{% block Description %}Все теги сайта CADpoint{% endblock %} {% block Description %}Все теги сайта CADpoint{% endblock %}
{% block meta_description %}Все теги сайта CADpoint{% endblock %}
{% block og_description %}Все теги сайта CADpoint{% endblock %}
{% block twitter_description %}Все теги сайта CADpoint{% endblock %}
{% block Keywords %}cadpoint, теги, alltags, новости{% endblock %} {% block Keywords %}cadpoint, теги, alltags, новости{% endblock %}
{% block CONTENT %} {% block CONTENT %}

View File

@@ -1,36 +1,46 @@
<!DOCTYPE html>{% load static %}<html lang="ru"> <!DOCTYPE html>{% load static %}<html lang="ru">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta http-equiv="content-language" content="ru" />
<meta http-equiv="Date" content="{% block Date4Meta %}{% now 'c' %}{% endblock %}" />
<meta http-equiv="Last-Modified" content="{% block Last4Meta %}{% now 'c' %}{% endblock %}" />
<meta http-equiv="Expires" content="{% block Expires4Meta %}{% now 'c' %}{% endblock %}" />
<meta http-equiv="Cache-Control" content="no-cache">
<meta name="generator" content="Microsoft FrontPage 1.0" />
<meta name="viewport" content="width=device-width, initial-scale=0.5" /> <meta name="viewport" content="width=device-width, initial-scale=0.5" />
<meta name="description" content="{% block Description %}{% endblock %}" /> <meta name="description" content="{% block Description %}CADpoint — новости о 3D-печати и САПР{% endblock %}" />
<meta name="keywords" content="{% block Keywords %}{% endblock %}" /> <meta name="keywords" content="{% block Keywords %}cadpoint, 3D-печать, САПР, новости{% endblock %}" />
<meta name="copyright" lang="ru" content="Sergei Erjemin{% block CopyrightAuthor4Meta %}{% endblock %}." /> <meta name="copyright" lang="ru" content="Sergei Erjemin{% block CopyrightAuthor4Meta %}{% endblock %}." />
<meta name="robots" content="index,follow" /> <meta name="robots" content="index,follow" />
<meta name="document-state" content="{{ META_DOCUMENT_STATE|default:'Dynamic' }}" />
<meta name="generator" content="CADPOINT.RU 0.1β by Python/Django" />
<meta name="format-detection" content="telephone=no" /> <meta name="format-detection" content="telephone=no" />
<meta name="twitter:dnt" content="on" /> <title>{% block page_title %}CADPOINT.RU{% endblock %}</title>
<title>CADPOINT.RU - {% block Title %}{% endblock %}</title> <meta name="meta-title" content="{% block meta_title %}CADPOINT.RU{% endblock %}" />
<link rel="canonical" href="{% block canonical %}https://cadpoint.ru{% endblock %}" />{% comment %} <meta name="meta-description" content="{% block meta_description %}CADpoint — новости о 3D-печати и САПР{% endblock %}" />
<!-- Favicons -->{% endcomment %} <link rel="canonical" href="{% block canonical %}{{ request.scheme }}://{{ request.get_host }}{{ request.path }}{% endblock %}" />
<meta name="theme-color" content="#F5F5F5" />{% comment %}theme-color предоставляет браузерам цвет CSS для <meta property="og:site_name" content="CADPOINT.RU" />
настройки отображения страницы или окружающего пользовательского интерфейса.{% endcomment %} <meta property="og:locale" content="ru_RU" />
<meta property="og:title" content="{% block og_title %}CADPOINT.RU{% endblock %}" />
<meta property="og:description" content="{% block og_description %}CADpoint — новости о 3D-печати и САПР{% endblock %}" />
<meta property="og:url" content="{% block og_url %}{{ request.scheme }}://{{ request.get_host }}{{ request.path }}{% endblock %}" />
<meta property="og:type" content="{% block og_type %}website{% endblock %}" />
<meta property="og:image" content="{% block og_image %}{% static 'img/og-cadpoint-default.png' %}{% endblock %}" />
<meta property="og:image:alt" content="{% block og_image_alt %}CADPOINT.RU{% endblock %}" />
<meta name="twitter:card" content="{% block twitter_card %}summary_large_image{% endblock %}" />
<meta name="twitter:title" content="{% block twitter_title %}CADPOINT.RU{% endblock %}" />
<meta name="twitter:description" content="{% block twitter_description %}CADpoint — новости о 3D-печати и САПР{% endblock %}" />
<meta name="twitter:image" content="{% block twitter_image %}{% static 'img/og-cadpoint-default.png' %}{% endblock %}" />
<meta name="theme-color" content="#F5F5F5" />
{# Шутка #}<meta name="generator" content="Microsoft FrontPage 1.0"/>
<link rel="icon" type="image/svg+xml" href="{% static 'svgs/favicon.svg' %}" /> <link rel="icon" type="image/svg+xml" href="{% static 'svgs/favicon.svg' %}" />
<link rel="icon" type="image/png" href="{% static 'img/favicon.png' %}" /> <link rel="icon" type="image/png" href="{% static 'img/favicon.png' %}" />
<link rel="shortcut icon" type="image/x-icon" href="{% static 'img/favicon.ico' %}" /> <link rel="shortcut icon" type="image/x-icon" href="{% static 'img/favicon.ico' %}" />
<link rel="apple-touch-icon" href="{% static 'img/favicon.png' %}" />{% comment %} <link rel="apple-touch-icon" href="{% static 'img/favicon.png' %}" />
<!-- css -->{% endcomment %}
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous" /> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous" />
<link rel="stylesheet" href="{% static 'css/cadpoint.css' %}" /> <link rel="stylesheet" href="{% static 'css/cadpoint.css' %}" />{% block ExtraHead %}
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "WebSite",
"name": "CADPOINT.RU",
"url": "{{ request.scheme }}://{{ request.get_host }}/"
}
</script>{% endblock %}
</head> </head>
<body>{% block META_OG %}{% endblock %}{% block BODY %} <body>{% block BODY %}
{% block Top_CSS1 %}{% endblock %}{% block Top_CSS2 %}{% endblock %}{% block Top_CSS3 %}{% endblock %}{% include "blocks/header_nav.jinja2" %}{% block CONTENT %}{% endblock %} {% block Top_CSS1 %}{% endblock %}{% block Top_CSS2 %}{% endblock %}{% block Top_CSS3 %}{% endblock %}{% include "blocks/header_nav.jinja2" %}{% block CONTENT %}{% endblock %}
{% include "blocks/footer.jinja2" %}{% if COOKIES %} {% include "blocks/footer.jinja2" %}{% if COOKIES %}
{% include "blocks/accept-cookies.jinja2" %}{% endif %} {% include "blocks/accept-cookies.jinja2" %}{% endif %}

View File

@@ -1,51 +1,23 @@
{% extends "base.jinja2" %}{% load static %}{% load thumbnail %}{% load slug_ru %} {% extends "base.jinja2" %}{% load static %}{% load thumbnail %}{% load slug_ru %}
{% block META_OG %}{% comment %} РАЗМЕТКА Open Graph ДЛЯ СОЦ-СЕТЕЙ {% block page_title %}{% if EMPTY_STATE_TITLE %}{{ EMPTY_STATE_TITLE }}{% elif SELECTED_TAGS %}Тема: {% for I in SELECTED_TAGS %}{{ I.name }} - {% endfor %}{% elif TAGS_L %}Тема: {% for I in TAGS_IN_PAGE %}{% if I.slug in TAGS_L %}{{ I.name }} - {% endif %}{% endfor %}{% endif %}{% if PAGE_OF_LIST and not EMPTY_STATE_TITLE %}Страница {{ PAGE_OF_LIST|add:'1' }}{% elif not SELECTED_TAGS and not EMPTY_STATE_TITLE %}Главная{% endif %} | CADpoint{% endblock %}
подробности: https://habr.com/ru/company/macloud/blog/555082/{% endcomment %} {% block meta_title %}{% if EMPTY_STATE_TITLE %}{{ EMPTY_STATE_TITLE }}{% elif SELECTED_TAGS %}Тема: {% for I in SELECTED_TAGS %}{{ I.name }} - {% endfor %}{% elif TAGS_L %}Тема: {% for I in TAGS_IN_PAGE %}{% if I.slug in TAGS_L %}{{ I.name }} - {% endif %}{% endfor %}{% endif %}{% if PAGE_OF_LIST and not EMPTY_STATE_TITLE %}Страница {{ PAGE_OF_LIST|add:'1' }}{% elif not SELECTED_TAGS and not EMPTY_STATE_TITLE %}Главная{% endif %} | CADpoint{% endblock %}
<meta property="og:title" content="CADPOINT.RU / {% if TAGS_L %}тег: {% for I in TAGS_IN_PAGE %}{% if I.slug in TAGS_L %}{{ I.name|upper }} / {% endif %}{% endfor %}{% endif %}{% if PAGE_OF_LIST %}СТРАНИЦА {{ PAGE_OF_LIST|add:'1' }}{% else %}ГЛАВНАЯ{% endif %}">{% comment %} Уникальное название страницы. {% block og_title %}{% if EMPTY_STATE_TITLE %}{{ EMPTY_STATE_TITLE }}{% elif SELECTED_TAGS %}Тема: {% for I in SELECTED_TAGS %}{{ I.name }} - {% endfor %}{% elif TAGS_L %}Тема: {% for I in TAGS_IN_PAGE %}{% if I.slug in TAGS_L %}{{ I.name }} - {% endif %}{% endfor %}{% endif %}{% if PAGE_OF_LIST and not EMPTY_STATE_TITLE %}Страница {{ PAGE_OF_LIST|add:'1' }}{% elif not SELECTED_TAGS and not EMPTY_STATE_TITLE %}Главная{% endif %} | CADpoint{% endblock %}
Используется парсерами URL-адресов в социальных сетях, таких как Twitter или Facebook{% endcomment %} {% block twitter_title %}{% if EMPTY_STATE_TITLE %}{{ EMPTY_STATE_TITLE }}{% elif SELECTED_TAGS %}Тема: {% for I in SELECTED_TAGS %}{{ I.name }} - {% endfor %}{% elif TAGS_L %}Тема: {% for I in TAGS_IN_PAGE %}{% if I.slug in TAGS_L %}{{ I.name }} - {% endif %}{% endfor %}{% endif %}{% if PAGE_OF_LIST and not EMPTY_STATE_TITLE %}Страница {{ PAGE_OF_LIST|add:'1' }}{% elif not SELECTED_TAGS and not EMPTY_STATE_TITLE %}Главная{% endif %} | CADpoint{% endblock %}
<meta property="og:description" content="CADpoint / {% if TAGS_L %}тег: {% for I in TAGS_IN_PAGE %}{% if I.slug in TAGS_L %}{{ I.name }} / {% endif %}{% endfor %}{% endif %}{% if PAGE_OF_LIST %}Страница {{ PAGE_OF_LIST|add:'1' }}{% else %}Главная{% endif %}" />{% comment %} Уникальное описание страницы. {% block Description %}{% if EMPTY_STATE_MESSAGE %}{{ EMPTY_STATE_MESSAGE }}{% else %}CADpoint / {% if SELECTED_TAGS %}тег: {% for I in SELECTED_TAGS %}{{ I.name }} / {% endfor %}{% elif TAGS_L %}тег: {% for I in TAGS_IN_PAGE %}{% if I.slug in TAGS_L %}{{ I.name }} / {% endif %}{% endfor %}{% endif %}{% if PAGE_OF_LIST and not SELECTED_TAGS %}Страница {{ PAGE_OF_LIST|add:'1' }}{% elif not SELECTED_TAGS %}Главная{% endif %}{% endif %}{% endblock %}
Используется парсерами URL-адресов в социальных сетях, таких как Twitter или Facebook.{% endcomment %} {% block meta_description %}{% if EMPTY_STATE_MESSAGE %}{{ EMPTY_STATE_MESSAGE }}{% else %}CADpoint / {% if SELECTED_TAGS %}тег: {% for I in SELECTED_TAGS %}{{ I.name }} / {% endfor %}{% elif TAGS_L %}тег: {% for I in TAGS_IN_PAGE %}{% if I.slug in TAGS_L %}{{ I.name }} / {% endif %}{% endfor %}{% endif %}{% if PAGE_OF_LIST and not SELECTED_TAGS %}Страница {{ PAGE_OF_LIST|add:'1' }}{% elif not SELECTED_TAGS %}Главная{% endif %}{% endif %}{% endblock %}
<meta property="og:image" content="{% static 'img/og-cadpoint-default.png' %}" />{% comment %} Изображение, отображаемое, когда вы {% block og_description %}{% if EMPTY_STATE_MESSAGE %}{{ EMPTY_STATE_MESSAGE }}{% else %}CADpoint / {% if SELECTED_TAGS %}тег: {% for I in SELECTED_TAGS %}{{ I.name }} / {% endfor %}{% elif TAGS_L %}тег: {% for I in TAGS_IN_PAGE %}{% if I.slug in TAGS_L %}{{ I.name }} / {% endif %}{% endfor %}{% endif %}{% if PAGE_OF_LIST and not SELECTED_TAGS %}Страница {{ PAGE_OF_LIST|add:'1' }}{% elif not SELECTED_TAGS %}Главная{% endif %}{% endif %}{% endblock %}
делитесь ссылкой на страницу в социальных сетях, приложениях чата или других сайтах, {% block twitter_description %}{% if EMPTY_STATE_MESSAGE %}{{ EMPTY_STATE_MESSAGE }}{% else %}CADpoint / {% if SELECTED_TAGS %}тег: {% for I in SELECTED_TAGS %}{{ I.name }} / {% endfor %}{% elif TAGS_L %}тег: {% for I in TAGS_IN_PAGE %}{% if I.slug in TAGS_L %}{{ I.name }} / {% endif %}{% endfor %}{% endif %}{% if PAGE_OF_LIST and not SELECTED_TAGS %}Страница {{ PAGE_OF_LIST|add:'1' }}{% elif not SELECTED_TAGS %}Главная{% endif %}{% endif %}{% endblock %}
которые очищают URL-адреса. {% block Keywords %}cadpoint, {% if SELECTED_TAGS %}{% for I in SELECTED_TAGS %}{{ I.name }}, {% endfor %}{% else %}{% for I in TAGS_IN_PAGE %}{{ I.name }}, {% endfor %}{% endif %}новости{% if PAGE_OF_LIST %} , стр. {{ PAGE_OF_LIST|add:'1' }}{% endif %}{% endblock %}
В идеале это должно быть квадратное изображение с важным содержанием, размещенным
в центре квадрата в прямоугольнике с соотношением сторон 2:1. Это гарантирует,
что изображение будет хорошо смотреться на карточках с изображениями прямоугольной
и квадратной формы.{% endcomment %}
<meta property="og:image:alt" content="cadpint.ru: новости 3D-печати и Систем Автоматичекого Проектирования" />{% comment %}
Описание изображения.
Не используйте этот метатег, если изображение носит чисто декоративный характер
и не содержит значимой информации. Программы чтения с экрана игнорируют
изображение, если мы предоставлен замещающий текст.{% endcomment %}
<meta property="og:locale" content="ru_RU" />{% comment %} Естественный язык страницы.{% endcomment %}
<meta property="og:type" content="website" />{% comment %} Тип контента, которым вы делитесь,
например website, article, или video.movie{% endcomment %}
<meta property="og:url" content="https://cadpoint.ru{% if TAGS_L %}{{ TAGS_S }}{% endif %}{% if PAGE_OF_LIST %}/p{{ PAGE_OF_LIST }}{% endif %}" />{% comment %} Канонический URL страницы.
Обязательное свойство для допустимых страниц Open Graph.{% endcomment %}
<meta name="twitter:card" content="summary_large_image" />{% comment %} определяет, как будут выглядеть
карточки при публикации в Twitter. Есть два варианта для веб-сайтов: summary
и summary_large_image{% endcomment %}
<meta property="article:modified_time" content="{{ LENTA.0.dtContentTimeStamp|date:'c' }}" />
<meta property="article:published_time" content="{{ LENTA.0.tdContentPublishUp|date:'c' }}" />{% endblock %}
<!--- ТИТУЛ --->
{% block Title %}{% if TAGS_L %}Тема: {% for I in TAGS_IN_PAGE %}{% if I.slug in TAGS_L %}{{ I.name }} - {% endif %}{% endfor %}{% endif %}{% if PAGE_OF_LIST %}Страница {{ PAGE_OF_LIST|add:'1' }}{% else %}Главная{% endif %}{% endblock %}
{% block canonical %}https://cadpoint.ru{% if TAGS_L %}{{ TAGS_S }}{% endif %}{% if PAGE_OF_LIST %}/p{{ PAGE_OF_LIST }}{% endif %}{% endblock %}
{% block Description %}CADpoint / {% if TAGS_L %}тег: {% for I in TAGS_IN_PAGE %}{% if I.slug in TAGS_L %}{{ I.name }} / {% endif %}{% endfor %}{% endif %}{% if PAGE_OF_LIST %}Страница {{ PAGE_OF_LIST|add:'1' }}{% else %}Главная{% endif %}{% endblock %}
{% block Keywords %}cadpoint, {% for I in TAGS_IN_PAGE %}{{ I.name }}, {% endfor %}новости{% if PAGE_OF_LIST %} , стр. {{ PAGE_OF_LIST|add:'1' }}{% endif %}{% endblock %}
{% block Date4Meta %}{{ LENTA.0.tdContentPublishUp|date:'c' }}{% endblock %}"
{% block Last4Meta %}{{ LENTA.0.dtContentTimeStamp|date:'c' }}{% endblock %}"
{% block Expires4Meta %}{% now 'c' %}{% endblock %}"
{% block CONTENT %}{# <!-- ХЛЕБНЫЕ КРОШКИ: НАЧАЛО -->#} {% block CONTENT %}{# <!-- ХЛЕБНЫЕ КРОШКИ: НАЧАЛО -->#}
<div class="container bread-crumb"> <div class="container bread-crumb">
<div class="row"> <div class="row">
<nav class="col-12 х" aria-label="breadcrumb"> <nav class="col-12 х" aria-label="breadcrumb">
<ol class="breadcrumb"> <ol class="breadcrumb">
<li class="breadcrumb-item nw s"><a href="/"><i class="bi bi-house-door" title="Главная"></i>&#8199;Главная</a></li>{% for t in TAGS_IN_PAGE %}{% if t.slug in TAGS_L %} <li class="breadcrumb-item nw s"><a href="/"><i class="bi bi-house-door" title="Главная"></i>&#8199;Главная</a></li>{% if SELECTED_TAGS %}{% for t in SELECTED_TAGS %}
<li class="breadcrumb-item nw s"><a href="{{ TAGS_S|rm_tag:t.slug }}"><i class="bi bi-tag" title="тэг"></i>&#8199;{{ t.name }}</a></li>{% endif %}{% endfor %}{% if PAGE_OF_LIST %} <li class="breadcrumb-item nw s"><a href="{{ TAGS_S|rm_tag:t.slug }}"><i class="bi bi-tag" title="тэг"></i>&#8199;{{ t.name }}</a></li>{% endfor %}{% else %}{% for t in TAGS_IN_PAGE %}{% if t.slug in TAGS_L %}
<li class="breadcrumb-item nw s"><a href="{{ TAGS_S|rm_tag:t.slug }}"><i class="bi bi-tag" title="тэг"></i>&#8199;{{ t.name }}</a></li>{% endif %}{% endfor %}{% endif %}{% if PAGE_OF_LIST %}
<li class="breadcrumb-item active nw s" aria-current="page">Страница {{ PAGE_OF_LIST|add:'1' }}</li>{% endif %} <li class="breadcrumb-item active nw s" aria-current="page">Страница {{ PAGE_OF_LIST|add:'1' }}</li>{% endif %}
</ol> </ol>
</nav> </nav>
@@ -100,6 +72,21 @@
</ul> </ul>
</nav> </nav>
</div>{# НАВИГАЦИЯ ПО СТРАНИЦАМ СО СПИСКАМИ КОНТЕНТА ИЗ ПРОИЗВОЛЬНОЙ КАТЕГОРИИ: КОНЕЦ #}{% endif %} </div>{# НАВИГАЦИЯ ПО СТРАНИЦАМ СО СПИСКАМИ КОНТЕНТА ИЗ ПРОИЗВОЛЬНОЙ КАТЕГОРИИ: КОНЕЦ #}{% endif %}
{% if not LENTA %}
<div class="row default-list">
<div class="col-12">
<div class="alert alert-warning mb-0" role="alert">
<h3 class="h5 mb-2">{% if EMPTY_STATE_TITLE %}{{ EMPTY_STATE_TITLE }}{% else %}Новостей не найдено{% endif %}</h3>
<p class="mb-0">{% if EMPTY_STATE_MESSAGE %}{{ EMPTY_STATE_MESSAGE }}{% else %}По выбранному разделу пока нет новостей.{% endif %}</p>
<p class="mb-0 mt-3">
<a href="/">На главную</a>
<span class="mx-2">&middot;</span>
<a href="/alltags">Все теги</a>
</p>
</div>
</div>
</div>
{% endif %}
</div> </div>
</div> </div>
</div>{#<!-- POINT-СРАНИЦА: КОНЕЦ -->#}{% endblock %} </div>{#<!-- POINT-СРАНИЦА: КОНЕЦ -->#}{% endblock %}

View File

@@ -1,46 +1,71 @@
{% extends "base.jinja2" %}{% load static %}{% load thumbnail %}{% load slug_ru %} {% extends "base.jinja2" %}{% load static %}{% load thumbnail %}{% load slug_ru %}
{% block META_OG %}{% comment %} РАЗМЕТКА Open Graph ДЛЯ СОЦ-СЕТЕЙ {% block page_title %}{{ ITEM.szContentHead|safe_html_ss }} | CADpoint{% endblock %}
подробности: https://habr.com/ru/company/macloud/blog/555082/ и https://ogp.me/{% endcomment %}
<meta property="og:title" content="cadpoint.ru: {{ ITEM.szContentHead|safe_html_ss }}">{% comment %} Уникальное название страницы. {% block canonical %}{{ request.scheme }}://{{ request.get_host }}/item/{{ ITEM.id }}-{{ ITEM.szContentSlug }}{% endblock %}
Используется парсерами URL-адресов в социальных сетях, таких как Twitter или Facebook{% endcomment %} {% block og_url %}{{ request.scheme }}://{{ request.get_host }}/item/{{ ITEM.id }}-{{ ITEM.szContentSlug }}{% endblock %}
<meta property="og:description" content="CADpoint: {% if ITEM.szContentDescription %}{{ ITEM.szContentDescription }}{% else %}{{ ITEM.szContentHead|safe_html_ss }}{% endif %}" />{% comment %} Уникальное описание страницы.
Используется парсерами URL-адресов в социальных сетях, таких как Twitter или Facebook.{% endcomment %} {% block Description %}{% if ITEM.szContentDescription %}{{ ITEM.szContentDescription }}{% else %}{{ ITEM.szContentHead|safe_html_ss }}{% endif %}{% endblock %}
<meta property="og:image" content="{% if ITEM.imgContentPreview is None %}{% static 'img/og-cadpoint-default.png' %}{% else %}{% thumbnail ITEM.imgContentPreview 680x680 upscale %}{% endif %}" />{% comment %} Изображение, отображаемое, когда вы
делитесь ссылкой на страницу в социальных сетях, приложениях чата или других сайтах, {% block meta_title %}{{ ITEM.szContentHead|safe_html_ss }} | CADpoint{% endblock %}
которые очищают URL-адреса.
В идеале это должно быть квадратное изображение с важным содержанием, размещенным {% block og_title %}{{ ITEM.szContentHead|safe_html_ss }} | CADpoint{% endblock %}
в центре квадрата в прямоугольнике с соотношением сторон 2:1. Это гарантирует,
что изображение будет хорошо смотреться на карточках с изображениями прямоугольной {% block twitter_title %}{{ ITEM.szContentHead|safe_html_ss }} | CADpoint{% endblock %}
и квадратной формы.{% endcomment %}
<meta property="og:image:alt" content="cadpoint.ru: {{ ITEM.szContentHead|safe_html_ss }}" />{% comment %} {% block meta_description %}{% if ITEM.szContentDescription %}{{ ITEM.szContentDescription }}{% else %}{{ ITEM.szContentHead|safe_html_ss }}{% endif %}{% endblock %}
Описание изображения.
Не используйте этот метатег, если изображение носит чисто декоративный характер {% block og_description %}{% if ITEM.szContentDescription %}{{ ITEM.szContentDescription }}{% else %}{{ ITEM.szContentHead|safe_html_ss }}{% endif %}{% endblock %}
и не содержит значимой информации. Программы чтения с экрана игнорируют
изображение, если мы предоставлен замещающий текст.{% endcomment %} {% block twitter_description %}{% if ITEM.szContentDescription %}{{ ITEM.szContentDescription }}{% else %}{{ ITEM.szContentHead|safe_html_ss }}{% endif %}{% endblock %}
<meta property="og:locale" content="ru_RU" />{% comment %} Естественный язык страницы.{% endcomment %}
<meta property="og:type" content="website" />{% comment %} Тип контента, которым вы делитесь, {% block Keywords %}cadpoint{% if ITEM.szContentKeywords %}, {{ ITEM.szContentKeywords }}{% endif %}{% with item_tags=ITEM.tags.all %}{% if item_tags %}, {% for t in item_tags %}{{ t.name }}{% if not forloop.last %}, {% endif %}{% endfor %}{% endif %}{% endwith %}, новости{% endblock %}
например website, article, или video.movie{% endcomment %}
<meta property="og:url" content="https://cadpoint.ru/item/{{ ITEM.id }}-{{ ITEM.szContentSlug }}" />{% comment %} Канонический URL страницы. {% block og_type %}article{% endblock %}
Обязательное свойство для допустимых страниц Open Graph.{% endcomment %}
<meta name="twitter:card" content="summary_large_image" />{% comment %} определяет, как будут выглядеть {% block og_image %}{% if ITEM.imgContentPreview is None %}{% static 'img/og-cadpoint-default.png' %}{% else %}{% thumbnail ITEM.imgContentPreview 680x680 upscale %}{% endif %}{% endblock %}
карточки при публикации в Twitter. Есть два варианта для веб-сайтов: summary
и summary_large_image{% endcomment %}{% for t in ITEM.tags.all%} {% block og_image_alt %}{{ ITEM.szContentHead|safe_html_ss }} | CADpoint{% endblock %}
<meta property="article:tag" content="{{ t.name|lower }}">{% comment %} определяет тематику статьи, ключевые слова или хэштеги. Если тематик несколько, перечислите их в разных метатегах.{% endcomment %}{% endfor %}
<meta property="article:modified_time" content="{{ ITEM.dtContentTimeStamp|date:'c' }}"> {% block twitter_card %}summary_large_image{% endblock %}
<meta property="article:published_time" content="{{ ITEM.tdContentPublishUp|date:'c' }}">
{% block twitter_image %}{% if ITEM.imgContentPreview is None %}{% static 'img/og-cadpoint-default.png' %}{% else %}{% thumbnail ITEM.imgContentPreview 680x680 upscale %}{% endif %}{% endblock %}
{% block ExtraHead %}{{ block.super }}{% for t in ITEM.tags.all %}
<meta property="article:tag" content="{{ t.name|lower }}" />{% endfor %}
<meta property="article:published_time" content="{{ ITEM.tdContentPublishUp|date:'Y-m-d' }}T{{ ITEM.tdContentPublishUp|date:'H:i' }}{{ ITEM.tdContentPublishUp|date:'O'|slice:':3' }}:{{ ITEM.tdContentPublishUp|date:'O'|slice:'3:' }}" />
<meta property="article:modified_time" content="{{ ITEM.dtContentTimeStamp|date:'Y-m-d' }}T{{ ITEM.dtContentTimeStamp|date:'H:i' }}{{ ITEM.dtContentTimeStamp|date:'O'|slice:':3' }}:{{ ITEM.dtContentTimeStamp|date:'O'|slice:'3:' }}" />
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Article",
"mainEntityOfPage": {
"@type": "WebPage",
"@id": "{{ request.scheme }}://{{ request.get_host }}/item/{{ ITEM.id }}-{{ ITEM.szContentSlug }}"
},
"headline": "{{ ITEM.szContentHead|safe_html_ss|escapejs }}",
"description": "{% if ITEM.szContentDescription %}{{ ITEM.szContentDescription|escapejs }}{% else %}{{ ITEM.szContentHead|safe_html_ss|escapejs }}{% endif %}",
"image": [
"{% if ITEM.imgContentPreview is None %}{% static 'img/og-cadpoint-default.png' %}{% else %}{% thumbnail ITEM.imgContentPreview 680x680 upscale %}{% endif %}"
],
"datePublished": "{{ ITEM.tdContentPublishUp|date:'Y-m-d' }}T{{ ITEM.tdContentPublishUp|date:'H:i' }}{{ ITEM.tdContentPublishUp|date:'O'|slice:':3' }}:{{ ITEM.tdContentPublishUp|date:'O'|slice:'3:' }}",
"dateModified": "{{ ITEM.dtContentTimeStamp|date:'Y-m-d' }}T{{ ITEM.dtContentTimeStamp|date:'H:i' }}{{ ITEM.dtContentTimeStamp|date:'O'|slice:':3' }}:{{ ITEM.dtContentTimeStamp|date:'O'|slice:'3:' }}",
"author": {
"@type": "Person",
"name": "Sergei Erjemin"
},
"publisher": {
"@type": "Organization",
"name": "CADPOINT.RU",
"logo": {
"@type": "ImageObject",
"url": "{{ request.scheme }}://{{ request.get_host }}{% static 'svgs/favicon.svg' %}"
}
}
}
</script>
{% endblock %} {% endblock %}
<!--- ТИТУЛ --->
{% block Title %}{{ ITEM.szContentHead|safe_html_ss }}{% endblock %}
{% block canonical %}https://cadpoint.ru/item/{{ ITEM.id }}-{{ ITEM.szContentSlug }}{% endblock %}
{% block Description %}CADpoint: {% if ITEM.szContentDescription %}{{ ITEM.szContentDescription }}{% else %}{{ ITEM.szContentHead|safe_html_ss }}{% endif %}{% endblock %}
{% block Keywords %}cadpoint, {% for t in ITEM.tags.all %}{{ t.name }}, {% endfor %}{{ ITEM.szContentKeywords }}, новости{% endblock %}
{% block Date4Meta %}{{ ITEM.tdContentPublishUp|date:'c' }}{% endblock %}"
{% block Last4Meta %}{{ ITEM.dtContentTimeStamp|date:'c' }}{% endblock %}"
{% block Expires4Meta %}{% now 'c' %}{% endblock %}"
{% block CONTENT %}{# <!-- ХЛЕБНЫЕ КРОШКИ: НАЧАЛО -->#}<div class="container bread-crumb"> {% block CONTENT %}{# <!-- ХЛЕБНЫЕ КРОШКИ: НАЧАЛО -->#}<div class="container bread-crumb">
<div class="row"> <div class="row">
<nav class="col-12 х" aria-label="breadcrumb"> <nav class="col-12 х" aria-label="breadcrumb">
@@ -76,7 +101,7 @@
</div>{% endif %}{% endfor %} </div>{% endif %}{% endfor %}
</nav>{#<!-- боковая навигация: конец-->#} </nav>{#<!-- боковая навигация: конец-->#}
</div> </div>
{# <!-- НИЖНЯЯ НАВИГАЦИЯ-ПАДЖИНАТОР ПО НОВОСТЯМ: НАЧАЛО--> #}{% if PER_PAGE %} {# <!-- НИЖНЯЯ НАВИГАЦИЯ-ПАДЖИНАТОР ПО НОВОСТЯМ: НАЧАЛО--> #}{% if PER_PAGE %}
<nav class="row" aria-label="Навигация по контенту"> <nav class="row" aria-label="Навигация по контенту">
<ul class="col offset-md-3 pagination px-1">{% if ITEMS_BEFORE.0.id %} <ul class="col offset-md-3 pagination px-1">{% if ITEMS_BEFORE.0.id %}
<li class="page-item"><a class="page-link" href="/item/{{ ITEMS_BEFORE.0.id }}-{{ ITEMS_BEFORE.0.szContentSlug }}?p={{ ITEMS_BEFORE.0.pp }}&n={{ ITEMS_BEFORE.0.nn }}"><i class="bi bi-arrow-left" title="Предыдущая"></i></a></li>{% else %}<li class="page-item disabled"><a class="page-link" href="#"><i class="bi bi-arrow-left" title="Предыдущая"></i></a></li>{% endif %} <li class="page-item"><a class="page-link" href="/item/{{ ITEMS_BEFORE.0.id }}-{{ ITEMS_BEFORE.0.szContentSlug }}?p={{ ITEMS_BEFORE.0.pp }}&n={{ ITEMS_BEFORE.0.nn }}"><i class="bi bi-arrow-left" title="Предыдущая"></i></a></li>{% else %}<li class="page-item disabled"><a class="page-link" href="#"><i class="bi bi-arrow-left" title="Предыдущая"></i></a></li>{% endif %}

View File

@@ -1,5 +0,0 @@
{% load static %}{% load thumbnail %}{% load slug_ru %}<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url><loc>https://cadpoint.ru/</loc><priority>1</priority></url>{% for I in ITEMS %}
<url><loc>https://cadpoint.ru/item/{{ I.id }}-{{ I.szContentSlug }}</loc><priority>1</priority></url>{% endfor %}
</urlset>

64
cadpoint/web/sitemaps.py Normal file
View File

@@ -0,0 +1,64 @@
from django.contrib.sitemaps import Sitemap
from django.urls import reverse
from django.utils import timezone
from django.db.models import Q
from taggit.models import Tag
from web.models import TbContent
class CadpointSitemap(Sitemap):
"""Одна карта сайта для публичных страниц CADpoint."""
changefreq = 'weekly'
def items(self):
now_value = timezone.now()
content_items = list(
TbContent.objects.filter(
bContentPublish=True,
tdContentPublishUp__lte=now_value,
).filter(
Q(tdContentPublishDown__isnull=True) | Q(tdContentPublishDown__gt=now_value)
).order_by('-tdContentPublishUp', 'id')
)
latest_content = content_items[0].dtContentTimeStamp if content_items else None
sitemap_items = [
{'kind': 'home', 'lastmod': latest_content},
{'kind': 'alltags', 'lastmod': latest_content},
]
tags = Tag.objects.filter(taggit_taggeditem_items__isnull=False).distinct().order_by('name')
sitemap_items.extend({'kind': 'tag', 'tag': tag} for tag in tags)
sitemap_items.extend({'kind': 'item', 'item': item} for item in content_items)
return sitemap_items
def location(self, item):
kind = item['kind']
if kind == 'home':
return '/'
if kind == 'alltags':
return reverse('web_alltags')
if kind == 'tag':
return f"/tag_{item['tag'].slug}"
return f"/item/{item['item'].id}-{item['item'].szContentSlug}"
def lastmod(self, item):
kind = item['kind']
if kind in {'home', 'alltags'}:
return item.get('lastmod')
if kind == 'item':
return item['item'].dtContentTimeStamp
return None
def priority(self, item):
kind = item['kind']
if kind == 'home':
return 1.0
if kind == 'alltags':
return 0.6
if kind == 'tag':
return 0.5
return 0.8

View File

@@ -82,6 +82,13 @@ class AdminTypographFormTests(SimpleTestCase):
'html', 'html',
) )
for field_name in ('szContentKeywords', 'szContentDescription'):
self.assertIsInstance(form.fields[field_name].widget, Textarea)
self.assertNotEqual(
form.fields[field_name].widget.attrs.get('data-codemirror-editor'),
'1',
)
self.assertIn('codemirror/editor.js', str(form.media)) self.assertIn('codemirror/editor.js', str(form.media))
def test_tbcontent_model_has_no_btypograf_field(self): def test_tbcontent_model_has_no_btypograf_field(self):
@@ -181,6 +188,41 @@ class AllTagsPageTests(TestCase):
self.assertContains(response, '<b class="_tag">1</b>') self.assertContains(response, '<b class="_tag">1</b>')
class TagEmptyStateTests(TestCase):
def setUp(self):
self.item = TbContent.objects.create(
szContentHead='Тест 1',
szContentIntro='Анонс 1',
szContentBody='Тело 1',
szContentSlug='test-1',
bContentPublish=True,
)
self.item.tags.add('alpha')
Tag.objects.create(name='lonely')
def test_tag_page_with_news_still_renders_entries(self):
response = self.client.get('/tag_alpha')
self.assertEqual(response.status_code, 200)
self.assertContains(response, 'Тест 1')
self.assertNotContains(response, 'Новостей не найдено')
def test_tag_page_without_news_shows_empty_state(self):
response = self.client.get('/tag_lonely')
self.assertEqual(response.status_code, 200)
self.assertContains(response, 'Новостей не найдено')
self.assertContains(response, 'По этому тегу пока нет опубликованных новостей.')
self.assertNotContains(response, 'Тест 1')
def test_tag_page_for_missing_slug_shows_missing_tag_message(self):
response = self.client.get('/tag_rebranded-tag')
self.assertEqual(response.status_code, 200)
self.assertContains(response, 'Тег не найден')
self.assertContains(response, 'не найден или был переименован')
class TypographTests(TestCase): class TypographTests(TestCase):
def test_save_generates_slug_from_clean_text(self): def test_save_generates_slug_from_clean_text(self):
item = TbContent(szContentHead='<b>Привет&nbsp;мир</b>') item = TbContent(szContentHead='<b>Привет&nbsp;мир</b>')
@@ -277,16 +319,107 @@ class TypographTests(TestCase):
szContentHead='Проверка просмотра', szContentHead='Проверка просмотра',
szContentIntro='Короткий анонс', szContentIntro='Короткий анонс',
szContentBody='Полный текст', szContentBody='Полный текст',
szContentSlug='proverka-prosmotra', szContentSlug='real-slug',
bContentPublish=True, bContentPublish=True,
) )
timestamp_before = item.dtContentTimeStamp timestamp_before = item.dtContentTimeStamp
response = self.client.get(f'/item/{item.id}-{item.szContentSlug}') response = self.client.get(f'/item/{item.id}-wrong-slug')
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertContains(response, '<title>Проверка просмотра | CADpoint</title>')
self.assertContains(
response,
f'<link rel="canonical" href="http://testserver/item/{item.id}-{item.szContentSlug}" />',
)
self.assertNotContains(response, 'wrong-slug')
self.assertContains(response, '<script type="application/ld+json">')
self.assertContains(response, '"@type": "WebSite"')
self.assertContains(response, '"@type": "Article"')
self.assertRegex(
response.content.decode(),
r'"datePublished": "\d{4}-\d{2}-\d{2}T\d{2}:\d{2}[+-]\d{2}:\d{2}"',
)
self.assertRegex(
response.content.decode(),
r'"dateModified": "\d{4}-\d{2}-\d{2}T\d{2}:\d{2}[+-]\d{2}:\d{2}"',
)
item.refresh_from_db() item.refresh_from_db()
self.assertEqual(item.iContentHits, 1) self.assertEqual(item.iContentHits, 1)
self.assertEqual(item.dtContentTimeStamp, timestamp_before) self.assertEqual(item.dtContentTimeStamp, timestamp_before)
def test_show_item_keywords_prioritize_explicit_seo_values(self):
item = TbContent.objects.create(
szContentHead='SEO ключи',
szContentIntro='Анонс',
szContentBody='Текст',
szContentSlug='seo-klyuchi',
szContentKeywords='ключ1, ключ2',
bContentPublish=True,
)
item.tags.add('alpha', 'beta')
response = self.client.get(f'/item/{item.id}-{item.szContentSlug}')
self.assertEqual(response.status_code, 200)
self.assertContains(
response,
'<meta name="keywords" content="cadpoint, ключ1, ключ2, alpha, beta, новости" />',
)
self.assertNotContains(response, 'None')
def test_show_item_keywords_without_explicit_value_do_not_render_none(self):
item = TbContent.objects.create(
szContentHead='SEO пусто',
szContentIntro='Анонс',
szContentBody='Текст',
szContentSlug='seo-pusto',
szContentKeywords=None,
bContentPublish=True,
)
item.tags.add('alpha')
response = self.client.get(f'/item/{item.id}-{item.szContentSlug}')
self.assertEqual(response.status_code, 200)
self.assertContains(
response,
'<meta name="keywords" content="cadpoint, alpha, новости" />',
)
self.assertNotContains(response, 'None')
class SitemapTests(TestCase):
def setUp(self):
self.published = TbContent.objects.create(
szContentHead='Опубликованная статья',
szContentIntro='Анонс',
szContentBody='Текст',
szContentSlug='opublikovannaya-statya',
bContentPublish=True,
)
self.published.tags.add('alpha')
TbContent.objects.create(
szContentHead='Скрытая статья',
szContentIntro='Анонс',
szContentBody='Текст',
szContentSlug='skrytaya-statya',
bContentPublish=False,
)
def test_sitemap_uses_django_framework_and_lists_public_pages(self):
response = self.client.get(reverse('web_sitemap'))
self.assertEqual(response.status_code, 200)
self.assertContains(response, '<?xml version="1.0" encoding="UTF-8"?>', html=False)
self.assertContains(response, '<loc>http://testserver/</loc>', html=False)
self.assertContains(response, '<loc>http://testserver/alltags</loc>', html=False)
self.assertContains(
response,
f'<loc>http://testserver/item/{self.published.id}-{self.published.szContentSlug}</loc>',
html=False,
)
self.assertContains(response, '<loc>http://testserver/tag_alpha</loc>', html=False)
self.assertNotContains(response, 'skrytaya-statya')

View File

@@ -90,6 +90,8 @@ def index(request,
""" """
template = "index.jinja2" # шаблон template = "index.jinja2" # шаблон
to_template: dict[str, object] = {"COOKIES": check_cookies(request)} to_template: dict[str, object] = {"COOKIES": check_cookies(request)}
empty_state_title = ""
empty_state_message = ""
page_number = max(int(ppage), 0) page_number = max(int(ppage), 0)
now_value = timezone.now() now_value = timezone.now()
@@ -109,6 +111,7 @@ def index(request,
# Список тегов должен быть отсортированным для канонического URL. # Список тегов должен быть отсортированным для канонического URL.
return HttpResponseRedirect("tag_%s" % "_".join(sorted(selected_tags))) return HttpResponseRedirect("tag_%s" % "_".join(sorted(selected_tags)))
content_qs = content_qs.filter(tags__slug__in=selected_tags).distinct() content_qs = content_qs.filter(tags__slug__in=selected_tags).distinct()
to_template["SELECTED_TAGS"] = Tag.objects.filter(slug__in=selected_tags).order_by("slug")
to_template["TAGS_S"] = "/tag_" + slug_tags to_template["TAGS_S"] = "/tag_" + slug_tags
to_template["TAGS_L"] = selected_tags to_template["TAGS_L"] = selected_tags
@@ -116,6 +119,26 @@ def index(request,
total_items = q_content.count() total_items = q_content.count()
total_page = max(math.ceil(total_items / settings.NUM_ITEMS_IN_PAGE) - 1, 0) if total_items else 0 total_page = max(math.ceil(total_items / settings.NUM_ITEMS_IN_PAGE) - 1, 0) if total_items else 0
if selected_tags:
existing_tags = set(Tag.objects.filter(slug__in=selected_tags).values_list("slug", flat=True))
missing_tags = [tag_slug for tag_slug in selected_tags if tag_slug not in existing_tags]
if missing_tags:
if len(missing_tags) == 1:
empty_state_title = "Тег не найден"
empty_state_message = f"Тег «{missing_tags[0]}» не найден или был переименован."
else:
empty_state_title = "Теги не найдены"
empty_state_message = f"Теги «{', '.join(missing_tags)}» не найдены или были переименованы."
elif not total_items:
if len(selected_tags) == 1:
empty_state_message = "По этому тегу пока нет опубликованных новостей."
else:
empty_state_message = "По выбранным тегам пока нет опубликованных новостей."
elif page_number > total_page:
empty_state_message = "На этой странице больше нет новостей. Откройте первую страницу ленты."
elif not total_items:
empty_state_message = "Пока здесь нет новостей."
q_content = q_content[page_number * settings.NUM_ITEMS_IN_PAGE: q_content = q_content[page_number * settings.NUM_ITEMS_IN_PAGE:
page_number * settings.NUM_ITEMS_IN_PAGE+ settings.NUM_ITEMS_IN_PAGE] page_number * settings.NUM_ITEMS_IN_PAGE+ settings.NUM_ITEMS_IN_PAGE]
@@ -138,6 +161,8 @@ def index(request,
to_template["TAGS_IN_PAGE"] = q_tags to_template["TAGS_IN_PAGE"] = q_tags
to_template["PAGE_OF_LIST"] = page_number to_template["PAGE_OF_LIST"] = page_number
to_template["TOTAL_PAGE"] = total_page to_template["TOTAL_PAGE"] = total_page
to_template["EMPTY_STATE_TITLE"] = empty_state_title
to_template["EMPTY_STATE_MESSAGE"] = empty_state_message
return render(request, template, to_template) return render(request, template, to_template)
@@ -223,16 +248,3 @@ def show_item(request,
except (ValueError, AttributeError, TbContent.DoesNotExist, TbContent.MultipleObjectsReturned): except (ValueError, AttributeError, TbContent.DoesNotExist, TbContent.MultipleObjectsReturned):
raise Http404("Контента с таким id не существует") raise Http404("Контента с таким id не существует")
def sitemap(request):
template = "sitemap.jinja2" # шаблон
q_items = TbContent.objects.filter(
bContentPublish=True,
tdContentPublishUp__lte=timezone.now(),
).filter(
Q(tdContentPublishDown__isnull=True) | Q(tdContentPublishDown__gt=timezone.now())
).order_by("-tdContentPublishUp", "id").all()
to_template: dict[str, object] = {"ITEMS": q_items}
print(q_items)
response = render(request, template, to_template)
return response

19
public/llms.txt Normal file
View File

@@ -0,0 +1,19 @@
# CADpoint
CADpoint — русскоязычный сайт о 3D-печати, CAD и смежных темах.
## Основные страницы
- https://cadpoint.ru/ — главная лента новостей и публикаций
- https://cadpoint.ru/alltags — список всех тегов (тем) сайта
- https://cadpoint.ru/sitemap.xml — полный список опубликованных страниц
## Страницы контента
- https://cadpoint.ru/item/<id>-<slug> — статья или публикация
- https://cadpoint.ru/tag_<slug> — страницы с материалами по тематическому тегу
## Как ориентироваться
- Основной контент на русском языке.
- Приоритет отдавайте публичным страницам статей и тегов.
- Админка, debug-URL и служебные страницы не являются основным контентом.
- Для ссылок используйте канонические публичные URL без служебных параметров.

View File

@@ -1,3 +1,11 @@
# robots.txt for CADpoint
User-agent: * User-agent: *
Disallow: /admin/ # Оставляем только нужное: игнорируем параметр `p` и 'n' у страниц статей.
Clean-param: ref /item/ Clean-param: p&n /item/
# подсказываем карту сайта и основной хост.
Sitemap: https://cadpoint.ru/sitemap.xml
Host: cadpoint.ru
# Ссылка на файл для ИИ-моделей: https://cadpoint.ru/llms.txt