Djangoでlorem ipsum?

2024/02/15に公開

はじめに

ナイトウ(@engineer_naito)と申します。

とあるDjangoの記事を書いています。
その記事を書くためにいろいろ調べものをしていたときに {% lorem %} テンプレートタグを見つけました。
この機能についてこの記事では深掘りしていきます。

lorem ipsum とは

https://en.wikipedia.org/wiki/Lorem_ipsum

In publishing and graphic design, Lorem ipsum (/ˌlɔː.rəm ˈɪp.səm/) is a placeholder text commonly used to demonstrate the visual form of a document or a typeface without relying on meaningful content.

文章などの表示の確認のために用いられるプレースホルダー(ダミーテキスト)のことです。
lorem ipsum はラテン語由来の全く意味を持たない文章の羅列です。(そのような文章を "greeking" というそうです。)

デザインのプロトタイプ制作の際などに、正式な文章は完成していないけど、文章の 見た目 を確認、調整するときにダミーテキストを用いることが多くあります。

https://www.lipsum.com/
では、lorem ipsum を生成することができます。
段落数、単語数、文字数、リスト数(?)を指定することができます。

https://www.lipsum.com/

https://loripsum.net/
では、lorem ipsum を生成するWeb APIが提供されています。

https://loripsum.net/

Djangoの lorem ipsum

Djangoの組み込みテンプレートタグ

https://docs.djangoproject.com/en/5.0/ref/templates/builtins/#built-in-tag-reference
にあるように、Djangoには多くの組み込みテンプレートタグが存在します。

  • if文(条件分岐): {% if %}
  • for文(繰り返し): {% for %}
  • csrf対策: {% csrf_token %}
  • テンプレートの継承: {% extends %}

など、便利な(ないと困る?)ものがたくさん用意されています。

テンプレートタグは {% %} で囲まれてテンプレート上で表現されているもので、自作することもできます。
しかし、組み込みで多くの機能が提供されているので、自分でテンプレートタグを定義することはそこまで多くないと思っています。

ぼく「 {% lorem %} もあるの?」

ぼくはDjangoの自作テンプレートタグの例を用意したくて、いろいろ考えていました。
ググったり対話型AIくんたちに聞いてもあんまりいいアドバイスをくれませんでした。

ぼくが最初に思い付いたのはレンダリングされた時刻を表示する機能を自作しようと思いつきました。
しかし、すでに組み込みで存在していました。
https://docs.djangoproject.com/en/5.0/ref/templates/builtins/#now

{% now "F jS Y H:i" %}

のようにフォーマットも指定でき、

2月 9日 2024 09:00

のようにレンダリングされます。

次に思い付いたのが lorem ipsum でした。
ぼくがDjangoを業務で利用していた際には、ダミーデータを手で作っていました。
(50文字必要であれば「あああああ」を10回コピペするなどしてました。)

知りませんでした。
Djangoに lorem ipsum があったなんて。
https://docs.djangoproject.com/en/5.0/ref/templates/builtins/#lorem

{% lorem %}

{% lorem %} の使い方

lorem タグはオプションの引数を3つとります。

{% lorem [count] [method] [random] %}
  • count
    生成する段落数、文字数を指定できます。
    (デフォルトは1。)

  • method
    w, p, b の3種類の文字が認められています。
    (デフォルトは b。)

    • w: 単語
    • p: HTMLのpタグでの段落
    • b: プレーンテキストでの段落
  • random
    random と指定することでランダムなラテン語を出力するようになります。

{% lorem %} の例

<!-- Lorem Ipsum の最初から5単語 -->
{% lorem 5 w %}

<!-- ランダムなラテン語を2段落(p要素 * 2) -->
{% lorem 2 p random %}

<!-- Lorem Ipsum の最初から2段落(1つのテキストに連結) -->
{% lorem 2 b %}

{% lorem %} の実装

ぼくは2個目の案がボツになって気落ちしましたが、{% lorem %} の実装が気になってしまいました。
外部のWeb APIを叩くのでしょうか?
実際にGitHubでソースコードを見てみます。
特に理由はないですが、最新っぽい stable/5.0.x ブランチを見ます。

https://github.com/django

https://github.com/django/django/blob/stable/5.0.x/django/template/defaulttags.py
にお目当ての lorem の定義があります。
(https://github.com/django/django/blob/222bf2932b55ebc964ffc5f9a6f47bad083e5ac2/django/template/defaulttags.py#L1102-L1146)

django/django/template/defaulttags.py
@register.tag
def lorem(parser, token):
"""docstringの引用は省略"""
    bits = list(token.split_contents())
    tagname = bits[0]
    # Random bit
    common = bits[-1] != "random"
    if not common:
        bits.pop()
    # Method bit
    if bits[-1] in ("w", "p", "b"):
        method = bits.pop()
    else:
        method = "b"
    # Count bit
    if len(bits) > 1:
        count = bits.pop()
    else:
        count = "1"
    count = parser.compile_filter(count)
    if len(bits) != 1:
        raise TemplateSyntaxError("Incorrect format for %r tag" % tagname)
    return LoremNode(count, method, common) 

token としてテンプレートタグの内容そのものを受け取っています。
(parser は今回は無視します。)

 {% my_tag 'arg1' 'arg2' %}

のようなタグの場合、token"my_tag 'arg1' 'arg2'" という 文字列 です。
本当にタグの中身をそのまま受け取ります。
split_contents() によって文字列が分割され、bits というリストに格納されます。
(['my_tag', 'arg1', 'arg2'] 。)

例:
{% lorem 2 w random %}
-> ['lorem', '2', 'w', 'random']

bits[0]tagname ('lorem')に代入されます。
bits[-1]'random' かどうかが common に代入されます。

そのあとも順番に後ろの要素から bits を見ていって最後に LoremNode(count, method, common) を返します。

LoremNode は同ファイル内に定義されています。
(https://github.com/django/django/blob/222bf2932b55ebc964ffc5f9a6f47bad083e5ac2/django/template/defaulttags.py#L332-L349)

django/django/template/defaulttags.py
class LoremNode(Node):
    def __init__(self, count, method, common):
        self.count = count
        self.method = method
        self.common = common

    def render(self, context):
        try:
            count = int(self.count.resolve(context))
        except (ValueError, TypeError):
            count = 1
        if self.method == "w":
            return words(count, common=self.common)
        else:
            paras = paragraphs(count, common=self.common)
        if self.method == "p":
            paras = ["<p>%s</p>" % p for p in paras]
        return "\n\n".join(paras)

words, paras

django/django/template/defaulttags.py
from django.utils.lorem_ipsum import paragraphs, words

と、それぞれモジュールをインポートされています。
utils/lorem_ipsum.py を見てみましょう。
ようやくdjangoにおける lorem ipsum の実装の秘密(?)を知ることになります。

django/utils/lorem_ipsum.py
COMMON_P = (
    "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod "
    "tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim "
    "veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea "
    "commodo consequat. Duis aute irure dolor in reprehenderit in voluptate "
    "velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint "
    "occaecat cupidatat non proident, sunt in culpa qui officia deserunt "
    "mollit anim id est laborum."
)

lorem ipsum の段落と単語を内部で定義しているようです。
(COMMON_P, WORDS, COMMON_WORDS)

外部のWeb APIを叩いているわけではなさそうです。

まとめ

Djangoの組み込みテンプレートタグ {% lorem %} は、外部APIを叩いているわけではありませんでした。
文字列を定数として定義して、それらの定数から目的に応じた要素を生成しています。

最後に

Djangoは業務で1年以上利用していた技術ですが、実装については今までほとんど気にしたことがありませんでした。
利用しているフレームワークのコードを読むのも楽しいなと思いました。

最後まで読んでいただきありがとうございました!

Discussion