diff --git a/lpon_site/frontend/views.py b/lpon_site/frontend/views.py index 0ff3a74..aebce3b 100644 --- a/lpon_site/frontend/views.py +++ b/lpon_site/frontend/views.py @@ -2,7 +2,7 @@ from dataclasses import dataclass -from django.shortcuts import render +from django.shortcuts import render, get_object_or_404 from django.http import HttpRequest, HttpResponse from frontend.models import TbArticle @@ -110,7 +110,7 @@ def catalog(request: HttpRequest | None) -> HttpResponse: return render(request, 'catalog.html', {"breadcrumbs": breadcrumbs}) -def txt_articles_list(request: HttpRequest | None) -> HttpResponse: +def info_articles_list(request: HttpRequest | None) -> HttpResponse: """ Представление (View) для отображения списка текстовых статей (тип ArticleType.TXT). @@ -122,7 +122,7 @@ def txt_articles_list(request: HttpRequest | None) -> HttpResponse: 3. Оптимизирует выборку связанного файла обложки (k_article_to_image) через select_related, чтобы избежать N+1 запросов при рендеринге изображений в списке. 4. Формирует хлебные крошки (BreadcrumbItem) с элементом "Инструкции". - 5. Рендерит шаблон `txt_list.html`, передавая в контекст список статей и хлебные крошки. + 5. Рендерит шаблон `info_list.html`, передавая в контекст список статей и хлебные крошки. Аргументы: request (HttpRequest | None): Объект HTTP-запроса Django. @@ -133,7 +133,7 @@ def txt_articles_list(request: HttpRequest | None) -> HttpResponse: # Выбираем опубликованные текстовые статьи из базы данных с правильной сортировкой articles = ( TbArticle.objects.filter( - l_article_type=TbArticle.ArticleType.TXT, + l_article_type=TbArticle.ArticleType.INFO, b_article_published=True, ) .select_related('k_article_to_image') @@ -150,4 +150,51 @@ def txt_articles_list(request: HttpRequest | None) -> HttpResponse: "articles": articles, "breadcrumbs": breadcrumbs, } - return render(request, "roll/txt_list.html", context) \ No newline at end of file + return render(request, "roll/info_list.html", context) + + +def info_article_detail(request: HttpRequest | None, slug: str) -> HttpResponse: + """ + Представление (View) для детального отображения отдельной текстовой статьи (тип ArticleType.INFO). + + Функциональность: + ----------------- + 1. Ищет опубликованную статью (b_article_published=True) по её слагу (slug). + Если статья не найдена или не опубликована — возвращает HTTP 404 (Page Not Found). + 2. Вызывает метод `increment_views()` у объекта статьи для атомарного увеличения + счётчика просмотров `i_article_views`. + 3. Формирует цепочку хлебных крошек (BreadcrumbItem): + - "Инструкции" (со ссылкой на /info/); + - Заголовок текущей статьи (без ссылки, текущий пункт). + 4. Рендерит шаблон `content/article_detail.html`, передавая объект статьи и крошки. + + Аргументы: + request (HttpRequest | None): Объект HTTP-запроса Django. + slug (str): Уникальный URL-слаг статьи. + + Возвращает: + HttpResponse: Сформированная HTML-страница детального просмотра статьи. + """ + # Получаем опубликованную статью по слагу или отдаем 404 Not Found + article = get_object_or_404( + TbArticle, + slug=slug, + b_article_published=True, + ) + + # Безопасно инкрементируем счетчик просмотров статьи + article.increment_views() + + # Формируем цепочку хлебных крошек (пункт "Главная" добавляется автоматически в шаблоне) + article_title = article.s_article_title_html or article.s_article_title + breadcrumbs = [ + BreadcrumbItem(title="Инструкции", url="/info/"), + BreadcrumbItem(title=article_title), + ] + + # Передаем объект статьи и крошки в шаблон детального просмотра + context = { + "article": article, + "breadcrumbs": breadcrumbs, + } + return render(request, "content/article_detail.html", context) \ No newline at end of file diff --git a/lpon_site/lpon_site/urls.py b/lpon_site/lpon_site/urls.py index 567a9bf..0cd55e1 100644 --- a/lpon_site/lpon_site/urls.py +++ b/lpon_site/lpon_site/urls.py @@ -27,7 +27,8 @@ urlpatterns = [ re_path(r'^$', views.index), re_path(r'^catalog/$', views.catalog), - re_path(r'^info/?$', views.txt_articles_list, name='txt_articles_list'), + re_path(r'^info/?$', views.info_articles_list, name='info_articles_list'), + re_path(r'^info/(?P[-a-zA-Z0-9_]+)/?$', views.info_article_detail, name='info_article_detail'), ] # Сервировать медиа-файлы в разработке (в production использовать nginx/Apache) diff --git a/lpon_site/templates/content/article_detail.html b/lpon_site/templates/content/article_detail.html new file mode 100644 index 0000000..0a5486a --- /dev/null +++ b/lpon_site/templates/content/article_detail.html @@ -0,0 +1,39 @@ +{% extends "_base.html" %} {% load static %} +{# SEO Мета-данные статьи #} +{% block PAGE_TITLE %}{{ article.seo_title|default:article.s_article_title_html|striptags|default:article.s_article_title }} — LPON.RU{% endblock %} + +{% block DESCRIPTION %}{{ article.seo_description|default:article.s_article_teaser_html|striptags|default:"" }}{% endblock %} + +{% block CONTENT %}{# Подключение блока хлебных крошек #}{% include "block/breadcrumbs.html" %}{% comment %} + КОНТЕНТ НАЧАЛО + +{% endcomment %}
+ {# ПРАВАЯ КОЛОНКА / Обложка / Иллюстрация статьи, если есть #}{% if article.k_article_to_image %}
+ {{ article.k_article_to_image.default_alt_text|default:article.s_article_title }} +
{% endif %} + {# ЛЕВАЯ КОЛОНКА / Основной текст статьи #}
+ +

{% if article.s_article_title_html %}{{ article.s_article_title_html|safe }}{% else %}{{ article.s_article_title }}{% endif %}

+ + +
+ +
+ + {% if article.s_article_teaser_html %} +
+ {{ article.s_article_teaser_html|safe }} +
{% endif %} + + {% if article.s_article_content_html %} +
+ {{ article.s_article_content_html|safe }} +
+ {% endif %} + + + +
+
{% endblock CONTENT %} \ No newline at end of file diff --git a/lpon_site/templates/css/tailwind-custom.css b/lpon_site/templates/css/tailwind-custom.css index 4e2ee6a..f8a04fd 100644 --- a/lpon_site/templates/css/tailwind-custom.css +++ b/lpon_site/templates/css/tailwind-custom.css @@ -78,4 +78,72 @@ hover:text-(--color-link-hover) dark:hover:text-indigo-200 hover:border-solid transition-colors; } + } + + @layer components { /* -- СЛОЙ COMPONENTS для компонентов и HTML-контента из БД -- {% comment %} + Стили для форматированного HTML-контента статей (из БД). + + Поскольку HTML-текст статьи (s_article_content_html) приходит из CodeMirror-редактора + и с высокой вероятностью не содержит явных Tailwind-классов на каждом p, h2, ul, blockquote, + мы оборачиваем контент в шаблонный контейнер
...
, + а здесь через @apply и каскадные селекторы задаем правила оформления элементов. + {% endcomment %} */ + + /* Контейнер форматированного текста статьи */ + .article-content { + @apply text-slate-800 dark:text-slate-200 leading-relaxed text-base md:text-lg; + + /* Заголовки внутри контента статьи */ + h1, h2, h3, h4 { + @apply font-bold text-slate-900 dark:text-slate-100 mt-8 mb-4 leading-snug; + } + h1 { @apply text-3xl md:text-3xl border-b border-slate-200 dark:border-slate-800 pb-2; } + h2 { @apply text-xl md:text-2xl; } + h3 { @apply text-lg md:text-xl; } + h4 { @apply text-base md:text-lg text-slate-700 dark:text-slate-300; } + + /* Параграфы и текстовые блоки */ + p { + @apply my-4 leading-relaxed; + } + + /* Списки (маркированные и нумерованные) */ + ul { + @apply list-disc list-outside space-y-2 pl-4; + } + ol { + @apply list-decimal list-outside space-y-2 pl-4; + } + li { + @apply leading-normal; + } + + /* Цитаты и выделенные блоки */ + blockquote { + @apply border-l-4 border-indigo-500 bg-slate-100 dark:bg-slate-900/80 + px-4 py-3 my-6 italic text-slate-700 dark:text-slate-300 rounded-r-lg; + } + + /* Изображения внутри контента */ + img { + @apply my-6 rounded-xl border border-slate-200 dark:border-slate-800 h-auto max-w-full mx-auto; + } + + /* Инлайновый код и блоки кода */ + code { + @apply bg-slate-200 dark:bg-slate-800 text-indigo-700 dark:text-indigo-300 + px-1.5 py-0.5 rounded text-sm font-mono; + } + pre { + @apply bg-slate-900 text-slate-100 p-4 rounded-xl my-6 overflow-x-auto text-sm font-mono; + } + pre code { + @apply bg-transparent text-inherit p-0; + } + + /* Горизонтальный разделитель */ + hr { + @apply my-8 border-t border-slate-200 dark:border-slate-800; + } + } } \ No newline at end of file