I'm trying to write unit tests for a page with a fairly complicated StreamField. I'm having issues with that so I've created a very pared down version to try to understand how WagtailPageTests
works and build my way up.
My pared down model:
from wagtail.admin.edit_handlers import FieldPanel
from wagtail.core.fields import RichTextField
from wagtail.core.models import Page
class SEPage(Page):
banner_text = RichTextField(blank=True, features=["bold", "italic", "html"])
body = RichTextField(blank=True, features=["bold", "italic", "html"])
parent_page_types = ["LandingPage"]
content_panels = Page.content_panels [
FieldPanel("banner_text"),
FieldPanel("body"),
]
The relevant bits of my test:
class SEPageTest(WagtailPageTests):
def test_can_create_se_page(self):
# Assert that a SEPage can be made here, with this POST data
form = nested_form_data({
'slug': 'test',
'title': 'title',
'banner_text': rich_text('About us'),
'body': rich_text('About us'),
})
print(f"form: {form}")
self.assertCanCreate(self.landing, SEPage, form)
That test passes as I would expect. However then I changed the body
in the nested_form_data
to
'body': streamfield([
('text', 'Lorem ipsum dolor sit amet'),
])
without changing the model and the test still passes.
I would expect that with the body
in the model set up as a RichTextField
and the test passing in a streamfield that the test would fail.
Can anyone explain why this test is passing?
CodePudding user response:
In short: the streamfield
helper will generate a bunch of data fields under names like body-count
and body-0-value
which will be ignored by the rich text field, because it's expecting a field simply named body
. Since it doesn't find one, the resulting value for body
is blank, which passes validation because the RichTextField is defined with blank=True
.
The longer answer: The purpose of the form data helpers (nested_form_data
, rich_text
and streamfield
) is to construct a dictionary of data in the same format that the page edit form in the Wagtail admin interface would submit as a POST request. For simple fields like title and slug, this works as you'd expect: a single item in the dictionary, mapping the field name to its value.
For more complex fields, the data format is an internal Wagtail detail that you wouldn't normally have to deal with (which is why Wagtail provides these helpers to do it for you). In the case of a rich text field, there's still a single entry in the dictionary under the field name (body
here), but the value has to be sent in the Draftail editor's internal JSON format. In the case of a StreamField, though, the data consists of lots of individual form fields holding information about how many blocks there are, what type they are, what their values are, whether they've been deleted or reordered, and various other details. These come through in the form submission as a set of entries in the dictionary, all prefixed with body-
.
So, when you have a rich text field on the page, the form is expecting to receive a submission like:
{
'slug': 'test',
'title': 'title',
'banner_text': '{"blocks": [{"key": "1", "text": "About us"}]}',
'body': '{"blocks": [{"key": "1", "text": "About us"}]}',
}
and indeed this is what you're providing when you use the rich_text
helper. However, if you use the streamfield
helper instead, it'll come out as something like:
{
'slug': 'test',
'title': 'title',
'banner_text': '{"blocks": [{"key": "1", "text": "About us"}]}',
'body-count': '1',
'body-0-type': 'text',
'body-0-value': 'Lorem ipsum dolor sit amet',
'body-0-deleted': '',
'body-0-order': '0',
}
What happens when you submit this incorrect dictionary of data? Wagtail will go through each field defined in SEPage
in turn, picking out the entry or entries that are meaningful to that field. Since none of the fields defined in SEPage
are looking for entries named body-count
, body-0-type
and so on, those will just get ignored. The body
rich text field is expecting an entry simply named body
, and doesn't find one, so the resulting value for that field is blank. However, the body
field is defined with blank=True
, and so a blank value is treated as valid. As a result, it's able to successfully create a page with an empty body field, and the test passes.