Recipe content types with advanced StreamField patterns and custom recipe blocks
The recipes app demonstrates advanced StreamField usage with custom blocks for ingredients, cooking steps with difficulty ratings, and multiple content areas.
class RecipePage(Page): """ Recipe pages are more complex than blog pages, demonstrating more advanced StreamField patterns. """ date_published = models.DateField("Date article published", blank=True, null=True) subtitle = models.CharField(blank=True, max_length=255) introduction = models.TextField(blank=True, max_length=500) backstory = StreamField( BaseStreamBlock(), block_counts={ "heading_block": {"max_num": 1}, "image_block": {"max_num": 1}, "embed_block": {"max_num": 1}, }, blank=True, use_json_field=True, help_text="Use only a minimum number of headings and large blocks.", ) recipe_headline = RichTextField( blank=True, max_length=120, features=["bold", "italic", "link"], help_text="Keep to a single line", ) body = StreamField( RecipeStreamBlock(), blank=True, use_json_field=True, help_text="The recipe's step-by-step instructions and any other relevant information.", )
Recipes support 1-3 authors through the RecipePersonRelationship model:
bakerydemo/recipes/models.py
class RecipePersonRelationship(Orderable, models.Model): """ This defines the relationship between the `Person` within the `base` app and the RecipePage. This allows people to be added to a RecipePage. """ page = ParentalKey( "RecipePage", related_name="recipe_person_relationship", on_delete=models.CASCADE, ) person = models.ForeignKey( "base.Person", related_name="person_recipe_relationship", on_delete=models.CASCADE, )
The authors() method filters to show only live authors:
def authors(self): # Only return authors that are not in draft return [ n.person for n in self.recipe_person_relationship.filter( person__live=True ).select_related("person") ]
content_panels = Page.content_panels + [ FieldPanel("date_published"), # Using `title` classname to make field larger FieldPanel("subtitle", classname="title"), MultiFieldPanel( [ HelpPanel( "Refer to keywords analysis and correct international " "ingredients names to craft the best introduction backstory, " "and headline." ), FieldPanel("introduction"), FieldPanel("backstory"), FieldPanel("recipe_headline"), ], heading="Preface", ), FieldPanel("body"), MultipleChooserPanel( "recipe_person_relationship", chooser_field_name="person", heading="Authors", label="Author", help_text="Select between one and three authors", panels=None, min_num=1, max_num=3, ),]
Notable features:
HelpPanel provides editorial guidance
MultiFieldPanel groups related preface fields
Authors limited to 1-3 selections
Subtitle uses classname="title" for larger display
class RecipeIndexPage(Page): """ Index page for recipes. We need to alter the page model's context to return the child page objects, the RecipePage objects, so that it works as an index page """ introduction = models.TextField(help_text="Text to describe the page", blank=True) content_panels = Page.content_panels + [ FieldPanel("introduction"), ]