kopia lustrzana https://github.com/wagtail/bakerydemo
Convert demo StreamField pages to RecipePage data model
rodzic
8bbf239cf6
commit
f3754842d6
|
@ -1,54 +1,74 @@
|
|||
from django import forms
|
||||
from wagtail.contrib.table_block.blocks import TableBlock
|
||||
from wagtail.contrib.typed_table_block.blocks import TypedTableBlock
|
||||
from wagtail.core.blocks import (
|
||||
CharBlock,
|
||||
ChoiceBlock,
|
||||
FloatBlock,
|
||||
ListBlock,
|
||||
PageChooserBlock,
|
||||
RichTextBlock,
|
||||
StreamBlock,
|
||||
StructBlock,
|
||||
TextBlock,
|
||||
URLBlock,
|
||||
)
|
||||
from wagtail.documents.blocks import DocumentChooserBlock
|
||||
from wagtail.embeds.blocks import EmbedBlock
|
||||
from wagtail.images.blocks import ImageChooserBlock
|
||||
|
||||
from bakerydemo.base.blocks import BlockQuote, HeadingBlock, ImageBlock
|
||||
|
||||
class RecipeTableBlock(StructBlock):
|
||||
title = CharBlock()
|
||||
description = TextBlock()
|
||||
table = TypedTableBlock(
|
||||
|
||||
class RecipeStepBlock(StructBlock):
|
||||
text = RichTextBlock(features=["bold", "italic", "link"])
|
||||
difficulty = ChoiceBlock(
|
||||
widget=forms.RadioSelect,
|
||||
choices=[("S", "Small"), ("M", "Medium"), ("L", "Large")],
|
||||
default="S",
|
||||
)
|
||||
|
||||
class Meta:
|
||||
template = 'blocks/recipe_step_block.html'
|
||||
icon = 'tick'
|
||||
|
||||
|
||||
class RecipeStreamBlock(StreamBlock):
|
||||
"""
|
||||
Define the custom blocks that `StreamField` will utilize
|
||||
"""
|
||||
|
||||
heading_block = HeadingBlock(group="Content")
|
||||
paragraph_block = RichTextBlock(
|
||||
icon="pilcrow", template="blocks/paragraph_block.html", group="Content"
|
||||
)
|
||||
block_quote = BlockQuote(group="Content")
|
||||
table_block = TableBlock(group="Content")
|
||||
typed_table_block = TypedTableBlock(
|
||||
[
|
||||
("text", CharBlock()),
|
||||
("numeric", FloatBlock()),
|
||||
("rich_text", RichTextBlock()),
|
||||
("image", ImageChooserBlock()),
|
||||
]
|
||||
],
|
||||
group="Content",
|
||||
)
|
||||
|
||||
image_block = ImageBlock(group="Media")
|
||||
embed_block = EmbedBlock(
|
||||
help_text="Insert an embed URL e.g https://www.youtube.com/watch?v=SGJFWirQ3ks",
|
||||
icon="media",
|
||||
template="blocks/embed_block.html",
|
||||
group="Media",
|
||||
)
|
||||
|
||||
class RecipeStreamBlock(StreamBlock):
|
||||
page = PageChooserBlock()
|
||||
embed = EmbedBlock()
|
||||
image = ImageChooserBlock()
|
||||
|
||||
|
||||
BLOCKS = [
|
||||
("char", CharBlock()),
|
||||
(
|
||||
"choice",
|
||||
ChoiceBlock(choices=[("M", "Medium"), ("L", "Large"), ("XL", "Extra large")]),
|
||||
),
|
||||
("list", ListBlock(child_block=CharBlock())),
|
||||
("page", PageChooserBlock()),
|
||||
("text", TextBlock()),
|
||||
("rich_text", RichTextBlock()),
|
||||
("url", URLBlock()),
|
||||
("document", DocumentChooserBlock()),
|
||||
("embed", EmbedBlock()),
|
||||
("image", ImageChooserBlock()),
|
||||
("table", RecipeTableBlock()),
|
||||
("stream", RecipeStreamBlock()),
|
||||
]
|
||||
ingredients_list = ListBlock(
|
||||
RichTextBlock(features=["bold", "italic", "link"]),
|
||||
min_num=2,
|
||||
max_num=10,
|
||||
icon="list-ol",
|
||||
group="Cooking",
|
||||
)
|
||||
steps_list = ListBlock(
|
||||
RecipeStepBlock(),
|
||||
min_num=2,
|
||||
max_num=10,
|
||||
icon="tasks",
|
||||
group="Cooking",
|
||||
)
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
# Generated by Django 4.0.8 on 2022-11-17 15:14
|
||||
# Generated by Django 4.1.4 on 2022-12-20 14:17
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import modelcluster.fields
|
||||
import wagtail.blocks
|
||||
import wagtail.contrib.table_block.blocks
|
||||
import wagtail.contrib.typed_table_block.blocks
|
||||
import wagtail.documents.blocks
|
||||
import wagtail.embeds.blocks
|
||||
import wagtail.fields
|
||||
import wagtail.images.blocks
|
||||
|
@ -16,10 +16,35 @@ class Migration(migrations.Migration):
|
|||
initial = True
|
||||
|
||||
dependencies = [
|
||||
("base", "0012_person_expire_at_person_expired_and_more"),
|
||||
("wagtailcore", "0078_referenceindex"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="RecipeIndexPage",
|
||||
fields=[
|
||||
(
|
||||
"page_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="wagtailcore.page",
|
||||
),
|
||||
),
|
||||
(
|
||||
"introduction",
|
||||
models.TextField(blank=True, help_text="Text to describe the page"),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"abstract": False,
|
||||
},
|
||||
bases=("wagtailcore.page",),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="RecipePage",
|
||||
fields=[
|
||||
|
@ -34,169 +59,260 @@ class Migration(migrations.Migration):
|
|||
to="wagtailcore.page",
|
||||
),
|
||||
),
|
||||
("title_1", models.CharField(default="Title 1", max_length=255)),
|
||||
(
|
||||
"section_1",
|
||||
"date_published",
|
||||
models.DateField(
|
||||
blank=True, null=True, verbose_name="Date article published"
|
||||
),
|
||||
),
|
||||
("subtitle", models.CharField(blank=True, max_length=255)),
|
||||
("introduction", models.TextField(blank=True, max_length=500)),
|
||||
(
|
||||
"backstory",
|
||||
wagtail.fields.StreamField(
|
||||
[
|
||||
("char", wagtail.blocks.CharBlock()),
|
||||
(
|
||||
"choice",
|
||||
wagtail.blocks.ChoiceBlock(
|
||||
choices=[
|
||||
("M", "Medium"),
|
||||
("L", "Large"),
|
||||
("XL", "Extra large"),
|
||||
]
|
||||
),
|
||||
),
|
||||
(
|
||||
"list",
|
||||
wagtail.blocks.ListBlock(
|
||||
child_block=wagtail.blocks.CharBlock()
|
||||
),
|
||||
),
|
||||
("page", wagtail.blocks.PageChooserBlock()),
|
||||
("text", wagtail.blocks.TextBlock()),
|
||||
("rich_text", wagtail.blocks.RichTextBlock()),
|
||||
("url", wagtail.blocks.URLBlock()),
|
||||
(
|
||||
"document",
|
||||
wagtail.documents.blocks.DocumentChooserBlock(),
|
||||
),
|
||||
("embed", wagtail.embeds.blocks.EmbedBlock()),
|
||||
("image", wagtail.images.blocks.ImageChooserBlock()),
|
||||
(
|
||||
"table",
|
||||
"heading_block",
|
||||
wagtail.blocks.StructBlock(
|
||||
[
|
||||
("title", wagtail.blocks.CharBlock()),
|
||||
("description", wagtail.blocks.TextBlock()),
|
||||
(
|
||||
"table",
|
||||
wagtail.contrib.typed_table_block.blocks.TypedTableBlock(
|
||||
[
|
||||
(
|
||||
"text",
|
||||
wagtail.blocks.CharBlock(),
|
||||
),
|
||||
(
|
||||
"numeric",
|
||||
wagtail.blocks.FloatBlock(),
|
||||
),
|
||||
(
|
||||
"rich_text",
|
||||
wagtail.blocks.RichTextBlock(),
|
||||
),
|
||||
(
|
||||
"image",
|
||||
wagtail.images.blocks.ImageChooserBlock(),
|
||||
),
|
||||
]
|
||||
"heading_text",
|
||||
wagtail.blocks.CharBlock(
|
||||
form_classname="title", required=True
|
||||
),
|
||||
),
|
||||
(
|
||||
"size",
|
||||
wagtail.blocks.ChoiceBlock(
|
||||
blank=True,
|
||||
choices=[
|
||||
("", "Select a header size"),
|
||||
("h2", "H2"),
|
||||
("h3", "H3"),
|
||||
("h4", "H4"),
|
||||
],
|
||||
required=False,
|
||||
),
|
||||
),
|
||||
]
|
||||
),
|
||||
),
|
||||
(
|
||||
"stream",
|
||||
wagtail.blocks.StreamBlock(
|
||||
"paragraph_block",
|
||||
wagtail.blocks.RichTextBlock(
|
||||
icon="pilcrow",
|
||||
template="blocks/paragraph_block.html",
|
||||
),
|
||||
),
|
||||
(
|
||||
"image_block",
|
||||
wagtail.blocks.StructBlock(
|
||||
[
|
||||
("page", wagtail.blocks.PageChooserBlock()),
|
||||
("embed", wagtail.embeds.blocks.EmbedBlock()),
|
||||
(
|
||||
"image",
|
||||
wagtail.images.blocks.ImageChooserBlock(),
|
||||
wagtail.images.blocks.ImageChooserBlock(
|
||||
required=True
|
||||
),
|
||||
),
|
||||
(
|
||||
"caption",
|
||||
wagtail.blocks.CharBlock(required=False),
|
||||
),
|
||||
(
|
||||
"attribution",
|
||||
wagtail.blocks.CharBlock(required=False),
|
||||
),
|
||||
]
|
||||
),
|
||||
),
|
||||
(
|
||||
"block_quote",
|
||||
wagtail.blocks.StructBlock(
|
||||
[
|
||||
("text", wagtail.blocks.TextBlock()),
|
||||
(
|
||||
"attribute_name",
|
||||
wagtail.blocks.CharBlock(
|
||||
blank=True,
|
||||
label="e.g. Mary Berry",
|
||||
required=False,
|
||||
),
|
||||
),
|
||||
]
|
||||
),
|
||||
),
|
||||
(
|
||||
"embed_block",
|
||||
wagtail.embeds.blocks.EmbedBlock(
|
||||
help_text="Insert an embed URL e.g https://www.youtube.com/watch?v=SGJFWirQ3ks",
|
||||
icon="media",
|
||||
template="blocks/embed_block.html",
|
||||
),
|
||||
),
|
||||
],
|
||||
blank=True,
|
||||
help_text="Section 1 is a StreamField in a regular FieldPanel",
|
||||
help_text="Use only a minimum number of headings and large blocks.",
|
||||
use_json_field=True,
|
||||
),
|
||||
),
|
||||
("title_2", models.CharField(default="Title 2", max_length=255)),
|
||||
(
|
||||
"section_2",
|
||||
"recipe_headline",
|
||||
wagtail.fields.RichTextField(
|
||||
blank=True, help_text="Keep to a single line", max_length=120
|
||||
),
|
||||
),
|
||||
(
|
||||
"body",
|
||||
wagtail.fields.StreamField(
|
||||
[
|
||||
("char", wagtail.blocks.CharBlock()),
|
||||
(
|
||||
"choice",
|
||||
wagtail.blocks.ChoiceBlock(
|
||||
choices=[
|
||||
("M", "Medium"),
|
||||
("L", "Large"),
|
||||
("XL", "Extra large"),
|
||||
]
|
||||
),
|
||||
),
|
||||
(
|
||||
"list",
|
||||
wagtail.blocks.ListBlock(
|
||||
child_block=wagtail.blocks.CharBlock()
|
||||
),
|
||||
),
|
||||
("page", wagtail.blocks.PageChooserBlock()),
|
||||
("text", wagtail.blocks.TextBlock()),
|
||||
("rich_text", wagtail.blocks.RichTextBlock()),
|
||||
("url", wagtail.blocks.URLBlock()),
|
||||
(
|
||||
"document",
|
||||
wagtail.documents.blocks.DocumentChooserBlock(),
|
||||
),
|
||||
("embed", wagtail.embeds.blocks.EmbedBlock()),
|
||||
("image", wagtail.images.blocks.ImageChooserBlock()),
|
||||
(
|
||||
"table",
|
||||
"heading_block",
|
||||
wagtail.blocks.StructBlock(
|
||||
[
|
||||
("title", wagtail.blocks.CharBlock()),
|
||||
("description", wagtail.blocks.TextBlock()),
|
||||
(
|
||||
"table",
|
||||
wagtail.contrib.typed_table_block.blocks.TypedTableBlock(
|
||||
[
|
||||
(
|
||||
"text",
|
||||
wagtail.blocks.CharBlock(),
|
||||
),
|
||||
(
|
||||
"numeric",
|
||||
wagtail.blocks.FloatBlock(),
|
||||
),
|
||||
(
|
||||
"rich_text",
|
||||
wagtail.blocks.RichTextBlock(),
|
||||
),
|
||||
(
|
||||
"image",
|
||||
wagtail.images.blocks.ImageChooserBlock(),
|
||||
),
|
||||
]
|
||||
"heading_text",
|
||||
wagtail.blocks.CharBlock(
|
||||
form_classname="title", required=True
|
||||
),
|
||||
),
|
||||
]
|
||||
(
|
||||
"size",
|
||||
wagtail.blocks.ChoiceBlock(
|
||||
blank=True,
|
||||
choices=[
|
||||
("", "Select a header size"),
|
||||
("h2", "H2"),
|
||||
("h3", "H3"),
|
||||
("h4", "H4"),
|
||||
],
|
||||
required=False,
|
||||
),
|
||||
),
|
||||
],
|
||||
group="Content",
|
||||
),
|
||||
),
|
||||
(
|
||||
"stream",
|
||||
wagtail.blocks.StreamBlock(
|
||||
"paragraph_block",
|
||||
wagtail.blocks.RichTextBlock(
|
||||
group="Content",
|
||||
icon="pilcrow",
|
||||
template="blocks/paragraph_block.html",
|
||||
),
|
||||
),
|
||||
(
|
||||
"block_quote",
|
||||
wagtail.blocks.StructBlock(
|
||||
[
|
||||
("page", wagtail.blocks.PageChooserBlock()),
|
||||
("embed", wagtail.embeds.blocks.EmbedBlock()),
|
||||
("text", wagtail.blocks.TextBlock()),
|
||||
(
|
||||
"attribute_name",
|
||||
wagtail.blocks.CharBlock(
|
||||
blank=True,
|
||||
label="e.g. Mary Berry",
|
||||
required=False,
|
||||
),
|
||||
),
|
||||
],
|
||||
group="Content",
|
||||
),
|
||||
),
|
||||
(
|
||||
"table_block",
|
||||
wagtail.contrib.table_block.blocks.TableBlock(
|
||||
group="Content"
|
||||
),
|
||||
),
|
||||
(
|
||||
"typed_table_block",
|
||||
wagtail.contrib.typed_table_block.blocks.TypedTableBlock(
|
||||
[
|
||||
("text", wagtail.blocks.CharBlock()),
|
||||
("numeric", wagtail.blocks.FloatBlock()),
|
||||
("rich_text", wagtail.blocks.RichTextBlock()),
|
||||
(
|
||||
"image",
|
||||
wagtail.images.blocks.ImageChooserBlock(),
|
||||
),
|
||||
]
|
||||
],
|
||||
group="Content",
|
||||
),
|
||||
),
|
||||
(
|
||||
"image_block",
|
||||
wagtail.blocks.StructBlock(
|
||||
[
|
||||
(
|
||||
"image",
|
||||
wagtail.images.blocks.ImageChooserBlock(
|
||||
required=True
|
||||
),
|
||||
),
|
||||
(
|
||||
"caption",
|
||||
wagtail.blocks.CharBlock(required=False),
|
||||
),
|
||||
(
|
||||
"attribution",
|
||||
wagtail.blocks.CharBlock(required=False),
|
||||
),
|
||||
],
|
||||
group="Media",
|
||||
),
|
||||
),
|
||||
(
|
||||
"embed_block",
|
||||
wagtail.embeds.blocks.EmbedBlock(
|
||||
group="Media",
|
||||
help_text="Insert an embed URL e.g https://www.youtube.com/watch?v=SGJFWirQ3ks",
|
||||
icon="media",
|
||||
template="blocks/embed_block.html",
|
||||
),
|
||||
),
|
||||
(
|
||||
"ingredients_list",
|
||||
wagtail.blocks.ListBlock(
|
||||
wagtail.blocks.RichTextBlock(
|
||||
features=["bold", "italic", "link"]
|
||||
),
|
||||
group="Cooking",
|
||||
icon="list-ol",
|
||||
max_num=10,
|
||||
min_num=2,
|
||||
),
|
||||
),
|
||||
(
|
||||
"steps_list",
|
||||
wagtail.blocks.ListBlock(
|
||||
wagtail.blocks.StructBlock(
|
||||
[
|
||||
(
|
||||
"text",
|
||||
wagtail.blocks.RichTextBlock(
|
||||
features=["bold", "italic", "link"]
|
||||
),
|
||||
),
|
||||
(
|
||||
"difficulty",
|
||||
wagtail.blocks.ChoiceBlock(
|
||||
choices=[
|
||||
("S", "Small"),
|
||||
("M", "Medium"),
|
||||
("L", "Large"),
|
||||
]
|
||||
),
|
||||
),
|
||||
]
|
||||
),
|
||||
group="Cooking",
|
||||
icon="tasks",
|
||||
max_num=10,
|
||||
min_num=2,
|
||||
),
|
||||
),
|
||||
],
|
||||
blank=True,
|
||||
help_text="Section 2 is a StreamField in a MultiFieldPanel",
|
||||
help_text="The recipe’s step-by-step instructions and any other relevant information.",
|
||||
use_json_field=True,
|
||||
),
|
||||
),
|
||||
|
@ -207,7 +323,7 @@ class Migration(migrations.Migration):
|
|||
bases=("wagtailcore.page",),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="Item",
|
||||
name="RecipePersonRelationship",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
|
@ -222,180 +338,22 @@ class Migration(migrations.Migration):
|
|||
"sort_order",
|
||||
models.IntegerField(blank=True, editable=False, null=True),
|
||||
),
|
||||
("title_1", models.CharField(default="Title 1", max_length=255)),
|
||||
(
|
||||
"section_1",
|
||||
wagtail.fields.StreamField(
|
||||
[
|
||||
("char", wagtail.blocks.CharBlock()),
|
||||
(
|
||||
"choice",
|
||||
wagtail.blocks.ChoiceBlock(
|
||||
choices=[
|
||||
("M", "Medium"),
|
||||
("L", "Large"),
|
||||
("XL", "Extra large"),
|
||||
]
|
||||
),
|
||||
),
|
||||
(
|
||||
"list",
|
||||
wagtail.blocks.ListBlock(
|
||||
child_block=wagtail.blocks.CharBlock()
|
||||
),
|
||||
),
|
||||
("page", wagtail.blocks.PageChooserBlock()),
|
||||
("text", wagtail.blocks.TextBlock()),
|
||||
("rich_text", wagtail.blocks.RichTextBlock()),
|
||||
("url", wagtail.blocks.URLBlock()),
|
||||
(
|
||||
"document",
|
||||
wagtail.documents.blocks.DocumentChooserBlock(),
|
||||
),
|
||||
("embed", wagtail.embeds.blocks.EmbedBlock()),
|
||||
("image", wagtail.images.blocks.ImageChooserBlock()),
|
||||
(
|
||||
"table",
|
||||
wagtail.blocks.StructBlock(
|
||||
[
|
||||
("title", wagtail.blocks.CharBlock()),
|
||||
("description", wagtail.blocks.TextBlock()),
|
||||
(
|
||||
"table",
|
||||
wagtail.contrib.typed_table_block.blocks.TypedTableBlock(
|
||||
[
|
||||
(
|
||||
"text",
|
||||
wagtail.blocks.CharBlock(),
|
||||
),
|
||||
(
|
||||
"numeric",
|
||||
wagtail.blocks.FloatBlock(),
|
||||
),
|
||||
(
|
||||
"rich_text",
|
||||
wagtail.blocks.RichTextBlock(),
|
||||
),
|
||||
(
|
||||
"image",
|
||||
wagtail.images.blocks.ImageChooserBlock(),
|
||||
),
|
||||
]
|
||||
),
|
||||
),
|
||||
]
|
||||
),
|
||||
),
|
||||
(
|
||||
"stream",
|
||||
wagtail.blocks.StreamBlock(
|
||||
[
|
||||
("page", wagtail.blocks.PageChooserBlock()),
|
||||
("embed", wagtail.embeds.blocks.EmbedBlock()),
|
||||
(
|
||||
"image",
|
||||
wagtail.images.blocks.ImageChooserBlock(),
|
||||
),
|
||||
]
|
||||
),
|
||||
),
|
||||
],
|
||||
blank=True,
|
||||
help_text="Section 1 is a StreamField in a regular FieldPanel",
|
||||
use_json_field=True,
|
||||
),
|
||||
),
|
||||
("title_2", models.CharField(default="Title 2", max_length=255)),
|
||||
(
|
||||
"section_2",
|
||||
wagtail.fields.StreamField(
|
||||
[
|
||||
("char", wagtail.blocks.CharBlock()),
|
||||
(
|
||||
"choice",
|
||||
wagtail.blocks.ChoiceBlock(
|
||||
choices=[
|
||||
("M", "Medium"),
|
||||
("L", "Large"),
|
||||
("XL", "Extra large"),
|
||||
]
|
||||
),
|
||||
),
|
||||
(
|
||||
"list",
|
||||
wagtail.blocks.ListBlock(
|
||||
child_block=wagtail.blocks.CharBlock()
|
||||
),
|
||||
),
|
||||
("page", wagtail.blocks.PageChooserBlock()),
|
||||
("text", wagtail.blocks.TextBlock()),
|
||||
("rich_text", wagtail.blocks.RichTextBlock()),
|
||||
("url", wagtail.blocks.URLBlock()),
|
||||
(
|
||||
"document",
|
||||
wagtail.documents.blocks.DocumentChooserBlock(),
|
||||
),
|
||||
("embed", wagtail.embeds.blocks.EmbedBlock()),
|
||||
("image", wagtail.images.blocks.ImageChooserBlock()),
|
||||
(
|
||||
"table",
|
||||
wagtail.blocks.StructBlock(
|
||||
[
|
||||
("title", wagtail.blocks.CharBlock()),
|
||||
("description", wagtail.blocks.TextBlock()),
|
||||
(
|
||||
"table",
|
||||
wagtail.contrib.typed_table_block.blocks.TypedTableBlock(
|
||||
[
|
||||
(
|
||||
"text",
|
||||
wagtail.blocks.CharBlock(),
|
||||
),
|
||||
(
|
||||
"numeric",
|
||||
wagtail.blocks.FloatBlock(),
|
||||
),
|
||||
(
|
||||
"rich_text",
|
||||
wagtail.blocks.RichTextBlock(),
|
||||
),
|
||||
(
|
||||
"image",
|
||||
wagtail.images.blocks.ImageChooserBlock(),
|
||||
),
|
||||
]
|
||||
),
|
||||
),
|
||||
]
|
||||
),
|
||||
),
|
||||
(
|
||||
"stream",
|
||||
wagtail.blocks.StreamBlock(
|
||||
[
|
||||
("page", wagtail.blocks.PageChooserBlock()),
|
||||
("embed", wagtail.embeds.blocks.EmbedBlock()),
|
||||
(
|
||||
"image",
|
||||
wagtail.images.blocks.ImageChooserBlock(),
|
||||
),
|
||||
]
|
||||
),
|
||||
),
|
||||
],
|
||||
blank=True,
|
||||
help_text="Section 2 is a StreamField in a MultiFieldPanel",
|
||||
use_json_field=True,
|
||||
),
|
||||
),
|
||||
(
|
||||
"page",
|
||||
modelcluster.fields.ParentalKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="items",
|
||||
related_name="recipe_person_relationship",
|
||||
to="recipes.recipepage",
|
||||
),
|
||||
),
|
||||
(
|
||||
"person",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="person_recipe_relationship",
|
||||
to="base.person",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"ordering": ["sort_order"],
|
||||
|
|
|
@ -1,73 +1,152 @@
|
|||
from django.db import models
|
||||
from modelcluster.fields import ParentalKey
|
||||
from wagtail.admin.panels import FieldPanel, InlinePanel, MultiFieldPanel
|
||||
from wagtail.fields import StreamField
|
||||
from wagtail.admin.panels import FieldPanel, HelpPanel, InlinePanel, MultiFieldPanel
|
||||
from wagtail.fields import RichTextField, StreamField
|
||||
from wagtail.models import Orderable, Page
|
||||
from wagtail.search import index
|
||||
|
||||
from .blocks import BLOCKS
|
||||
from bakerydemo.base.blocks import BaseStreamBlock
|
||||
from bakerydemo.base.models import Person
|
||||
|
||||
from .blocks import RecipeStreamBlock
|
||||
|
||||
|
||||
class RecipePersonRelationship(Orderable, models.Model):
|
||||
"""
|
||||
This defines the relationship between the `Person` within the `base`
|
||||
app and the RecipePage below. This allows people to be added to a RecipePage.
|
||||
|
||||
We have created a two way relationship between RecipePage and Person using
|
||||
the ParentalKey and ForeignKey
|
||||
"""
|
||||
|
||||
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,
|
||||
)
|
||||
panels = [FieldPanel("person")]
|
||||
|
||||
|
||||
class RecipePage(Page):
|
||||
"""
|
||||
RecipePage, to illustrate some Recipe Wagtail scenario's
|
||||
Recipe pages are more complex than blog pages, demonstrating more advanced StreamField patterns.
|
||||
"""
|
||||
|
||||
title_1 = models.CharField(max_length=255, default="Title 1")
|
||||
section_1 = StreamField(
|
||||
BLOCKS,
|
||||
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(),
|
||||
# Demonstrate block_counts to keep the backstory concise.
|
||||
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="Section 1 is a StreamField in a regular FieldPanel",
|
||||
help_text="Use only a minimum number of headings and large blocks.",
|
||||
)
|
||||
title_2 = models.CharField(max_length=255, default="Title 2")
|
||||
section_2 = StreamField(
|
||||
BLOCKS,
|
||||
|
||||
# An example of using rich text for single-line content.
|
||||
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="Section 2 is a StreamField in a MultiFieldPanel",
|
||||
help_text="The recipe’s step-by-step instructions and any other relevant information.",
|
||||
)
|
||||
|
||||
content_panels = Page.content_panels + [
|
||||
FieldPanel("title_1"),
|
||||
FieldPanel("section_1"),
|
||||
FieldPanel("date_published"),
|
||||
# Using `title` to make a field larger.
|
||||
FieldPanel("subtitle", classname="title"),
|
||||
MultiFieldPanel(
|
||||
[
|
||||
FieldPanel("title_2"),
|
||||
FieldPanel("section_2"),
|
||||
# Example use case for HelpPanel.
|
||||
HelpPanel(
|
||||
"Refer to keywords analysis and correct international ingredients names to craft the best introduction backstory, and headline."
|
||||
),
|
||||
FieldPanel("introduction"),
|
||||
# StreamField inside a MultiFieldPanel.
|
||||
FieldPanel("backstory"),
|
||||
FieldPanel("recipe_headline"),
|
||||
],
|
||||
heading="MultiFieldPanel for section 2",
|
||||
heading="Preface",
|
||||
),
|
||||
FieldPanel("body"),
|
||||
InlinePanel(
|
||||
"items", label="Items", help_text="Related items via an inline panel"
|
||||
"recipe_person_relationship",
|
||||
heading="Authors",
|
||||
label="Author",
|
||||
help_text="Select between one and three authors",
|
||||
panels=None,
|
||||
min_num=1,
|
||||
max_num=3,
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
class Item(Orderable):
|
||||
page = ParentalKey(RecipePage, on_delete=models.CASCADE, related_name="items")
|
||||
title_1 = models.CharField(max_length=255, default="Title 1")
|
||||
section_1 = StreamField(
|
||||
BLOCKS,
|
||||
blank=True,
|
||||
use_json_field=True,
|
||||
help_text="Section 1 is a StreamField in a regular FieldPanel",
|
||||
)
|
||||
title_2 = models.CharField(max_length=255, default="Title 2")
|
||||
section_2 = StreamField(
|
||||
BLOCKS,
|
||||
blank=True,
|
||||
use_json_field=True,
|
||||
help_text="Section 2 is a StreamField in a MultiFieldPanel",
|
||||
)
|
||||
|
||||
panels = [
|
||||
FieldPanel("title_1"),
|
||||
FieldPanel("section_1"),
|
||||
MultiFieldPanel(
|
||||
[
|
||||
FieldPanel("title_2"),
|
||||
FieldPanel("section_2"),
|
||||
],
|
||||
heading="MultiFieldPanel for section 2",
|
||||
),
|
||||
search_fields = Page.search_fields + [
|
||||
index.SearchField("backstory"),
|
||||
index.SearchField("body"),
|
||||
]
|
||||
|
||||
def authors(self):
|
||||
"""
|
||||
Returns the BlogPage's related people. Again note that we are using
|
||||
the ParentalKey's related_name from the BlogPersonRelationship model
|
||||
to access these objects. This allows us to access the Person objects
|
||||
with a loop on the template. If we tried to access the blog_person_
|
||||
relationship directly we'd print `blog.BlogPersonRelationship.None`
|
||||
"""
|
||||
return Person.objects.filter(live=True, person_recipe_relationship__page=self)
|
||||
|
||||
# Specifies parent to Recipe as being RecipeIndexPages
|
||||
parent_page_types = ["RecipeIndexPage"]
|
||||
|
||||
# Specifies what content types can exist as children of RecipePage.
|
||||
# Empty list means that no child content types are allowed.
|
||||
subpage_types = []
|
||||
|
||||
|
||||
class RecipeIndexPage(Page):
|
||||
"""
|
||||
Index page for recipe.
|
||||
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"),
|
||||
]
|
||||
|
||||
# Specifies that only RecipePage objects can live under this index page
|
||||
subpage_types = ["RecipePage"]
|
||||
|
||||
# Defines a method to access the children of the page (e.g. RecipePage
|
||||
# objects).
|
||||
def children(self):
|
||||
return self.get_children().specific().live()
|
||||
|
||||
# Overrides the context to list all child items, that are live, by the
|
||||
# date that they were published
|
||||
# https://docs.wagtail.org/en/stable/getting_started/tutorial.html#overriding-context
|
||||
def get_context(self, request):
|
||||
context = super(RecipeIndexPage, self).get_context(request)
|
||||
context["recipes"] = (
|
||||
RecipePage.objects.descendant_of(self).live().order_by("-date_published")
|
||||
)
|
||||
return context
|
||||
|
|
|
@ -54,6 +54,7 @@ INSTALLED_APPS = [
|
|||
"wagtail.admin",
|
||||
"wagtail.api.v2",
|
||||
"wagtail.locales",
|
||||
"wagtail.contrib.table_block",
|
||||
"wagtail.contrib.typed_table_block",
|
||||
"wagtail.contrib.modeladmin",
|
||||
"wagtail.contrib.routable_page",
|
||||
|
|
|
@ -404,6 +404,17 @@ blockquote .attribute-name {
|
|||
height: 100%;
|
||||
}
|
||||
|
||||
/* stylelint-disable-next-line selector-class-pattern */
|
||||
:is(.block-table_block, .block-typed_table_block) :is(table, tr, td, th) {
|
||||
border: 1px solid currentColor;
|
||||
padding: 0.25em;
|
||||
}
|
||||
|
||||
/* stylelint-disable-next-line selector-class-pattern */
|
||||
:is(.block-table_block, .block-typed_table_block) table {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
.header,
|
||||
.footer {
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
{{ self.text }}
|
|
@ -0,0 +1,18 @@
|
|||
{% extends "base.html" %}
|
||||
{% load wagtailcore_tags navigation_tags wagtailimages_tags %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
<div class="blog-list">
|
||||
{% if recipes %}
|
||||
{% for recipe in recipes %}
|
||||
{% include "includes/card/blog-listing-card.html" with blog=recipe %}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<div class="col-md-12">
|
||||
<p>Oh, snap. Looks like we were too busy baking to write any recipes. Sorry.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock content %}
|
|
@ -0,0 +1,40 @@
|
|||
{% extends "base.html" %}
|
||||
{% load navigation_tags wagtailimages_tags wagtailcore_tags %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% image self.image fill-1920x600 as hero_img %}
|
||||
{% include "base/include/header-blog.html" %}
|
||||
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
<div class="blog__meta">
|
||||
{% if page.authors %}
|
||||
<div class="blog__avatars">
|
||||
{% for author in page.authors %}
|
||||
<div class="blog__author">{% image author.image fill-50x50-c100 class="blog__avatar" %}
|
||||
{{ author.first_name }} {{ author.last_name }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if page.backstory %}
|
||||
{{ page.backstory }}
|
||||
|
||||
<hr>
|
||||
{% endif %}
|
||||
|
||||
{# Give a label to the recipe section for screen reader users. #}
|
||||
<div id="recipe-headline" class="sr-only">
|
||||
Recipe{% if page.recipe_headline %}: {{ page.recipe_headline|richtext }}{% endif %}
|
||||
</div>
|
||||
|
||||
<section aria-labelledby="recipe-headline">
|
||||
{{ page.body }}
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock content %}
|
|
@ -1,14 +0,0 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
{% include "base/include/header-hero.html" %}
|
||||
|
||||
<div class="container bread-detail">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<p>Recipe page is about admin capabilities and does not render content.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock content %}
|
Ładowanie…
Reference in New Issue