在 Wagtail 中实现文章和页面模型之间的一对多
Implementing one to many between an article and page models in Wagtail
我正在尝试设置一个包含文章到页面结构的 Wagtail 网站,但我遇到了困难。例如,评论文章可能有介绍页面、基准页面和结论页面。我想弄清楚如何在 wagtail 中允许这种关系并拥有它,以便编辑可以将多个页面添加到同一页面上的同一篇文章。我可以想象页面界面看起来有点像您在页面上拥有内容、推广和设置的方式,但具有添加、重命名和重新排序页面的能力。我试过在链接到一篇文章的页面模型上使用外键,但我无法按照我想要的方式在管理员中显示它。
这是我想要使用的模型布局的 django 版本。您有一篇由一页或多页组成的父文章。这些页面应该是可编辑的、可订购的,并且可以在管理的一个面板中使用流域创建:
Class Article(models.Model)
STATE_DRAFT = 0
STATE_REVIEW= 1
STATE_PUBLICATION = 2
STATE_HIDDEN = 3
STATE = (
(STATE_DRAFT, 'draft'),
(STATE_REVIEW, 'pending review'),
(STATE_PUBLICATION, 'ready for publication'),
(STATE_HIDDEN, 'hide and ignore'),
)
title = models.CharField(_('title'), max_length=256)
slug = models.SlugField(
_('slug'), unique=True, blank=True, default='', max_length=256
)
description = models.TextField(
_('description'), max_length=256, blank=True, default=''
)
author = models.ForeignKey(
User, on_delete=models.CASCADE, related_name='article'
)
publication = models.DateTimeField(
null=True, blank=True, default=None, db_index=True, help_text='''
What date and time should the article get published
'''
)
state = models.PositiveIntegerField(
default=0, choices=STATE, help_text='What stage is the article at?'
)
featured = models.BooleanField(
default=False,
help_text='Whether or not the article should get featured'
)
class Page(Page):
article = models.ForeignKey(
'Article', on_delete=models.CASCADE, related_name='pages'
)
title = models.CharField(max_length=256)
number = models.PositiveIntegerField(default=1) # So pages are ordered
body = models.TextField(blank=True)
根据我的评论,我认为除了实施完全定制的 CMS 之外,您无法实现您正在寻找的一切 - 但如果您能够弯曲 UI 和数据建模要求,那么 Wagtail 的 RoutablePageMixin
是实现将一篇文章作为单个单元编辑的一般模式的一种可能方式,同时在前端将其呈现为多个页面。
在这种方法中,您将创建 Article
一个 Wagtail 页面模型,所有子页面内容都定义为该模型上的字段(或 InlinePanel 子模型)。 (如果您想在编辑界面中将内容条目拆分为选项卡,请参阅 Customising the tabbed interface,尽管这不支持动态添加/重新排序它们。)然后您将定义一个 URL 路由和模板对于文章的每个子页面:
from wagtail.core.models import Page
from wagtail.contrib.routable_page.models import RoutablePageMixin, route
class ArticlePage(RoutablePageMixin, Page):
intro = StreamField(...)
main_page = StreamField(...)
conclusion = StreamField(...)
@route(r'^$')
def intro_view(self, request):
render(request, 'article/intro.html', {
'page': self,
})
@route(r'^main/$')
def main_page_view(self, request):
render(request, 'article/main_page.html', {
'page': self,
})
@route(r'^conclusion/$')
def conclusion_view(self, request):
render(request, 'article/conclusion.html', {
'page': self,
})
在此示例中,三个子页面是硬编码的,但通过更多的工作(可能是一个带有 slug 字段和 StreamField 的 InlinePanel 子模型),您可以使子页面动态化。
我看到 gasman 已经回答了你的问题,但我还是要写一个答案有两个原因:
我认为您需要更多关于 为什么 gasmans 的建议比您的建议更好的解决方案,但是在评论中写太多了.
我以前实现过类似的解决方案,其中有一个顶层 'Article'-like object 具有多个可重新排序的 child objects ,实际内容所在的位置。
为什么要创建 Article
子页面class
您选择不将 Article
设为 Page
的子 class,您说这是因为 Article
本身不包含任何内容,仅包含元数据关于一篇文章。这不是一个非常奇怪的思维过程,但我认为您正在查看 Article
模型的错误要求。
让我们看看 Wagtail 自己的 Page
模型。它开箱即用地提供了什么样的功能?
- 它提供了一个包含 parent 和 child 页面的树结构,因此您的页面可以放置在您网站层次结构中的某个位置
- 它提供了一个
slug_field
,这样 Wagtail 就可以自动处理链接到您的页面。
- 它提供起草、发布和取消发布的功能。
Wagtail 不会规定任何内容,让您决定要在 Page
subclass 上放置什么样的内容(如果有的话)。没有 body 的页面示例为:
- 联系表格。
- 博客索引页。
在决定是否要 Model
成为 Page
的子 class 时,您可以问的好问题是:
- 我想让这个 object 拥有自己的 url 吗?
- 我是否希望能够将此 object 放置在我的网站层次结构中的某个位置?
- 我想要 object 的 SEO 优势吗?
- 我想要publish/unpublish这个object还是不想要?
对于 Article
,您几乎可以回答所有这些问题,因此明智的做法是将其设为 Page
子 class。这样,您就不必重新发明轮子了。
如何定义页面的实际 'body' 由您决定。
您可以将实际内容放在该文章的片段或子页面中。或者您甚至可以选择在您的模型中创建一个 StreamFields 列表。
如何实现有序子内容。
我以前实现过这样的结构。
我这样做的方式与加斯曼提出的非常相似。
就我而言,我需要创建一个网站,您可以在其中找到 object(如您的文章)并为其显示不同类型的解释模块。对于每个文档,我创建了一个 ArticlePage
,对于每个解释模块,我创建了一个名为 ExplanationModule
.
的片段
然后我创建了一个带排序的直通模型,并向 class 添加了一个 RoutablePageMixin,就像 gasman 解释的那样。
结构看起来像这样:
@register_snippet
class ArticlePageModule(models.Model):
...
title = models.CharField(max_length=100)
body = StreamField(LAYOUT_STREAMBLOCKS, null=True, blank=True)
panels = [
FieldPanel('title'),
StreamFieldPanel('body'),
]
class ArticlePageModulePlacement(Orderable, models.Model):
page = ParentalKey('articles.ArticlePage', on_delete=models.CASCADE, related_name='article_module_placements')
article_module = models.ForeignKey(ArticlePageModule, on_delete=models.CASCADE, related_name='+')
slug = models.SlugField()
panels = [
FieldPanel('slug'),
SnippetChooserPanel('article_module'),
]
class ArticlePage(Page, RoutablePageMixin):
# Metadata and other member values
....
content_panels = [
...
InlinePanel('article_module_placements', label="Modules"),
]
@route(r'^module/(?P<slug>[\w\-]+)/$')
def page_with_module(self, request, slug=None):
self.article_module_slug = slug
return self.serve(request)
def get_context(self, request):
context = super().get_context(request)
if hasattr(self, 'article_module_slug'):
context['ArticlePageModule'] = self.article_module_placements.filter(slug = self.article_module).first().article_module
return context
它的作用如下:
创建一个 ArticlePageModule 片段,它只是某种内容,例如标题和 body。
创建一个将 ArticlePage 链接到模块的 ArticlePageModulePlacement,并添加以下内容:
- 鼻涕虫
- 一个 Ordering(因为它是 Orderable 混合的子class)
创建一个做两件事的 ArticlePage:
- 定义一个 ArticlePageModuleplacement 面板,允许您添加 ArticlePageModulePlacements
- Subclass RoutablePagemixin,如 gasman 的回答所述。
这为您提供了一种 Wagtail-proof、可重复使用且可靠的方法来创建包含子内容的文章。
这些模块不会显示在选项卡中,但会显示在名为 'Modules'.
面板下的页面布局页面上
我正在尝试设置一个包含文章到页面结构的 Wagtail 网站,但我遇到了困难。例如,评论文章可能有介绍页面、基准页面和结论页面。我想弄清楚如何在 wagtail 中允许这种关系并拥有它,以便编辑可以将多个页面添加到同一页面上的同一篇文章。我可以想象页面界面看起来有点像您在页面上拥有内容、推广和设置的方式,但具有添加、重命名和重新排序页面的能力。我试过在链接到一篇文章的页面模型上使用外键,但我无法按照我想要的方式在管理员中显示它。
这是我想要使用的模型布局的 django 版本。您有一篇由一页或多页组成的父文章。这些页面应该是可编辑的、可订购的,并且可以在管理的一个面板中使用流域创建:
Class Article(models.Model)
STATE_DRAFT = 0
STATE_REVIEW= 1
STATE_PUBLICATION = 2
STATE_HIDDEN = 3
STATE = (
(STATE_DRAFT, 'draft'),
(STATE_REVIEW, 'pending review'),
(STATE_PUBLICATION, 'ready for publication'),
(STATE_HIDDEN, 'hide and ignore'),
)
title = models.CharField(_('title'), max_length=256)
slug = models.SlugField(
_('slug'), unique=True, blank=True, default='', max_length=256
)
description = models.TextField(
_('description'), max_length=256, blank=True, default=''
)
author = models.ForeignKey(
User, on_delete=models.CASCADE, related_name='article'
)
publication = models.DateTimeField(
null=True, blank=True, default=None, db_index=True, help_text='''
What date and time should the article get published
'''
)
state = models.PositiveIntegerField(
default=0, choices=STATE, help_text='What stage is the article at?'
)
featured = models.BooleanField(
default=False,
help_text='Whether or not the article should get featured'
)
class Page(Page):
article = models.ForeignKey(
'Article', on_delete=models.CASCADE, related_name='pages'
)
title = models.CharField(max_length=256)
number = models.PositiveIntegerField(default=1) # So pages are ordered
body = models.TextField(blank=True)
根据我的评论,我认为除了实施完全定制的 CMS 之外,您无法实现您正在寻找的一切 - 但如果您能够弯曲 UI 和数据建模要求,那么 Wagtail 的 RoutablePageMixin
是实现将一篇文章作为单个单元编辑的一般模式的一种可能方式,同时在前端将其呈现为多个页面。
在这种方法中,您将创建 Article
一个 Wagtail 页面模型,所有子页面内容都定义为该模型上的字段(或 InlinePanel 子模型)。 (如果您想在编辑界面中将内容条目拆分为选项卡,请参阅 Customising the tabbed interface,尽管这不支持动态添加/重新排序它们。)然后您将定义一个 URL 路由和模板对于文章的每个子页面:
from wagtail.core.models import Page
from wagtail.contrib.routable_page.models import RoutablePageMixin, route
class ArticlePage(RoutablePageMixin, Page):
intro = StreamField(...)
main_page = StreamField(...)
conclusion = StreamField(...)
@route(r'^$')
def intro_view(self, request):
render(request, 'article/intro.html', {
'page': self,
})
@route(r'^main/$')
def main_page_view(self, request):
render(request, 'article/main_page.html', {
'page': self,
})
@route(r'^conclusion/$')
def conclusion_view(self, request):
render(request, 'article/conclusion.html', {
'page': self,
})
在此示例中,三个子页面是硬编码的,但通过更多的工作(可能是一个带有 slug 字段和 StreamField 的 InlinePanel 子模型),您可以使子页面动态化。
我看到 gasman 已经回答了你的问题,但我还是要写一个答案有两个原因:
我认为您需要更多关于 为什么 gasmans 的建议比您的建议更好的解决方案,但是在评论中写太多了.
我以前实现过类似的解决方案,其中有一个顶层 'Article'-like object 具有多个可重新排序的 child objects ,实际内容所在的位置。
为什么要创建 Article
子页面class
您选择不将 Article
设为 Page
的子 class,您说这是因为 Article
本身不包含任何内容,仅包含元数据关于一篇文章。这不是一个非常奇怪的思维过程,但我认为您正在查看 Article
模型的错误要求。
让我们看看 Wagtail 自己的 Page
模型。它开箱即用地提供了什么样的功能?
- 它提供了一个包含 parent 和 child 页面的树结构,因此您的页面可以放置在您网站层次结构中的某个位置
- 它提供了一个
slug_field
,这样 Wagtail 就可以自动处理链接到您的页面。 - 它提供起草、发布和取消发布的功能。
Wagtail 不会规定任何内容,让您决定要在 Page
subclass 上放置什么样的内容(如果有的话)。没有 body 的页面示例为:
- 联系表格。
- 博客索引页。
在决定是否要 Model
成为 Page
的子 class 时,您可以问的好问题是:
- 我想让这个 object 拥有自己的 url 吗?
- 我是否希望能够将此 object 放置在我的网站层次结构中的某个位置?
- 我想要 object 的 SEO 优势吗?
- 我想要publish/unpublish这个object还是不想要?
对于 Article
,您几乎可以回答所有这些问题,因此明智的做法是将其设为 Page
子 class。这样,您就不必重新发明轮子了。
如何定义页面的实际 'body' 由您决定。 您可以将实际内容放在该文章的片段或子页面中。或者您甚至可以选择在您的模型中创建一个 StreamFields 列表。
如何实现有序子内容。
我以前实现过这样的结构。 我这样做的方式与加斯曼提出的非常相似。
就我而言,我需要创建一个网站,您可以在其中找到 object(如您的文章)并为其显示不同类型的解释模块。对于每个文档,我创建了一个 ArticlePage
,对于每个解释模块,我创建了一个名为 ExplanationModule
.
然后我创建了一个带排序的直通模型,并向 class 添加了一个 RoutablePageMixin,就像 gasman 解释的那样。
结构看起来像这样:
@register_snippet
class ArticlePageModule(models.Model):
...
title = models.CharField(max_length=100)
body = StreamField(LAYOUT_STREAMBLOCKS, null=True, blank=True)
panels = [
FieldPanel('title'),
StreamFieldPanel('body'),
]
class ArticlePageModulePlacement(Orderable, models.Model):
page = ParentalKey('articles.ArticlePage', on_delete=models.CASCADE, related_name='article_module_placements')
article_module = models.ForeignKey(ArticlePageModule, on_delete=models.CASCADE, related_name='+')
slug = models.SlugField()
panels = [
FieldPanel('slug'),
SnippetChooserPanel('article_module'),
]
class ArticlePage(Page, RoutablePageMixin):
# Metadata and other member values
....
content_panels = [
...
InlinePanel('article_module_placements', label="Modules"),
]
@route(r'^module/(?P<slug>[\w\-]+)/$')
def page_with_module(self, request, slug=None):
self.article_module_slug = slug
return self.serve(request)
def get_context(self, request):
context = super().get_context(request)
if hasattr(self, 'article_module_slug'):
context['ArticlePageModule'] = self.article_module_placements.filter(slug = self.article_module).first().article_module
return context
它的作用如下:
创建一个 ArticlePageModule 片段,它只是某种内容,例如标题和 body。
创建一个将 ArticlePage 链接到模块的 ArticlePageModulePlacement,并添加以下内容:
- 鼻涕虫
- 一个 Ordering(因为它是 Orderable 混合的子class)
创建一个做两件事的 ArticlePage:
- 定义一个 ArticlePageModuleplacement 面板,允许您添加 ArticlePageModulePlacements
- Subclass RoutablePagemixin,如 gasman 的回答所述。
这为您提供了一种 Wagtail-proof、可重复使用且可靠的方法来创建包含子内容的文章。 这些模块不会显示在选项卡中,但会显示在名为 'Modules'.
面板下的页面布局页面上