Как в Wagtail создать отношение "многие-ко-многим" для нескольких моделей страниц с одним полем PageChooser для выбора из всех?
1. Введение: Довольно легко создать двухстороннее отношение между более чем двумя моделями, создав класс отношений, охватывающий их все. Например:
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')
А затем использовать в модели страницы:
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')
]
и так для других моделей...
2. Но было бы эффективнее иметь только одно поле related pages для выбора из всех страниц. Нет проблем определить для этого поле выбора PageChooserPanel
или AutocompletePanel('related_all', target_model='wagtailcore.Page')
. Но есть проблема с созданием универсального отношения между моделями страниц с двумя колонками. Интуитивно понятно, что можно попробовать что-то вроде этого:
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')
и затем:
related_pages = models.ManyToManyField("wagtailcore.Page", through=PageRelation)
Но это не сработает, поскольку внешний ключ должен быть явно установлен на конкретную модель страницы, в которой используется это поле, а wagtailcore.Page отвергается: 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. Другой подход, который я пробовал работает, но ему явно не хватает симметрии, таков:
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')
И может использоваться следующим образом:
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"))
...
Отсутствие симметрии объясняется тем, что каждая модель имеет свой столб источника.