Фрагменты
Фрагменты — это фрагменты контента, для отображения которых не требуется полная веб-страница. Они могут использоваться для создания вторичного контента, такого как заголовки, подвалы и боковые панели, редактируемые в админке 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.
Связывание страниц с фрагментами
В приведённом выше примере список объявлений — это фиксированный список, который отображается с помощью настраиваемого тега шаблона независимо от другого контента на странице. Это может быть то, что вам нужно для общей панели в боковой панели, но в другом сценарии вы можете захотеть отобразить только один конкретный экземпляр фрагмента на определённой странице. Это можно сделать, определив внешний ключ к модели фрагмента в вашей модели страницы и добавив 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 заполнилось в таблице базы данных.
Сохранение черновиков изменений сниппетов
Новое в версии 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