Spec-Zone.ru › Wagtail

Фрагменты

Фрагменты — это фрагменты контента, для отображения которых не требуется полная веб-страница. Они могут использоваться для создания вторичного контента, такого как заголовки, подвалы и боковые панели, редактируемые в админке Wagtail. Фрагменты — это модели Django, которые не наследуют класс Page и поэтому не организованы в дереве Wagtail. Однако их всё ещё можно сделать редактируемыми, назначив панели и определив модель как фрагмент с декоратором класса register_snippet.

Фрагменты лишены многих функций страниц, таких как возможность упорядочения в админке Wagtail или определения URL. Внимательно взвесьте, может ли тип контента, который вы хотите создать как фрагмент, быть более подходящим для страницы.

Модели фрагментов

Вот пример модели фрагмента:

from django.db import models

from wagtail.admin.panels import FieldPanel
from wagtail.snippets.models import register_snippet

# ...

@register_snippet
class Advert(models.Model):
    url = models.URLField(null=True, blank=True)
    text = models.CharField(max_length=255)

    panels = [
        FieldPanel('url'),
        FieldPanel('text'),
    ]

    def __str__(self):
        return self.text

Модель Advert использует базовый класс модели Django и определяет два свойства: текст и URL. Интерфейс редактирования очень похож на интерфейс для моделей, производных от Page, с полями, назначенными в свойстве panels. Фрагменты не используют несколько вкладок полей, а также не предоставляют функций «сохранить как черновик» или «отправить на модерацию».

@register_snippet сообщает Wagtail, что модель должна обрабатываться как фрагмент. Список panels определяет поля, которые должны отображаться на странице редактирования фрагмента. Также важно предоставить строковое представление класса через def __str__(self): для того, чтобы объекты фрагментов были понятны при отображении в админке Wagtail.

Включение фрагментов в теги шаблонов

Самый простой способ сделать ваши фрагменты доступными для шаблонов — это использование тега шаблона. Это в основном делается с помощью стандартного Django, поэтому, возможно, будет полезно ознакомиться с документацией Django по настраиваемым тегам шаблонов. Тем не менее, мы рассмотрим основы и укажем все моменты, которые нужно учитывать для Wagtail.

Сначала добавьте новый python-файл в папку templatetags вашей приложения — например, myproject/demo/templatetags/demo_tags.py. Нам нужно будет загрузить некоторые модули Django и модели вашего приложения, а также подготовить декоратор register:

from django import template
from demo.models import Advert

register = template.Library()

# ...

# Advert snippets
@register.inclusion_tag('demo/tags/adverts.html', takes_context=True)
def adverts(context):
    return {
        'adverts': Advert.objects.all(),
        'request': context['request'],
    }

@register.inclusion_tag() принимает два параметра: шаблон и логическое значение, указывающее, должен ли этот шаблон получать контекст запроса. Включить контекст запроса в настраиваемые теги шаблонов — хорошая идея, так как некоторые теги шаблонов, специфичные для Wagtail, например, pageurl, нуждаются в контексте для корректной работы. Функция тега шаблона может принимать аргументы и фильтровать объявления, чтобы вернуть определённый экземпляр модели, но ради краткости мы будем использовать только Advert.objects.all().

Вот содержимое шаблона, используемого этим тегом шаблона:

{% for advert in adverts %}
    <p>
        <a href="{{ advert.url }}">
            {{ advert.text }}
        </a>
    </p>
{% endfor %}

Затем в ваших собственных шаблонах страниц вы можете включить ваш тег шаблона фрагмента следующим образом:

{% load wagtailcore_tags demo_tags %}

...

{% block content %}

    ...

    {% adverts %}

{% endblock %}

Связывание страниц с фрагментами

В приведённом выше примере список объявлений — это фиксированный список, который отображается с помощью настраиваемого тега шаблона независимо от другого контента на странице. Это может быть то, что вам нужно для общей панели в боковой панели, но в другом сценарии вы можете захотеть отобразить только один конкретный экземпляр фрагмента на определённой странице. Это можно сделать, определив внешний ключ к модели фрагмента в вашей модели страницы и добавив FieldPanel в список content_panels страницы. Например, если вы хотите отобразить определённое объявление на экземпляре BookPage:

  # ...
  class BookPage(Page):
      advert = models.ForeignKey(
          'demo.Advert',
          null=True,
          blank=True,
          on_delete=models.SET_NULL,
          related_name='+'
      )

      content_panels = Page.content_panels + [
          FieldPanel('advert'),
          # ...
      ]

Тогда фрагмент можно получить в вашем шаблоне как page.advert.

Для прикрепления нескольких объявлений к странице, FieldPanel можно поместить на вложенный дочерний объект BookPage вместо BookPage. В данном случае эта дочерняя модель называется BookPageAdvertPlacement (так её назвали, потому что для каждого размещения объявления на странице BookPage существует такой объект):

from django.db import models

from wagtail.models import Page, Orderable

from modelcluster.fields import ParentalKey

# ...

class BookPageAdvertPlacement(Orderable, models.Model):
    page = ParentalKey('demo.BookPage', on_delete=models.CASCADE, related_name='advert_placements')
    advert = models.ForeignKey('demo.Advert', on_delete=models.CASCADE, related_name='+')

    class Meta(Orderable.Meta):
        verbose_name = "advert placement"
        verbose_name_plural = "advert placements"

    panels = [
        FieldPanel('advert'),
    ]

    def __str__(self):
        return self.page.title + " -> " + self.advert.text


class BookPage(Page):
    # ...

    content_panels = Page.content_panels + [
        InlinePanel('advert_placements', label="Adverts"),
        # ...
    ]

Теперь эти дочерние объекты доступны через свойство страницы advert_placements, и оттуда мы можем получить связанный фрагмент Advert как advert. В шаблоне для BookPage мы могли бы включить следующее:

{% for advert_placement in page.advert_placements.all %}
    <p>
        <a href="{{ advert_placement.advert.url }}">
            {{ advert_placement.advert.text }}
        </a>
    </p>
{% endfor %}

Предварительный просмотр фрагментов

В версии 4.0: Класс PreviewableMixin был добавлен.

Если модель фрагмента наследуется от PreviewableMixin, Wagtail автоматически добавит панель предварительного просмотра в редакторе. В дополнение к наследованию миксина, модель также должна переопределить get_preview_template() или serve_preview(). Например, фрагмент Advert можно сделать просматриваемым следующим образом:

# ...

from wagtail.models import PreviewableMixin

# ...

@register_snippet
class Advert(PreviewableMixin, models.Model):
    url = models.URLField(null=True, blank=True)
    text = models.CharField(max_length=255)

    panels = [
        FieldPanel('url'),
        FieldPanel('text'),
    ]

    def get_preview_template(self, request, mode_name):
        return "demo/previews/advert.html"

С помощью следующего шаблона demo/previews/advert.html:

<!DOCTYPE html>
<html>
    <head>
        <title>{{ object.text }}</title>
    </head>
    <body>
        <a href="{{ object.url }}">{{ object.text }}</a>
    </body>
</html>

Переменные, доступные в контексте по умолчанию, это request (объект HttpRequest - замена) и object (экземпляр фрагмента). Чтобы настроить контекст, вы можете переопределить метод get_preview_context().

По умолчанию метод serve_preview возвращает TemplateResponse, который рендерится с помощью объекта запроса, шаблона, возвращённого get_preview_template, и объекта контекста, возвращённого get_preview_context. Вы можете переопределить метод serve_preview для настройки логики рендеринга и/или маршрутизации.

Аналогично страницам, вы можете определить несколько режимов предварительного просмотра, переопределив свойство preview_modes. Например, следующий фрагмент Advert имеет два режима предварительного просмотра:

# ...

from wagtail.models import PreviewableMixin

# ...

@register_snippet
class Advert(PreviewableMixin, models.Model):
    url = models.URLField(null=True, blank=True)
    text = models.CharField(max_length=255)

    panels = [
        FieldPanel('url'),
        FieldPanel('text'),
    ]

    @property
    def preview_modes(self):
        return PreviewableMixin.DEFAULT_PREVIEW_MODES + [("alt", "Alternate")]

    def get_preview_template(self, request, mode_name):
        templates = {
            "": "demo/previews/advert.html",  # Default preview mode
            "alt": "demo/previews/advert_alt.html",  # Alternate preview mode
        }
        return templates.get(mode_name, templates[""])

    def get_preview_context(self, request, mode_name):
        context = super().get_preview_context(request, mode_name)
        if mode_name == "alt":
            context["extra_context"] = "Alternate preview mode"
        return context

Поиск фрагментов

Если модель фрагмента наследует от wagtail.search.index.Indexed, как описано в Индексация пользовательских моделей, Wagtail автоматически добавит поле поиска к интерфейсу выбора для этого типа фрагмента. Например, фрагмент Advert можно сделать поисковым следующим образом:

# ...

from wagtail.search import index

# ...

@register_snippet
class Advert(index.Indexed, models.Model):
    url = models.URLField(null=True, blank=True)
    text = models.CharField(max_length=255)

    panels = [
        FieldPanel('url'),
        FieldPanel('text'),
    ]

    search_fields = [
        index.SearchField('text', partial_match=True),
    ]

Сохранение версий фрагментов

В версии 4.0: Класс RevisionMixin был добавлен.

Если модель фрагмента наследует от RevisionMixin, Wagtail автоматически сохранит версии при сохранении любых изменений в админке фрагментов. В дополнение к наследованию миксина рекомендуется определить GenericRelation к модели Revision и переопределить свойство revisions для возвращения GenericRelation. Например, фрагмент Advert можно сделать изменяемым с сохранением версий следующим образом:

# ...

from django.contrib.contenttypes.fields import GenericRelation
from wagtail.models import RevisionMixin

# ...

@register_snippet
class Advert(RevisionMixin, models.Model):
    url = models.URLField(null=True, blank=True)
    text = models.CharField(max_length=255)
    _revisions = GenericRelation("wagtailcore.Revision", related_query_name="advert")

    panels = [
        FieldPanel('url'),
        FieldPanel('text'),
    ]

    @property
    def revisions(self):
        return self._revisions

Таблица RevisionMixin включает поле latest_revision , которое нужно добавить в вашу таблицу базы данных. Убедитесь, что вы запустили команды управления makemigrations и migrate после внесения вышеуказанных изменений для применения изменений в вашей базе данных.

После применения RevisionMixin, любые изменения, внесённые в админке фрагментов, создадут экземпляр модели Revision , содержащей состояние экземпляра фрагмента. Экземпляр ревизии прикрепляется к записи в журнале аудита действия редактирования, позволяя вернуться к предыдущей ревизии или сравнить изменения между ревизиями со страницы истории фрагмента.

Вы также можете программно сохранить ревизии, вызвав метод save_revision(). После применения миксина рекомендуется вызвать этот метод (или сохранить фрагмент в админке) по крайней мере один раз для каждого существующего экземпляра фрагмента (если таковые имеются), чтобы поле latest_revision заполнилось в таблице базы данных.

END_OF_DOCUMENT_MARKER

Сохранение черновиков изменений сниппетов

Новое в версии 4.0: Класс DraftStateMixin был представлен.

Новое в версии 4.1: Поддержка планируемой публикации через PublishingPanel была добавлена.

Если модель сниппета наследуется от DraftStateMixin, Wagtail автоматически добавит столбец состояния "опубликовано/черновик" в представление списка, изменит действие «Сохранить» в меню на «Сохранить черновик» и добавит новое действие «Опубликовать» в редакторе. Все изменения, которые вы сохраните в админке сниппетов, будут сохранены как версии и не будут отображены в экземпляре «опубликованного» сниппета до тех пор, пока вы не опубликуете изменения.

Wagtail также позволит вам задавать расписания публикации для экземпляров модели, если в определении панелей модели есть PublishingPanel.

Например, сниппет Advert может сохранять черновики изменений и расписания публикации, определив его следующим образом:

# ...

from django.contrib.contenttypes.fields import GenericRelation
from wagtail.admin.panels import PublishingPanel
from wagtail.models import DraftStateMixin, RevisionMixin

# ...

@register_snippet
class Advert(DraftStateMixin, RevisionMixin, models.Model):
    url = models.URLField(null=True, blank=True)
    text = models.CharField(max_length=255)
    _revisions = GenericRelation("wagtailcore.Revision", related_query_name="advert")

    panels = [
        FieldPanel('url'),
        FieldPanel('text'),
        PublishingPanel(),
    ]

    @property
    def revisions(self):
        return self._revisions

DraftStateMixin включает дополнительные поля, которые необходимо добавить в вашу таблицу базы данных. Убедитесь, что после внесения вышеуказанных изменений вы запустите команды управления makemigrations и migrate, чтобы применить изменения к вашей базе данных.

Вы можете программно опубликовать версии, вызвав instance.publish(revision) или вызвав revision.publish(). После применения миксина рекомендуется опубликовать как минимум одну версию для каждого экземпляра сниппета, который уже существует (если таковые имеются), чтобы поля latest_revision и live_revision были заполнены в таблице базы данных.

Если вы используете функцию планируемой публикации, убедитесь, что вы периодически запускаете команду управления publish_scheduled. Дополнительные сведения см. в разделе Планируемая публикация.

Предупреждение

Wagtail пока не имеет механизма для предотвращения включения редакторами неопубликованных («черновиковых») сниппетов в страницы. При включении сниппета, поддерживающего DraftStateMixin, в страницы, убедитесь, что вы добавили необходимые проверки для обработки того, как должен отображаться черновик сниппета (например, проверив его поле live). Мы планируем улучшить это в будущем.

Тегирование сниппетов

Добавление тегов к сниппетам очень похоже на добавление тегов к страницам. Единственное отличие заключается в том, что taggit.manager.TaggableManager следует использовать вместо ClusterTaggableManager.

from modelcluster.fields import ParentalKey
from modelcluster.models import ClusterableModel
from taggit.models import TaggedItemBase
from taggit.managers import TaggableManager

class AdvertTag(TaggedItemBase):
    content_object = ParentalKey('demo.Advert', on_delete=models.CASCADE, related_name='tagged_items')

@register_snippet
class Advert(ClusterableModel):
    # ...
    tags = TaggableManager(through=AdvertTag, blank=True)

    panels = [
        # ...
        FieldPanel('tags'),
    ]

Документация по тегированию страниц содержит больше информации о том, как использовать теги в представлениях.

Настройка представлений администратора сниппетов

Вы можете настроить представления администратора для сниппетов, указав пользовательский подкласс SnippetViewSet для register_snippet.

Это можно сделать, удалив декоратор @register_snippet из вашего класса модели и вызвав register_snippet (как функцию, а не декоратор) в вашем файле wagtail_hooks.py вместо этого следующим образом:

register_snippet(MyModel, viewset=MyModelViewSet)

Например, с помощью следующей модели Member и класса MemberFilterSet:

# models.py
from django.db import models
from wagtail.admin.filters import WagtailFilterSet


class Member(models.Model):
    class ShirtSize(models.TextChoices):
        SMALL = "S", "Small"
        MEDIUM = "M", "Medium"
        LARGE = "L", "Large"
        EXTRA_LARGE = "XL", "Extra Large"

    name = models.CharField(max_length=255)
    shirt_size = models.CharField(max_length=5, choices=ShirtSize.choices, default=ShirtSize.MEDIUM)

    def get_shirt_size_display(self):
        return self.ShirtSize(self.shirt_size).label

    get_shirt_size_display.admin_order_field = "shirt_size"
    get_shirt_size_display.short_description = "Size description"


class MemberFilterSet(WagtailFilterSet):
    class Meta:
        model = Member
        fields = ["shirt_size"]

Вы можете определить атрибут list_display для указания столбцов, отображаемых в представлении списка. Вы также можете добавить возможность фильтрации представления списка, определив атрибут filterset_class в подклассе SnippetViewSet. Например:

# views.py
from wagtail.admin.ui.tables import UpdatedAtColumn
from wagtail.snippets.views.snippets import SnippetViewSet

from myapp.models import MemberFilterSet


class MemberViewSet(SnippetViewSet):
    list_display = ["name", "shirt_size", "get_shirt_size_display", UpdatedAtColumn()]
    filterset_class = MemberFilterSet

Затем передайте представление в вызов register_snippet.

# wagtail_hooks.py
from wagtail.snippets.models import register_snippet

from myapp.models import Member
from myapp.views import MemberViewSet


register_snippet(Member, viewset=MemberViewSet)

Параметр viewset вызова register_snippet также принимает путь к подклассу с точкой, например, "myapp.views.MemberViewSet".

Доступны различные дополнительные атрибуты для настройки представления - см. SnippetViewSet.

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

Spec-Zone.ru

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