本教程向您展示如何使用 Wagtail 构建博客。此外,本教程还为您提供了一些 Wagtail 的实践经验。

为了完成本教程,我们建议您具备一些基本的编程知识,以及对web开发概念的理解。对 Python 和 Django 框架有基本的了解可以确保对本教程有更扎实的理解,但这不是强制性的。

注意:
如果你想将Wagtail添加到现有的Django项目中,请参考将 Wagtail 集成到 Django 项目中。
{class="text-info"}

安装并运行 Wagtail

安装依赖

Wagtail 支持 Python 3.8、3.9、3.10 和 3.11。

要检查是否有合适的Python 3版本,请运行以下命令:

python --version
# Or:
python3 --version
# **On Windows** (cmd.exe, with the Python Launcher for Windows):
py --version

如果这不返回版本号或返回低于 3.8 的版本,您将需要安装 Python 3 。

创建并激活虚拟环境

本教程建议使用虚拟环境,它将已安装的依赖项与其他项目隔离。本教程使用 venv ,它与 Python 3一起封装。在Ubuntu上,可能需要运行sudo apt install python3-venv来安装它。

在 Windows 上(cmd.exe),运行如下命令:

py -m venv mysite\env

# then

mysite\env\Scripts\activate.bat

# if mysite\env\Scripts\activate.bat doesn't work, run:

mysite\env\Scripts\activate

在 GNU/Linux 或 MacOS (bash) 上:

python -m venv mysite/env
# Then:
source mysite/env/bin/activate

对于其他 shell,请参阅 venv 文档

注意:
如果您使用版本控制,例如 git, mysite 将是您的项目的目录。其中的 env 目录应排除在任何版本控制之外。

安装 Wagtail

要安装 Wagtail 及其依赖项,使用Python打包的pip:

pip install wagtail

生成您的网站

Wagtail 提供了与 django-admin startproject 类似的 start 命令。在项目中运行 wagtail start mysite 将生成一个新的 mysite 文件夹,其中包含一些 Wagtail 特定的附加功能,包括所需的项目设置、带有空白 HomePage 模型和基本模板的“主”应用程序以及示例“搜索”应用程序。

由于文件夹 mysite 已由 venv 创建,因此请使用附加参数运行 wagtail start 以指定目标目录:

wagtail start mysite mysite

这是生成的项目的结构:

mysite/
├── .dockerignore
├── Dockerfile
├── home/
├── manage.py*
├── mysite/
├── requirements.txt
└── search/

安装项目依赖项

cd mysite
pip install -r requirements.txt

这可确保您拥有 Wagtail、Django 的相关版本以及刚刚创建的项目的任何其他依赖项。 requirements.txt 文件包含运行该项目所需的所有依赖项。

创建数据库

默认情况下,您的数据库是SQLite。要将数据库表与项目模型匹配,请运行以下命令:

python manage.py migrate

该命令确保数据库中的表与项目中的模型匹配。每次更改模型时,都必须运行python manage.py migrate命令来更新数据库。例如,如果您向模型添加一个字段,那么您必须运行命令。

创建管理员用户

python manage.py createsuperuser

这提示您创建一个具有完全权限的新admin用户帐户。重要的是,出于安全原因,在输入密码文本时将不可见。

启动服务器

python manage.py runserver

服务器启动后,进入 http://127.0.0.1:8000 查看欢迎页面:

wagtail 教程

本教程使用http://127.0.0.1:8000 作为开发服务器的URL,但根据您的设置,这可以是不同的IP地址或端口。请阅读 manage.py runserver 的控制台输出,以确定本地站点的正确URL。

现在,您可以使用使用createssuperuser创建管理员用户时输入的用户名和密码登录到 http://127.0.0.1:8000/admin, 访问管理界面。

wagtail 教程

扩展主页模型

开箱即用的“主页”应用程序在 models.py 中定义了一个空白 HomePage 模型,以及创建主页并配置 Wagtail 以使用它的迁移。

按如下方式编辑 home/models.py ,以将 body 字段添加到模型中:

from django.db import models

from wagtail.models import Page
from wagtail.fields import RichTextField
from wagtail.admin.panels import FieldPanel


class HomePage(Page):
    body = RichTextField(blank=True)

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

body 是一个 RichTextField,一个特殊的Wagtail字段。当blank=True时,这意味着该字段不是强制性的,您可以将其保留为空。你可以使用Django核心字段content_panels 定义了编辑界面的功能和布局。向content_panels添加字段使您能够在Wagtail管理界面中编辑它们。您可以在 Page 模型中阅读更多相关内容。

运行:

# Creates the migrations file.
python manage.py makemigrations

# Executes the migrations and updates the database with your model changes.
python manage.py migrate

每次对模型定义进行更改时,都必须运行上述命令。下面是终端的预期输出:

Migrations for 'home':
  home/migrations/0003_homepage_body.py
    - Add field body to homepage
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, home, sessions, taggit, wagtailadmin, wagtailcore, wagtaildocs, wagtailembeds, wagtailforms, wagtailimages, wagtailredirects, wagtailsearch, wagtailusers
Running migrations:
  Applying home.0003_homepage_body... OK

您现在可以在Wagtail管理界面中编辑主页。在侧边栏上,转到 Pages 并单击 Home 旁边的 edit 以查看新的 body 字段。

wagtail 教程

输入文字“欢迎来到我们的新网站!”,并通过选择页面编辑器底部的“发布”而不是“保存草稿”来发布页面。

您必须更新页面模板以反映对模型所做的更改。Wagtail使用普通的Django模板来呈现每个页面类型。默认情况下,它查找由应用程序和模型名称组成的模板文件名,用下划线分隔大写字母。例如,“home”应用中的主页为home/home_page.html。这个模板文件可以存在于Django模板规则识别的任何位置。按照惯例,你可以把它放在应用的模板文件夹中。

编辑 home/templates/home/home_page.html 以包含以下内容:

{% extends "base.html" %}

<!-- load wagtailcore_tags by adding this: -->
{% load wagtailcore_tags %}

{% block body_class %}template-homepage{% endblock %}

<!-- replace everything below with: -->
{% block content %}
    {{ page.body|richtext }}
{% endblock %}

base.html 指的是父模板,并且必须始终是模板中使用的第一个模板标记。从此模板进行扩展可以使您免于重写代码,并允许应用程序中的页面共享相似的框架(通过在子模板中使用块标记,您可以覆盖父模板中的特定内容)。

同时,你必须加载 wagtailcore_tags 到模板顶部,并为 Django 提供的标签提供附加标签。

wagtail 教程

Wagtail 模板标签

除了 Django 的 template tags and filters 之外, Wagtail 还提供了许多自己的 template tags & filters ,可以通过在模板文件顶部包含{% load wagtailcore_tags %}来加载。

在本教程中,我们使用富文本过滤器转义并打印 RichTextField 的内容:

{% load wagtailcore_tags %}
{{ page.body|richtext }}

产出:

<p>Welcome to our new site!</p>

注意:您需要在每个使用 Wagtail 标签的模板中包含{% load wagtailcoretags %}。如果标签未加载,Django 将抛出 TemplateSyntaxError

一个基本的博客

现在可以创建博客了,使用下面的命令行在Wagtail项目中创建一个新的应用程序。

python manage.py startapp blog

将新的博客应用添加到mysite/settings/base.py中的INSTALLED_APPS中。

INSTALLED_APPS = [
    "blog", # <- Our new blog app.
    "home",
    "search",
    "wagtail.contrib.forms",
    "wagtail.contrib.redirects",
    "wagtail.embeds",
    "wagtail.sites",
    "wagtail.users",
    #... other packages
]

注意:
你必须在mysite/settings目录下base.py文件的INSTALLEDAPPS部分注册所有的应用程序。看看这个文件,看看start命令是如何列出项目的应用程序的。

博客索引和帖子

首先为你的博客创建一个简单的索引页。编辑blog/models.py以包括:

from django.db import models

# Add these:
from wagtail.models import Page
from wagtail.fields import RichTextField
from wagtail.admin.panels import FieldPanel


class BlogIndexPage(Page):
    intro = RichTextField(blank=True)

    content_panels = Page.content_panels + [
        FieldPanel('intro')
    ]

既然你在应用中添加了一个新模型,你必须创建并运行一次数据库迁移:

python manage.py makemigrations
python manage.py migrate

另外,由于模型名是 BlogIndexPage,默认模板名是 blog_index_page.html,除非你重写它。Django会在你的 blog app文件夹的templates目录中寻找一个与你的Page模型名称匹配的模板。如果您愿意,可以覆盖此默认行为。要为BlogIndexPage模型创建一个模板,请在blog/templates/blog/blog_index_page.html位置创建一个文件。

注意:
你需要在你的博客应用程序文件夹创建文件夹templates/blog

blog_index_page.html 文件中输入以下内容:

{% extends "base.html" %}

{% load wagtailcore_tags %}

{% block body_class %}template-blogindexpage{% endblock %}

{% block content %}
    <h1>{{ page.title }}</h1>

    <div class="intro">{{ page.intro|richtext }}</div>

    {% for post in page.get_children %}
        <h2><a href="{% pageurl post %}">{{ post.title }}</a></h2>
        {{ post.specific.intro }}
        {{ post.specific.body|richtext }}
    {% endfor %}

{% endblock %}

除了使用get_children之外,前面的blog_index_page.html模板与前面使用home_page.html模板的工作类似。您将在本教程后面了解get_children的用法。

如果你有Django背景,那么你会注意到pageurl标签类似于Django的url标签,但它接受一个Wagtail Page对象作为附加参数。

现在这已经完成了,下面是如何从Wagtail管理界面创建页面:

  • 转到 http://127.0.0.1:8000/admin 并使用您的admin用户信息登录。
  • 在Wagtail管理界面中,转到 Pages,然后单击Home。
  • 通过单击屏幕顶部的…并选择Add child page选项,将子页面添加到主页。
  • 从页面类型列表中选择Blog索引页。
  • 使用“我们的博客”作为你的页面标题,确保它在“推广”选项卡上有“博客”,然后发布它。

您现在可以访问您网站的URL http://127.0.0.1:8000/blog。 这将给您一个错误页面,显示“TemplateDoesNotExist”,因为您还没有为新页面创建模板。另外,请注意Promote选项卡中的缩略名(slug)是如何定义页面URL的。

现在我们需要一个博客文章的模型和模板。在 blog/models.py 中:

from wagtail.models import Page
from wagtail.fields import RichTextField
from wagtail.admin.panels import FieldPanel

# add this:
from wagtail.search import index


# Keep the definition of BlogIndexPage model, and add the BlogPage model:

class BlogPage(Page):
    date = models.DateField("Post date")
    intro = models.CharField(max_length=250)
    body = RichTextField(blank=True)

    search_fields = Page.search_fields + [
        index.SearchField('intro'),
        index.SearchField('body'),
    ]

    content_panels = Page.content_panels + [
        FieldPanel('date'),
        FieldPanel('intro'),
        FieldPanel('body'),
    ]

在上面的模型中,我们导入 index ,因为这使得模型可搜索。然后,您可以列出您希望用户可搜索的字段。

由于models.py文件中发生了新变化,您必须再次迁移数据库:

python manage.py makemigrations
python manage.py migrate

在位置 blog/templates/blog/blog_page.html 创建一个新的模板文件。现在将以下内容添加到新创建的 blog_page.html 文件中:

{% extends "base.html" %}

{% load wagtailcore_tags %}

{% block body_class %}template-blogpage{% endblock %}

{% block content %}
    <h1>{{ page.title }}</h1>
    <p class="meta">{{ page.date }}</p>

    <div class="intro">{{ page.intro }}</div>

    {{ page.body|richtext }}

    <p><a href="{{ page.get_parent.url }}">Return to blog</a></p>

{% endblock %}

请注意,使用 Wagtail 的内置 get_parent() 方法来获取本文所属博客的 URL。

现在,进入你的管理界面,按照以下步骤创建一些博客文章作为 BlogIndexPage 的子类:

  • 单击Wagtail侧边栏中的 Pages,然后单击 Home
  • 将鼠标悬停在我们的博客上,然后单击 Add child page

wagtail 教程

选择页面类型,Blog page:

wagtail 教程

用您选择的内容填充字段:

wagtail 教程

若要在富文本 Body 字段添加链接,请高亮显示要将链接附加到的文本。现在您可以看到一个弹出式模式,其中有几个由图标表示的操作。单击适当的图标以添加链接。还可以单击出现在字段左侧的 + 图标,以获得与弹出式模式中显示的操作类似的操作。

要添加图像,请按 enter 键移动到字段中的下一行。然后单击 + 图标并从操作列表中选择 Image 以添加图像。

注意 Wagtail 使您能够完全控制可以在各种父内容类型下创建的内容类型。默认情况下,任何页面类型都可以是任何其他页面类型的子类型。

当你完成编辑后,发布每一篇博客文章。

恭喜你!现在你已经有了一个博客。如果您在浏览器中访问 http://127.0.0.1:8000/blog, 您可以看到您按照上述步骤创建的所有帖子(post):

wagtail 教程

标题应链接到帖子页面,并且返回博客主页的链接应出现在每个帖子页面的页脚中。

父节点和子节点(Parent and children)

您将在 Wagtail 中完成的大部分工作都围绕由节点和叶组成的分层“树”结构的概念(请参阅 Theory )。在本例中, BlogIndexPage 是“节点”,各个 BlogPage 实例是“叶子”。

再看看 blog_index_page.html的内部结构:

{% for post in page.get_children %}
    <h2><a href="{% pageurl post %}">{{ post.title }}</a></h2>
    {{ post.specific.intro }}
    {{ post.specific.body|richtext }}
{% endfor %}

Wagtail中的每个“页面”都可以从它在层次结构中的位置调用它的父节点或子节点。但是为什么一定要指定 post.specific.intro 而不是 post.intro 呢? 这与你定义模型的方式有关 class BlogPage(Page)get_children() 方法为您获取 Page 基类的实例列表。当您想要引用从基类继承实例的属性时,Wagtail 提供了特定的方法来检索实际的 BlogPage 记录。“title” 字段出现在基本页面模型中,而 “intro” 字段只出现在 BlogPage 模型中。所以你需要 .specific 来访问它。

你可以通过使用 Django with 标签来简化模板代码。现在,修改你的 blog_index_page.html :

{% for post in page.get_children %}
    {% with post=post.specific %}
        <h2><a href="{% pageurl post %}">{{ post.title }}</a></h2>
        <p>{{ post.intro }}</p>
        {{ post.body|richtext }}
    {% endwith %}
{% endfor %}

当您开始编写更多自定义的 Wagtail 代码时,您将发现一整套 QuerySet 修饰符来帮助您导航层次结构。

# 给定一个页面对象“somepage”:
MyModel.objects.descendant_of(somepage)
child_of(page) / not_child_of(somepage)
ancestor_of(somepage) / not_ancestor_of(somepage)
parent_of(somepage) / not_parent_of(somepage)
sibling_of(somepage) / not_sibling_of(somepage)
# ... 和 ...
somepage.get_children()
somepage.get_ancestors()
somepage.get_descendants()
somepage.get_siblings()

欲了解更多信息,请参阅:页面 QuerySet 引用

覆盖 Context

我们的博客索引视图存在几个问题:

  • 博客通常按时间倒序显示内容
  • 我们希望确保只显示已发布的内容。

为了完成这些事情,我们需要做的不仅仅是在模板中获取索引页面的子页面。相反,我们需要修改模型定义中的查询集。 Wagtail 通过可重写的 get_context() 方法使这成为可能。像这样修改您的 BlogIndexPage 模型:

class BlogIndexPage(Page):
    intro = RichTextField(blank=True)

    def get_context(self, request):
        # 更新上下文以仅包含已发布的帖子,按反向时间排序
        context = super().get_context(request)
        blogpages = self.get_children().live().order_by('-first_published_at')
        context['blogpages'] = blogpages
        return context

以下是你所做更改的快速细分:

  • 您检索了原始上下文(context)。
  • 您创建了一个自定义QuerySet。
  • 您向检索的上下文添加了自定义QuerySet。
  • 您将修改后的上下文返回给视图。

您还需要稍微修改一下您的 blog_index_page.html 模板。改变:

{% for post in page.get_children %}{% for post in blogpages %}

现在,取消发布你的一篇文章。未发表的文章应该从博客的索引页中消失。另外,剩下的帖子(post)现在应该重新排序,最近发布的帖子排在前面。

图像(Images)

让我们添加将图片库附加到我们的博客文章中的功能。虽然可以简单地将图像插入到 body 富文本字段中,但将我们的图库图像设置为数据库中的新专用对象类型有几个优点 - 这样,您就可以完全控制图像的布局和样式。模板,而不必在富文本字段中以特定方式布局它们。它还使得图像可以独立于博客文本在其他地方使用 - 例如,在博客索引页上显示缩略图。

models.py 中添加新的 BlogPageGalleryImage 模型:

# New imports added for ParentalKey, Orderable, InlinePanel

from modelcluster.fields import ParentalKey

from wagtail.models import Page, Orderable
from wagtail.fields import RichTextField
from wagtail.admin.panels import FieldPanel, InlinePanel
from wagtail.search import index

# ... Keep the definition of BlogIndexPage, update the content_panels of BlogPage, and add a new BlogPageGalleryImage model:

class BlogPage(Page):
    date = models.DateField("Post date")
    intro = models.CharField(max_length=250)
    body = RichTextField(blank=True)

    search_fields = Page.search_fields + [
        index.SearchField('intro'),
        index.SearchField('body'),
    ]

    content_panels = Page.content_panels + [
        FieldPanel('date'),
        FieldPanel('intro'),
        FieldPanel('body'),

        # Add this:
        InlinePanel('gallery_images', label="Gallery images"),
    ]


class BlogPageGalleryImage(Orderable):
    page = ParentalKey(BlogPage, on_delete=models.CASCADE, related_name='gallery_images')
    image = models.ForeignKey(
        'wagtailimages.Image', on_delete=models.CASCADE, related_name='+'
    )
    caption = models.CharField(blank=True, max_length=250)

    panels = [
        FieldPanel('image'),
        FieldPanel('caption'),
    ]

运行 python manage.py makemigrationspython manage.py migrate

这里有一些新概念,所以让我们逐个介绍它们:

  1. 继承自 Orderable ,为模型添加了 sort_order 字段,以跟踪图库中图像的顺序。
  2. ParentalKeyBlogPage 将图库图像附加到特定页面。 ParentalKey 的工作方式与 ForeignKey 类似,但也将 BlogPageGalleryImage 定义为 BlogPage 模型的“子节点”,以便在提交审核和跟踪修订历史记录等操作中将其视为页面的基本部分。
  3. image 是到 Wagtail 内置 Image 模型的 ForeignKey ,图像本身存储在其中。这在页面编辑器中显示为弹出界面,用于选择现有图像或上传新图像。通过这种方式,我们允许图像存在于多个图库中 - 实际上,我们在页面和图像之间创建了多对多关系。
  4. 在外键上指定 on_delete=models.CASCADE 意味着如果从系统中删除图像,则图库条目也会被删除。(在其他情况下,保留该条目可能是适当的 - 例如,如果“我们的员工”页面包含带有头像的人员列表,并且其中一张照片已被删除,我们宁愿将该人留在放置在没有照片的页面上。在这种情况下,我们将外键设置为 blank=True, null=True, on_delete=models.SET_NULL 。)
  5. 最后,将 InlinePanel 添加到 BlogPage.content_panels 使得图库图像可以在 BlogPage 的编辑界面上使用。

在编辑完 blog/models.py 之后,您应该在侧边栏中看到 Images 和一个 Gallery Images 字段,其中有上传图像的选项,并在博客文章的编辑屏幕中为其提供标题。

编辑你的博客页面模板 blog_page.html,以包括图像:

<!-- Load the wagtailimages_tags: -->
{% load wagtailcore_tags wagtailimages_tags %}

{% block body_class %}template-blogpage{% endblock %}

{% block content %}
    <h1>{{ page.title }}</h1>
    <p class="meta">{{ page.date }}</p>

    <div class="intro">{{ page.intro }}</div>

    {{ page.body|richtext }}

    <!-- Add this: -->
    {% for item in page.gallery_images.all %}
        <div style="float: left; margin: 10px">
            {% image item.image fill-320x240 %}
            <p>{{ item.caption }}</p>
        </div>
    {% endfor %}

    <p><a href="{{ page.get_parent.url }}">Return to blog</a></p>

{% endblock %}

如果您想在编辑博客页面模板后显示它们,请确保在编辑博客页面时上传一些图像。

这里我们使用 {% image %} 标签(存在于 wagtailimages_tags 库中,在模板顶部导入)插入 <img> 元素,并使用 fill-320x240 参数指示应调整图像大小并裁剪以填充 320x240 矩形。您可以在 docs 中阅读有关在模板中使用图像的更多信息。

wagtail 教程

由于图库图像本身就是数据库对象,因此现在可以独立于博客文章主体查询和重用它们。现在,在你的 BlogPage 模型中定义一个 main_image 方法,它从第一个图库项目返回图像,如果没有图库项目,则返回 None:

class BlogPage(Page):
    date = models.DateField("Post date")
    intro = models.CharField(max_length=250)
    body = RichTextField(blank=True)
    # Add the main_image method:
    def main_image(self):
        gallery_item = self.gallery_images.first()
        if gallery_item:
            return gallery_item.image
        else:
            return None

    search_fields = Page.search_fields + [
        index.SearchField('intro'),
        index.SearchField('body'),
    ]

    content_panels = Page.content_panels + [
        FieldPanel('date'),
        FieldPanel('intro'),
        FieldPanel('body'),
        InlinePanel('gallery_images', label="Gallery images"),
    ]

现在可以从模板中使用该方法。更新 blog_index_page.html 以加载 wagtailimages_tag,并将主图像作为缩略图包含在每篇帖子旁边:

<!-- Load wagtailimages_tags: -->
{% load wagtailcore_tags wagtailimages_tags %}

<!-- Modify this: -->
{% for post in blogpages %}
    {% with post=post.specific %}
        <h2><a href="{% pageurl post %}">{{ post.title }}</a></h2>

        <!-- Add this: -->
        {% with post.main_image as main_image %}
            {% if main_image %}{% image main_image fill-160x100 %}{% endif %}
        {% endwith %}

        <p>{{ post.intro }}</p>
        {{ post.body|richtext }}
    {% endwith %}
{% endfor %}

作者(Authors)

您可能希望您的博客文章显示作者,这是博客的基本特性。要做到这一点的方法是有一个固定的列表,由网站所有者通过管理界面的一个单独区域管理。

首先,定义 Author 模型。这个模型本身并不是一个页面。你必须把它定义为一个标准的 Django 模型。模型,而不是从页面继承。Wagtail 引入了 snippet 的概念,用于可重用的内容片段,这些内容片段本身并不作为页面树的一部分存在。您可以通过管理界面管理代码片段。您可以使用 @register_snippet 装饰器将模型注册为代码片段。此外,您还可以在代码片段上使用到目前为止在页面上使用的所有字段类型。

要创建 Authors 并为每个作者提供作者图像和名称,请在 blog/models.py 中添加以下代码:

# Add this to the top of the file
from wagtail.snippets.models import register_snippet

# ... Keep BlogIndexPage, BlogPage, BlogPageGalleryImage models, and then add the Author model:
@register_snippet
class Author(models.Model):
    name = models.CharField(max_length=255)
    author_image = models.ForeignKey(
        'wagtailimages.Image', null=True, blank=True,
        on_delete=models.SET_NULL, related_name='+'
    )

    panels = [
        FieldPanel('name'),
        FieldPanel('author_image'),
    ]

    def __str__(self):
        return self.name

    class Meta:
        verbose_name_plural = 'Authors'

注意 这里使用的是 panels,而不是 content_panels。由于 snippet 通常不需要诸如 slug 或发布日期之类的字段,因此它们的编辑界面不会拆分为单独的 “content” / “promote” / “settings” 选项卡。所以没有必要区分“内容面板”和“推广面板”。

通过运行 python management .py makemigrationpython management .py migrate 迁移此更改。使用 Snippets 区域创建几个作者会出现在Wagtail管理界面中。

现在可以将作者作为多对多字段添加到 BlogPage 模型中。为此使用的字段类型是 ParentalManyToManyField。这个字段是标准 Django ManyToManyField 的一个变体,它确保选择的对象与修订历史中的页面记录正确关联。它的操作方式类似于一对多关系中 ParentalKey 取代 ForeignKey 的方式。要将作者添加到 BlogPage,修改 blog app 文件夹中的 models.py

# New imports added for forms and ParentalManyToManyField, and MultiFieldPanel
from django import forms
from django.db import models

from modelcluster.fields import ParentalKey, ParentalManyToManyField
from wagtail.models import Page, Orderable
from wagtail.fields import RichTextField
from wagtail.admin.panels import FieldPanel, InlinePanel, MultiFieldPanel
from wagtail.search import index
from wagtail.snippets.models import register_snippet

class BlogPage(Page):
    date = models.DateField("Post date")
    intro = models.CharField(max_length=250)
    body = RichTextField(blank=True)

    # Add this:
    authors = ParentalManyToManyField('blog.Author', blank=True)

    # ... Keep the main_image method and search_fields definition. Modify your content_panels:
    content_panels = Page.content_panels + [
        MultiFieldPanel([
            FieldPanel('date'),
            FieldPanel('authors', widget=forms.CheckboxSelectMultiple),
        ], heading="Blog information"),
        FieldPanel('intro'),
        FieldPanel('body'),
        InlinePanel('gallery_images', label="Gallery images"),
    ]

在修改前面的模型中,您使用了 FieldPanel 定义中的 widget 关键字参数来指定一个更加用户友好的基于复选框的小部件,而不是默认的多个选择框。此外,您在 content_panels 中使用 MultiFieldPanel 将日期和作者字段分组在一起以提高可读性。

最后,通过运行 python management .py makemigrationpython management .py migrate 来迁移数据库。迁移数据库后,更新 blog_page.html 模板以显示 Authors:

{% block content %}
    <h1>{{ page.title }}</h1>
    <p class="meta">{{ page.date }}</p>

    <!-- Add this: -->
    {% with authors=page.authors.all %}
        {% if authors %}
            <h3>Posted by:</h3>
            <ul>
                {% for author in authors %}
                    <li style="display: inline">
                        {% image author.author_image fill-40x60 style="vertical-align: middle" %}
                        {{ author.name }}
                    </li>
                {% endfor %}
            </ul>
        {% endif %}
    {% endwith %}

    <div class="intro">{{ page.intro }}</div>

    {{ page.body|richtext }}

    {% for item in page.gallery_images.all %}
        <div style="float: left; margin: 10px">
            {% image item.image fill-320x240 %}
            <p>{{ item.caption }}</p>
        </div>
    {% endfor %}

    <p><a href="{{ page.get_parent.url }}">Return to blog</a></p>

{% endblock %}

现在转到管理界面,在侧边栏中,您可以看到新的 Snippets 选项。单击此按钮以创建作者。在创建了作者之后,到你的博客文章中添加作者。从你的博客索引页点击你的博客文章,现在应该出现一个类似于下图的页面:

wagtail 教程

标签化帖子(Tag posts)

假设我们想让编辑“标记”他们的帖子,以便读者可以一起查看所有与自行车相关的内容。为此,我们需要调用与 Wagtail 捆绑的标签系统,将其附加到 BlogPage 模型和内容面板,并在博客文章模板上呈现链接标签。当然,我们还需要一个有效的特定于标签的 URL 视图。

首先,再次修改 models.py

from django import forms
from django.db import models

# New imports added for ClusterTaggableManager, TaggedItemBase

from modelcluster.fields import ParentalKey, ParentalManyToManyField
from modelcluster.contrib.taggit import ClusterTaggableManager
from taggit.models import TaggedItemBase

from wagtail.models import Page, Orderable
from wagtail.fields import RichTextField
from wagtail.admin.panels import FieldPanel, InlinePanel, MultiFieldPanel
from wagtail.search import index


# ... Keep the definition of BlogIndexPage model and add a new BlogPageTag model
class BlogPageTag(TaggedItemBase):
    content_object = ParentalKey(
        'BlogPage',
        related_name='tagged_items',
        on_delete=models.CASCADE
    )

# Modify the BlogPage model:
class BlogPage(Page):
    date = models.DateField("Post date")
    intro = models.CharField(max_length=250)
    body = RichTextField(blank=True)
    authors = ParentalManyToManyField('blog.Author', blank=True)

    # Add this:
    tags = ClusterTaggableManager(through=BlogPageTag, blank=True)

    # ... Keep the main_image method and search_fields definition. Then modify the content_panels:
    content_panels = Page.content_panels + [
        MultiFieldPanel([
            FieldPanel('date'),
            FieldPanel('authors', widget=forms.CheckboxSelectMultiple),

            # Add this:
            FieldPanel('tags'),
        ], heading="Blog information"),
        FieldPanel('intro'),
        FieldPanel('body'),
        InlinePanel('gallery_images', label="Gallery images"),
    ]

运行 python management .py makemigrationpython management .py migrate 命令。

你所做的改变可以总结如下:

新的 modelclustertaggit 导入 增加了一个新的 BlogPageTag 模型,并在 BlogPage 上添加了一个标签字段。

编辑你的一个 BlogPage 实例,现在你应该可以标记文章了:

wagtail 教程

要在 BlogPage 上渲染标签,将下面的代码添加到 blog_page.html 中:

<p><a href="{{ page.get_parent.url }}">Return to blog</a></p>

<!-- Add this: -->
{% with tags=page.tags.all %}
    {% if tags %}
        <div class="tags">
            <h3>Tags</h3>
            {% for tag in tags %}
                <a href="{% slugurl 'tags' %}?tag={{ tag }}"><button type="button">{{ tag }}</button></a>
            {% endfor %}
        </div>
    {% endif %}
{% endwith %}

请注意,我们使用内置 slugurl 标签链接到此处的页面,而不是我们之前使用的 pageurl 。不同之处在于 slugurl 采用页面 slug(来自“升级”选项卡)作为参数。 pageurl 更常用,因为它是明确的并且避免了额外的数据库查找。但在此循环的情况下,Page 对象并不容易获得,因此我们求助于不太受欢迎的 slugurl 标记。

到目前为止所做的修改,访问带有标签的博客文章将在底部显示一系列链接按钮,每个按钮对应与该文章关联的标签。但是,单击按钮将导致 404 错误页面,因为您还没有定义“标签”视图。

返回到 blog/models.py 并添加一个新的 BlogTagIndexPage 模型:

class BlogTagIndexPage(Page):

    def get_context(self, request):

        # Filter by tag
        tag = request.GET.get('tag')
        blogpages = BlogPage.objects.filter(tags__name=tag)

        # Update template context
        context = super().get_context(request)
        context['blogpages'] = blogpages
        return context

请注意,这个基于页面的模型没有定义自己的字段。即使没有字段, Page 的子类化也使其成为 Wagtail 生态系统的一部分,以便您可以在管理中为其指定标题和 URL,并且可以通过从其 get_context() 方法返回 QuerySet 来操作其内容。

通过运行 python manage.py makemigrationpython manage.py migrate 来迁移它。迁移新更改后,在管理界面中创建一个新的 BlogTagIndexPage。要创建 BlogTagIndexPage,请遵循创建 BlogIndexPage 时所遵循的相同过程,并在 Promote 选项卡上给它添加 “tags”。这意味着 BlogTagIndexPage 是主页的子页面,并且在管理界面中与 Our Blog 并行。

访问 /tags 和 Django 会告诉你一些你可能已经知道的事情。您需要创建模板 blog/templates/blog/blog_tag_index_page.html ,并添加以下内容:

{% extends "base.html" %}
{% load wagtailcore_tags %}

{% block content %}

    {% if request.GET.tag %}
        <h4>Showing pages tagged "{{ request.GET.tag }}"</h4>
    {% endif %}

    {% for blogpage in blogpages %}

          <p>
              <strong><a href="{% pageurl blogpage %}">{{ blogpage.title }}</a></strong><br />
              <small>Revised: {{ blogpage.latest_revision_created_at }}</small><br />
          </p>

    {% empty %}
        No pages found with that tag.
    {% endfor %}

{% endblock %}

我们在 Page 模型上调用了内置 latest_revision_created_at 字段。

点击博客文章底部的标签按钮,页面呈现如下:

wagtail 教程

恭喜你!

您已经完成了本教程🥳。为自己鼓掌,给自己一块饼干!

感谢您的阅读,欢迎来到Wagtail社区!

下一步

  • 阅读 Wagtail 主题和参考文档
  • 学习如何为自由格式的页面内容实现StreamField
  • 浏览高级主题部分并阅读第三方教程