Представления
В директории приложения открываем views.py и наполняем следующим:
from django.shortcuts import render as _render, get_object_or_404, redirect
from django.db.models import F
from django.conf import settings
from core.utils import pagination
from .models import Book, Chapter
def render(request, template_name, context=None, *args, **kwargs):
context = context or {}
context['BOOKS_SUBTITLE'] = settings.BOOKS_SUBTITLE
return _render(request, template_name, context, *args, **kwargs)
def index_view(request):
books = Book.public.all()
return render(request, 'books/index.html', {
'books': books,
})
def book_view(request, slug, pk):
# получаем все объекты для предварительного просмотра администратором
queryset = Book.objects if request.user.is_superuser else Book.public
book = get_object_or_404(queryset, pk=pk)
if book.slug != slug:
# делаем постоянный редирект, слаг был изменен
return redirect(book, permanent=True)
return render(request, 'books/book.html', {
'book': book,
'chapters': book.chapters(),
})
def chapter_view(request, book_slug, book_pk, slug, pk):
# получаем все объекты для предварительного просмотра администратором
queryset = Chapter.objects if request.user.is_superuser else Chapter.public
chapter = get_object_or_404(queryset.select_related(), pk=pk)
if chapter.slug != slug or chapter.book.slug != book_slug or chapter.book.pk != int(book_pk):
# делаем постоянный редирект, слаг был изменен или указан несуществующий pk книги
return redirect(chapter, permanent=True)
# добавляем еще один просмотр главе
Chapter.public.filter(pk=pk).update(n_views=F('n_views') + 1)
return render(request, 'books/chapter.html', {
'chapter': chapter,
'page': pagination(request, chapter.pages(), 1),
})
Для начала мы модифицируем стандартную функцию вывода render, добавляя в нее контекст: подзаголовок приложения.
К слову, в приложении articles мы добавляем аналогичную функцию, но соответствующей настройкой приложения: ARTICLES_SUBTITLE.
Далее в index_view выводим список доступных на данный момент книг.
Следующее представление призвано отобразить содержимое выбранной книги.
В начале мы определяем статус пользователя, если это администратор, мы сможем просмотреть даже неопубликованную на данный момент книгу.
Как и предыдущих приложениях мы запрашиваем объекты только по их идентификатору, в случае если слаги отличаются перенаправляем на новый маршрут.
Как бы странно это не звучало, но название книг тоже может меняться, например, издательства часто этим грешат.
Представление chapter_view очень похоже на предыдущее, но здесь стоит выделить несколько моментов.
В запросе мы используем select_related поскольку в шаблоне нам необходимо вывести название книги.
Далее проверяем не только слаг главы, но и книги и ее идентификатор, для актуальности ссылки.
Также стоит отметить что переменные в представлении имеют строковой тип, поэтому принудительно приводим идентификатор книги к целому типу.
Затем нам необходимо обновить счетчик просмотров у опубликованной главы.
За один запрос получаем содержимое количества просмотров и тут же увеличиваем его на единицу.
Функцию pagination мы описывали в приложении вопросов — это просто удобная обертка над родным средством пагинации.
Здесь мы получаем всего лишь один объект на страницу, но у нас появляются инструменты для работы с предыдущей и следующей страницами.
Более подробно мы все это рассмотрим в шаблонах.
Ленты обновлений книг и глав
В каталоге приложения создаем файл feeds.py:
from django.contrib.syndication.views import Feed
from django.utils.feedgenerator import Atom1Feed
from django.urls import reverse_lazy
from django.conf import settings
from .models import Book, Chapter
class BooksFeed(Feed):
feed_type = Atom1Feed
title = settings.SITE_TITLE
link = reverse_lazy('books:index')
subtitle = settings.BOOKS_SUBTITLE
author_name = settings.SITE_AUTHOR
def items(self):
return Book.public.all()
def item_title(self, item):
return item.title
def item_description(self, item):
return item.summary
class ChaptersBookFeed(Feed):
feed_type = Atom1Feed
subtitle = settings.BOOKS_SUBTITLE
author_name = settings.SITE_AUTHOR
def get_object(self, request, *args, **kwargs):
return Book.public.get(pk=kwargs['pk'])
def title(self, obj):
return '{} | {}'.format(obj.title, settings.SITE_TITLE)
def link(self, obj):
return obj.get_absolute_url()
def items(self, obj):
return Chapter.public.filter(book_id=obj.pk).select_related()
def item_title(self, item):
return item.title
def item_description(self, item):
return item.summary
По просьбе заказчика убраны ограничения на вывод объектов и используются стандартные сортировки моделей, в том числе для вывода глав по порядку.
Карта книг и глав
В директории приложения создаем файл sitemaps.py:
from django.contrib.sitemaps import GenericSitemap
from .models import Book, Chapter
books_sitemaps = {
'books': GenericSitemap({
'queryset': Book.public,
'date_field': 'updated',
}, priority=1.0, changefreq=None),
'books_chapters': GenericSitemap({
'queryset': Chapter.public.select_related(),
'date_field': 'updated',
}, priority=0.9, changefreq=None),
}
Приложение Книги теперь является основным, приоритет выставлен 1.0 для книг и 0.9 для глав соответственно.
В приложении Статьи приоритет объектов изменяем на 0.8.
Маршруты
В каталоге приложения создаем файл urls.py:
from django.conf.urls import url
from . import views
from . import feeds
app_name = 'books'
urlpatterns = [
url(r'^$', views.index_view, name='index'),
url(r'^feed/$', feeds.BooksFeed(), name='feed'),
url(r'^feed/(?P<pk>\d+)/$', feeds.ChaptersBookFeed(), name='book_feed'),
url(r'^(?P<slug>[\w-]+)-(?P<pk>\d+)/$', views.book_view, name='book'),
url(r'^(?P<book_slug>[\w-]+)-(?P<book_pk>\d+)/(?P<slug>[\w-]+)-(?P<pk>\d+)/$', views.chapter_view, name='chapter'),
]
В директории проекта переходим в каталог zero, затем открываем urls.py и приводим к следующему виду:
from django.conf.urls import url, include
from django.contrib import admin
from django.views.generic.base import RedirectView
from django.contrib.sitemaps.views import sitemap
from django.conf.urls.static import static
from django.conf import settings
from questions.sitemaps import questions_sitemaps
from articles.sitemaps import articles_sitemaps
from books.sitemaps import books_sitemaps
sitemaps = {}
sitemaps.update(questions_sitemaps)
sitemaps.update(articles_sitemaps)
sitemaps.update(books_sitemaps)
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^ckeditor/', include('ckeditor_uploader.urls')),
url(r'^accounts/', include('accounts.urls')),
url(r'^questions/', include('questions.urls')),
url(r'^articles/', include('articles.urls')),
url(r'^books/', include('books.urls')),
url(r'^$', RedirectView.as_view(pattern_name='books:index'), name='index'),
url(r'^sitemap\.xml$', sitemap, {'sitemaps': sitemaps}, name='sitemap'),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
# Django Debug Toolbar
if settings.DEBUG:
import debug_toolbar
urlpatterns += [
url(r'^__debug__/', include(debug_toolbar.urls)),
]
Сначала мы подключили маршруты приложения Книги и установили на него перенаправление с главной страницы сайта.
Затем добавили в карту сайта список книг и глав для поисковых систем.