ヘッドレスキグテール、何が痛みポイントですか?


熟したNCCで昨年、我々は書き直しましたRIPE Labs Wagtail、Djangoに基づいてCMSを使用します.それは、Djangoテンプレートでサーバー側レンダリングHTMLを吐き出すモノリシックアプリケーションで終わりました.
今年、我々はWagtailを我々の書換えに再訪問していますmain site そして、私はバックエンドとフロントエンドのデカップリングを提案しました.
このような構造を提案したのは、次のような反応とメタフレームワークを利用することである.JSまたはギャツビー.それは私がよりよく知っているものであるので、私はほとんど反応の点で話します、しかし、我々もVueを評価しました、そして、それは等しくよく適用されます.
そのようなプロジェクトをどのように設定するかをよりよく理解するためにThe Definitive Guide to Next.js and Wagtail マイケルYinとこれによってboilerplate repo フロジュによって.私はあなたがレポ最初にチェックアウトをお勧めし、本を得る場合は、さらに説明が必要です.
この一連のポストでは、WagtailとプロジェクトをヘッドレスCMSとして設定する方法を紹介します.フロントエンドのJS私は痛みのポイントを強調するために最善を尽くしますので、このセットアップの正直な評価を出すことができます.
Here's a link to the companion repo that contains all the code from these posts .
あなたが熟したNCCで我々の選択について興味があるならば、我々は今度もdjangoテンプレートを使います.

JavaScriptフレームワークを使う理由は?
まず最初に、なぜ私はJavaScriptのフレームワークを使用したいのですか?両方のアプローチの良い理由があります.
UIを再利用可能なコンポーネントに分解すると、一貫性のあるUIの開発が容易になります.さらに、カプセル化されたコンポーネントは個々にテストするのがより簡単です、そして、あなたがDjangoテンプレートエンジンが決してキャッチしないビルド時間で、あなたがエラーを捕えますtypescriptの助けを借りて.
フリップ側は、それが少し複雑さを増やすということです:Djangoテンプレートは、セッションと認証のようなジャンゴの他のすべての特徴で、箱から働きます.
第二に、次のようなメタフレームワーク.JSまたはGatsbyはDXのテンプレートよりもはるかに良い、ローカルのフロントエンドの開発に大きなDXを提供します.より重要なのは、事前にページをレンダリングし、静的なビルドを生産する彼らの能力は、ヘッドレスのCMSに最適です.

ワタシからのJSONのサービング
を、十分な話!何をする必要がありますあなたのWagtailセットアップでそれを完全にヘッドレスに変更する必要がありますか?
ワタシはan optional module これは公開、読み取り専用、JSONフォーマットAPIを公開します.しかし、それは最も直感的ではない、それはまだすべてのあなたのページのHTMLテンプレートを仮定します.
デフォルトではwagtail.core.Page ページのレンダリングに関するメソッド serve , を返します.TemplateResponse Wagtail 2.16.1現在.
class Page(...):
    ...
    def serve(self, request, *args, **kwargs):
        request.is_preview = getattr(request, "is_preview", False)

        return TemplateResponse(
            request,
            self.get_template(request, *args, **kwargs),
            self.get_context(request, *args, **kwargs),
        )
私は、すべてのWagtail/api/ このメソッドをオーバーライドして代わりにJSONを返します.このように、あなたのフロントエンドパスはあなたのヘッドレスCMSで1 : 1をマップできます.別のサブドメインでWagtailを実行することもできます.
例えば、パスを持つページ/careers が一致する/api/careers WagtailからJSON形式でページの内容を返します.
ヘッダーでの要求の下の実装でContent-Type: application/json to /api/careers JSONを受け付けないリクエストは、JSONレスポンスを受け取ります/careers .
# models.py
class BasePage(Page):
    class Meta:
        abstract = True

    ...

    def serve(self, request, *args, **kwargs):
        """
        If the request accepts JSON, return an object with all
        the page's data. Otherwise redirect to the rendered frontend.
        """
        if request.content_type == "application/json":
            # this is very important, we'll see why later
            response = self.serialize_page()
            return JsonResponse(response)
        else:
            full_path = request.get_full_path()
            return HttpResponseRedirect(
                urllib.parse.urljoin(
                    settings.BASE_URL,
                    full_path.replace("/api", ""),
                )
            )
これが動作する前に、Django RESTフレームワークを使用してカスタムSerializersを定義する必要があります.ほとんどの部分については、これは大きな取引は、我々はlast section , リッチテキストではいくつかの調整が必要です.

Wagtailのリッチテキストのレンダリング
WagtailはJSON API用の組み込みモジュールを持っているので、それらのシリアライザーを再利用できると思いますStreamField !), しかし、そうすることでリッチテキストの内部データベース表現が返されます.
内部オブジェクトの表現は次のようになります.
<!-- Link to another page -->
<a linktype="page" id="3">Contact us</a>

<!-- Embedded image -->
<embed embedtype="image" id="10" alt="A pied wagtail" format="left" />
実際のURLと画像は、|richtext テンプレートフィルタexpand_db_html 機能からwagtail.core.rich_text .
それで、あなたはget_api_representation メソッドRichTextBlock このように
# blocks.py
from wagtail.core.blocks import RichTextBlock
from wagtail.core.rich_text import expand_db_html

class CustomRichTextBlock(RichTextBlock):
    def get_api_representation(self, value, context=None):
        return expand_db_html(value.source).replace("/api", "")
同様にRichTextField , 次のようにカスタムフィールドシリアライザーを使います.
# fields.py
from rest_framework.fields import Field
from wagtail.core.rich_text import expand_db_html


class CustomRichTextField(Field):
    def to_representation(self, value):
        return expand_db_html(value).replace("/api", "")
あなたがprependedしたならば、注意してください/api Wagtail URLには、フロントエンドでレンダリングするときにプレフィックスを削除します.

Django RESTフレームワークによるページ直列化器の記述
JSONを吐き出す前に必要な最後のものはシリアライザーです.serializersとは何ですか?
Serializers allow complex data such as querysets and model instances to be converted to native Python datatypes that can then be easily rendered into JSON

我々の要約のためにシリアライザーを作成することによって、行動でそれを見ましょうBasePage . すべてのモデルがこのクラスから継承するので、すべてのページが同じメタデータを返すように、ここで一般のフィールドを加えることができます.
# serializers.py
from rest_framework import serializers

from .models import BasePage


class BasePageSerializer(serializers.ModelSerializer):
    class Meta:
        model = BasePage
        fields = ("id", "slug", "title", "url", "first_published_at")
これまでのところ、これらのフィールドをシリアル化するのは簡単です.なぜなら、これらのフィールドは文字列、整数、そしてdatetimeオブジェクトです.
しかし、Wagtailページの主要なビルディングブロックのうちの1つはStreamField そして、ジャンゴは箱からそれを扱うことができません、しかし、幸運にもワタシはそうすることができます.シリアル化を更新しましょうStreamField フィールド.
# serializers.py
from rest_framework import serializers
from wagtail.api.v2 import serializers as wagtail_serializers
from wagtail.core import fields

from .models import BasePage


class BasePageSerializer(serializers.ModelSerializer):
    serializer_field_mapping = serializers.ModelSerializer.serializer_field_mapping.copy()
    serializer_field_mapping.update({
       fields.StreamField: wagtail_serializers.StreamField,
    })

    class Meta:
        model = BasePage
        fields = ("id", "slug", "title", "url", "first_published_at")
我々は今我々が我々の最初の非抽象モデルを作成するために必要なすべてを持っています.

すべてをまとめる

すべてと言われて、私たちのサイトに記事を追加するページモデルを作成しましょう.私たちの記事は今のところ簡単になります:彼らは要約と本文と豊富なテキストとイメージがあります.
# models.py
from wagtail.core.fields import StreamField
from wagtail.images.blocks import ImageChooserBlock

from .blocks import CustomRichTextBlock

...
class ArticlePage(BasePage):
    summary = models.CharField(max_length=300)
    body = StreamField(
        [
            ("paragraph", CustomRichTextBlock()),
            ("image", ImageChooserBlock(icon="image")),
        ],
    )

    content_panels = [
        FieldPanel("title"),
        FieldPanel("summary", widget=forms.Textarea(attrs={"rows": "4"})),
        StreamFieldPanel("body"),
    ]
我々が加える各々のモデルがそれ自身のシリアライザーを必要とするのを思い出してください.を加えましょうArticlePageSerializer です.なぜなら、モデルはBasePage , 我々は、拡張する必要がありますMeta.fields 2つの新しいフィールドをArticlePage .
以来summary 文字列は、ジャンゴレストは自動的に処理されます以降bodyStreamField , BasePageSerializer 既にそれを処理する方法を知っている.
# serializers.py
from .models import ArticlePage
...
class ArticlePageSerializer(BasePageSerializer):
    class Meta:
        model = ArticlePage
        fields = BasePageSerializer.Meta.fields + (
            "summary",
            "body",
        )
これを実行してみたらエラーが出ます.我々は定義していないserialize_page メソッドオンBasePage . 我々は今それを書くつもりです、そして、それはパズルの最も重要な部分です.
# models.py
class BasePage(Page):
    ...
     serializer_class = None

     def serialize_page(self):
         if not self.serializer_class:
             raise Exception(
                  f"serializer_class is not set {self.__class__.__name__}",
              )
         serializer_class = import_string(self.serializer_class)
         return {
             "type": self.__class__.__name__,
             "data": serializer_class(self).data,
         }
    ...


class ArticlePage(BasePage):
      serializer_class = "your_app.serializers.ArticlePageSerializer"
    ...
ここにたくさん行くので、私はそれを壊させてください.
  • 私たちはserializer_class プロパティNone Serializersとしてデフォルトでは抽象モデルでは動作しません.
  • 私たちはserializer_class プロパティArticlePage への道ArticlePageSerializer 前に付け加えた.
  • インserialize_page シリアライザーを動的にインポートし、現在のオブジェクトをシリアル化します.
  • クラス名を返します"type" , このガイドの次の部分でこれ以上.
  • 最終結果はserialize_page これらの行に沿って辞書を返します.
    {
      "type": "ArticlePage",
      "data": {
        "id": 3,
        "slug": "my-article",
        "title": "My article",
        "url": "/api/my-article/",
        "first_published_at": "2022-02-25T15:14:13.030300Z",
        "summary": "My article's summary",
        "body": [
          {
            "type": "paragraph",
            "value": "<p data-block-key=\"itzwt\">This is my article</p>",
            "id": "4519e337-b467-44dd-a075-d2db4df0f0c8"
          }
        ]
      }
    }
    
    And BasePage.serve リクエストを送信する場合、JSONフォームでそれを返します/api/my-article .

    結論
    これは私のガイドの最初の部分で、WagtailをヘッドレスCMSとして走らせています.この最初の部分では、Wagtailの既定の動作をテンプレートのレンダリングの代わりにJSONを返すように変更する方法を見ました.
    このガイドの次のインストールでは、このJSON APIを消費するフロントエンドを設定する方法を詳しく見ていきます.
    もともとブログで投稿しました.https://tommasoamici.com/blog/headless-wagtail-what-are-the-pain-points