1. Introduction: 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…
2. 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'.
3. Another approach I've tried 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.