How to programatically create a Wagtail StructBlock containing a StreamField?

269 Views Asked by At

I have defined a custom StructBlock with a StreamField allowing StructBlock instances, such as follows:

class NavigationPageChooserBlock(wagtail_blocks.StructBlock):
    title = wagtail_blocks.CharBlock()
    page = wagtail_blocks.PageChooserBlock()


class NavigationDropdownMenuBlock(wagtail_blocks.StructBlock):
    title = wagtail_blocks.CharBlock()
    menu_items = wagtail_blocks.StreamBlock(
        [
            ("page", NavigationPageChooserBlock()),
        ]
    )

For full context, the blocks are used as part of a custom NavigationMenuSetting that I need to use in a content importer from a Drupal site.

@register_setting
class NavigationMenuSetting(BaseSiteSetting):
    menu_items = StreamField(
        [
            ("drop_down", NavigationDropdownMenuBlock()),
        ],
        use_json_field=True,
    )

I'm having a lot of difficulty trying to figure out how to create a NavigationDropdownMenuBlock instance with child menu_items in Python code. I've tried the following, but get an error.

/wagtail/blocks/stream_block.py", line 290, in get_prep_value
    return value.get_prep_value()
           ^^^^^^^^^^^^^^^^^^^^
AttributeError: 'list' object has no attribute 'get_prep_value'

Here is my importer code:

# example_page is created previously

# NavigationDropdownMenuBlock (StructBlock)
example_dropdown = {
    "title": "Dropdown Menu",
    # StreamBlock
    "menu_items": [
        (
            "page",
            # NavigationPageChooserBlock (StructBlock)
            {
                "title": "Example Page Link",
                "page": example_page,
            },
        ),
    ],
}

navigation_items = [
    ("drop_down", example_dropdown),
]
navigation_menu = NavigationMenuSetting(
    menu_items=navigation_items,
    site_id=1,
)

navigation_menu.save()

I've also tried using StreamValue, but can't figure out what to use for the string_block attribute:

navigation_dropdown = NavigationDropdownMenuBlock(
    title="Dropdown Menu",
)

navigation_dropdown.menu_items = StreamValue(
    stream_block=navigation_dropdown.menu_items.stream_block,
    stream_data=[
        {
            "type": "page",
            # StructBlock
            "value": {
                "title": "Example Page Link",
                "page": example_page,
            },
        },
    ],
)
stream_block=navigation_dropdown.menu_items.stream_block,
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'NavigationDropdownMenuBlock' object has no attribute 'menu_items'

How can I change my import script to properly create the example_dropdown item for the NavigationMenuSetting.menu_items?

1

There are 1 best solutions below

0
Rich - enzedonline On

Did you see the Wagtail doc on modifying streamfields? https://docs.wagtail.org/en/stable/topics/streamfield.html#modifying-streamfield-data

If you've used BeautifulSoup before, it works a little similarly: create a tag then append/insert at the appropriate place. You can't just add blocks via the raw data as the UID's would be missing from the blocks, Wagtail wouldn't know what to do with them.

You would need to create NavigationPageChooserBlock instances for each NavigationDropdownMenuBlock instance you need to create, then append those to your NavigationMenuSetting.menu_items field.

Is there a reason for using a Streamfield and not a clusterable model with orderables? This would make your life a lot easier.