2024年8月7日、Django 5.1がリリースされました。

Django 5.1のコードネームはKaleidoscope(万華鏡)とのことです。 私は長年Djangoを使っていますが、コードネームというのは初めて見たような気がします。

Django 5.1, code-named Kaleidoscope(Dall-E 3で作成)

Django 5.1, code-named Kaleidoscope(Dall-E 3で作成)

公式サイトでのリリース情報は以下を参照してください。

Django 5.1 release notes | Django documentation | Django

5.1のサポート期限は2025年12月です。4.2 LTSからアップデートするとサポート期限が短くなってしまうことに注意してください(4.2 LTSのサポート期限は2026年4月)。 サポート期限を短くしたくない場合は、2025年4月リリース予定の5.2 LTSまでアップデートしない手もあります(5.2 LTSのサポート期限は2028年4月)。 各バージョンのサポート期限についての詳細は以下公式ドキュメント「Supported Versions」を参照してください。

Download Django | Django

それでは、主な変更点について紹介します。

{% querystring %} template tag

テンプレートにクエリ文字列を埋め込むためのテンプレートタグ{% querystring %}が追加されました。

以下のように書くと、URLのクエリ文字列を埋め込めます。

{% querystring %}

以下のように引数を指定すると、クエリ文字列を追加できます。

{% querystring page=1 %}

クエリ文字列に同じクエリキーがある場合は、引数の値に上書きされます。 たとえば、URLにpage=2があったとしても、HTMLに埋め込まれるのはpage=1です。

具体的に{% querystring %}が役立つケースを説明するために、書籍を管理するDjangoアプリケーションbooksを作りました。 booksアプリケーションの要件は以下のとおりです。

  • 書籍のタイトル、著者、価格の一覧を表示する
  • 20件ずつページネーションする
  • 検索フォームで検索条件を指定できる
  • ページネーションのリンクにクエリ文字列を埋め込む

以下がbooksアプリケーションのコードです。 なお、検索フォームについてはURLにクエリ文字列を入れるために作ったもので、検索機能は実装していません。

"""books/models.py"""
from django.db import models

class Book(models.Model):
    title = models.CharField(max_length=100)
    author = models.CharField(max_length=100)
    price = models.IntegerField()

    def __str__(self):
        return self.title
"""books/views.py"""
from django.views.generic import ListView

from .models import Book

class HomeView(ListView):
    model = Book
    queryset = Book.objects.order_by("pk")
    template_name = "books/home.html"
    context_object_name = "books"
    paginate_by = 20
{# books/templates/books/home.html #}
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Books</title>
</head>
<body>
  <h1>Books</h1>
  <form method="get">
    <label for="search">Search:</label>
    <input type="text" name="title" id="title" value="{{ request.GET.title }}">
    <input type="number" name="price" id="price" value="{{ request.GET.price }}">
    <input type="text" name="author" id="author" value="{{ request.GET.author }}">
    <button type="submit">Search</button>
  <body>
  <table>
     <thead>
       <tr>
         <th>Title</th>
         <th>Author</th>
         <th>Price</th>
       </tr>
      </thead>
      <tbody>
        {% for book in books %}
        <tr>
          <td>{{ book.title }}</td>
          <td>{{ book.author }}</td>
          <td>{{ book.price }}</td>
        </tr>
        {% endfor %}
      </tbody>
  </table>
  {# ここで {% querystring %}を使っている #}
  <div class="pagination">
    <span class="step-links">
      {% if page_obj.has_previous %}
      <a href="{% querystring page=1 %}">&laquo; first</a>
        <a href="{% querystring page=page_obj.previous_page_number %}">previous</a>
      {% endif %}

      <span class="current">
        Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}.
      </span>

      {% if page_obj.has_next %}
        <a href="{% querystring page=page_obj.next_page_number %}">next</a>
        <a href="{% querystring page=page_obj.paginator.num_pages %}">last &raquo;</a>
      {% endif %}
    </span>
  </div>
</body>
</html>

books/templates/books/home.htmlの以下の部分で{% querystring %}を使っています。

  {# ここで {% querystring %}を使っている #}
  <div class="pagination">
    <span class="step-links">
      {% if page_obj.has_previous %}
      <a href="{% querystring page=1 %}">&laquo; first</a>
        <a href="{% querystring page=page_obj.previous_page_number %}">previous</a>
      {% endif %}

      <span class="current">
        Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}.
      </span>

      {% if page_obj.has_next %}
        <a href="{% querystring page=page_obj.next_page_number %}">next</a>
        <a href="{% querystring page=page_obj.paginator.num_pages %}">last &raquo;</a>
      {% endif %}
    </span>
  </div>

別のページへのリンクの部分は、検索フォームに入力した条件も引き継ぐようにしなければなりません。 今までは、以下のように書くしかありませんでした。

  <a href="?{% for key, values in request.GET.iterlists %}
    {% if key != "page" %}
      {% for value in values %}
        {{ key }}={{ value }}&amp;
      {% endfor %}
    {% endif %}
  {% endfor %}page={{ page.next_page_number }}">next</a>

{% querystring %}によって、複雑なコードを書かなくてもクエリ文字列を埋め込むことができるようになりました。

PostgreSQL Connection Pools

PostgreSQLの接続プールをサポートするようになりました。 以下のようにsettings.pyのOPTIONSpoolを追加すると接続プールを有効化できます。

DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.postgresql",
        # ...
        "OPTIONS": {
            # ↓これを追加
            "pool": {
                "min_size": 2,
                "max_size": 4,
                "timeout": 10,
            }
        },
    },
}

Middleware to require authentication by default

デフォルトで認証を要求するミドルウェアLoginRequiredMiddlewareが追加されました。 LoginRequiredMiddlewareミドルウェアは、以下のようにAuthenticationMiddlewareの下に追加します。

MIDDLEWARE = [
    "...",
    "django.contrib.auth.middleware.AuthenticationMiddleware",
    "django.contrib.auth.middleware.LoginRequiredMiddleware",
    "...",
]

LoginRequiredMiddlewareミドルウェアを有効にした場合は、認証を要求しないビューにはlogin_not_required()デコレータを使う必要があります。

また、login_required()で以下の引数を指定した場合は、指定した値を優先します。

  • login_url: ログインページのURL
  • redirect_field_name: ログイン後にリダイレクトするURLのクエリパラメータ名

ただし、LoginRequiredMixinクラスにもlogin_urlredirect_field_nameという属性がありますが、LoginRequiredMiddlewareミドルウェアはこれらを参照しないことに注意してください。