Рецепты
Переопределение метода serve()
Wagtail по умолчанию обслуживает модели, производные от Page, передавая ссылку на объект страницы в шаблон Django HTML, соответствующий имени модели. Но предположим, что вам нужно обслуживать что-то кроме HTML? Вы можете переопределить метод serve(), предоставляемый классом Page, и напрямую обрабатывать запрос и ответ Django.
Рассмотрим этот пример со models.py демонстрационного сайта 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.url_routing) содержит всю информацию, необходимую Wagtail для вызова метода serve() страницы и возвращения конечного ответа: эта информация включает в себя объект страницы и любые дополнительные args/kwargs, которые должны быть переданы в serve().
Переопределяя метод route(), мы можем создавать настраиваемые конечные точки для каждого объекта в дереве Wagtail. Одним из вариантов использования может быть использование альтернативного шаблона при встрече конечной точки print/ в пути. Другим может быть REST API, который взаимодействует с текущим объектом. Просто чтобы увидеть, что вовлечено, давайте создадим простую модель, которая выводит все компоненты пути своих дочерних элементов.
Во-первых, models.py:
from django.shortcuts import render
from wagtail.url_routing import RouteResult
from django.http.response import Http404
from wagtail.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, поэтому объекты смогут иметь настраиваемое название и ссылку. Шаблону просто нужно отобразить наше свойство {{ echo }}.
Теперь, после создания новой страницы Echoer в админке Wagtail с названием «Echo Base», запросы, такие как:
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, чтобы установить отношение «многие ко многим» между моделью 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 редакторы больше не могут вводить произвольный текст в поле тега и должны вместо этого выбирать существующие теги из раскрывающегося списка автодополнения.
© 2014-present Torchbox Ltd and individual contributors.
All rights are reserved.
Licensed under the BSD License.
https://docs.wagtail.org/en/v3.0.3/reference/pages/model_recipes.html