Last update on .

Wagtail是一款基于Django的CMS开源框架,集成的功能非常多,很适合做定制站点开发。本文是从Wagtail官网翻译的一篇教程(官网教程:Your first Wagtail site),由于原文比较长,我把它分成了三个部分,今天这篇文章是第二部分,主要内容是搭建能够显示博客列表和博客文章的一个简单博客。

一个简单的博客应用

现在我们准备创建一个简单的博客应用,首先,运行下面的命令,在我们的项目中创建一个blog应用。

python manage.py startapp blog

在mysite/settings/base.py 的INSTALLED_APPS中添加blog。

博客列表和博客文章(post)

让我们从创建博客列表的页面开始,在blog/models.py中,编辑代码如下:

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', classname="full")    
    ]
运行
python manage.py makemigrations
python manage.py migrate

因为Model的名称叫BlogIndexPage, 默认的模板名称就是
blog/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。注意pageurl标签,这个标签就类似Django的url标签,只是这里需要将Wagtail的page作为参数。

接下来就是创建出对应的页面。进入Wagtail的admin后台,创建BlogIndexPage,注意选择作为Homepage的下级页面, 同时确保在Promote属性页中的slug输入blog,然后发布(publish),正常情况我们可以正常访问
http://127.0.0.1:8000/blog/了。

这时候相信大家已经发现Wagtail与常规Django编程的差别了,那就是目前为止,我们没有用到MTV架构的View。

现在我们需要为Blog文章(BlogPage)创建model和模板,回到blog/models.py:

from django.db import models
from wagtail.models import Page
from wagtail.fields import RichTextField
from wagtail.admin.panels import FieldPanel
from wagtail.search import index
# Keep the definition of BlogIndexPage, and add:
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', classname="full"),]

在上面的Model中,我们引入索引(index)使得这个model具有搜索功能,也就是说后面通过扩展首页的搜索功能,就可以按照文章进行搜索了,目前我们只需要将需要搜索的字段通过SearchField加入即可。修改好Model后运行:

python manage.py makemigrations 
python manage.py migrate.

接下来创建模板,添加模板文件:
blog/templates/blog/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()来获得当前Page的父节点的链接。

回到Admin管理员界面,我们为BlogIndexPage创建若干个子Page,Page的类型选择“Blog Page”。

 

 

Wagtail的Page管理十分强大,你可以在每个Page下面,创建任何类型的Page子页面。

把我们刚才添加的Page全部发布。这时候我们就完成了一个非常简单的Blog系统,通过访问/blog,我们可以看到类似这个界面:

 

博客文件标题应该可以链接到具体的博客文章页面,在具体的文章页面底部应该有一个返回到列表的链接。

父页面和子页面

在Wagtail中我们大部分的工作都围绕着树形结构这个概念开展,树形结构由节点和树叶组成,在这个例子中,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中的每一个Page都可以调用它的父页面或子页面,为什么这里我们还明确要使用post.specific.intro而不直接用post.intro?我们来解释一下:

模板代码中get_children()方法会返回给我们一个Page实例列表,当我们想遍历Page实例中定义的属性的时候,我们是无法知道具体page实例是由哪个类创建的。所以Wagtail提供了specific方法,通过这个方法,可以感知Page实例的类是什么(在这里就是BlogPage),这样就可以按照BlogPage的实例去获得title和intro的值。也就是说我们需要 specific出来具体的类,然后访问实例的属性。这段比较难理解,实在不理解直接照葫芦画瓢先使用即可:)

为了让模板代码更具可读性(不出现specific),也可以使用with标签:

{% 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的方法(如下列示)来帮助遍历和定位到需要的页面层次结构(总结说就是想要怎么查都可以)。

# Given a page object '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)
# ... and ...
somepage.get_children()
somepage.get_ancestors()
somepage.get_descendants()
somepage.get_siblings()

重写(Overriding)Context

目前在我们的Blog列表中有几个问题:

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

为了完成这些事情,我们需要做的不仅仅是在模板中抓取索引页的子页面。我们希望的是修改模型定义中的查询集。Wagtail通过重写的get_context()方法来改变返回的默认列表。修改BlogIndexPage类如下:

class BlogIndexPage(Page):
   intro = RichTextField(blank=True)
    def get_context(self, request):    
    # Update context to include only    published posts, ordered by reverse-chron    
    context = super().get_context(request)    
    blogpages = self.get_children().live().order_by('-first_published_at')    
    context['blogpages'] = blogpages
    return context

我们在这里所做的就是检索原始上下文,创建一个自定义查询集,将其添加到检索到的上下文中,然后将修改后的上下文返回到视图中。之后我们还需要将blog_index_page.html中的{% for post in page.get_children %}

修改为{% for post in blogpages %}

现在试着取消发布你的一篇文章——它应该会从博客列表页面中消失。剩下的博客文章也应该是按照最新日期降序排列。   

评论

No comments yet.

Please log in by using LinkedIn Weibo to leave a comment.