How to make many-to-many relation for multiple page models with one PageChooser field to select from all of them in Wagtail?

How can I make a universal symmetrical relation for all models that inherit from wagtailcore.Page (like in "Approach #1" below, but that will actually work?)

Background:

It is quite easy to make a both side relation between more than two models by creating relation classess to cover all of them. For example:

from django.db import models
from modelcluster.fields import ParentalKey
    
class NewsToCalendarRelation(models.Model):
    news      = ParentalKey('pages.NewsPage', on_delete=models.CASCADE, related_name='news_calendar_source',null=True)
    calendar  = ParentalKey('pages.CalendarPage', on_delete=models.CASCADE, related_name='news_calendar_target',null=True)
    
    class Meta:
        unique_together = ('news', 'calendar')
    
class NewsToStaticRelation(models.Model):
    news      = ParentalKey('pages.NewsPage', on_delete=models.CASCADE, related_name='news_static_source',null=True)
    static    = ParentalKey('pages.StaticPage', on_delete=models.CASCADE, related_name='news_static_target',null=True)
    
    class Meta:
        unique_together = ('news', 'static') 
        
class StaticToCalendarRelation(models.Model):
    static    = ParentalKey('pages.StaticPage', on_delete=models.CASCADE, related_name='static_calendar_source',null=True)
    calendar  = ParentalKey('pages.CalendarPage', on_delete=models.CASCADE, related_name='static_calendar_target',null=True)
    
    class Meta:
        unique_together = ('static', 'calendar')

And then to use in page model:


class NewsPage(Page):

    related_news = ParentalManyToManyField('self', blank=True,symmetrical=True)
    related_calendar = ParentalManyToManyField('pages.CalendarPage', through=NewsToCalendarRelation, blank=True)
    related_static = ParentalManyToManyField('pages.StaticPage', through=NewsToStaticRelation, blank=True)

    content_panels = [
        AutocompletePanel('related_news', target_model='pages.NewsPage'),
        AutocompletePanel('related_calendar', target_model='pages.CalendarPage'),
        AutocompletePanel('related_static', target_model='pages.StaticPage')
    ]

class CalendarPage(Page):

    related_news = ParentalManyToManyField('pages.NewsPage', blank=True, through=NewsToCalendarRelation)
    related_calendar = ParentalManyToManyField('self', symmetrical=True)
    related_static = ParentalManyToManyField('pages.StaticPage', blank=True, through=StaticToCalendarRelation)

    content_panels = [
        AutocompletePanel('related_news', target_model='pages.NewsPage'),
        AutocompletePanel('related_calendar', target_model='pages.CalendarPage'),
        AutocompletePanel('related_static', target_model='pages.StaticPage')
    ]

and so for other models…

Approach #1

But it would be more efficient to have only one related pages field** to choose from all pages. It is no problem to define chooser field for that either PageChooserPanel or AutocompletePanel('related_all', target_model='wagtailcore.Page'). But there is a problem with creating an universal relation between page models with two columns. It is quite intuitive to try something like this:

class PageRelation(models.Model):
    page_from = models.ForeignKey(Page, on_delete=models.CASCADE, related_name='page_relations_from',blank=True,null=True)
    page_to = models.ForeignKey(Page, on_delete=models.CASCADE, related_name='page_relations_to',blank=True,null=True)

    class Meta:
        unique_together = ('page_from', 'page_to')

and then:

related_pages = models.ManyToManyField("wagtailcore.Page", through=PageRelation)

But this won't work because the foreign key must be explicitly set to specific page model where this field is used in and wagtailcore.Page is rejected: pages.PageRelation: (fields.E336) The model is used as an intermediate model by 'pages.NewsPage.related_pages', but it does not have a foreign key to 'NewsPage' or 'Page'.

Approach #2

Another approach I've tried that works but obviously lacks of symmetry is this:

class PageRelation(models.Model):
    news        = ParentalKey('pages.NewsPage', on_delete=models.CASCADE, related_name='news',null=True)
    calendar    = ParentalKey('pages.CalendarPage', on_delete=models.CASCADE, related_name='calendar',null=True)
    static      = ParentalKey('pages.CalendarPage', on_delete=models.CASCADE, related_name='static',null=True)
    target      = ParentalKey('wagtailcore.Page', on_delete=models.CASCADE, related_name='page',null=True)

    class Meta:
        unique_together = ('news', 'calendar','static', 'target')

And can be used as follows:

class NewsPage(Page):
    related_pages = ParentalManyToManyField('wagtailcore.Page', blank=True, through=PageRelation,related_name="news_relation",through_fields=("news", "target"))
    ...

class CalendarPage(Page):
    related_pages = ParentalManyToManyField('wagtailcore.Page', blank=True, through=PageRelation,related_name="news_relation",through_fields=("calendar", "target"))
    ...

The lack of symmetry is because every model has different source column.

Approach #3

Another approach I've found would be to use GenericForeignKey and GenericRelation but GenericRelation is read-only field and cannot be used on model page PageChooserPanel or AutocompletePanel.


To repeat the question, how can I make an universal symmetrical relation for all models that inherit from wagtailcore.Page?

Upvotes: 0

Views: 36

Answers (0)

Related Questions