Панель трясогузки для самореферентных отношений "многие ко многим" со сквозной моделью
Я занят созданием чего-то вроде графа знаний в wagtail.
CurriculumContentItem является узлом на этом графе. Он имеет отношения многие-ко-многим с самим собой, а сквозная модель имеет важные поля.
Я изо всех сил пытаюсь добиться того, чтобы это можно было использовать на странице администратора. Пожалуйста, посмотрите встроенные комментарии:
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"),
]
Если бы я хотел сделать это в обычной админке Django, я бы использовал инлайн для указания предварительных условий. Что-то вроде:
class ContentItemOrderPostAdmin(admin.TabularInline):
model = models.ContentItem.prerequisites.through
fk_name = "post"
class ContentItemOrderPreAdmin(admin.TabularInline):
model = models.ContentItem.unlocks.through
fk_name = "pre"
Есть ли подобный механизм в Wagtail?
Похоже, что мне нужно создать пользовательскую панель для этого.
Я бы предложил создать InlinePanel, указывающую на вашу "сквозную" модель, что означает, что вы работаете с отношением один-ко-многим, а не многие-ко-многим:
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"),
]
Это работает:
- Сделайте сквозную модель наследуемой от Orderable .
- Используйте
ParentalKeyвместоForeignKey - Используйте InlinePanel, ссылающуюся на связанные имена полей в сквозных моделях
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"),
]
Я беспокоился, что на одну строку будет приходиться 2 поля PageChooser, но трясогузка достаточно умна (и волшебна), чтобы просто нарисовать то, что нам нужно