Spec-Zone.ru › Wagtail 3

Рецепты

Переопределение метода 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 редакторы больше не могут вводить произвольный текст в поле тега и должны вместо этого выбирать существующие теги из раскрывающегося списка автодополнения.

Управление тегами с помощью ModelAdmin

Чтобы управлять всеми тегами, используемыми в проекте, вы можете использовать ModelAdmin для добавления модели Tag в админ-панель Wagtail. Это позволит вам иметь интерфейс администрирования тегов в главном меню, в котором вы сможете добавлять, редактировать или удалять теги.

Теги, которые удаляются из контента, не удаляются из модели Tag и по-прежнему будут отображаться в подсказках автодополнения тегов. Таким образом, интерфейс тегов — отличный способ полностью избавиться от ненужных тегов.

Чтобы добавить интерфейс тегов, добавьте следующий блок кода в файл wagtail_hooks.py в любом приложении вашего проекта:

from wagtail.contrib.modeladmin.options import ModelAdmin, modeladmin_register
from taggit.models import Tag


class TagsModelAdmin(ModelAdmin):
    Tag.panels = [FieldPanel("name")]  # only show the name field
    model = Tag
    menu_label = "Tags"
    menu_icon = "tag"  # change as required
    menu_order = 200  # will put in 3rd place (000 being 1st, 100 2nd)
    list_display = ["name", "slug"]
    search_fields = ("name",)


modeladmin_register(TagsModelAdmin)

Модель Tag имеет обязательные поля name и slug. Если вы решите добавить тег, рекомендуется отображать только панель поля name, так как поле slug автоматически заполняется при заполнении поля name, и вам не нужно вводить одно и то же имя в обоих полях.

© 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

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API