[Django] 08 - StreamField of Wagtail

 怎么说呢,streamfield 太赞了!

 

 

用户页面

 

一、基本套路

  • 定义

(1) 添加 app: streams,

(2) 新建 一个文件 blocks.py

(3) 定义 各种 block 类,如下: TitleAndTextBlock。

""" Streamfields live in here. """

from wagtail.core import blocks


class TitleAndTextBlock(blocks.StructBlock):
    """ Title and text and nothing else. """
    title = blocks.CharBlock(required=True, help_text='Add your title')
    text  = blocks.TextBlock(required=True, help_text='Add additional text')

    class Meta:  # noqa
        template = "streams/title_and_text_block.html"
        icon     = "edit"
        label    = "Title & Text"

 

  • 调用

只是在 flex/models.py 中添加上这项就好了。

from django.db import models

from wagtail.admin.edit_handlers import FieldPanel, StreamFieldPanel
from wagtail.core.models import Page
from wagtail.core.fields import StreamField

from streams import blocks


class FlexPage(Page):
    """Flexible page class."""

    template = "flex/flex_page.html"

# --------------------------------------------------------------------
content = StreamField( [ ('title_and_text', blocks.TitleAndTextBlock())  # <---- ], null = True, blank = True ) subtitle = models.CharField(max_length=100, null=True, blank=True)
# --------------------------------------------------------------------
content_panels = Page.content_panels + [ FieldPanel("subtitle"), StreamFieldPanel("content") ]
# --------------------------------------------------------------------
class Meta: # noaq verbose_name = "Flex Page" verbose_name_plural = "Flex Pages"

 

二、自定义 "富文本"

class RichTextBlock(blocks.RichTextBlock):
    """ Rick text and nothing else. """

    # 默认就提供了一个富文本编辑框
    class Meta:  # noqa
        template = "streams/richtext_block.html"
        icon = "doc-full"
        label = "Full RichText

 

三、自定义 "图卡"

  • admin

class CardBlack(blocks.StructBlock):
    """ Cards with image and text and button(s) """
    title = blocks.CharBlock(required=True, help_text='Add your title')

    cards = blocks.ListBlock(
        blocks.StructBlock(
            [
                ("image", ImageChooserBlock(required=True)),
                ("title", blocks.CharBlock(required=True, max_length=40)),
                ("text", blocks.TextBlock(required=True, max_length=200)),
                ("button_page", blocks.PageChooserBlock(required=True)),
                ("button_url", blocks.URLBlock(required=False)),  # 与 button_page是“或”的关系
            ]
        )
    )

    class Meta:  # noqa
        template = "streams/card_block.html"
        icon = "placeholder"
        label = "Staff Cards"

 

  • client

页面如何实现?

Goto: https://getbootstrap.com/docs/4.2/components/card/【直接找example代码即可】

<div class="card" style="width: 18rem;">
  <img src="..." class="card-img-top" alt="...">
  <div class="card-body">
    <h5 class="card-title">Card title</h5>
    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
    <a href="#" class="btn btn-primary">Go somewhere</a>
  </div>
</div>
View Code

 

四、Call To Action

与 card 的sub-block非常像,没有图片而已。

Creating a Call to Action StreamField in Wagtail CMS using a StructBlock

 

五、Saved in the Database

设计 rest api 时,考虑的“表”。

streamfield的数据库存储格式是JSON。

[
  {
    "type": "title_and_embed",
    "value": {
      "title": "Canva Embedded link",
      "text": "js + iframe code"
    },
    "id": "7a104840-c03a-41dc-b539-ff6ea1c06fde"
  },
  {
    "type": "title_and_embed",
    "value": {
      "title": "Google slices link",
      "text": "iframe code"
    },
    "id": "f834e380-e75d-4a8c-b5d6-8366cc9d6a59"
  }
]

 

六、Draftail RichText (WYSIWYG)

Ref: Wagtail CMS: How to extend the Draftail RichText (WYSIWYG) Editor

 

  • 减少选项

通过 重写 self.features,

# 自定义的富文本特性
class SimpleRichtextBlock(blocks.RichTextBlock):
    """Richtext without (limited) all the features."""

    # 这里实际上就是重写覆盖
    def __init__(self, required=True, help_text=None, editor='default',
                 features=None, **kwargs):
        super().__init__(**kwargs)
        self.features = ["bold", "italic", "link"]  # <---- 这里做了限制

    class Meta:  # noqa
        template = "streams/richtext_block.html"
        icon = "edit"
        label = "Simple RichText"

 

  • 添加选项

貌似也是react.js实现的,这里使用hook的方式,添加新的选项。

这个则要保证:setting中添加 app: 'streams'。

 

[ CODE ]

@hooks.register("register_rich_text_features")
def register_code_styling(features):
    """Add the <code> to the richtext editor and page."""

    # Step 1
    feature_name = "code"
    type_ = "CODE"
    tag = "code"

        # Step 2
        control = {
            "type": type_,
            "label": "</>",
            "description": "Code"
        }

    # Step 3
    features.register_editor_plugin(
        "draftail", feature_name, draftail_features.InlineStyleFeature(control)
    )

        # Step 4
        db_conversion = {
            "from_database_format": {tag: InlineStyleElementHandler(type_)},
            "to_database_format": {"style_map": {type_: {"element": tag}}}
        }

    # Step 5
    features.register_converter_rule("contentstate", feature_name, db_conversion)

    # Step 6. This is optional
    # This will register this feature with all richtext editors by default
    features.default_features.append(feature_name)

  

[ HTML ]

@hooks.register("register_rich_text_features")
def register_centertext_feature(features):
    """Creates centered text in our richtext editor and page."""

    # Step 1
    feature_name = "center"
    type_ = "CENTERTEXT"
    tag = "div"

        # Step 2
        control = {
            "type": type_,
            "label": "Center",
            "description": "Center Text",
            "style": {
                "display": "block",
                "text-align": "center",
            },
        }

    # Step 3
    features.register_editor_plugin(
        "draftail", feature_name, draftail_features.InlineStyleFeature(control)
    )

        # Step 4
        db_conversion = {
            "from_database_format": {tag: InlineStyleElementHandler(type_)},
            "to_database_format": {
                "style_map": {
                    type_: {
                        "element": tag,
                        "props": {
                            "class": "d-block text-center"
                        }
                    }
                }
            }
        }

    # Step 5
    features.register_converter_rule("contentstate", feature_name, db_conversion)

    # Step 6, This is optional.
    features.default_features.append(feature_name)

 

  

 

Home页

Ref: How to use Orderables in Wagtail CMS【美化自己的主页】

 

一、添加"图片滑动栏" 

  • 定义 model

文件 ./home/models.py,可以看成是一个“子组件”的定义。

# 主页的图片栏
class HomePageCarouselImages(Orderable):
    """Between 1 and 5 images for the home page carousel."""
# inline到HomePage上的控件 page = ParentalKey("home.HomePage", related_name="carousel_images")  # 体现了 inline 的特性 carousel_image = models.ForeignKey( "wagtailimages.Image", null=True, blank=False, on_delete=models.SET_NULL, related_name="+", ) panels = [ ImageChooserPanel("carousel_image") ]

 

  • 编写 template

样例代码:https://getbootstrap.com/docs/4.2/components/carousel/ 

    <div id="carouselExampleControls" class="carousel slide" data-ride="carousel">
        <div class="carousel-inner">
            {% comment %} 结合model,这里体现了 carousel_images 与 carousel_image 的关系 {% endcomment %}
            {% for loop_cycle in self.carousel_images.all %}
                {% image loop_cycle.carousel_image fill-900x400 as img %}
                {% comment %} 注意这里的 active  {% endcomment %}
                <div class="carousel-item{% if forloop.counter == 1 %} active{% endif %}">
                    <img src="{{ img.url }}" class="d-block w-100" alt="{{ img.alt }}">
                </div>
            {% endfor %}
        </div>
        <a class="carousel-control-prev" href="#carouselExampleControls" role="button" data-slide="prev">
            <span class="carousel-control-prev-icon" aria-hidden="true"></span>
            <span class="sr-only">Previous</span>
        </a>
        <a class="carousel-control-next" href="#carouselExampleControls" role="button" data-slide="next">
            <span class="carousel-control-next-icon" aria-hidden="true"></span>
            <span class="sr-only">Next</span>
        </a>
    </div>

可见,若干图片,依次 active 其中的一张,就显示了出来。

 

二、添加"主页内容"

  • 定义 model

因为是StreamField,添加多个block;

到底提供了多少个block类型呢?这里只提供了一个:CTABlock。

    # 5. 内容栏
    content = StreamField(
        [
            ("cta", blocks.CTABlock()),
        ],
        null=True,
        blank=True,
    )

效果图。

 

  • 编写 template

样例代码:https://getbootstrap.com/docs/4.2/components/carousel/

    {% for block in page.content %}
        {% include_block block %}
    {% endfor %}

 

三、集成各种 Panels

注意类中的这几个变量的对应关系。按规则办事即可。

    # Finally, 再将 以上的内容 依次 加入到 orm model (数据库)中
    # 通过 MultiFieldPanel对admin的组件进行了layout updated.
    content_panels = Page.content_panels + [
        MultiFieldPanel([
            FieldPanel("banner_title"),
            FieldPanel("banner_subtitle"),
            ImageChooserPanel("banner_image"),
            PageChooserPanel("banner_cta"),
        ], heading="Banner Options"),

        MultiFieldPanel([
            InlinePanel("carousel_images"),
        ], heading="Carousel Images"),

        StreamFieldPanel("content"),
    ]

也可以给 content 加个 MultiFieldPanel,看上去,更为一致。

 

 

 

Custom StreamField "Logic"

Ref: Wagtail CMS: Adding Custom StreamField Logic

一、过去的问题

希望能将逻辑写在model里,而不是模板里。

[古板写法]

{% load wagtailimages_tags %}

<div class="container mb-sm-5 mt-sm-5">
    <h1 class="text-center mb-sm-5">{{ self.title }}</h1>
    <div class="card-deck">
        {% for card in self.cards %}
            {% image card.image fill-300x200 as img %}
            <div class="card">
                <img src="{{ img.url }}" alt="{{ img.alt }}" class="card-img-top" />
                <div class="card-body">
                    <h5 class="card-title">{{ card.title }}</h5>
                    <p class="card-text">{{ card.text }}</p>
--------------------------- 以下使用了 if ... else ---------------- {% if card.button_page %} <a href="{{ card.button_page.url }}" class="btn btn-primary"> Learn More </a> {% elif card.button_url %} <a href="{{ card.button_url }}" class="btn btn-primary"> Learn More </a> {% endif %}
-----------------------------------------------------------------
</div> </div> {% endfor %} </div> </div>

 

二、添加 button block

  • block.py 内新增 ButtonBlock

有一点要注意,block的角色是子模型,而不是主模型,对应了一个 page。

class LinkStructValue(blocks.StructValue):
    """Additional logic for our urls."""

    def url(self):
        button_page = self.get('button_page')
        button_url = self.get('button_url')
        if button_page:
            return button_page.url
        elif button_url:
            return button_url

        return None

    # def latest_posts(self):
    #     return BlogDetailPage.objects.live()[:3]


class ButtonBlock(blocks.StructBlock):
    """An external or internal URL."""

    button_page = blocks.PageChooserBlock(required=False, help_text='If selected, this url will be used first')
    button_url  = blocks.URLBlock(required=False, help_text='If added, this url will be used secondarily to the button page')

    # def get_context(self, request, *args, **kwargs):
    #     context = super().get_context(request, *args, **kwargs)
    #     context['latest_posts'] = BlogDetailPage.objects.live().public()[:3]
    #     return context

    class Meta:  # noqa
        template = "streams/button_block.html"
        icon = "placeholder"
        label = "Single Button"
        value_class = LinkStructValue

 

    • 定义 模板

[改进写法]

可见,逻辑转移到了 LinkStructValue 函数中。

<div class="container mb-sm-5 mt-sm-5">
    <div class="row">
        <div class="col-md-5 offset-md-1 col-sm-12 text-center">
            <h1>
                <a href="{{ self.url }}">{{ self.url }}</a>
            </h1>
        </div>
    </div>
</div>

 

  • 使用 ButtonBlock

    content = StreamField(
        [
            ("title_and_text", blocks.TitleAndTextBlock()),
            ("full_richtext", blocks.RichtextBlock()),
            ("simple_richtext", blocks.SimpleRichtextBlock()),
            ("cards", blocks.CardBlock()),
            ("cta", blocks.CTABlock()),
            ("button", blocks.ButtonBlock()),
        ],
        null=True,
        blank=True
    )

 

End.

posted @ 2020-12-19 19:26  郝壹贰叁  阅读(294)  评论(0编辑  收藏  举报