Python 在Wagtail中实现文章和页面模型之间的一对多

Python 在Wagtail中实现文章和页面模型之间的一对多,python,django,wagtail,Python,Django,Wagtail,我正试图建立一个带有文章到页面结构的摇摆网站,但我很挣扎。例如,一篇评论文章可能有一个介绍页、一个基准页和一个结论页。我想弄清楚如何在wagtail中允许这种关系,并使其具有这种关系,以便编辑可以将多个页面添加到同一页面上的同一篇文章中。我可以想象页面界面看起来有点像页面上的内容、升级和设置,但是能够添加、重命名和重新排序页面。我曾尝试在链接到文章的页面模型上使用外键,但我无法让它以我想要的方式显示在管理员中 这是我希望使用的django版本的模型布局。您有一篇由一页或多页组成的父文章。页面应该

我正试图建立一个带有文章到页面结构的摇摆网站,但我很挣扎。例如,一篇评论文章可能有一个介绍页、一个基准页和一个结论页。我想弄清楚如何在wagtail中允许这种关系,并使其具有这种关系,以便编辑可以将多个页面添加到同一页面上的同一篇文章中。我可以想象页面界面看起来有点像页面上的内容、升级和设置,但是能够添加、重命名和重新排序页面。我曾尝试在链接到文章的页面模型上使用外键,但我无法让它以我想要的方式显示在管理员中

这是我希望使用的django版本的模型布局。您有一篇由一页或多页组成的父文章。页面应该是可编辑的、可排序的,并且可以从管理中的一个面板中创建,其中包含streamfields:

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之外,你不可能实现你想要的一切——但是如果你能够改变用户界面和数据建模的要求,那么Wagtail是一种可能的方式,可以实现将一篇文章作为一个单元进行编辑的一般模式,同时在前端将其显示为多个页面

在这种方法中,您将使
文章
成为一个摇摆页面模型,所有子页面内容都定义为该模型上的字段(或InlinePanel子模型)。(如果要在编辑界面中将内容条目拆分为选项卡,请参阅,尽管这不支持动态添加/重新排序它们。)然后,您需要为文章的每个子页面定义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已经为您的问题提供了答案,但我仍然要写一个答案,原因有两个:

  • 我想你需要更多的建议来说明为什么盖斯曼的建议比你的更好,但在评论中写下这是一个很好的方法

  • 我以前也实现过类似的解决方案,其中有一个顶级的“Article”类对象,其中包含多个可重新排序的子对象,实际内容就驻留在这个子对象中

为什么要将
文章
设置为页面子类 您选择不将
文章
作为
页面
的子类,您说这是因为
文章
本身不包含任何内容,只包含关于文章的元数据。这不是一个非常奇怪的思考过程,但我认为您的
文章
模型的需求是错误的

让我们看看Wagtail自己的
页面
模型。它提供了什么样的现成功能

  • 它提供了一个包含父页面和子页面的树状结构,因此您的页面可以放置在网站层次结构中的某个位置
  • 它提供了一个
    slug\u字段
    ,这样Wagtail就可以自动处理与页面的链接
  • 它提供了绘图、发布和取消发布功能
Wagtail不规定任何内容,让您决定要在
页面
子类(如果有的话)上放置什么类型的内容。没有正文的页面示例如下:

  • 联系方式
  • 博客索引页面
在决定是否将
模型
作为
页面
的子类时,您可以提出以下好问题:

  • 我想让这个对象有自己的url吗
  • 我是否希望能够将此对象放置在我的网站层次结构中的某个位置
  • 我想有SEO优势的对象吗
  • 我是否希望能够发布/取消发布此对象
在您的
文章
中,您可以对几乎所有这些问题都说是的,因此明智的做法是将其设置为
页面
子类。这样,你就不必重新发明轮子了

如何定义页面的实际“正文”取决于您。 您可以将实际内容放在该文章的片段或子页面中。或者,您甚至可以选择在模型中创建流字段列表

如何实现有序分包。 我以前实现过这样的结构。 我这样做的方式与加斯曼的提议非常相似

在我的例子中,我需要创建一个网站,在那里你可以找到一个对象(比如你的文章),并为它显示不同类型的解释模块。对于每个文档,我创建了一个
ArticlePage
,对于每个解释模块,我创建了一个名为
ExplanationModule
的代码段

然后我创建了一个带有排序的直通模型,并向类中添加了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
@register\u代码段
类ArticlePageModule(models.Model):
...
title=models.CharField(最大长度=100)
body=StreamField(布局\流块,null=True,blank=True)
面板=[
现场面板(“标题”),
StreamFieldPanel(“主体”),
]
类ArticlePageModulePlacement(可订购,模型.Model):
page=ParentalKey('articles.ArticlePage',在\u delete=models.CASCADE上,相关的\u name='article\u module\u placements')
article_module=models.ForeignKey(ArticlePageModule,on_delete=models.CASCADE,related_name='+'))
slug=models.SlugField()
面板=[
现场面板(“slug”),
SnippetChooserPanel('article_module'),
]
类ArticlePage(第页,RoutablePageMixin):
#元数据和其他成员值
....
内容面板=[
...
InlinePanel(“物品模块放置”,label=“模块”),
]
@路由(r'^module/(?P[\w\-]+)/$)
def page_和_模块(自、请求、slug=None):
self.article\u模块\u slug=s