djangowagtailwagtail-streamfield

Wagtail: Dynamically load StreamField blocks in the admin EditView


I have a use case where we need dynamic blocks for a StreamField. Unfortunately this doesn't seem possible, since block types are specified in the model's StreamField field.

I dug through the FieldPanel, StreamBlock and GroupPanel code and cannot clearly find a way to override the available blocks for the StreamField outside the model definition.

This is my current admin EditView, where you can see what I'm trying to accomplish (see the NOTE/FIXME comments below:

class EditProductView(EditView):

    def get_edit_handler(self):
        summary_panels = [
            FieldPanel('title'),
            FieldPanel('description'),
            FieldPanel('body'),
        ]

        attributes_panel = [
            # NOTE/FIXME: This is where we need the custom blocks to load, based
            # on `self.instance.product_type`.

            FieldPanel('attributes'),

            # StreamFieldPanel (which is deprecated) does not work because you can
            # only set the blocks within the model, when defining the StreamFieldPanel.
            # I would love to be able to do something like:

            StreamFieldPanel('attributes', block_types=[
                ('test', blocks.CharBlock()),   # Like this for example
            ])

        ]

        settings_panels = [
            FieldPanel('categories'),
            StateMachinePanel('state', sm_prop='sm', heading="State"),
        ]

        return TabbedInterface([
            ObjectList(summary_panels, heading='Summary'),
            ObjectList(attributes_panel, heading='Attributes'),
            ObjectList(settings_panels, heading='Settings'),
        ]).bind_to_model(self.model_admin.model)

Is it possible to override available StreamField blocks in this way, or are there potential issues with json_field DB integrity?


Solution

  • This wouldn't work, because the StreamField block definition isn't just used for the edit form - any time you access that field, even just to output it on a template, the StreamField needs to look at the block definition to unpack the JSON and return the appropriate Python objects. For example, suppose the database contained:

    [{'type': 'test', 'value': 123}]
    

    What should page.attributes[0].value return? If the test block is of type IntegerBlock, it would return 123 - but if it was a PageChooserBlock instead, it would return the Page instance with ID 123. If it was an ImageChooserBlock, it would return the Image instance with ID 123, and so on. In your example, the only way to find out the block type is to call get_edit_handler and construct the entire editing interface as a side effect - this would be mixing up model-level logic with admin interface code, and be massively inefficient just for accessing a single field.