Djangoでlorem ipsum？

# はじめに

ナイトウ([@engineer_naito](https://twitter.com/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

```django
{% 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

```django
{% lorem %}
```

## `{% lorem %}` の使い方

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

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

- method
`w`, `p`, `b` の3種類の文字が認められています。
(デフォルトは `b`。)
    - w: 単語
    - p: HTMLのpタグでの段落
    - b: プレーンテキストでの段落

- random
`random` と指定することでランダムなラテン語を出力するようになります。

## `{% lorem %}` の例



# `{% 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)

```python: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` は今回は無視します。)

```django
 {% 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)

```python: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` は

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

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

```python: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年以上利用していた技術ですが、実装については今までほとんど気にしたことがありませんでした。
利用しているフレームワークのコードを読むのも楽しいなと思いました。

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