2026年8月5日、Django 6.1がリリースされました。

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

https://docs.djangoproject.com/en/6.1/releases/6.1/

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

Download Django | Django

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

Model field fetch modes

モデルに「フェッチモード」という機能が追加されました。 この機能を使うと、未取得のフィールドにアクセスした際にデータベースからデータを取得する方法を制御できます。

フェッチモードの種類は以下のとおりです。

フェッチモードの種類説明
FETCH_ONE(デフォルト)現在のインスタンスの不足しているフィールドのみを取得する
FETCH_PEERS同じインスタンスの未取得のフィールドを取得する(明示的にprefetch_related()メソッドを読んだ場合と同じ挙動)
FETCH_RAISE未取得のフィールドにアクセスするとFieldFetchBlocked例外を送出する

以下のモデルを定義して、Django Debug Toolbarのdebugsqlshellコマンドでどんなクエリが発行されるのか見てみましょう。

from django.db import models

class Author(models.Model):
    name = models.CharField(max_length=100)

    def __str__(self):
        return self.name

class Book(models.Model):
    title = models.CharField(max_length=200)
    author = models.ForeignKey(Author, on_delete=models.CASCADE)

    def __str__(self):
        return self.title

FETCH_ONEの場合:

>>> from django.db import models
>>> books = Book.objects.fetch_mode(models.FETCH_ONE)
>>> for book in books:
...     print(book.author.name)  # authorを呼ぶたびにクエリを投げる(従来通りの挙動)
...
SELECT "books_author"."id",
       "books_author"."name"
FROM "books_author"
WHERE "books_author"."id" = 1
LIMIT 21 [0.51ms]
test1
SELECT "books_author"."id",
       "books_author"."name"
FROM "books_author"
WHERE "books_author"."id" = 2
LIMIT 21 [0.11ms]
test2
SELECT "books_author"."id",
       "books_author"."name"
FROM "books_author"
WHERE "books_author"."id" = 3
LIMIT 21 [0.07ms]
test3

FETCH_PEERSの場合:

>>> from django.db import models
>>> books = Book.objects.fetch_mode(models.FETCH_PEERS)
>>> for book in books:
...     print(book.author.name)  # あらかじめauthorをプリフェッチしてくれる
...
SELECT "books_book"."id",
       "books_book"."title",
       "books_book"."author_id"
FROM "books_book" [0.12ms]
SELECT "books_author"."id",
       "books_author"."name"
FROM "books_author"
WHERE ("books_author"."id") IN ((1), (2), (3)) [0.31ms]
test1
test2
test3

FETCH_RAISEの場合:

>>> from django.db import models
>>> books = Book.objects.fetch_mode(models.FETCH_RAISE)
>>> for book in books:
...     print(book.author.name)  # プリフェッチしていないと例外を投げる
...
SELECT "books_book"."id",
       "books_book"."title",
       "books_book"."author_id"
FROM "books_book" [0.22ms]
Traceback (most recent call last):
  File "<console>", line 2, in <module>
  File "/****/site-packages/django/db/models/fields/related_descriptors.py", line 267, in __get__
    instance._state.fetch_mode.fetch(self, instance)
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^
  File "/****/site-packages/django/db/models/fetch_modes.py", line 55, in fetch
    raise FieldFetchBlocked(f"Fetching of {klass}.{field_name} blocked.") from None
django.core.exceptions.FieldFetchBlocked: Fetching of Book.author blocked.
>>> for book in books.prefetch_related("author"):
...     print(book.author.name)  # プリフェッチしていると例外を投げない
...
SELECT "books_book"."id",
       "books_book"."title",
       "books_book"."author_id"
FROM "books_book" [0.15ms]
SELECT "books_author"."id",
       "books_author"."name"
FROM "books_author"
WHERE ("books_author"."id") IN ((1), (2), (3)) [0.28ms]
test1
test2
test3

Database-level delete options for ForeignKey.on_delete

ForeignKey.on_deleteにデータベースレベルの削除オプションを指定できるようになりました。今までは、フレームワークから紐づくデータを削除するクエリを投げていましたが、これにより1回のクエリで削除もできるようになりました。

指定できる削除オプションは以下のとおりです。

前述のBook、Authorモデルを使って、どんなクエリが投げられるのか見てみましょう。 まずは、従来通りのCASCADEオプションの場合のクエリを見てみます。 books_book、books_authorテーブルに対してそれぞれDELETE文が投げれられていることが分かります。

>>> Author.objects.filter(id=1).delete()
SELECT "books_author"."id",
       "books_author"."name"
FROM "books_author"
WHERE "books_author"."id" = 1 [0.24ms]
BEGIN [0.08ms]
DELETE
FROM "books_book"
WHERE "books_book"."author_id" IN (1) [1.38ms]
DELETE
FROM "books_author"
WHERE "books_author"."id" IN (1) [0.22ms]
(2, {'books.Book': 1, 'books.Author': 1})

続いて、モデル定義を以下のように変更してから、もう一度削除処理を実行してみます。

from django.db import models

class Author(models.Model):
    name = models.CharField(max_length=100)

    def __str__(self):
        return self.name

class Book(models.Model):
    title = models.CharField(max_length=200)
    author = models.ForeignKey(Author, on_delete=models.DB_CASCADE)  # ここを変更

    def __str__(self):
        return self.title

DB_CASCADEの場合は、以下のようにデータベースレベルで紐づくデータを削除するため、DELETE文は1回だけ発行されます。

>>> Author.objects.filter(id=2).delete()
BEGIN [0.25ms]
DELETE
FROM "books_author"
WHERE "books_author"."id" = 2 [0.68ms]
(1, {'books.Author': 1})

Mailers

メール送信の設定が変更され、複数の設定を使い分けることができるようになりました。 以下のように、MAILERSに辞書を登録します。

MAILERS = {
    "default": {  # デフォルトの設定
        "BACKEND": "django.core.mail.backends.smtp.EmailBackend",
        "OPTIONS": {"host": "smtp.example.com", "use_tls": True},
    },
    "marketing": {  # 目的ごとに別のメール送信設定を書ける
        "BACKEND": "example.third.party.EmailBackend",
        "OPTIONS": {"region": "africa-1"},
    },
}

上記の設定を呼び出すには、以下のように書きます。

from django.core.mail import mailers
mailers["marketing"]  # marketingの設定を呼び出す
mailers.default  # デフォルトの設定を呼び出す

また、従来のメール設定は非推奨になります(Django 2028で削除予定)。 移行方法についての詳細は、以下の公式サイトを参照してください。

Migrating email to mailers | Django documentation | Django