Author: Marty Ballard
I recently had a client who wanted the ability to display multiple videos about their products and have them in a scrollable carousel. As I've learned over the years, a client's initial request is not actually what they want. So I dug a bit deeper and discoverened that they actually want the ability to show multiple videos and some written content telling the user what the video is about. After learning this I felt I had the fully story. I had previously created a product page in Wagtail that allowed for a single video element and other fields that described various attributes of the product.
Now I needed to modify this page to allow for multiple videos and descriptions for each product. This should be simple right? I (incorrectly) assumed I could add a list of objects to the page. Just create a list of fields? Hmm. I searched the Wagtail documents and the Django documents and was unable to find the elusive ListField. In fact, I don't believe one exists. (Feel free to comment if that is incorrect) I truly believe that is something can be imagined then it can be built, so I knew there must be a solution out there and I had to figure it out.
After spending a bit of time trying to understand how this was going to be possible I came across StreamFields. Everything I was reading was pointing me back to StreamFields as the solution but I was having a really hard time finding any real information about how to implement StreamFields with a RichTextField and a URLField (or any set of multiple fields). Finally I came across the StructBlock.
Structural block types In addition to the basic block types above, it is possible to define new block types made up of sub-blocks: for example, a ‘person’ block consisting of sub-blocks for first name, surname and image, or a ‘carousel’ block consisting of an unlimited number of image blocks. These structures can be nested to any depth, making it possible to have a structure containing a list, or a list of structures.
The example (PersonBlock) that is provided made this clear that I had found my way. This shows a class that contains multiple related fields, which is eactly what I was looking for to solve my client's problem.
class PersonBlock(blocks.StructBlock): first_name = blocks.CharBlock() surname = blocks.CharBlock() photo = ImageChooserBlock(required=False) biography = blocks.RichTextBlock() class Meta: icon = 'user'
And then to use this on a Wagtail page the below example is shown. OK, that's not exactly what I was going for, but I believe it will get me there. The StreamField below shows many blocks within the StreamField, but I am looking to only add a single StructBlock.
body = StreamField([ ('heading', blocks.CharBlock(form_classname="full title")), ('paragraph', blocks.RichTextBlock()), ('image', ImageChooserBlock()), ('person', PersonBlock()), ])
So my actual solution was to create a StructBlock class that conatins a URLBlock and a RichTextBlock and then embed that into a StreamField on my page.
For the StructBlock class I created a new class called DescAndUrlBlock. First, in this class I add a RichTextBlock called description, which I set required equal to True and added help text of 'Add your description'. Then I added the URLBlock called link, this is also set to requried equal True and has a help text of 'Add video URL'. Below this I add a meta class which contains the icon and a label. If you want to see all of the available icons in Wagtail, add 'wagtail.contrib.styleguide' to your settings. Then in your admin page you can navigate to Settings > Styleguide and click on Icons to view all of the available choices.
Below is the code -
from wagtail.core import blocks class DescAndUrlBlock(blocks.StructBlock): """Description and URL""" description = blocks.RichTextBlock(required=True, help_text="Add your description") link = blocks.URLBlock(required=True, help_text="Add video URL") class Meta: icon = "snippet" label = "Add a Video link and Description"
Next I update my page, I imported StreamFieldPanel and StreamField from Wagtail, and imported the new DescAndUrlBlock that I had just created. In the page class I add the StreamField which takes in a list of tuples. Each tuple requires a desciption and a block (remember, I added a StrucBlock, which allows for more than one block). I also set null equal to False and blank equal to False. I created a meta class to give my page a name and plural name. Then I add my content element to the content_panels, here I use the StreamFieldPanel and pass in the content field. With that I have everything in place.
from wagtail.core.models import Page from wagtail.admin.edit_handlers import StreamFieldPanel from wagtail.core.fields import StreamField from .blocks import DescAndUrlBlock class VideoPage(Page): content = StreamField([ ('desc_and_url', DescAndUrlBlock()) ], null=False, blank=False) class Meta: verbose_name = "Video Page" verbose_name_plural = "Videos Pages" content_panels = Page.content_panels + [ StreamFieldPanel('content') ]