add: view и шаблоны для блогов и страниц
This commit is contained in:
49
etpgrf_site/blog/templates/blog/page_detail.html
Normal file
49
etpgrf_site/blog/templates/blog/page_detail.html
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
{% extends 'typograph/base.html' %}
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
{% block title %}{{ page.seo_title|default:page.title }} — ETPGRF{% endblock %}
|
||||||
|
{% block description %}{{ page.seo_description|default:page.content|striptags|truncatechars:160 }}{% endblock %}
|
||||||
|
{% block og_title %}{{ page.seo_title|default:page.title }}{% endblock %}
|
||||||
|
{% block og_description %}{{ page.seo_description|default:page.content|striptags|truncatechars:160 }}{% endblock %}
|
||||||
|
{% block og_image %}{% if page.image %}{{ request.scheme }}://{{ request.get_host }}{{ page.image.url }}{% else %}{{ request.scheme }}://{{ request.get_host }}{% static 'img/etpgrf-logo-for-fb-vk-x.gif' %}{% endif %}{% endblock %}
|
||||||
|
{% block twitter_title %}{{ page.seo_title|default:page.title }}{% endblock %}
|
||||||
|
{% block twitter_description %}{{ page.seo_description|default:page.content|striptags|truncatechars:160 }}{% endblock %}
|
||||||
|
{% block twitter_image %}{% if page.image %}{{ request.scheme }}://{{ request.get_host }}{{ page.image.url }}{% else %}{{ request.scheme }}://{{ request.get_host }}{% static 'img/etpgrf-logo-for-fb-vk-x.gif' %}{% endif %}{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row">
|
||||||
|
|
||||||
|
{# Левая колонка: Дата и Картинка #}
|
||||||
|
<div class="col-lg-2 align-self-start text-end mb-4">
|
||||||
|
<p class="small align-self-end">
|
||||||
|
<small class="bg-secondary bg-opacity-10 p-2 text-nowrap">
|
||||||
|
{{ page.published_at|date:"d.M.Y"|lower }}
|
||||||
|
</small>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{% if post.image %}
|
||||||
|
<img src="{{ post.image.url }}" class="w-100 rounded" alt="{{ post.title|safe }}" />
|
||||||
|
{% else %}
|
||||||
|
<img src="{% static 'img/etpgrf-logo-for-fb-vk-x.gif' %}" class="w-100 rounded opacity-50" alt="{{ post.title|safe }}" />
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{# Правая колонка: Контент #}
|
||||||
|
<div class="col-lg-10 border-start ps-lg-4">
|
||||||
|
|
||||||
|
<h1 class="display-4 mb-4">{{ page.title|safe }}</h1>
|
||||||
|
|
||||||
|
{% if page.excerpt %}
|
||||||
|
<div class="lead bg-secondary bg-opacity-10 p-3 rounded">
|
||||||
|
{{ page.excerpt|safe }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="page-content mt-4">
|
||||||
|
{{ page.content|safe }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
58
etpgrf_site/blog/templates/blog/post_detail.html
Normal file
58
etpgrf_site/blog/templates/blog/post_detail.html
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
{% extends 'typograph/base.html' %}
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
{% block title %}{{ post.seo_title|default:post.title }} — ETPGRF{% endblock %}
|
||||||
|
{% block description %}{{ post.seo_description|default:post.excerpt|default:post.content|striptags|truncatechars:160 }}{% endblock %}
|
||||||
|
{% block og_title %}{{ post.seo_title|default:post.title }}{% endblock %}
|
||||||
|
{% block og_description %}{{ post.seo_description|default:post.excerpt|default:post.content|striptags|truncatechars:160 }}{% endblock %}
|
||||||
|
{% block og_image %}{% if post.image %}{{ request.scheme }}://{{ request.get_host }}{{ post.image.url }}{% else %}{{ request.scheme }}://{{ request.get_host }}{% static 'img/etpgrf-logo-for-fb-vk-x.gif' %}{% endif %}{% endblock %}
|
||||||
|
{% block twitter_title %}{{ post.seo_title|default:post.title }}{% endblock %}
|
||||||
|
{% block twitter_description %}{{ post.seo_description|default:post.excerpt|default:post.content|striptags|truncatechars:160 }}{% endblock %}
|
||||||
|
{% block twitter_image %}{% if post.image %}{{ request.scheme }}://{{ request.get_host }}{{ post.image.url }}{% else %}{{ request.scheme }}://{{ request.get_host }}{% static 'img/etpgrf-logo-for-fb-vk-x.gif' %}{% endif %}{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row">
|
||||||
|
|
||||||
|
{# Левая колонка: Дата и Картинка #}
|
||||||
|
<div class="col-lg-2 align-self-start text-end mb-4">
|
||||||
|
<p class="small align-self-end">
|
||||||
|
<small class="bg-secondary bg-opacity-10 p-2 text-nowrap">
|
||||||
|
{{ post.published_at|date:"d.M.Y"|lower }}
|
||||||
|
</small>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{% if post.image %}
|
||||||
|
<img src="{{ post.image.url }}" class="w-100 rounded" alt="{{ post.title }}" />
|
||||||
|
{% else %}
|
||||||
|
<img src="{% static 'img/etpgrf-logo-for-fb-vk-x.gif' %}" class="w-100 rounded opacity-50" alt="{{ post.title }}" />
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="d-none d-lg-block mt-5">
|
||||||
|
<a href="{% url 'blog:post_list' %}" class="btn btn-sm btn-outline-secondary w-100">← В блог</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{# Правая колонка: Контент #}
|
||||||
|
<div class="col-lg-10 border-start ps-lg-4">
|
||||||
|
|
||||||
|
<h1 class="display-4 mb-4">{{ post.title }}</h1>
|
||||||
|
|
||||||
|
{% if post.excerpt %}
|
||||||
|
<p class="lead bg-secondary bg-opacity-10 p-3 rounded">
|
||||||
|
{{ post.excerpt|linebreaksbr }}
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="post-content mt-4">
|
||||||
|
{{ post.content|safe }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-lg-none mt-5 border-top pt-3">
|
||||||
|
<a href="{% url 'blog:post_list' %}" class="btn btn-outline-secondary">← Назад к списку статей</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
76
etpgrf_site/blog/templates/blog/post_list.html
Normal file
76
etpgrf_site/blog/templates/blog/post_list.html
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
{% extends 'typograph/base.html' %}
|
||||||
|
|
||||||
|
{% block title %}Блог — ETPGRF{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-lg-8">
|
||||||
|
<h1 class="mb-4">Блог</h1>
|
||||||
|
|
||||||
|
{% for post in page_obj %}
|
||||||
|
<article class="card mb-4 shadow-sm">
|
||||||
|
{% if post.image %}
|
||||||
|
<img src="{{ post.image.url }}" class="card-img-top" alt="{{ post.title }}" style="max-height: 300px; object-fit: cover;" />
|
||||||
|
{% endif %}
|
||||||
|
<div class="card-body">
|
||||||
|
<h2 class="card-title h4">
|
||||||
|
<a href="{{ post.get_absolute_url }}" class="text-decoration-none text-reset">{{ post.title }}</a>
|
||||||
|
</h2>
|
||||||
|
<p class="card-text text-muted small mb-2">
|
||||||
|
{{ post.published_at|date:"d E Y" }}
|
||||||
|
</p>
|
||||||
|
<p class="card-text">
|
||||||
|
{% if post.excerpt %}
|
||||||
|
{{ post.excerpt|linebreaks }}
|
||||||
|
{% else %}
|
||||||
|
{{ post.content|striptags|truncatewords:30 }}
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
<a href="{{ post.get_absolute_url }}" class="btn btn-outline-primary btn-sm">Читать далее →</a>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
{% empty %}
|
||||||
|
<p class="text-muted">Пока нет записей.</p>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{# Пагинация #}
|
||||||
|
{% if page_obj.has_other_pages %}
|
||||||
|
<nav aria-label="Page navigation">
|
||||||
|
<ul class="pagination justify-content-center">
|
||||||
|
{% if page_obj.has_previous %}
|
||||||
|
<li class="page-item">
|
||||||
|
<a class="page-link" href="?page={{ page_obj.previous_page_number }}">«</a>
|
||||||
|
</li>
|
||||||
|
{% else %}
|
||||||
|
<li class="page-item disabled">
|
||||||
|
<span class="page-link">«</span>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% for i in page_obj.paginator.page_range %}
|
||||||
|
{% if page_obj.number == i %}
|
||||||
|
<li class="page-item active">
|
||||||
|
<span class="page-link">{{ i }}</span>
|
||||||
|
</li>
|
||||||
|
{% else %}
|
||||||
|
<li class="page-item">
|
||||||
|
<a class="page-link" href="?page={{ i }}">{{ i }}</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{% if page_obj.has_next %}
|
||||||
|
<li class="page-item">
|
||||||
|
<a class="page-link" href="?page={{ page_obj.next_page_number }}">»</a>
|
||||||
|
</li>
|
||||||
|
{% else %}
|
||||||
|
<li class="page-item disabled">
|
||||||
|
<span class="page-link">»</span>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
16
etpgrf_site/blog/urls.py
Normal file
16
etpgrf_site/blog/urls.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
from django.urls import path
|
||||||
|
from . import views
|
||||||
|
from blog.models import PostType # Для использования в корневом urls.py
|
||||||
|
|
||||||
|
app_name = 'blog' # Пространство имен для приложения blog
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
# Лента блога: /blog/
|
||||||
|
path('', views.post_list, name='post_list'),
|
||||||
|
|
||||||
|
# Песочница для верстки: /blog/tmp/
|
||||||
|
path('tmp/', views.tmp_view, name='tmp'),
|
||||||
|
|
||||||
|
# Детальная страница поста: /blog/my-awesome-post/
|
||||||
|
path('<slug:slug>/', views.post_detail, name='post_detail'),
|
||||||
|
]
|
||||||
60
etpgrf_site/blog/views.py
Normal file
60
etpgrf_site/blog/views.py
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
from django.shortcuts import render, get_object_or_404
|
||||||
|
from django.utils import timezone
|
||||||
|
from django.core.paginator import Paginator
|
||||||
|
from .models import Post, PostType
|
||||||
|
|
||||||
|
|
||||||
|
def post_list(request):
|
||||||
|
"""
|
||||||
|
Отображает список опубликованных постов блога с пагинацией.
|
||||||
|
"""
|
||||||
|
# Фильтруем только посты блога, опубликованные и с датой публикации не позднее текущего момента
|
||||||
|
posts_queryset = Post.objects.filter(
|
||||||
|
post_type=PostType.BLOG,
|
||||||
|
is_published=True,
|
||||||
|
published_at__lte=timezone.now()
|
||||||
|
).order_by('-published_at') # Сортируем по дате публикации (от новых к старым)
|
||||||
|
|
||||||
|
# Настраиваем пагинацию: 10 постов на страницу
|
||||||
|
paginator = Paginator(posts_queryset, 10)
|
||||||
|
page_number = request.GET.get('page') # Получаем номер страницы из GET-параметра
|
||||||
|
page_obj = paginator.get_page(page_number) # Получаем объект страницы
|
||||||
|
|
||||||
|
return render(request, 'blog/post_list.html', {'page_obj': page_obj})
|
||||||
|
|
||||||
|
|
||||||
|
def post_detail(request, slug):
|
||||||
|
"""
|
||||||
|
Отображает детальную страницу конкретного поста блога.
|
||||||
|
"""
|
||||||
|
# Ищем пост по слагу, типу 'BLOG', опубликованный и с датой публикации не позднее текущего момента
|
||||||
|
post = get_object_or_404(
|
||||||
|
Post,
|
||||||
|
slug=slug,
|
||||||
|
post_type=PostType.BLOG,
|
||||||
|
is_published=True,
|
||||||
|
published_at__lte=timezone.now()
|
||||||
|
)
|
||||||
|
return render(request, 'blog/post_detail.html', {'post': post})
|
||||||
|
|
||||||
|
|
||||||
|
def page_detail(request, slug):
|
||||||
|
"""
|
||||||
|
Отображает детальную страницу статической страницы (например, /privacy-policy/).
|
||||||
|
"""
|
||||||
|
# Ищем страницу по слагу, типу 'PAGE' и опубликованную
|
||||||
|
page = get_object_or_404(
|
||||||
|
Post,
|
||||||
|
slug=slug,
|
||||||
|
post_type=PostType.PAGE,
|
||||||
|
is_published=True
|
||||||
|
)
|
||||||
|
return render(request, 'blog/page_detail.html', {'page': page})
|
||||||
|
|
||||||
|
|
||||||
|
def tmp_view(request):
|
||||||
|
"""
|
||||||
|
Временная страница для верстки постов.
|
||||||
|
Доступна только в DEBUG режиме (или можно оставить, если не мешает).
|
||||||
|
"""
|
||||||
|
return render(request, 'blog/tmp.html')
|
||||||
@@ -2,19 +2,22 @@ from django.contrib import admin
|
|||||||
from django.urls import path, include
|
from django.urls import path, include
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.conf.urls.static import static
|
from django.conf.urls.static import static
|
||||||
from django.views.generic.base import RedirectView
|
from blog import views as blog_views # Импортируем views из приложения blog
|
||||||
from django.contrib.staticfiles.storage import staticfiles_storage
|
from blog.models import PostType # Для использования в корневом urls.py
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path(route='adm-in/', view=admin.site.urls),
|
path('adm-in/', admin.site.urls),
|
||||||
path(route='', view=include('typograph.urls')),
|
path('', include('typograph.urls')), # Основное приложение типографа
|
||||||
|
|
||||||
|
# Блог
|
||||||
|
path('blog/', include('blog.urls')),
|
||||||
|
|
||||||
|
# Статические страницы (ловушка в самом конце, чтобы не перехватывать другие URL)
|
||||||
|
# Исключаем слаги, которые могут конфликтовать с другими URL-ами
|
||||||
|
# Например, 'admin', 'blog', 'static', 'media'
|
||||||
|
path('<slug:slug>/', blog_views.page_detail, name='page_detail'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# Для отдачи медиафайлов в режиме разработки
|
||||||
if settings.DEBUG:
|
if settings.DEBUG:
|
||||||
# urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
|
||||||
# runserver автоматически раздает статику из STATICFILES_DIRS,
|
|
||||||
# поэтому добавлять static(settings.STATIC_URL...) НЕ НУЖНО.
|
|
||||||
# Это только ломает путь, направляя его в STATIC_ROOT.
|
|
||||||
|
|
||||||
# А вот медиа runserver не раздает, поэтому это нужно:
|
|
||||||
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||||
|
|||||||
Reference in New Issue
Block a user