Панель трясогузки для самореферентных отношений "многие ко многим" со сквозной моделью

Я занят созданием чего-то вроде графа знаний в wagtail.

CurriculumContentItem является узлом на этом графе. Он имеет отношения многие-ко-многим с самим собой, а сквозная модель имеет важные поля.

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

class ContentItemOrder(models.Model):
    post = models.ForeignKey(
        "CurriculumContentItem", on_delete=models.PROTECT, related_name="pre_ordered_content"
    )
    pre = models.ForeignKey(
        "CurriculumContentItem", on_delete=models.PROTECT, related_name="post_ordered_content"
    )
    hard_requirement = models.BooleanField(default=True)

class CurriculumContentItem(Page):
    body = RichTextField(blank=True)

    prerequisites = models.ManyToManyField(
        "CurriculumContentItem",
        related_name="unlocks",
        through="ContentItemOrder",
        symmetrical=False,
    )

    content_panels = Page.content_panels + [
        # FieldPanel("prerequisites") 
        # FieldPanel just lets me select CurriculumContentItems, but I need to access fields in the through model

        # InlinePanel("prerequisites"), 
        # This causes a recursion error

        FieldPanel('body', classname="full collapsible"),
    ]

Если бы я хотел сделать это в обычной админке Django, я бы использовал инлайн для указания предварительных условий. Что-то вроде:

class ContentItemOrderPostAdmin(admin.TabularInline):
    model = models.ContentItem.prerequisites.through
    fk_name = "post"

class ContentItemOrderPreAdmin(admin.TabularInline):
    model = models.ContentItem.unlocks.through
    fk_name = "pre"

Есть ли подобный механизм в Wagtail?

Похоже, что мне нужно создать пользовательскую панель для этого.

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

class ContentItemOrder(models.Model):
    post = ParentalKey(
        "CurriculumContentItem", related_name="pre_ordered_content"
    )
    pre = models.ForeignKey(
        "CurriculumContentItem", on_delete=models.PROTECT, related_name="post_ordered_content"
    )
    hard_requirement = models.BooleanField(default=True)

    panels = [
        PageChooserPanel('pre'),
        FieldPanel('hard_requirement')
    ]


class CurriculumContentItem(Page):
    body = RichTextField(blank=True)

    content_panels = Page.content_panels + [
        InlinePanel("pre_ordered_content"), 

        FieldPanel('body', classname="full collapsible"),
    ]

Это работает:

  1. Сделайте сквозную модель наследуемой от Orderable
  2. .
  3. Используйте ParentalKey вместо ForeignKey
  4. Используйте InlinePanel, ссылающуюся на связанные имена полей в сквозных моделях
from modelcluster.fields import ParentalKey
from wagtail.core.models import Page, Orderable


class ContentItemOrder(Orderable):   ### 1
    post = ParentalKey(   ### 2
        "CurriculumContentItem", on_delete=models.PROTECT, related_name="pre_ordered_content"
    )
    pre = ParentalKey(   ### 2
        "CurriculumContentItem", on_delete=models.PROTECT, related_name="post_ordered_content"
    )
    hard_requirement = models.BooleanField(default=True)

    panels = [
        PageChooserPanel('pre'),
        PageChooserPanel('post'),
        FieldPanel('hard_requirement'),
    ]


class CurriculumContentItem(Page):
    body = RichTextField(blank=True)

    prerequisites = models.ManyToManyField(
        "CurriculumContentItem",
        related_name="unlocks",
        through="ContentItemOrder",
        symmetrical=False,
    )

    content_panels = Page.content_panels + [
        InlinePanel('pre_ordered_content', label="prerequisites"),  ### 3
        InlinePanel('post_ordered_content', label="unlocks"),       ### 3

        FieldPanel('body', classname="full collapsible"),
    ]

Я беспокоился, что на одну строку будет приходиться 2 поля PageChooser, но трясогузка достаточно умна (и волшебна), чтобы просто нарисовать то, что нам нужно

Вернуться на верх