Page 页面模型¶
Wagtail 每个 Page 页面模型是基于 Django modal 模型实现的。 所有页面模型都要从 wagtail.core.models.Page
类继承。
在页面模型里定义字段可以使用 Django 提供的所有字段类型,参考 Model field reference 提供的字段类型。 在这此类型的基础上,Wagtail 提供了富文本、图片、文档、流等许多扩展字段类型。
Wagtail 模型示例¶
下面的例子展示了一个典型的博客页面模型:
from django.db import models
from modelcluster.fields import ParentalKey
from wagtail.core.models import Page, Orderable
from wagtail.core.fields import RichTextField
from wagtail.admin.edit_handlers import FieldPanel, MultiFieldPanel, InlinePanel
from wagtail.images.edit_handlers import ImageChooserPanel
from wagtail.search import index
class BlogPage(Page):
# Database fields
body = RichTextField()
date = models.DateField("Post date")
feed_image = models.ForeignKey(
'wagtailimages.Image',
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name='+'
)
# Search index configuration
search_fields = Page.search_fields + [
index.SearchField('body'),
index.FilterField('date'),
]
# Editor panels configuration
content_panels = Page.content_panels + [
FieldPanel('date'),
FieldPanel('body', classname="full"),
InlinePanel('related_links', label="Related links"),
]
promote_panels = [
MultiFieldPanel(Page.promote_panels, "Common page configuration"),
ImageChooserPanel('feed_image'),
]
# Parent page / subpage type rules
parent_page_types = ['blog.BlogIndex']
subpage_types = []
class BlogPageRelatedLink(Orderable):
page = ParentalKey(BlogPage, on_delete=models.CASCADE, related_name='related_links')
name = models.CharField(max_length=255)
url = models.URLField()
panels = [
FieldPanel('name'),
FieldPanel('url'),
]
重要
在定义字段名时,字段名不要和模型名同名。否则会导致处理关系时出错 (read more)。 做为开发规约,在编写程序时最好给页面模型加上 “Page” 后缀。
编写页面模型¶
下面将上节定义模型的各段程序逐一进行说明。
数据库字段¶
每个 Wagtail 页面类型是一个 Django model, 对于数据库一个独立的表(公共字段保存在页面公共表中)。
每个页面类型定了一套自有的字段集,例如博客有正文主体和发布日期, 而事件有内容和开始、结束时间。
Wagtail 中字段除了使用 Django 的字段类外,还可以使用许多第三方扩展的字段类。
Wagtail 的核心模块提供了富文本和流的字段类型:
RichTextField
- 富文本
StreamField
- 流(基于区块组合的内容字段) (参考: 使用 StreamField 实现页面灵活定制)
对于标签,Wagtail 全完支持 django-taggit 插件。
查找¶
search_fields
属性定义哪些字段用于做内容查找使用,以及字段的索引方式。这个属性是 SearchField
以及 FilterField
对象列表。
SearchField
增加供全文检索使用的字段,FilterField
增加供过滤使用的字段。一个字段可以使用两种方式加入到查找序列中,但每种方式只能使用一次。
在上面的例子中,使用 body
做全文搜索查找,而 date
用来做为过滤条件。哪些类型可以做为参数使用,请查看详细说明:额外字段索引。
编辑面板¶
在后台管理界面的内容编辑页面中,定义了下面三种 Tab 页:
content_panels
- 内容编辑项目, 例如内容主体字段
promote_panels
- 推广元数据(metadata), 例如标签、缩略图、页面推广标题
settings_panels
- 配置项, 例如发布日期
这几个属性的内容是 EditHandler
对象列表, 定义了界面各个 Tab 页显示项目及组合顺序。
关于 Wagtail 提供的 EditHandler
类定义,请查看 Available panel types 详细介绍。简要说来提供下面三种编辑手段:
Basic
使用下面两个类定义最基础的编辑方式,FieldPanel
根据字段类型及属性选择合适的界面组件进行编辑。
StreamField
字段用于流布局方式的区块内容编辑。
Structural
使用如下类来定义界面中一组结构化数据编辑的方式:
MultiFieldPanel
- 用于一组相关字段的编辑
InlinePanel
- 用于 inlining 方式的子模型编辑
FieldRowPanel
- 用于一行编辑多个字段
Chooser
对于 ForeignKey
字段,如果链接 Wagtail 的基础类型,Wagtail 基于 ChooserPanel
类提供如下一些选择弹框来供用用户选择内容项。
在图形/文档(image/document)选择框中还提供了即时上传图片或文档的功能,不需要关闭选择器就可操作。
注解
这些选择器只能在 Wagtail 页面字段链接到其它页面、图片、文档或片段时使用。
链接到其它模型类型,应使用 FieldPanel
,默认使用下拉框显示选择内容。
页面编辑器定制¶
页面编辑器可以深度定制,详细内容请参考: Customising the editing interface。
父页面 / 子页面类型限制¶
有两个属性可以控制网站显示的页面类型。页面类型控制主要使用在诸如”博客索引页下面只能新建博客页面”之类的需求。
下面两个属性要赋予模型类或模型名的列表。模型名的格式一般为 app_label.ModelName
,如果 app_label
省略,默认在同一个应用(app)中。
parent_page_types
限定当前页面类型可以创建在哪些页面类型下面。subpage_types
限定当前页面类型可以创建哪些子页面。
缺省情况下是不加限制。而设置 parent_page_types
为一个空的列表,表示当前页面不能创建在任何页面下面。
页面 URLs¶
获得页面 URL 的常用方式是使用模板的 {% pageurl %}
标签。因为是从模板中调用的,pageurl
会自动包含如下一些优化内容,更多的说明请参考 pageurl。
页面模型(Page) 还提供如下几种底层的 URL 获取方式。
定制页面模型的 URL 模式¶
通常 Page.get_url_parts(request)
方法不会直接调用,一般在子类中复写此方法来返回页面的访问路径。此方法返回 (site_id, root_url, page_path)
格式的元组,
get_url
和 get_full_url
都会调用此方法来获得给定页面的 URL。
get_url_parts()
的参数是 *args, **kwargs
:
def get_url_parts(self, *args, **kwargs):
这些参数要传给父类的 super
的 get_url_parts
方法,例如:
super().get_url_parts(*args, **kwargs)
也可以直传递 request
参数,但为了更好的与后来的版本兼容,还是要采用上述的方式。
方法的详细说明参见: wagtail.core.models.Page.get_url_parts()
.
获取页面实例的 URLs¶
任何时候需要获得一个页面实例的 url 时,可以调用 Page.get_url(request)
方法。当页面在访问请求站点(根据``request`` 主机名)范围内时,这个返回不带协议和域名的本地 URL。
get_url(request)
通常用在页面导航菜单等定制的模板标签中。在定制这些模板标签时,应使用 takes_context=True
参数,并使用 context.get('request')
获取请求,模板上下文没有 request 时应使用 None
。
方法的详细说明参见: wagtail.core.models.Page.get_url()
.
在需要包含协议及域名的 URL 时,请使用 Page.get_full_url(request)
方法。 request
一般不要省略,以便在一次请求中进行处理结果缓存使用。
方法的详细说明参见: wagtail.core.models.Page.get_full_url()
.
模板渲染¶
每个页面模型可以使用一个默认的页面模板。这是最简单和常用的方式。
创建页面模板¶
Wagtail 默认使用应用标签(app label)及模型名(model class name)来引用小写形式模板文件名,模型名中大写字母转换成下划线加小写字母。
模板格式: <app_label>/<model_name (snake cased)>.html
上例中的博客页面模型 blog.BlogPage 的模板对应于: blog/blog_page.html
文件。
这个文件根据 Django 的访问路径规则,放到可以访问的模板目录中(注意创建 blog 目录,模板目录一般不是 Python 代码的当前目录)。
模板的上下文¶
Wagtail 将当前页面实例赋值到页面上下文的 page
变量中。模板中可使用此变量来访问页面字段。
例如,使用 {{ page.title }}
获取页面的标题。 模板中可用的变量参考 context processors 。
定制模板上下文¶
所有页面都可以通过重写 get_context
方法将 Python 变量绑定到模板上下文的字典中:
class BlogIndexPage(Page):
...
def get_context(self, request):
context = super().get_context(request)
# Add extra variables and return the updated context
context['blog_entries'] = BlogPage.objects.child_of(self).live()
return context
模板中通过 blog_entries
变量就能访问到子页面的列表了:
{{ page.title }}
{% for entry in blog_entries %}
{{ entry.title }}
{% endfor %}
修改模板名¶
设置页面模型的 template
属性可以使用不同的模板文件:
class BlogPage(Page):
...
template = 'other_template.html'
动态使用不同的模板文件¶
通过重写 get_template
方法,可在页面实例运行中动态选择不同的模板文件,这个方法在每次页面渲染时都会调用:
class BlogPage(Page):
...
use_other_template = models.BooleanField()
def get_template(self, request):
if self.use_other_template:
return 'blog/other_blog_page.html'
return 'blog/blog_page.html'
在这个例子中,如果页面实例的 use_other_template
字段为真,则使用 blog/other_blog_page.html
文件做为页面模板,否则使用 blog/blog_page.html
文件做为页面模板。
Ajax 模板¶
通常 AJAX 调用方式只更新页面一小部分,会与网页请求方式返回不一样的格式。在这种情况下可以定义 ajax_template
属性为通过 AJAX 方式调用使用特定的模板。 (HTTP 请求中有 X-Requested-With: XMLHttpRequest
头变量):
class BlogPage(Page):
...
ajax_template = 'other_template_fragment.html'
template = 'other_template.html'
页面渲染更基础方法¶
在 Wagtail 页面渲染过程中,最基础的方法是 serve()
,这个方法在内部调用了 get_context
和 get_template
方法并完成页面渲染调用。
这个方法与 Django 视图函数类似,采用 Django Request
对象做参数,返回 Django Response
对象。
可以重写这个方法来完全控制页面的渲染过程:
For example, here’s a way to make a page respond with a JSON representation of itself:
from django.http import JsonResponse
class BlogPage(Page):
...
def serve(self, request):
return JsonResponse({
'title': self.title,
'body': self.body,
'date': self.date,
# Resizes the image to 300px width and gets a URL to it
'feed_image': self.feed_image.get_rendition('width-300').url,
})
内联(inline)模型¶
Wagtail 可以在页面中引用其它模型。这对于页面包含多个重复条目时是极其有用的,例如定义一个页面中的多个轮播图片、多个图片或附件、参考连接等。内联模型与页面内容一起纳入版本管理。
每个内联模型需要满足如下要求:
必须有一个
ParentalKey
字段定义引用它的模型
注解
django-modelcluster 和 ParentalKey
内联模型的特点请参考 django-modelcluster , ParentalKey
字段类型增加 import 语句如下:
from modelcluster.fields import ParentalKey
ParentalKey
是 Django ForeignKey
的子类,使用相同的调用参数。
例如,定义一个博客页面的相关链接 (名称与 URL 的列表) 内容:
from django.db import models
from modelcluster.fields import ParentalKey
from wagtail.core.models import Orderable
class BlogPageRelatedLink(Orderable):
page = ParentalKey(BlogPage, on_delete=models.CASCADE, related_name='related_links')
name = models.CharField(max_length=255)
url = models.URLField()
panels = [
FieldPanel('name'),
FieldPanel('url'),
]
将这个模型增加到后台管理编辑页面中,在 panel 类中使用 InlinePanel
方法引用:
content_panels = [
...
InlinePanel('related_links', label="Related links"),
]
方法的第一个参数应是内联模型中 ParentalKey
字段 related_name
属性定义的名称。
使用多页面模型¶
Wagtail 使用 Django’s multi-table inheritance 特征来实现在内容树中使用多个页面模型。
每个页面在创建时都升成一个 Wagtail 内置的 Page
模型以及一个用户自定义的模型 (例如前面创建的 BlogPage
模型)。
在 Python 代码中页面也是由两个类的实例来实现的,一个 Page
及一个自定义的页面模型实例。
在多个页面模型通用处理的场合,一般使用 Wagtail’s Page
模型实例,这种情况下,不能访问特定页面内容中的字段内容。
# Get all pages in the database
>>> from wagtail.core.models import Page
>>> Page.objects.all()
[<Page: Homepage>, <Page: About us>, <Page: Blog>, <Page: A Blog post>, <Page: Another Blog post>]
在使用指定单一页面类型的场合,可以使用 Page
类字段以及指定页面模型类的字段。
# Get all blog entries in the database
>>> BlogPage.objects.all()
[<BlogPage: A Blog post>, <BlogPage: Another Blog post>]
通过 Page
对象实例的 .specific
属性可以获得特定页面模型的实例,获取时会增加额外的数据库查询开销。
>>> page = Page.objects.get(title="A Blog post")
>>> page
<Page: A Blog post>
# Note: the blog post is an instance of Page so we cannot access body, date or feed_image
>>> page.specific
<BlogPage: A Blog post>
其他说明¶
使用友好的模型名¶
Wagtail 使用 Django 内部 Meta
类的 verbose_name
变量定义易读的页面模型名称,例如:
class HomePage(Page):
...
class Meta:
verbose_name = "主页"
这些在后台管理界面等使用页面模型名的地方会显示自定义名。例如在创建子页面时,界面会显示 verbose_name
定义的”主页”供用户选择,而不是原先的 “Home Page”。
Page 查询结果集(QuerySet)排序¶
基于 Page
的模型不能使用标准 Django 模型中内部 Meta
的 ordering 属性来进行排序,因为 Wagtail 是基于内容树来组织的,强制使用了 path 来排序。
class NewsItemPage(Page):
publication_date = models.DateField()
...
class Meta:
ordering = ('-publication_date', ) # will not work
要实现页面的排序,应构造明确的查询语句来实现排序功能:
news_items = NewsItemPage.objects.live().order_by('-publication_date')
定制 Page 查询管理器¶
Page
类可以使用定制的 Manager
。 通常定制的管理从 wagtail.core.models.PageManager
继承:
from django.db import models
from wagtail.core.models import Page, PageManager
class EventPageManager(PageManager):
""" Custom manager for Event pages """
class EventPage(Page):
start_date = models.DateField()
objects = EventPageManager()
另外一个简单的创建方法是只增加一个从 wagtail.core.models.PageQuerySet
继承的``QuerySet`` 类, 然后使用 from_queryset()
引用这个类,这样就创建了一个定制的 Manager
:
from django.db import models
from django.utils import timezone
from wagtail.core.models import Page, PageManager, PageQuerySet
class EventPageQuerySet(PageQuerySet):
def future(self):
today = timezone.localtime(timezone.now()).date()
return self.filter(start_date__gte=today)
EventPageManager = PageManager.from_queryset(EventPageQuerySet)
class EventPage(Page):
start_date = models.DateField()
objects = EventPageManager()