Home > Mobile >  Wagtail panel for self-referential many to many relationships with a through model
Wagtail panel for self-referential many to many relationships with a through model

Time:05-05

I am busy making something like a knowledge graph in wagtail.

CurriculumContentItem is a node on that graph. It has a many-to-many relationship with itself, and the through model has important fields.

I'm struggling to get this to be usable in the admin page. Please see the inline comments:

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"),
    ]

If I wanted to do this in the normal Django admin I would make use of an inlines to specify prerequisites. Something like:

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

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

Is there a similar mechanism in Wagtail?

It looks like I need to create a custom Panel for this.

CodePudding user response:

I'd suggest constructing an InlinePanel pointing to your 'through' model, which means that you're working with a one-to-many relation rather than many-to-many:

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"),
    ]

CodePudding user response:

This works:

  1. Make the through model inherit from Orderable
  2. Make use of ParentalKey instead of ForeignKey
  3. Use InlinePanel referring to the related names of the fields in the through models
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"),
    ]

I was worried that there would be 2 PageChooser fields available per inline but wagtail is clever (and magical) enough that it just draws the one we need

  • Related