============= Page 页面模型 ============= Wagtail 每个 Page 页面模型是基于 Django modal 模型实现的。 所有页面模型都要从 :class:`wagtail.core.models.Page` 类继承。 在页面模型里定义字段可以使用 Django 提供的所有字段类型,参考 :doc:`Model field reference ` 提供的字段类型。 在这此类型的基础上,Wagtail 提供了富文本、图片、文档、流等许多扩展字段类型。 .. topic:: Django models(模型) 如果您对 Django models(模型) 不熟悉,首先学习一下相关的知识: * :ref:`创建模型 ` * :doc:`Model 语法 ` Wagtail 模型示例 ============================= 下面的例子展示了一个典型的博客页面模型: .. code-block:: python 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'), ] .. important:: 在定义字段名时,字段名不要和模型名同名。否则会导致处理关系时出错 (`read more `_)。 做为开发规约,在编写程序时最好给页面模型加上 "Page" 后缀。 编写页面模型 =================== 下面将上节定义模型的各段程序逐一进行说明。 数据库字段 --------------- 每个 Wagtail 页面类型是一个 Django model, 对于数据库一个独立的表(公共字段保存在页面公共表中)。 每个页面类型定了一套自有的字段集,例如博客有正文主体和发布日期, 而事件有内容和开始、结束时间。 Wagtail 中字段除了使用 Django 的字段类外,还可以使用许多第三方扩展的字段类。 Wagtail 的核心模块提供了富文本和流的字段类型: - ``RichTextField`` - 富文本 - ``StreamField`` - 流(基于区块组合的内容字段) (参考: :doc:`/topics/streamfield`) 对于标签,Wagtail 全完支持 `django-taggit `_ 插件。 查找 ------ ``search_fields`` 属性定义哪些字段用于做内容查找使用,以及字段的索引方式。这个属性是 ``SearchField`` 以及 ``FilterField`` 对象列表。 ``SearchField`` 增加供全文检索使用的字段,``FilterField`` 增加供过滤使用的字段。一个字段可以使用两种方式加入到查找序列中,但每种方式只能使用一次。 在上面的例子中,使用 ``body`` 做全文搜索查找,而 ``date`` 用来做为过滤条件。哪些类型可以做为参数使用,请查看详细说明::ref:`wagtailsearch_indexing_fields`。 编辑面板 ------------- 在后台管理界面的内容编辑页面中,定义了下面三种 Tab 页: - ``content_panels`` - 内容编辑项目, 例如内容主体字段 - ``promote_panels`` - 推广元数据(metadata), 例如标签、缩略图、页面推广标题 - ``settings_panels`` - 配置项, 例如发布日期 这几个属性的内容是 ``EditHandler`` 对象列表, 定义了界面各个 Tab 页显示项目及组合顺序。 关于 Wagtail 提供的 ``EditHandler`` 类定义,请查看 :doc:`/reference/pages/panels` 详细介绍。简要说来提供下面三种编辑手段: **Basic** 使用下面两个类定义最基础的编辑方式,``FieldPanel`` 根据字段类型及属性选择合适的界面组件进行编辑。 ``StreamField`` 字段用于流布局方式的区块内容编辑。 - :class:`~wagtail.admin.edit_handlers.FieldPanel` - :class:`~wagtail.admin.edit_handlers.StreamFieldPanel` **Structural** 使用如下类来定义界面中一组结构化数据编辑的方式: - :class:`~wagtail.admin.edit_handlers.MultiFieldPanel` - 用于一组相关字段的编辑 - :class:`~wagtail.admin.edit_handlers.InlinePanel` - 用于 inlining 方式的子模型编辑 - :class:`~wagtail.admin.edit_handlers.FieldRowPanel` - 用于一行编辑多个字段 **Chooser** 对于 ``ForeignKey`` 字段,如果链接 Wagtail 的基础类型,Wagtail 基于 ``ChooserPanel`` 类提供如下一些选择弹框来供用用户选择内容项。 在图形/文档(image/document)选择框中还提供了即时上传图片或文档的功能,不需要关闭选择器就可操作。 - :class:`~wagtail.admin.edit_handlers.PageChooserPanel` - :class:`~wagtail.images.edit_handlers.ImageChooserPanel` - :class:`~wagtail.documents.edit_handlers.DocumentChooserPanel` - :class:`~wagtail.snippets.edit_handlers.SnippetChooserPanel` .. note:: 这些选择器只能在 Wagtail 页面字段链接到其它页面、图片、文档或片段时使用。 链接到其它模型类型,应使用 ``FieldPanel``,默认使用下拉框显示选择内容。 页面编辑器定制 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 页面编辑器可以深度定制,详细内容请参考: :doc:`/advanced_topics/customisation/page_editing_interface`。 .. _page_type_business_rules: 父页面 / 子页面类型限制 -------------------------------- 有两个属性可以控制网站显示的页面类型。页面类型控制主要使用在诸如"博客索引页下面只能新建博客页面"之类的需求。 下面两个属性要赋予模型类或模型名的列表。模型名的格式一般为 ``app_label.ModelName``,如果 ``app_label`` 省略,默认在同一个应用(app)中。 - ``parent_page_types`` 限定当前页面类型可以创建在哪些页面类型下面。 - ``subpage_types`` 限定当前页面类型可以创建哪些子页面。 缺省情况下是不加限制。而设置 ``parent_page_types`` 为一个空的列表,表示当前页面不能创建在任何页面下面。 .. _page_urls: 页面 URLs ----------- 获得页面 URL 的常用方式是使用模板的 ``{% pageurl %}`` 标签。因为是从模板中调用的,``pageurl`` 会自动包含如下一些优化内容,更多的说明请参考 :ref:`pageurl_tag`。 页面模型(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``: .. code-block:: python def get_url_parts(self, *args, **kwargs): 这些参数要传给父类的 ``super`` 的 ``get_url_parts`` 方法,例如: .. code-block:: python super().get_url_parts(*args, **kwargs) 也可以直传递 ``request`` 参数,但为了更好的与后来的版本兼容,还是要采用上述的方式。 方法的详细说明参见: :meth:`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``。 方法的详细说明参见: :meth:`wagtail.core.models.Page.get_url`. 在需要包含协议及域名的 URL 时,请使用 ``Page.get_full_url(request)`` 方法。 ``request`` 一般不要省略,以便在一次请求中进行处理结果缓存使用。 方法的详细说明参见: :meth:`wagtail.core.models.Page.get_full_url`. 模板渲染 ================== 每个页面模型可以使用一个默认的页面模板。这是最简单和常用的方式。 创建页面模板 ---------------------------------- Wagtail 默认使用应用标签(app label)及模型名(model class name)来引用小写形式模板文件名,模型名中大写字母转换成下划线加小写字母。 模板格式: ``/.html`` 上例中的博客页面模型 blog.BlogPage 的模板对应于: ``blog/blog_page.html`` 文件。 这个文件根据 Django 的访问路径规则,放到可以访问的模板目录中(注意创建 blog 目录,模板目录一般不是 Python 代码的当前目录)。 模板的上下文 ---------------- Wagtail 将当前页面实例赋值到页面上下文的 ``page`` 变量中。模板中可使用此变量来访问页面字段。 例如,使用 ``{{ page.title }}`` 获取页面的标题。 模板中可用的变量参考 :ref:`context processors ` 。 定制模板上下文 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 所有页面都可以通过重写 ``get_context`` 方法将 Python 变量绑定到模板上下文的字典中: .. code-block:: 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`` 变量就能访问到子页面的列表了: .. code-block:: HTML+Django {{ page.title }} {% for entry in blog_entries %} {{ entry.title }} {% endfor %} 修改模板名 --------------------- 设置页面模型的 ``template`` 属性可以使用不同的模板文件: .. code-block:: python class BlogPage(Page): ... template = 'other_template.html' 动态使用不同的模板文件 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 通过重写 ``get_template`` 方法,可在页面实例运行中动态选择不同的模板文件,这个方法在每次页面渲染时都会调用: .. code-block:: python 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`` 头变量): .. code-block:: python 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: .. code-block:: python 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 可以在页面中引用其它模型。这对于页面包含多个重复条目时是极其有用的,例如定义一个页面中的多个轮播图片、多个图片或附件、参考连接等。内联模型与页面内容一起纳入版本管理。 每个内联模型需要满足如下要求: - 必须从 :class:`wagtail.core.models.Orderable` 继承 - 必须有一个 ``ParentalKey`` 字段定义引用它的模型 .. note:: django-modelcluster 和 ParentalKey 内联模型的特点请参考 `django-modelcluster `_ , ``ParentalKey`` 字段类型增加 import 语句如下: .. code-block:: python from modelcluster.fields import ParentalKey ``ParentalKey`` 是 Django ``ForeignKey`` 的子类,使用相同的调用参数。 例如,定义一个博客页面的相关链接 (名称与 URL 的列表) 内容: .. code-block:: python 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 类中使用 :class:`~wagtail.admin.edit_handlers.InlinePanel` 方法引用: .. code-block:: python content_panels = [ ... InlinePanel('related_links', label="Related links"), ] 方法的第一个参数应是内联模型中 ``ParentalKey`` 字段 ``related_name`` 属性定义的名称。 使用多页面模型 ================== Wagtail 使用 Django's :ref:`multi-table inheritance ` 特征来实现在内容树中使用多个页面模型。 每个页面在创建时都升成一个 Wagtail 内置的 :class:`~wagtail.core.models.Page` 模型以及一个用户自定义的模型 (例如前面创建的 ``BlogPage`` 模型)。 在 Python 代码中页面也是由两个类的实例来实现的,一个 ``Page`` 及一个自定义的页面模型实例。 在多个页面模型通用处理的场合,一般使用 Wagtail's :class:`~wagtail.core.models.Page` 模型实例,这种情况下,不能访问特定页面内容中的字段内容。 .. code-block:: python # Get all pages in the database >>> from wagtail.core.models import Page >>> Page.objects.all() [, , , , ] 在使用指定单一页面类型的场合,可以使用 ``Page`` 类字段以及指定页面模型类的字段。 .. code-block:: python # Get all blog entries in the database >>> BlogPage.objects.all() [, ] 通过 ``Page`` 对象实例的 ``.specific`` 属性可以获得特定页面模型的实例,获取时会增加额外的数据库查询开销。 .. code-block:: python >>> page = Page.objects.get(title="A Blog post") >>> page # Note: the blog post is an instance of Page so we cannot access body, date or feed_image >>> page.specific 其他说明 ======== 使用友好的模型名 -------------------- Wagtail 使用 Django 内部 ``Meta`` 类的 ``verbose_name`` 变量定义易读的页面模型名称,例如: .. code-block:: python class HomePage(Page): ... class Meta: verbose_name = "主页" 这些在后台管理界面等使用页面模型名的地方会显示自定义名。例如在创建子页面时,界面会显示 ``verbose_name`` 定义的"主页"供用户选择,而不是原先的 "Home Page"。 Page 查询结果集(QuerySet)排序 -------------------------------- 基于 ``Page`` 的模型不能使用标准 Django 模型中内部 ``Meta`` 的 ordering 属性来进行排序,因为 Wagtail 是基于内容树来组织的,强制使用了 path 来排序。 .. code-block:: python class NewsItemPage(Page): publication_date = models.DateField() ... class Meta: ordering = ('-publication_date', ) # will not work 要实现页面的排序,应构造明确的查询语句来实现排序功能: .. code-block:: python news_items = NewsItemPage.objects.live().order_by('-publication_date') .. _custom_page_managers: 定制 Page 查询管理器 -------------------- ``Page`` 类可以使用定制的 ``Manager``。 通常定制的管理从 :class:`wagtail.core.models.PageManager` 继承: .. code-block:: python 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() 另外一个简单的创建方法是只增加一个从 :class:`wagtail.core.models.PageQuerySet` 继承的``QuerySet`` 类, 然后使用 :func:`~django.db.models.managers.Manager.from_queryset` 引用这个类,这样就创建了一个定制的 ``Manager`` : .. code-block:: python 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()