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

时间:2019-10-03 15:06:20

标签: python django wagtail

我正在尝试设置一个Wagtail网站,其中包含文章到页面的结构,但是我很挣扎。例如,评论文章可能具有简介页面,基准页面和结论页面。我想弄清楚如何在relationship中允许这种关系,并让编辑器可以将多个页面添加到同一页面上的同一文章中。我可以想象页面界面看起来有点像您在页面上的内容,促销和设置方式,但是能够添加,重命名和重新排序页面。我尝试在链接到文章的页面模型上使用外键,但是无法以所需的方式在管理员中显示它。

这是我要使用的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)

2 个答案:

答案 0 :(得分:2)

根据我的评论,我认为您无法实现完全定制的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,
        })

在此示例中,三个子页面是硬编码的,但是通过做更多的工作(例如具有子弹字段和StreamField的InlinePanel子模型),可以使子页面动态化。

答案 1 :(得分:0)

我看到加斯曼已经为您提供了问题的答案,但是由于两个原因,我仍然要写一个答案:

  • 我认为您需要更多有关为什么 gasmans提案的建议,比您的提案更好,但这是在评论中写很多东西的方式。

  • 我以前已经实现了类似的解决方案,其中存在一个顶级的类似于“ Article”的对象,该对象具有多个可重排序的子对象,实际内容位于其中。

为什么要将Article设为Page子类

您选择不让Article成为Page的子类,您说这是因为Article本身不包含任何内容,仅包含有关文章的元数据。那不是一个很奇怪的思考过程,但是我认为您正在为Article模型寻找错误的要求。

让我们看看Wagtail自己的Page模型。它提供了什么样的功能?

  • 它提供了带有父页面和子页面的树形结构,因此您的页面可以放置在网站层次结构中的某处
  • 它提供了slug_field,以便Wagtail可以自动处理到您页面的链接。
  • 它提供了用于起草,发布和取消发布的功能。

Wagtail对内容没有任何要求,让您决定要在Page子类上放置哪种内容(如果有)。没有正文的页面示例如下:

  • 联系表格。
  • 博客索引页面。

在决定是否要让Model成为Page的子类时可能要问的好问题是:

  • 我是否希望该对象拥有自己的网址?
  • 我是否希望能够将此对象放置在网站层次结构内的某个位置?
  • 我想为该对象提供SEO优势吗?
  • 我是否希望能够发布/取消发布该对象?

对于Article,您几乎可以对所有这些问题说“是”,因此将其设为Page子类是明智的。这样,您就不必重新发明轮子了。

如何定义页面的实际“主体”取决于您。 您可以将实际内容放置在该文章的摘要或子页面中。或者,您甚至可以选择在模型内创建StreamField列表。

如何实现有序子内容。

我之前已经实现了这样的结构。 我这样做的方式与加斯曼的提议非常相似。

就我而言,我需要创建一个网站,在这里您可以找到一个对象(如您的文章)并显示不同类型的解释模块。对于每个文档,我创建了一个ArticlePage,对于每个解释模块,我创建了一个名为ExplanationModule的代码段。

然后,我通过排序创建了一个贯通模型,并像Gasman所解释的那样,向类中添加了RoutablePageMixin。

结构看起来像这样:

@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片段,它只是某种内容,例如标题和正文。

  • 创建将ArticlePage链接到模块的ArticlePageModulePlacement,并添加以下内容:

    • 一个ug
    • 订购(因为它是订购混合的子类)
  • 创建一个做两件事的ArticlePage:

    • 定义“ ArticlePageModulePlacements”面板,该面板允许您添加ArticlePageModulePlacements
    • RoustablePagemixin的子类,如加斯曼的答案所述。

这为您提供了一种防Wa尾,可重用且可靠的方式来创建带有SubContent的文章。 这些模块不会显示在标签中,而是会显示在页面的布局页面上名为“模块”的面板下。