Wagtail技术问询:能否在Orderable内部嵌套另一个Orderable?
Absolutely! Nesting Orderable models within an InlinePanel is totally doable in Wagtail—this is exactly the kind of structured content workflow the framework was built to support. Let’s tweak your code to make each card hold 1-10 PDFs, while keeping your 0-20 card limit intact.
Step 1: Update Your Models
We’ll restructure your models to create a parent-child relationship:
- The main
ArchitectPage(mostly unchanged) - A parent
Orderablefor each card (acts as the container) - A child
Orderablefor individual PDF items linked to each card
Here’s the revised code:
from wagtail.models import Page, Orderable from wagtail.admin.panels import MultiFieldPanel, InlinePanel, ImageChooserPanel, FieldPanel, DocumentChooserPanel from wagtail.documents.models import Document from wagtail.images.models import Image from django.db import models class ArchitectPage(Page): search_fields = Page.search_fields + [] content_panels = Page.content_panels + [ MultiFieldPanel( # Manages the top-level cards (0-20 total) [InlinePanel('architect_cards', max_num=20, min_num=0, label="Architect Document Card")], heading="Architect PDF Cards" ), ] edit_handler = TabbedInterface([ ObjectList(content_panels, heading='Content'), ObjectList(Page.promote_panels, heading='SEO'), ObjectList(Page.settings_panels, heading='Settings', classname='settings'), ]) # Parent Orderable: Represents a single display card class ArchitectDownloadCard(Orderable): page = ParentalKey(ArchitectPage, on_delete=models.CASCADE, related_name='architect_cards') image = models.ForeignKey( 'wagtailimages.Image', null=True, blank=True, on_delete=models.CASCADE, related_name='+' ) caption = models.CharField(blank=True, max_length=250) panels = [ ImageChooserPanel('image'), FieldPanel('caption'), # Nested inline panel for PDFs (1-10 per card) InlinePanel('card_pdfs', max_num=10, min_num=1, label="PDF File"), ] # Child Orderable: Represents a single PDF linked to a card class ArchitectPdfItem(Orderable): card = ParentalKey(ArchitectDownloadCard, on_delete=models.CASCADE, related_name='card_pdfs') pdf_document = models.ForeignKey( 'wagtaildocs.Document', null=True, blank=False, on_delete=models.SET_NULL, related_name='+' ) pdf_label = models.CharField(blank=True, max_length=100, help_text="Optional label (e.g., 'Updated 2024' or 'Version 2')") panels = [ DocumentChooserPanel('pdf_document'), FieldPanel('pdf_label'), ]
Key Model Changes:
- Renamed your original
ArchitectDownloadstoArchitectDownloadCardto better reflect its role as a container - Added
ArchitectPdfItemas the childOrderableto handle individual PDFs per card - Set
min_num=1on the nestedInlinePanelto enforce at least one PDF per card (adjust this if you need more flexibility) - Added an optional
pdf_labelfield to help editors distinguish multiple PDFs on the same card
Step 2: Update Your Template
Now we’ll loop through the cards first, then iterate over each PDF inside its parent card. Here’s the adjusted template:
{% for card in page.architect_cards.all %} <div class="document line-up-card"> <div class="w3-card-4 w3-margin w3-white" data-aos="fade-down"> {% image card.image fill-150x150-c100 %} <div class="w3-container"> <p>{{ card.caption }}</p> </div> <hr> <!-- Loop through all PDFs for this card --> {% for pdf_item in card.card_pdfs.all %} <div class="download"> <a href="{{ pdf_item.pdf_document.url }}" class="smooth-over-button noDecoration"> <i class="fa fa-download"></i> <p class="btn-txt"> <!-- Show custom label if set, else use the document title --> {% if pdf_item.pdf_label %} {{ pdf_item.pdf_label }} {% else %} {{ pdf_item.pdf_document.title }} {% endif %} </p> </a> </div> {% endfor %} </div> </div> {% endfor %}
Template Improvements:
- Outer loop targets the top-level cards (
architect_cards) - Inner loop pulls all PDFs linked to the current card (
card_pdfs) - Added fallback logic to display the document title if no custom label is provided
- Cleaned up the structure to group all PDFs under their parent card
Critical Post-Change Step
Don’t forget to run database migrations to apply these model changes:
python manage.py makemigrations python manage.py migrate
This will update your database schema to support the new parent-child relationship between cards and PDFs.
内容的提问来源于stack exchange,提问作者Joelad

