Using a Wagtail "ChoiceBlock" with dynamic choices rather than a hardcoded list

We have a setup with a Blog model that has a manytomany relation for BlogPageCategory, and we have a "recent blog posts" streamfield block that lets you specify whether to show cards for X latest blog posts, or X latest blog posts from a specific category.

As such, we started with the following code:

from wagtail.core import blocks


class RecentBlogEntries(blocks.StructBlock):
    title = blocks.CharBlock(
        required=True,
    )

    category_filter = blocks.ChoiceBlock(
        label='Filter by Category',
        required=False,
        choices=[
            ('all', 'All'),
            ('First Category', 'First Category'),
            ('...',. '...'),
        ],
    )

    ...

But hardcoding the categories is kind of silly, and being able to pick them from "what the list is, right now, based on the CMS data for BlogPageCategory" would be far more convenient. However, the following code (of course) turns into an equally hardcoded migration:

from wagtail.core import blocks
from ... import BlogPageCategory


class RecentBlogEntries(blocks.StructBlock):
    title = blocks.CharBlock(
        required=True,
    )

    choices = [ (cat.name, cat.name) for cat in BlogPageCategory.objects.all()]
    choices.sort()
    choices.insert(0, ('all', 'All'))

    category_filter = blocks.ChoiceBlock(
        label='Filter by Category',
        required=False,
        choices=choices,
    )

    ...

Is there any way to make this a dynamic value instead of a list that is fixed by makemigrations?

Upvotes: 5

Views: 3702

Answers (1)

gasman
gasman

Reputation: 25292

ChoiceBlock accepts a callable function as the choices argument:

def get_categories():
    return [(cat.name, cat.name) for cat in BlogPageCategory.objects.all()]


class RecentBlogEntries(blocks.StructBlock):
    title = blocks.CharBlock(
        required=True,
    )

    category_filter = blocks.ChoiceBlock(
        label='Filter by Category',
        required=False,
        choices=get_categories,
    )

The callable needs to be defined at the top level of a module so that the migration can make a reference to it (i.e. it can't be a method on a class), and if it gets subsequently moved or renamed, you'll need to edit the migration accordingly.

Upvotes: 11

Related Questions