Как в 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"))
    ...

Отсутствие симметрии объясняется тем, что каждая модель имеет свой столб источника.

4. Другой подход, который я нашел, заключается в использовании GenericForeignKey и GenericRelation, но GenericRelation является полем только для чтения и не может быть использован на странице модели PageChooserPanel или AutocompletePanel.

5. ВОПРОС: Как сделать универсальное симметричное отношение для всех моделей, наследующих от wagtailcore.Page (как в пункте 2, но чтобы это действительно работало?)

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