wagtailwagtail-streamfield

How can I programmatically create a Page in wagtail with stream field


I am trying to test pages in wagtail. Creating a page like I did below works for pages without a streamfield.

blogPage = BlogPage(title="Test Blog",path="test-blog", excerpt=rich_text("<p>This is an excerpt </p>"), body=[{"id": "07a1f0b6-0023-45a3-83a0-38cb6a7893f1", "type": "heading", "value": "test heading"}])

root_page = Page.objects.get(id=2)
root_page.add_child(instance=blogPage)
root_page.save()

But when I try to create a page with a streamfield (body) when I run tests assertPageIsPreviewable and assertPageIsEditable I get an error AssertionError: Failed to load preview for BlogPage "Test Blog" with mode="": 'body-count'.

I could not find any documentation explaining how to programmatically create new pages in wagtail with a streamfield block. I was however able to find some documentation explaining how to use

nested_form_data({'data': streamfield([('heading', 'test heading'),])})

but setting

blogPage = BlogPage(title="Test Blog",path="test-blog", excerpt=rich_text("<p>This is an excerpt </p>"), body=nested_form_data({'body': streamfield([('heading', 'test heading'),])}))

doesn't work and setting it to

blogPage = BlogPage(title="Test Blog",path="test-blog", excerpt=rich_text("<p>This is an excerpt </p>"), body=streamfield([('heading', 'test heading'),]))

doesn't either work. I was finally able to find docs of how to do this in wagtail latest documentation https://docs.wagtail.org/en/latest/advanced_topics/testing.html#working-with-page-content so I tried:

blogPage = BlogPage(title="Test Blog",path="test-blog", excerpt=rich_text("<p>This is an excerpt </p>"))
blogPage.body.extend([('heading', "test heading")])
blogPage.save()
root_page = Page.objects.get(id=2)
root_page.add_child(instance=blogPage)
root_page.save()

and

blogPage = BlogPage(title="Test Blog",path="test-blog", excerpt=rich_text("<p>This is an excerpt </p>"))
root_page = Page.objects.get(id=2)
root_page.add_child(instance=blogPage)
root_page.save()
blogPage.body.extend([('heading', "test heading")])
blogPage.save()

Neither of these worked. What is the correct way to create pages in wagtail with streamfield for testing. I do not want to use fixtures. I found this question on stackoverflow and tried this solution but it doesn't work either.

class BlogTestCase(WagtailPageTestCase):
    @classmethod
    def setUpTestData(cls):
        cls.blogPage = BlogPage(title="Test Blog",path="test-blog", excerpt=rich_text("<p>This is an excerpt </p>"))
        cls.blogPage.body.extend([('heading', "test heading")])
        cls.blogPage.save()
        root_page = Page.objects.get(id=2)
        root_page.add_child(instance=cls.blogPage)
        root_page.save()
   def test_is_previewable(self):
       self.assertPageIsPreviewable(self.blogPage)
  
   def test_is_editable(self):
       self.assertPageIsEditable(self.blogPage)

Solution

  • assertPageIsPreviewable and assertPageIsEditable work by making a POST request to the edit page view, and checking that it returns a non-error response. These methods give you the option of supplying the body of the POST request - if you omit this, it will attempt to obtain a suitable POST body by making a GET request (which returns the edit form) and extracting the form data from the HTML. However, this won't work if the form contains a StreamField, because the editing interface for a StreamField is generated through Javascript, and isn't part of the HTML as rendered by the server. As a result, the shortcut of leaving out the POST data isn't available.

    The nested_form_data, rich_text, streamfield and inline_formset helpers provided in wagtail.test.utils.form_data are intended specifically for building up a POST body in the format required - you should not use these when working directly with the BlogPage object, which means that

    blogPage = BlogPage(body=nested_form_data(...))
    

    is invalid. Passing excerpt=rich_text("...") is also invalid, although this is probably benign - it'll just mean that your excerpt will be set to a string of JSON text rather than something HTML-like. excerpt="<p>This is an excerpt</p>" would be correct here.

    The final versions of your code, where you're passing a list of (block type, value) tuples to body.extend, are the correct way of building up StreamField content on a page object. (Note that you can't call blogPage.save() until you've added it to the page tree with root_page.add_child(instance=blogPage), and in fact add_child will have the effect of saving blogPage too, so you don't need to do both.)

    However, given the above-mentioned limitations of assertPageIsPreviewable and assertPageIsEditable, you'll end up having to supply this data in the format of a POST submission anyhow, and this will overwrite any setup you perform up-front on the blog page object, so it's a bit of a moot point.

    Using the POST-data helpers in wagtail.test.utils.form_data, your final code should come out as (untested):

    class BlogTestCase(WagtailPageTestCase):
        @classmethod
        def setUpTestData(cls):
            cls.blogPage = BlogPage(title="Test Blog", slug="test-blog", excerpt="<p>This is an excerpt</p>")
            root_page = Page.objects.get(id=2)
            root_page.add_child(instance=cls.blogPage)
            root_page.save()
    
            cls.post_data = nested_form_data({
                "title": "Test blog",
                "slug": "test blog",
                "excerpt": rich_text("<p>This is an excerpt</p>"),
                "body": streamfield([
                    ('heading', "test heading"),
                ]),
            })
    
       def test_is_previewable(self):
           self.assertPageIsPreviewable(self.blogPage, post_data=self.post_data)
      
       def test_is_editable(self):
           self.assertPageIsEditable(self.blogPage, post_data=self.post_data)