Рецепты
Переопределение метода serve()
Wagtail по умолчанию отображает модели, производные от Page, передавая ссылку на объект страницы в шаблон Django HTML, соответствующий имени модели, но предположим, что вы хотите отобразить что-то, кроме HTML? Вы можете переопределить метод serve(), предоставляемый классом Page, и более напрямую обработать запрос Django и ответ.
Рассмотрим этот пример со страницы демонстрационного сайта Wagtail, который отображает объект EventPage как файл iCal, если переменная format установлена в запросе:
class EventPage(Page):
...
def serve(self, request):
if "format" in request.GET:
if request.GET['format'] == 'ical':
# Export to ical format
response = HttpResponse(
export_event(self, 'ical'),
content_type='text/calendar',
)
response['Content-Disposition'] = 'attachment; filename=' + self.slug + '.ics'
return response
else:
# Unrecognised format error
message = 'Could not export event\n\nUnrecognised format: ' + request.GET['format']
return HttpResponse(message, content_type='text/plain')
else:
# Display event page as usual
return super().serve(request)
Метод serve() принимает объект запроса Django и возвращает объект ответа Django. Wagtail возвращает объект TemplateResponse с шаблоном и контекстом, которые он генерирует, что позволяет правильно функционировать middleware, поэтому имейте в виду, что более простой объект ответа, например, HttpResponse, не получит этих преимуществ.
С помощью этой стратегии вы можете использовать утилиты Django или Python для рендеринга вашей модели в формате JSON, XML или любом другом формате, который вам нужен.
Добавление конечных точек с пользовательскими методами route()
Примечание
Гораздо более простой способ добавления дополнительных конечных точек к страницам предоставляет модуль routable_page.
Wagtail обрабатывает запросы, перебирая компоненты пути (разделенные прямой чертой /), находя соответствующие объекты на основе их имени и делегируя дальнейшую обработку маршрутизации классу модели этого объекта. Исходный код Wagtail очень нагляден, чтобы понять, что происходит. Это метод по умолчанию route() класса Page:
class Page(...):
...
def route(self, request, path_components):
if path_components:
# request is for a child of this page
child_slug = path_components[0]
remaining_components = path_components[1:]
# find a matching child or 404
try:
subpage = self.get_children().get(slug=child_slug)
except Page.DoesNotExist:
raise Http404
# delegate further routing
return subpage.specific.route(request, remaining_components)
else:
# request is for this very page
if self.live:
# Return a RouteResult that will tell Wagtail to call
# this page's serve() method
return RouteResult(self)
else:
# the page matches the request, but isn't published, so 404
raise Http404
route() принимает текущий объект (self), объект request и список оставшихся компонентов path_components из URL запроса. Он либо продолжает делегирование маршрутизации, вызывая route() для одного из его дочерних элементов в дереве Wagtail, либо завершает процесс маршрутизации, возвращая объект RouteResult или подняв ошибку 404.
Объект RouteResult (определён в wagtail.core.url_routing) содержит всю необходимую Wagtail информацию для вызова метода serve() страницы и возвращения конечного ответа: эта информация состоит из объекта страницы и любых дополнительных args/kwargs, которые нужно передать методу serve().
Переопределяя метод route(), мы можем создать пользовательские конечные точки для каждого объекта в дереве Wagtail. Одним из вариантов использования может быть использование альтернативного шаблона при обнаружении конечной точки print/ в пути. Другим может быть REST API, взаимодействующий с текущим объектом. Для демонстрации, давайте создадим простую модель, которая выводит все компоненты пути своих дочерних элементов.
Во-первых, models.py:
from django.shortcuts import render
from wagtail.core.url_routing import RouteResult
from django.http.response import Http404
from wagtail.core.models import Page
...
class Echoer(Page):
def route(self, request, path_components):
if path_components:
# tell Wagtail to call self.serve() with an additional 'path_components' kwarg
return RouteResult(self, kwargs={'path_components': path_components})
else:
if self.live:
# tell Wagtail to call self.serve() with no further args
return RouteResult(self)
else:
raise Http404
def serve(self, path_components=[]):
return render(request, self.template, {
'page': self,
'echo': ' '.join(path_components),
})
Эта модель, Echoer, не определяет никаких свойств, но наследуется от Page, поэтому объекты смогут иметь пользовательское название и имя.
Теперь, после создания новой страницы Echoer в административной панели Wagtail с заголовком «Эхо База», запросы, такие как:
http://127.0.0.1:8000/echo-base/tauntaun/kennel/bed/and/breakfast/
Вернут:
tauntaun kennel bed and breakfast
Будьте осторожны, если вы добавляете новые обязательные аргументы в метод serve() - Wagtail всё ещё должен иметь возможность отображать стандартный вид страницы для предварительного просмотра и модерации, и по умолчанию будет пытаться сделать это, вызывая метод serve() с объектом запроса и без дополнительных аргументов. Если ваш метод serve() не принимает такую сигнатуру метода, вам нужно будет переопределить метод serve_preview() страницы, чтобы вызвать serve() с соответствующими аргументами:
def serve_preview(self, request, mode_name):
return self.serve(request, color='purple')
Тегирование
Wagtail предоставляет возможности тегирования с помощью двух модулей Django: django-taggit (который предоставляет реализацию тегирования общего назначения) и django-modelcluster (который расширяет TaggableManager django-taggit, чтобы разрешить управление связями тегов в памяти без записи в базу данных – это необходимо для обработки предварительных просмотров и исправлений). Чтобы добавить тегирование к модели страницы, вам нужно определить модель «through», наследующуюся от TaggedItemBase для настройки связи many-to-many между моделью Tag django-taggit и вашей моделью страницы, и добавить метод доступа ClusterTaggableManager к вашей модели страницы, чтобы представить эту связь как одно поле тега.
В этом примере мы настроили тегирование для BlogPage через модель BlogPageTag:
# models.py
from modelcluster.fields import ParentalKey
from modelcluster.contrib.taggit import ClusterTaggableManager
from taggit.models import TaggedItemBase
class BlogPageTag(TaggedItemBase):
content_object = ParentalKey('demo.BlogPage', on_delete=models.CASCADE, related_name='tagged_items')
class BlogPage(Page):
...
tags = ClusterTaggableManager(through=BlogPageTag, blank=True)
promote_panels = Page.promote_panels + [
...
FieldPanel('tags'),
]
Административная панель Wagtail предоставляет удобный интерфейс для ввода тегов в контент, с автодополнением тегов и дружественными значками тегов.
Теперь мы можем использовать отношение многие-ко-многим тегов в наших представлениях и шаблонах. Например, мы можем настроить главную страницу блога, чтобы принять параметр запроса ?tag=... для фильтрации списка BlogPage по тегу:
from django.shortcuts import render
class BlogIndexPage(Page):
...
def get_context(self, request):
context = super().get_context(request)
# Get blog entries
blog_entries = BlogPage.objects.child_of(self).live()
# Filter by tag
tag = request.GET.get('tag')
if tag:
blog_entries = blog_entries.filter(tags__name=tag)
context['blog_entries'] = blog_entries
return context
Здесь, blog_entries.filter(tags__name=tag) следует за отношением tags в BlogPage, для фильтрации списка только до тех страниц, у которых есть совпадающее имя тега, прежде чем передать это в шаблон для рендеринга. Теперь мы можем обновить шаблон blog_page.html для отображения списка тегов, связанных со страницей, с ссылками на отфильтрованную главную страницу:
{% for tag in page.tags.all %}
<a href="{% pageurl page.blog_index %}?tag={{ tag }}">{{ tag }}</a>
{% endfor %}
Итерация по page.tags.all отобразит каждый тег, связанный с page, в то время как ссылки на главную страницу используют опцию фильтра, добавленную к модели BlogIndexPage. Запрос Django также может использовать поле связанного имени tagged_items для получения объектов BlogPage , связанных с тегом.
Тот же подход можно использовать для добавления тегирования к моделям, отличным от страниц, управляемых Сниппетами и ModelAdmin. В этом случае модель должна унаследоваться от modelcluster.models.ClusterableModel для совместимости с ClusterTaggableManager.
Пользовательские модели тегов
В приведенном выше примере все новые созданные теги будут добавлены к стандартной модели Tag django-taggit, которая будет использоваться всеми другими моделями, использующими ту же схему, а также моделями изображений и документов Wagtail. В частности, это означает, что предложения автозаполнения для полей тегов будут включать теги, добавленные ранее к другим моделям. Чтобы этого избежать, вы можете настроить пользовательскую модель тега, наследующуюся от TagBase, наряду с моделью «through», наследующейся от ItemBase, которая предоставит независимый пул тегов для этой модели страницы.
from django.db import models
from modelcluster.contrib.taggit import ClusterTaggableManager
from modelcluster.fields import ParentalKey
from taggit.models import TagBase, ItemBase
class BlogTag(TagBase):
class Meta:
verbose_name = "blog tag"
verbose_name_plural = "blog tags"
class TaggedBlog(ItemBase):
tag = models.ForeignKey(
BlogTag, related_name="tagged_blogs", on_delete=models.CASCADE
)
content_object = ParentalKey(
to='demo.BlogPage',
on_delete=models.CASCADE,
related_name='tagged_items'
)
class BlogPage(Page):
...
tags = ClusterTaggableManager(through='demo.TaggedBlog', blank=True)
В административной панели поле тега автоматически распознает используемую пользовательскую модель тега и предложит автозаполнение, взятое из этой модели тегов.
Отключение свободного тегирования
По умолчанию поля тегов работают на основе «свободного тегирования»: редакторы могут вводить в поле что угодно, а при сохранении любой текст тега, не распознанный как существующий тег, будет автоматически создан. Чтобы отключить это поведение и разрешить редакторам вводить только теги, которые уже существуют в базе данных, пользовательские модели тегов принимают опцию free_tagging = False:
from taggit.models import TagBase
from wagtail.snippets.models import register_snippet
@register_snippet
class BlogTag(TagBase):
free_tagging = False
class Meta:
verbose_name = "blog tag"
verbose_name_plural = "blog tags"
Здесь мы зарегистрировали BlogTag как сниппет, чтобы предоставить интерфейс для администраторов (и других пользователей с соответствующими разрешениями) для управления набором разрешённых тегов. С опцией free_tagging = False редакторы больше не могут вводить произвольный текст в поле тега, а должны вместо этого выбирать существующие теги из раскрывающегося списка автозаполнения.
Автоматическое создание редиректов при изменении имени страницы
Возможно, вы захотите автоматически создавать редиректы при изменении URL в административной панели, чтобы избежать разрыва ссылок. Вы можете добавить следующий блок в файл wagtail_hooks.py в одном из приложений вашего проекта.
from wagtail.core import hooks
from wagtail.contrib.redirects.models import Redirect
# Create redirect when editing slugs
@hooks.register('before_edit_page')
def create_redirect_on_slug_change(request, page):
if request.method == 'POST':
if page.slug != request.POST['slug']:
Redirect.objects.create(
old_path=page.url[:-1],
site=page.get_site(),
redirect_page=page
)
Примечание: это не работает в некоторых случаях, например, при перенаправлении страницы, создании новой страницы на этом URL и последующем перемещении новой страницы. Однако это должно быть полезно в большинстве случаев.
© 2014-present Torchbox Ltd and individual contributors.
All rights are reserved.
Licensed under the BSD License.
https://docs.wagtail.org/en/v2.16.3/reference/pages/model_recipes.html