Реализация одного ко многим между статьей и моделями страниц в Wagtail - PullRequest
2 голосов
/ 03 октября 2019

Я пытаюсь настроить сайт трясогузки со структурой статьи на страницы, но я изо всех сил. Например, обзорная статья может содержать страницу введения, страницу оценки и страницу заключения. Я хочу выяснить, как разрешить эту связь в трясогузке, и чтобы она могла добавлять несколько страниц в одну и ту же статью на одной странице. Я могу представить, что интерфейс страниц выглядит примерно так, как у вас есть контент, реклама и настройки на страницах, но с возможностью добавления, переименования и изменения порядка страниц. Я пытался использовать внешний ключ на модели страницы, которая ссылается на статью, но не могу отобразить его в админке так, как я хочу.

Вот версия 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 ]

2 голосов
/ 03 октября 2019

В соответствии с моим комментарием, я не думаю, что вы сможете достичь всего, что ищете, за исключением внедрения полностью сделанной на заказ CMS - но если вы в состоянии изменить требования к пользовательскому интерфейсу и моделированию данных, тогда WagtailRoutablePageMixin - это один из возможных способов достижения общей схемы редактирования статьи как единого блока, при этом она представляется в виде нескольких страниц на переднем конце.

В этом подходе высделал бы Article моделью страницы трясогузки со всем содержимым подстраницы, определенной как поля (или дочерние модели 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,
        })

В этом примере три подстраницы жестко запрограммированы, но с некоторой дополнительной работой (возможно, дочерняя модель InlinePanel с полем slug и StreamField) выможет сделать подстраницы динамическими.

0 голосов
/ 04 октября 2019

Я видел, как газовщик уже дал ответ на ваш вопрос, но я все еще собираюсь написать ответ по двум причинам:

  • Я думаю, вам нужны еще несколько указателей, чтобы почему предложение gasmans является лучшим решением, чем ваше, но это очень много, чтобы написать в комментарии.

  • Я реализовывал подобное решение раньше, где тамявляется объектом верхнего уровня, похожим на Article, с несколькими переупорядочиваемыми дочерними объектами, в которых находится фактическое содержимое.

Почему вы должны сделать Article подклассом страницы

Вы решили не делать Article подклассом Page, и вы сказали, что это потому, что сам Article не содержит никакого контента, только метаданные о статье. Это не очень странный мыслительный процесс, но я думаю, что вы смотрите на неправильные требования для вашей Article модели.

Давайте посмотрим на собственную модель Page трясогузки. Какие функции он предоставляет «из коробки»?

  • Он предоставляет древовидную структуру с родительскими и дочерними страницами, так что ваша страница может быть размещена где-то в иерархии вашего сайта
  • Предоставляет slug_field, так что Wagtail может автоматически обрабатывать ссылки на вашу страницу.
  • Предоставляет функции для черчения, публикации и отмены публикации.

Wagtail не диктуетчто-нибудь о контенте, оставляя вам решать, какой контент вы хотите поместить в свой подкласс Page, если таковой имеется. Примеры страниц, у которых нет тела:

  • Контактные формы.
  • Страницы указателей блогов.

Хорошие вопросы, которые вы можете задать при принятии решенияхотите ли вы, чтобы Model был подклассом Page:

  • Хочу ли я, чтобы у этого объекта был свой собственный URL?
  • Хочу ли я иметь возможностьразместить этот объект где-то внутри иерархии моего сайта?
  • Хочу ли я иметь преимущества SEO для этого объекта?
  • Хочу ли я иметь возможность публиковать / отменять публикацию этого объекта или нет?

В вашем случае Article вы могли бы ответить «да» почти на все эти вопросы, поэтому было бы разумно сделать его подклассом Page. Таким образом, вам не нужно заново изобретать колесо.

Как вы определяете фактическое «тело» вашей страницы, зависит от вас. Вы можете поместить фактический контент либо в фрагменты, либо в подстраницы к этой статье. Или вы можете даже создать список StreamFields внутри вашей модели.

Как реализовать упорядоченный субконтент.

Ранее я реализовал такую ​​структуру. То, как я это сделал, было очень похоже на то, что предлагает газовик.

В моем случае мне нужно было создать веб-сайт, на котором можно было бы найти объект (например, статью) и отобразить различные типы модулей объяснения для него. Для каждого документа я создал ArticlePage, а для каждого модуля объяснения я создал фрагмент с именем ExplanationModule.

Затем я создал сквозную модель с упорядочением и добавил 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, которыйэто просто некоторый контент, такой как заголовок и тело.

  • Создайте ArticlePageModulePlacement, который связывает ArticlePage с модулем, и добавляет следующее:

    • Slug
    • Порядок (поскольку он подразделяет подстраиваемое микширование)
  • Создайте ArticlePage, которая выполняет две вещи:

    • Определите панель ArticlePageModuleplacement, которая позволяет добавлять ArticlePageModulePlacements
    • Подкласс RoutablePagemixin, как описано в ответе gasman.

Это дает вам Wagнадежный, многоразовый и надежный способ создания статей с помощью SubContent. Модули не отображаются на вкладках, но будут отображаться на странице макета страницы под панелью «Модули».

...