djangowagtailfaker

wagtail-factories Pagechooser block not working


I am trying to create a factory for my Navigation model but, here top_bar field is not creating. The issue is related to the URLChoiceStreamFactory but unable to solve it.

models.py

from wagtail.models import Page
from wagtail import blocks
from django.db import models
from modelcluster.models import ClusterableModel
from wagtail.admin.panels import FieldPanel
from wagtail.models import PreviewableMixin
from wagtail.snippets.models import register_snippet
from django.utils.translation import gettext_lazy as _
from wagtail.fields import StreamField
from django import forms

class URLBlock(blocks.FieldBlock):
    def __init__(
            self,
            required=True,
            help_text=(
                    "Either a relative URL that begins with a forward slash or an absolute URL that begins with http, https, "
                    "tel, or mailto."
            ),
            max_length=None,
            min_length=None,
            **kwargs,
    ):
        self.field = forms.CharField(
            required=required,
            help_text=help_text,
            max_length=max_length,
            min_length=min_length,
        )
        super().__init__(**kwargs)

    class Meta:
        icon = "site"


class HomePage(Page):
    pass


class URLChoiceStream(blocks.StreamBlock):
    internal = blocks.PageChooserBlock(icon="doc-empty-inverse")

    class Meta:
        max_num = 1


class LinkBlock(blocks.StructBlock):
    name = blocks.CharBlock(max_length=128)
    url = URLChoiceStream()

    class Meta:
        icon = "link"
        label = _("Link")


class TopBar(blocks.StructBlock):
    title = blocks.CharBlock(label=_("Title"))
    top_bar_links = blocks.ListBlock(LinkBlock(), max_num=5)

    panels = [
        FieldPanel("title"),
        FieldPanel("top_bar_links"),
    ]

    class Meta:
        icon = "resubmit"


@register_snippet
class Navigation(ClusterableModel, PreviewableMixin):
    name = models.CharField(
        unique=True,
        max_length=256,
        help_text=_("This is used for internal reference only."),
    )
    top_bar = StreamField(
        [("top_bar", TopBar())],
        max_num=1,
        verbose_name=_("Top Bar"),
        null=True,
        blank=True,
        use_json_field=True,
    )

    panels = [
        FieldPanel("name"),
        FieldPanel("top_bar"),
    ]

    def __str__(self):
        return self.name

factory.py

from home.models import Navigation, TopBar, LinkBlock, URLChoiceStream, HomePage
import factory
import wagtail_factories
from faker import Faker

faker = Faker()


class HomePageFactory(wagtail_factories.PageFactory):
    class Meta:
        model = HomePage


class HomePageChooserBlockFactory(wagtail_factories.PageChooserBlockFactory):
    page = factory.SubFactory(HomePageFactory)


class URLChoiceStreamFactory(wagtail_factories.StreamBlockFactory):
    internal = factory.SubFactory(HomePageChooserBlockFactory)

    class Meta:
        model = URLChoiceStream


class LinkBlockFactory(wagtail_factories.StructBlockFactory):
    name = factory.Faker("sentence", nb_words=2, variable_nb_words=False)
    url = factory.SubFactory(URLChoiceStreamFactory)

    class Meta:
        model = LinkBlock


class TopBarFactory(wagtail_factories.StructBlockFactory):
    title = faker.word()
    top_bar_links = wagtail_factories.ListBlockFactory(LinkBlockFactory)

    class Meta:
        model = TopBar


class NavFactory(factory.django.DjangoModelFactory):
    name = faker.name()
    top_bar = wagtail_factories.StreamFieldFactory(
        {
            "top_bar": factory.SubFactory(TopBarFactory),
        },
    )

    class Meta:
        model = Navigation

I debugged the issue, The HomePageFactory is creating but URLChoiceStreamFactory is not. wagtail-factories docs is very tiny. Not able to find related part in the docs.


Solution

  • URLChoiceStreamFactory.build()

    That builds an empty StreamValue by design.

    URLChoiceStreamFactory.build(invalid_param=0)

    *** wagtail_factories.builder.InvalidDeclaration: StreamFieldFactory declarations must be of the form <index>=<block_name>, <index>__<block_name>=value or <index>__<block_name>__<param>=value, got: invalid_param

    The way to build a non-empty StreamBlockFactory subclass instance is:

    URLChoiceStreamFactory.build(**{'0': 'internal'})
    # Out: <StreamValue [<block internal: <HomePage: Test page>>]>
    

    The way to build a DjangoModelFactory subclass instance containing a non-empty StreamFieldFactory attribute is:

    NavFactory.build(top_bar__0='top_bar')
    # Out: <Navigation: Faker Name>
    
    NavFactory.build(top_bar__0='top_bar').__dict__
    # {'_state': <django.db.models.base.ModelState object at 0x10bd5a670>,
    #  'id': None,
    #  'name': 'Faker Name',
    #  'top_bar': <StreamValue [
    #   <block top_bar: StructValue([
    #    ('title', 'fakerword'),
    #    ('top_bar_links', <ListValue: []>)])>]>}
    
    NavFactory.build(top_bar__0__top_bar__top_bar_links__0__url__0='internal').__dict__
    # {'_state': <django.db.models.base.ModelState object at 0x000000000>,
    #  'id': None,
    #  'name': 'Faker Name',
    #  'top_bar': <StreamValue [
    #   <block top_bar: StructValue([
    #    ('title', 'fakerword'),
    #    ('top_bar_links', <ListValue: [
    #     StructValue([
    #      ('name', 'Faker sentence.'),
    #      ('url', <StreamValue [<block internal: <HomePage: Test page>>]>)])]>)])>]>}