2025年4月2日、Django 5.2がリリースされました。

Django 5.2リリースおめでとう!

Django 5.2リリースおめでとう!

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

Django 5.2 release notes | Django documentation | Django

Django 5.2はlong-term support(LTS)版です。サポート期限は2028年4月です。 各バージョンのサポート期限についての詳細は以下公式ドキュメント「Supported Versions」を参照してください。

Download Django | Django

それでは、以下で主な新機能について解説します。

Automatic models import in the shell

shellコマンド起動時にモデル定義が自動インポートされるようになりました。 Django拡張として人気があるdjango-extentionsshell_plusコマンドの機能が公式に取り込まれたようなイメージです。

実際にshellコマンドを使っている様子は以下の動画を確認してください。

shellコマンドに--verbosity=2オプションを付けると、どんなモデルがインポートされるか確認できます。

% ./manage.py shell --verbosity=2
8 objects imported automatically:

  from example.models import Release
  from books.models import Book
  from django.contrib.sessions.models import Session
  from django.contrib.contenttypes.models import ContentType
  from django.contrib.auth.models import User, Group, Permission
  from django.contrib.admin.models import LogEntry

Python 3.13.2 (main, Feb  4 2025, 14:51:09) [Clang 16.0.0 (clang-1600.0.26.6)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>>

ちなみに、複数アプリケーションで同じ名前のモデルが存在している場合は、INSTALLED_APPSの上の方に書かれたアプリケーションに属するモデルが優先的にインポートされるようです。

Composite Primary Keys

モデルの複合主キーがサポートされるようになりました。 複合主キーを定義するには、以下のように、pk属性にdjango.db.models.CompositePrimaryKeyクラスを設定します。

from django.db import models

class Release(models.Model):
    pk = models.CompositePrimaryKey("version", "name")
    version = models.IntegerField()
    name = models.CharField(max_length=20)

上記のモデルを定義した際に作られるテーブル定義をsqlmigrateコマンドで確認した結果は以下のとおりです。

BEGIN;
--
-- Create model Release
--
CREATE TABLE "example_release" ("version" integer NOT NULL, "name" varchar(20) NOT NULL, PRIMARY KEY ("version", "name"));
COMMIT;

PRIMARY KEY ("version", "name")が出力されていることから、複合主キーが定義されていることがわかります。

Simplified override of BoundField

フォームクラスにカスタムフィールドを定義する方法が簡略化されました。 従来はField.get_bound_field()メソッドをオーバーライドする必要がありましたが1、5.2からは以下の方法がサポートされます。

BaseRenderer.bound_field_class(プロジェクトレベルで適用)

BaseRenderer.bound_field_classを使うと、プロジェクトレベルでカスタムフィールドを定義できます。

以下の書き方は、プロジェクト全体でフォームのフィールドを<p class="custom">...</p>で囲います。

"""django52_example/settings.py"""
from django.forms.renderers import DjangoTemplates  # DjangoTemplatesはBaseRendererのサブクラス
from django import forms

class CustomBoundField(forms.BoundField):

    custom_class = "custom"

    def css_classes(self, extra_classes=None):
        result = super().css_classes(extra_classes)
        if self.custom_class not in result:
            result += f" {self.custom_class}"
        return result.strip()

class CustomDjangoTemplate(DjangoTemplates):
    bound_field_class = CustomBoundField

FORM_RENDERER = "django52_example.settings.CustomDjangoTemplate"

Form.bound_field_class(フォームレベルで適用)

Form.bound_field_classを使うと、 フォームレベルでカスタムフィールドを定義できます。

以下の書き方は、CustomFormクラスに定義したフィールド全てを<p class="custom">...</p>で囲います。

"""example/forms.py"""
from django import forms

class CustomBoundField(forms.BoundField):

    custom_class = "custom"

    def css_classes(self, extra_classes=None):
        result = super().css_classes(extra_classes)
        if self.custom_class not in result:
            result += f" {self.custom_class}"
        return result.strip()

class CustomForm(forms.Form):
    bound_field_class = CustomBoundField

    name = forms.CharField(
        label="Your Name",
        max_length=100,
        required=False,
        widget=forms.TextInput(attrs={"class": "name-input-class"}),
    )
    email = forms.EmailField(label="Your Email")

Field.bound_field_class(フィールドレベルで適用)

Field.bound_field_classを使うと、フィールドレベルでカスタムフィールドを定義できます。

以下の書き方は、CustomFormクラスに定義したemailフィールドのみを<p class="custom">...</p>で囲います。

"""django52_example/settings.py"""
from django import forms

class CustomBoundField(forms.BoundField):

    custom_class = "custom"

    def css_classes(self, extra_classes=None):
        result = super().css_classes(extra_classes)
        if self.custom_class not in result:
            result += f" {self.custom_class}"
        return result.strip()

class CustomForm(forms.Form):
    name = forms.CharField(
        label="Your Name",
        max_length=100,
        required=False,
        widget=forms.TextInput(attrs={"class": "name-input-class"}),
    )
    email = forms.EmailField(label="Your Email", bound_field_class=CustomBoundField)