Implementing one to many between an article and page models in Wagtail

天大地大妈咪最大 提交于 2020-02-24 19:10:39

问题


I'm trying to setup a Wagtail site with an article to pages structure but I'm struggling. A review article for example may have an introduction page, a benchmark page and a conclusion page. I want to work out how to allow this relationship in wagtail and have it so that editors can add multiple pages to the same article on the same page. I can imagine the pages interface looking a bit like how you have content, promote and settings on pages but with the ability to add, rename and reorder pages. I've tried using a foreign key on a page model that links to an article but I can't get it to be shown in the admin the way I want.

Here is the django version of model layout I was looking to use. You have a parent article that is then made up of one or multiple pages. The pages should be editable, orderable and be created from within one panel in the admin with 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)

回答1:


As per my comment I don't think you'll be able to achieve everything you're looking for short of implementing an entirely bespoke CMS - but if you're able to bend the UI and data modelling requirements, then Wagtail's RoutablePageMixin is one possible way of achieving the general pattern of editing an article as a single unit, while presenting it as multiple pages on the front-end.

In this approach, you'd make Article a Wagtail Page model, with all of the sub-page content defined as fields (or InlinePanel child models) on that model. (If you want to split the content entry into tabs within the editing interface, see Customising the tabbed interface, although this won't support dynamically adding / reordering them.) You'd then define a URL route and template for each subpage of the article:

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,
        })

In this example the three sub-pages are hard-coded, but with some more work (perhaps an InlinePanel child model with a slug field and a StreamField) you could make the subpages dynamic.




回答2:


I saw gasman already provided an answer to you question, but I'm still going to write up an answer for two reasons:

  • I think you need some more pointers as to why gasmans' proposal is a better solution than yours, but it's way to much to write in a comment.

  • I have implemented a similar solution before, where there is a top level 'Article'-like object with multiple reorderable child objects, where the actual content resides.

Why you should make Article a Page subclass

You chose not to make Article a subclass of Page, and you said it was because the Article itself does not contain any content, only metadata about an article. That is not a very strange thought process, but I think you're looking at the wrong requirements for your Article model.

Let's look at Wagtail's own Page model. What kind of functionality does it provide out of the box?

  • It provides a tree structure with parent and child pages, so that your page can be placed somewhere in the hierarchy of your website
  • It provides a slug_field, so that Wagtail can automatically handle linking to your page.
  • It provides functionality for drafting, publishing and unpublishing.

Wagtail doesn't dictate anything about content, leaving you to decide what kind of content you want to put on your Page subclass, if any. Examples of Pages that do not have a body would be:

  • Contact forms.
  • Blog index pages.

Good questions you could ask when deciding whether you want a Model to be a subclass of a Page are:

  • Do I want this object to have it's own url?
  • Do I want to be able to place this object somewhere inside my website hierarchy?
  • Do I want to have SEO advantages for the object?
  • Do I want to be able to publish/unpublish this object or not?

In your case of the Article, you could say yes to almost al these question, so it'd be wise to make it a Page subclass. That way, you don't have to reinvent the wheel.

How you define the actual 'body' of your page is up to you. You can place the actual content in either snippets, or subpages to that Article. Or you can even choose to create a list of StreamFields inside your model.

How to implement ordered subcontent.

I have implemented a structure like this before. The way I did this was very similar to what gasman proposes.

In my case, I needed to create a website where you could find an object (like you article) and display different types of explanation modules for it. For each document, I created a ArticlePage, and for each explanation module, I created a snippet called ExplanationModule.

I then created a through model with an ordering, and added a RoutablePageMixin to the class like gasman explains.

The structure looks something like this:

@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

What this does is the following:

  • Create a ArticlePageModule snippet, which is just some kind of content, like a title and a body.

  • Create a ArticlePageModulePlacement which links a ArticlePage to a module, and adds the following:

    • A slug
    • An Ordering (Because it subclasses the Orderable mixing)
  • Create a ArticlePage which does two things:

    • Define a ArticlePageModuleplacement panel, which allows you to add ArticlePageModulePlacements
    • Subclass RoutablePagemixin, as described in gasman's answer.

This provides you with a Wagtail-proof, reusable and robust way of creating Articles with SubContent. The modules don't show up in tabs, but will be shown on the page's layout page under a panel called 'Modules'.



来源:https://stackoverflow.com/questions/58221962/implementing-one-to-many-between-an-article-and-page-models-in-wagtail

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!