【问题标题】:Implementing one to many between an article and page models in Wagtail在 Wagtail 中实现文章和页面模型之间的一对多
【发布时间】:2019-10-03 15:06:20
【问题描述】:

我正在尝试使用文章到页面结构设置 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)

【问题讨论】:

  • 你能分享一些你的代码吗? Article 也是Page 的子类吗?还是其他类?
  • @NicoGriffioen 我已经添加了我想要的模型布局的代码 sn-p。或许你能从中理解。
  • 我很难理解您在这里想要实现的目标。基本上你所说的是,一个页面链接到一篇文章,一篇文章可以显示多个页面。但是这篇文章在您的 Wagtail 中的作用是什么?你到底想达到什么目的,为什么不让 Article 成为 Wagtail 的默认页面的子类?
  • @NicoGriffioen 本质上,Article 类不包含任何内容。它只有关于文章的元数据,例如标题、sn-p 和缩略图。这是列表视图和 RSS 提要中显示的信息。页面模型是每个页面的内容与一个数字一起存储的地方,因此页面是有序的,还有一个标题。这是一个实现的例子。 techradar.com/uk/reviews/huawei-p30-pro。看看他们是如何拥有一篇有 5 页的基础文章
  • 老实说,听起来您打算重新发明 Wagtail 的许多本机功能集(从管理 UI 到发布工作流程,一直到页面树的整个概念)到你完成的时候,Wagtail 剩下的就很少了。最终,我认为您将不得不在某些架构上做出妥协以支持更接近 Wagtail 模型的东西,或者实施定制的 Django CMS。

标签: python django wagtail


【解决方案1】:

根据我的评论,我认为除了实施完全定制的 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 子模型),您可以使子页面动态化。

【讨论】:

    【解决方案2】:

    我看到 gasman 已经为你的问题提供了答案,但我仍然要写一个答案,原因有两个:

    • 我认为您需要更多关于 为什么gasmans 的建议比您的建议更好的解决方案的指示,但是在评论中写下它的方式太多了。

      李>
    • 我之前实现了一个类似的解决方案,其中有一个类似“文章”的顶级对象,其中包含多个可重新排序的子对象,实际内容位于其中。

    为什么要将 Article 设为 Page 子类

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

    让我们看看 Wagtail 自己的 Page 模型。它提供了哪些开箱即用的功能?

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

    Wagtail 对内容没有任何规定,让您决定要在 Page 子类(如果有)中放置什么样的内容。没有正文的页面示例如下:

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

    在决定是否希望 Model 成为 Page 的子类时,您可以提出以下好问题:

    • 我希望这个对象有自己的 url 吗?
    • 我是否希望能够将此对象放置在我的网站层次结构中的某个位置?
    • 我是否希望对象具有 SEO 优势?
    • 我是否希望能够发布/取消发布此对象?

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

    如何定义页面的实际“正文”取决于您。 您可以将实际内容放置在 sn-ps 或该文章的子页面中。或者你甚至可以选择在你的模型中创建一个 StreamFields 列表。

    如何实现有序子内容。

    我以前实现过这样的结构。 我这样做的方式与 gasman 的建议非常相似。

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

    然后我创建了一个带有排序的直通模型,并像 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 sn-p,它只是某种内容,例如标题和正文。

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

      • 蛞蝓
      • Ordering(因为它是 Orderable 混合的子类)
    • 创建一个做两件事的 ArticlePage:

      • 定义一个 ArticlePageModulePlacement 面板,允许您添加 ArticlePageModulePlacements
      • 子类 RoutablePagemixin,如 gasman 的回答中所述。

    这为您提供了一种防鹡鸰、可重复使用且稳健的方式来创建带有子内容的文章。 模块不会显示在选项卡中,但会显示在页面布局页面上名为“模块”的面板下。

    【讨论】:

    • 感谢您的支持,我们会综合考虑这两种解决方案并决定哪种方案最有效。
    • 嘿 Nico,非常感谢您详细的撰写!我是一个鹡鸰初学者,对理解为什么以及何时应该使用 Page 而不是 Django 模型感到非常沮丧,但还没有找到太多文档。在您的回答中,您说它提供了起草、发布和取消发布的功能。,我不明白为什么这是使用 Page 的好处,我们不能也使用这些功能(即面板或Django 模型中的 StreamField 或 RichTextField)?
    • 您还说**您可以将实际内容放在 sn-ps** 中,我想知道这是 sn-ps 存储数据的有效用例还是合适的用例?因为对我来说,sn-ps 用于存储与网站相关的简单数据。比如我要建一个电商网站,可能会产生很多订单,我觉得订单可以定义为django模型,而不是sn-ps。
    • 顺便说一句,这是我在 SO stackoverflow.com/q/62581737/2235936 上提出的问题
    猜你喜欢
    • 1970-01-01
    • 2019-02-10
    • 2020-10-05
    • 2020-05-03
    • 2013-02-01
    • 2022-08-15
    • 2017-07-21
    • 1970-01-01
    • 2021-10-09
    相关资源
    最近更新 更多