Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

No way to specify default values for StreamBlockFactory #57

Open
bcdickinson opened this issue Jul 13, 2022 · 5 comments
Open

No way to specify default values for StreamBlockFactory #57

bcdickinson opened this issue Jul 13, 2022 · 5 comments

Comments

@bcdickinson
Copy link
Contributor

bcdickinson commented Jul 13, 2022

This is a pre-emptive issue ahead of the merge of #55 to document a missing feature without unnecessarily holding up that PR. The PR adds a new StreamBlockFactory class that can be passed as the argument to a StreamFieldFactory declaration. The missing feature is the ability to specify the default value of a StreamBlockFactory subclass, either when it's passed to StreamFieldFactory or on the class definition itself.

The relationship between StreamFieldFactory and StreamBlockFactory is a bit like the relationship between SubFactory and Factory, so it'd be nice to be able to do something similar to the **kwargs that SubFactory.__call__ accepts to set the default value of the generated stream data. However, the values will always start with block indexes (e.g. 0__my_block_type__some_attribute="foo") which aren't legal Python kwarg names, so my proposal for this API would be to accept an optional dict-like object that defines the defaults, something like:

class MyPageFactory(wagtail_factories.PageFactory):
  body = wagtail_factories.StreamFieldFactory(
    MyStreamBlockFactory,
    {
      "0": "my_block_type",  # Default value for "my_block_type"
      "1__my_block_type__some_field": "foo",  # Explicit value for a nested field
    },
  )

The above should work well for defining default data for a given use of a StreamBlockFactory, it would also be useful to be able to specify default values as part of the StreamBlockFactory definition as well, so that the same data could be used in multiple places without being redefined. I'm less sure how the API for this should look, but one idea might be adding a new default_data member to the Meta class, something like this:

class MyStreamBlockFactory(wagtail_factories.StreamBlockFactory):
  my_block_type = factory.SubFactory(MyBlockTypeFactory)

  class Meta:
    model = MyStreamBlock
    default_data = {
      "0": "my_block_type",  # Default value for "my_block_type"
      "1__my_block_type__some_field": "foo",  # Explicit value for a nested field
    }
@tbrlpld
Copy link

tbrlpld commented Jul 15, 2022

@bcdickinson I am confused by the "0": "my_block_type", # Default value for "my_block_type". Is "0" the value?

@tbrlpld
Copy link

tbrlpld commented Jul 15, 2022

If the issue we are trying to solve is the fact that the block "descriptor" (not sure what to call it) starts with a number (because we don't know the name of the field on the parent), could we just use something generic as the start for the string like "self__0__my_block_type__some_field": ...?

@jams2
Copy link
Contributor

jams2 commented Jul 18, 2022

@tbrlpld

I am confused by the "0": "my_block_type", # Default value for "my_block_type". Is "0" the value?

In this case, this means that the 0th item in the generated StreamValue would get the default value for the my_block_type declaration - in this case it will be the default value of MyBlockTypeFactory.

@bcdickinson it might be worth changing StreamFieldFactory so it is a SubFactory subclass - that way one could provide a defaults dict in the declaration. From a high level this wouldn't be too difficult.

To allow specifying StreamBlockFactory defaults on the Meta class, I think we'd have to further extend StreamBlockStepBuilder. One way would be to update its __init__ method to merge extras and factory_meta.default_data before doing the work that generates the actual factory class that's used for object generation.

Question: given a StreamBlockFactory definition like this:

class BodyFactory(StreamBlockFactory):
    struct_1 = SubFactory(Struct1Factory)
    struct_2 = SubFactory(Struct2Factory)

    class Meta:
        default_data = {"0": "struct_1", "1": "struct_1"}

and a call like MyPageFactory(body__1="struct_2"), would you expect the declarations to be merged such that you effectively end up generating data from

{
    "0": "struct_1",  # from meta.default_data,
    "1": "struct_2",  # from extras at page factory instantiation
}

?

@jams2
Copy link
Contributor

jams2 commented Jul 28, 2022

Potentially related: #60

@tbrlpld
Copy link

tbrlpld commented Apr 21, 2023

I found, you can use factory boy's post_generation decorator to create a default set of blocks. Of course, this is only on the page where the block is used and not the block itself, but it's something.

class InformationPageFactory(wagtail_factories.PageFactory):
    class Meta:
        model = std_models.InformationPage

    title = factory.Faker("text", max_nb_chars=25)
    introduction = factory.Faker("text", max_nb_chars=200)

    @factory.post_generation
    def body(obj, create, extracted, **kwargs):
        blocks = kwargs or {"0": "paragraph"}
        obj.body = utils_factories.StoryBlockFactory(**blocks)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants