Spec-Zone.ru › Wagtail

Рецепты

Переопределение метода serve()

Wagtail по умолчанию отображает модели, производные от Page, передавая ссылку на объект страницы в шаблон Django HTML, соответствующий имени модели. Но что если вам нужно отобразить что-то, кроме HTML? Вы можете переопределить метод serve(), предоставляемый классом Page, и более непосредственно обработать запрос и ответ Django.

Рассмотрим этот пример объекта 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()

Примечание

Гораздо более простой способ добавления дополнительных конечных точек к страницам предоставляет миксин RoutablePageMixin.

Wagtail обрабатывает запросы, итерируя по компонентам пути (разделенным прямой чертой /), находя соответствующие объекты на основе их имени URL и делегируя дальнейшую обработку маршрутизации классу модели этого объекта. Источник кода 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, так что объекты смогут иметь настраиваемое название и имя URL. Шаблон только должен отображать наше свойство {{ echo }}.

Теперь, после создания новой страницы 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, variant='radiant')

Разметка

Wagtail предоставляет возможности разметки с помощью двух модулей Django, django-taggit (который предоставляет реализацию разметки общего назначения) и django-modelcluster (который расширяет TaggableManager django-taggit, позволяя управлять отношениями меток в памяти без записи в базу данных – необходимо для обработки предварительных просмотров и ревизий). Чтобы добавить разметку к модели страницы, вам нужно определить модель «через», наследующуюся от 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, вместе с моделью «через», наследующейся от 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 редакторы больше не могут вводить произвольный текст в поле метки, а должны вместо этого выбирать существующие метки из выпадающего списка автозаполнения.

Управление метками с помощью Wagtail's ModelAdmin

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

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

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

from wagtail.contrib.modeladmin.options import ModelAdmin, modeladmin_register
from wagtail.admin.edit_handlers import FieldPanel
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, так как поле имени URL заполняется автоматически при заполнении поля name, и вам не нужно вводить одно и то же имя в оба поля.

© 2014-present Torchbox Ltd and individual contributors.
All rights are reserved.
Licensed under the BSD License.
https://docs.wagtail.org/en/stable/reference/pages/model_recipes.html

Spec-Zone.ru

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