Django 4.0 がリリースされたので新機能を試してみた

Python の Web アプリケーションフレームワークである Django が、[先週 (2021/12/07) に 4.0 へメジャーアップデートされました](https://www.djangoproject.com/weblog/2021/dec/07/django-40-released/) :tada: :tada: :tada:
同年4月に LTS 版である Django 3.2 がリリースされたので業務で使う予定は全然ないんですが、プライベートで試してみた新機能について書いていこうと思います。
余裕ができたら他の機能についてもぼちぼち追加していこうかなー (やるとは言ってない)。

# [Redis cache backend](https://docs.djangoproject.com/en/4.0/releases/4.0/#redis-cache-backend)

キャッシュに Redis を指定できるようになりました。
これまでも [django-redis](https://pypi.org/project/django-redis/) を入れると使えましたが、やはり公式でサポートされていると安心感が違いますね。
導入方法は簡単で[公式ドキュメント](https://docs.djangoproject.com/en/4.0/topics/cache/#redis)に書いてあるように、以下のようなコードを settings.py に追加して [redis-py](https://pypi.org/project/redis/) をインストールするだけです。
(一緒に [hiredis-py](https://pypi.org/project/hiredis/) もインストールしておくのが推奨らしいです)

```python
CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.redis.RedisCache',
        'LOCATION': 'redis://127.0.0.1:6379',
    }
}
```

## 例

試しに以下のテンプレートのように、サーバサイドで生成した現在時刻の表示部に適用してみましょう。

```django
{% load cache %}

<p>Without cache:</p>
<span>{{ now }}</span>

{% cache 5 timer %}
<p>With cache:</p>
<span>{{ now }}</span>
{% endcache %}
```

一度表示した後にリロードした結果が以下の画像で、キャッシュした値が使われていることを確認できますね。

![django-redis-cache.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/158991/22cd157d-a203-ef8c-0173-e3aafeb2cb0b.png)

## 注意

なお、現時点 (2021/12/16) では、以下のように [bool 値を保存するとクラッシュするバグ](https://code.djangoproject.com/ticket/33361)があるので注意してください。
こちらは 2022/01/04 に修正リリースが入る予定のようです。

```python
>>> from django.core.cache import cache
>>> cache.set('foo', True)
...
redis.exceptions.DataError: Invalid input of type: 'bool'. Convert to a bytes, string, int or float first.
```

# [Template based form rendering](https://docs.djangoproject.com/en/4.0/releases/4.0/#template-based-form-rendering)

これまで Django の Form クラスに固定で紐づけられていたテンプレートをカスタマイズできる機能です。

## 例

言葉だけだと何のこっちゃかわかりにくいので、試しにフォーム要素をグリッドレイアウトで並べるテンプレートを作ってみました。
フィールドとかは公式ドキュメントにある `ContactForm` を参考にしています。

https://docs.djangoproject.com/en/4.0/topics/forms/

まず、以下の `ContactForm.template_name` に作成したテンプレートへのパスを指定します。
テンプレートのパスは `INSTALLED_APPS` に登録したアプリフォルダの `templates/` 下から見た位置になります。

```python
from django import forms
from django.http import HttpResponseRedirect
from django.shortcuts import render


class ContactForm(forms.Form):
    subject = forms.CharField(max_length=100)
    message = forms.CharField(widget=forms.Textarea)
    sender = forms.EmailField()
    cc_myself = forms.BooleanField(required=False)

    template_name = 'grid.html'


def contact(request):
    if request.method == 'POST':
        form = ContactForm(request.POST)
        if form.is_valid():
            return HttpResponseRedirect(reverse('/thanks/'))
    else:
        form = ContactForm()

    return render(request, 'contact.html', {'form': form})
```

レンダリングに使用するフォームとビューのテンプレートを以下のようにします。

```django:grid.html
<div style="display: grid; grid-template-columns: 200px 200px; gap: 10px;">
  {% for field, errors in fields %}
  <div style="grid-row: {{ forloop.counter }}; grid-column: 1;">{{ field.label_tag }}</div>
  <div style="grid-row: {{ forloop.counter }}; grid-column: 2;">{{ field }}</div>
  {% endfor %}
</div>
```

```django:contact.html
<form action="/contact/" method="post">
    {% csrf_token %}
    {{ form }}
    <input type="submit" value="Submit">
</form>
```

これを画面に表示した結果が以下になります。
(例だから簡略化してるとは言え何ともつまらん画面だ...)

![django-form-template.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/158991/5584002c-9aed-93a7-a987-7cb06f1b6fa3.png)

`ContactForm.template_name` から指定したテンプレートのレンダリング結果が `{{ form }}` に置き換わって、グリッドレイアウトによるフォームの表示が作成できるようになりました。
`template_name` の他にも `render()` から、直接↑の `{{ form }}` に該当する文字列を生成できるようですが、とりあえず試すのは HTML ベースのカスタマイズだけにします。

正直これまでも[フィールドを手動でレンダリングする](https://docs.djangoproject.com/ja/4.0/topics/forms/#rendering-fields-manually)ことはできたので、テンプレートを個別に `include` すれば同じことができたのですが、 Form インスタンスのみで再利用可能なフォームを作成できるのは良いですね。
(例だと汎用テンプレートを `ContactForm` という固有のフォームに紐づけちゃってるけど気にしない)

まだ全然書き方を掴めてないですが、 [Django の公式リポジトリ](https://github.com/django/django/tree/main/django/forms/templates/django/forms)に元となるテンプレートがあるので、ここら辺を参考にするのも良さそうです。

# 感想

今のところ、バージョン 3.0 に上がった時の ASGI support ほどの衝撃はなかった。