经过前面的准备工作,我们现在已经可以创建一个应用了。wagtail是一个基于Django的CMS管理系统,我们将根据官网的教程,创建一个博客应用。
创建应用
进入项目目录,在项目目录内运行下面命令:
python manage.py startapp blog
进入mysite/settings/base.py,在INSTALLED_APPS
中添加“blog”应用:
INSTALLED_APPS = [
"blog", # 刚刚创建的blog应用
"home",
"search",
"wagtail.contrib.forms",
"wagtail.contrib.redirects",
"wagtail.embeds",
"wagtail.sites",
"wagtail.users",
#... 其他应用
]
需要注意的是,所有应用都应该在INSTALLED_APPS
中注册。
创建博客目录
编辑blog/models.py, 创建一个简单的博客目录模型:
from django.db import models
from wagtail.models import Page
from wagtail.fields import RichTextField
from wagtail.admin.panels import FieldPanel
class BlogIndexPage(Page):
intro = RichTextField(verbose_name="简介", blank=True)
content_panels = Page.content_panels + [
FieldPanel('intro')
]
老规矩,创建模型就要运行migrate命令把字段写入数据库中:
python manage.py makemigrations
python manage.py migrate
按照上一章所说的,模板的命名根据模型的名称而来。默认情况下,BlogIndexPage模型对应的模板文件名称应为blog_index_page.html 。为了便于Django查找到模板文件,需要将文件放在templates文件夹下。在blog应用文件夹下创建templates/blog/blog_index_page.html文件。
在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 %}
page.title定义了页面的标题,page.intro定义了页面的内容简介。
这里使用了get_children来获取子页面的内容,不过此时还没有子页面。
进入wagtail后台管理页面,点击Home页。
在Home页添加子页面。
在页面类型页,选择blog_index_page类型
创建后,在blog index page页面输入标题和内容:
点击推荐页(promote),输入缩略名。默认是我们刚刚填写的标题。
发布后点击“实时查看”,已经有了标题和简介,但其他就是一片空白了,那是因为还没有创建博客。
创建博客页面
编辑blog/models.py ,先创建博客的发布模型:
# 增加一个引入
from wagtail.search import index
# BlogIndexPage模型不变, 增加一个BlogPage模型:
class BlogPage(Page):
date = models.DateField("发布日期")
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以便于用户进行搜索,并把‘intro’和‘body’字段做为可搜索字段。
运行migrate更新数据库模型:
python manage.py makemigrations
python manage.py migrate
创建blog_page.html页面,并放在blog/templates/blog/目录下,添加如下内容:
{% 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 %}
运行python manage.py runserver
在博客索引管理页添加子页面。
创建一个博客页面,输入一些内容:
按以上的方式,多创建几个页面并发布。点击“实时查看”,可以发现我们已经成功创建了博客。
点击“返回博客目录”,就回到了目录页。
父类页面和子类页面
wagtail的大部分工作都是围绕由节点和叶子组成的分层树结果。在我们的入门案例中,BlogIndexPage做为父结点,BlogPage做为叶子,处于树形结构的末端。BlogIndexPage是BlogPage的父结点,BlogPage是BlogIndexPage子结点。
我们再看一下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 %}
每个页面都可以访问该页面所处位置的父结点或子结点。
这里有个特殊的地方,就是为什么引用intro需要采用post.specific.info,而不是直接使用post.intro。这是因为page.get_children获取的是一个page基类列表。而intro属性不是page类的属性,只是继承于page类的一个子类的属性。wagtail提供了specific属性用于获取page子类的属值。而title是page基类中本来就有的属性,所以就不需要用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 %}
重写Context
其实我们现在的代码还是存在以下问题:
1.我们的博客是按发布时间顺序来排列的,发布的时间早的排在前面。而在实现项目中,我们一般会希望发布时间晚的排在前面。
2.不管是否已经发布,blog_index_page都会显示。但我们只想显示已发布的。
为了解决这两个问题,我们可以重写BlogIndexPage模型:
class BlogIndexPage(Page):
intro = RichTextField(blank=True)
# 添加 get_context 方法:
def get_context(self, request):
# 获取父类的context变量
context = super().get_context(request)
#只把已发布的进行放在变量中,并按时间倒序排列。
blogpages = self.get_children().live().order_by('-first_published_at')
# 把变量加入context变量中
context['blogpages'] = blogpages
return context
然后也需要在blog_index_page.html中做一些微小的修改,把{% for post in page.get_children %}
修改为 {% for post in blogpages %}
。因为此时blog_index_page的上下文中已经有了blogpages这个变量。
此时,如果你创建一个博客,但不发布,则不会显示在blog_index_page中。或者取消一个已经发布的博客,则这个博客也不会显示。同时,也会发现所有的博客已经按时间的倒序在排列。
图片处理
接下来我们创建一个图片库。这样我们就可以比较方便的将图片插入模板中。图片库独立于博客存在,这样就可以在不同的地方引用相同的图片。
创建一个图片库,并将图片库加到BlogPage的控制面板上:
from django.db import models
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
class BlogPage(Page):
date = models.DateField("发布日期")
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'),
# 在这里添加图片库,在博客编辑时可以选择图片。
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 makemigrations
和 python manage.py migrate
,然后再通过runserver启动服务。
进入后台博客编辑页面,可以看到已经有了图片库功能:
编辑blog_page.html
{% extends "base.html" %}
{% 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 }}
<!-- 增加以下内容 -->
{% for item in page.gallery_images.all %}
<div style="float: inline-start; margin: 10px">
{% image item.image fill-320x240 %}
<p>{{ item.caption }}</p>
</div>
{% endfor %}
<p><a href="{{ page.get_parent.url }}">返回博客目录</a></p>
{% endblock %}
插入一张图片就可以看到效果了,不过格式可能需要好好调一下。
创建一个main_image方法,用于返回图片库里的第一张图片:
class BlogPage(Page):
date = models.DateField("发布日期")
intro = models.CharField("简介", max_length=250)
body = RichTextField("内容", blank=True)
# 添加一个main_image方法,返回图库里的第一个图片
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="图片"),
]
更新blog_index_page.html:
<!-- 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 %}
给每一个博客上传一张照片,可以看到在blog_index_page页,每个博客都有一张小照片。
添加作者
一般来说,为每篇文章添加一个作者是博客的基本功能。wagtail采用片断(snippet)的方式为文章添加作者。我们需要定义一个继承自Django标准模型(models.Model)的作者模型,需要注意的是,这个模型是独立于Page模型存在的。
修改我们的blog/models.py:
# 引入一个新包
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_panel。
运行 python manage.py makemigrations
和 python manage.py migrate
命令,然后 runserver。
进入后台管理页面,我们可以发现在侧边栏里已经有了一个片断(snippet):
因为我们还没有创建作者,所以点击进去后是空白。点击作者旁边的“+”号或者“add now”,可以创建作者:
我们还需要把作者字段添加到BlogPage中,这样我们才能在后端的博客编辑页面看到它:
# 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)
# 添加一个字段
authors = ParentalManyToManyField('blog.Author', blank=True)
content_panels = Page.content_panels + [
MultiFieldPanel([
FieldPanel('date'),
FieldPanel('authors', widget=forms.CheckboxSelectMultiple),
], heading="基本信息"),
FieldPanel('intro'),
FieldPanel('body'),
InlinePanel('gallery_images', label="Gallery images"),
]
我们对content_panel做了一个小小的发行。把日期、作者分到基本信息组里。其它的还按原来的方式排列。
此时,我们选择上作者并发布,不会有任何变化。因为我们还没有配置模板。我们在
blog_page.html
增加一段代码用以显示我们的作者信息。
<!-- 增加下面这段代码 -->
{% with authors=page.authors.all %}
{% if authors %}
<h3>作者:</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 %}
然后再点击发布,通过“实时查看”我们可以看到,在blog页面中已经有了作者信息:
使用标签
如果我们想查看某一类的信息,标签就要上场了。和上面的作者信息一样,我们先创建一个标签类,然后再把标签类加到博客的字段中。当然,不要忘了把标签字段加到管理页面中去。
from django.db import models
from django import forms
# 增加ClusterTaggableManager, TaggedItemBase两个包
from modelcluster.contrib.taggit import ClusterTaggableManager
from taggit.models import TaggedItemBase
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
#其他的不要变,增加一个BlogPageTag类
class BlogPageTag(TaggedItemBase):
content_object = ParentalKey(
'BlogPage',
related_name='tagged_items',
on_delete=models.CASCADE
)
class BlogPage(Page):
date = models.DateField("发布日期")
intro = models.CharField("简介", max_length=250)
body = RichTextField("内容", blank=True)
authors = ParentalManyToManyField('blog.Author', blank=True)
# 增加一个tags字段
tags = ClusterTaggableManager(through=BlogPageTag, blank=True)
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 + [
MultiFieldPanel([
FieldPanel('date'),
FieldPanel('authors', widget=forms.CheckboxSelectMultiple),
# 把 tags字段添加到管理面板中
FieldPanel('tags'),
], heading="基本信息"),
FieldPanel('intro'),
FieldPanel('body'),
InlinePanel('gallery_images', label="图片"),
]
运行 python manage.py makemigrations
和 python manage.py migrate
命令。然后启动服务。进入后端管理页面,可以看到博客编辑页已经有了标签。
为了能够正常显示标签,我们还需要修改一下blog_page.html:
{% 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用于标签的查找。
创建一个标签并发布,用“实时查看“显示博客页面,可以看到在页面的最下面多了一个标签:
不过此时去点击”马克思“这个标签会显示404,因为我们还需要创建一个标签目录页,或者叫标签索引页。
编辑一下models.py,增加一个BlogTagIndex类:
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
在管理后面,创建一个blog_tag_index_page,由于它不属于博客,建议在Home Page下面创建,做为HomePage的子页面,和我们的博客目录平级。
在创建的blog_tag_index_page中输入标题。虽然没有任何字段,但在后面页面还是会有”内容(content)“和”推荐(promote)“页。
因为我们在博客页定义的slugurls是”tags",因此需要在我们的”推荐”页把缩略名修改为“tags”。
最后,在blog/templates/blog 目录下创建一个blog_tag_index_page.html,用于显示我们的标签:
{% extends "base.html" %}
{% load wagtailcore_tags %}
{% block content %}
{% if request.GET.tag %}
<h4>显示相关标签"{{ request.GET.tag }}"</h4>
{% endif %}
{% for blogpage in blogpages %}
<p>
<strong><a href="{% pageurl blogpage %}">{{ blogpage.title }}</a></strong><br />
<small>发布时间: {{ blogpage.latest_revision_created_at }}</small><br />
</p>
{% empty %}
没有相关标签。
{% endfor %}
{% endblock %}
此时,点击博客页的“马克思”就可以看到我们的标签目录了:
OK,我们的wagtail入门教程就这些,现在你应该已经可以用wagtail做一个简单的博客系统了。