LoginSignup
9
5

Django 5.0 主な変更点まとめ

Posted at

これはQiita Django Advent Calendar 2023 11日目の記事です。

2023年12月4日、Django 5.0がリリースされました。主な変更点について解説します。

Django5.0.png
Django 5.0リリースおめでとう!(Created with DALL-E)

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

Django 5.0はlong-term support(LTS)版ではありません。サポート期限は2025年4月です。
LTS版である4.2のサポート期限(2026年4月)より短いため、バージョンアップするメリットがあるのか慎重に検討してください。
各バージョンのサポート期限についての詳細は以下公式ドキュメント「Supported Versions」を参照してください。

Facet filters in the admin

adminのフィルターに該当件数(ファセット数)を表示できるようになりました。ModelAdmin.show_facets属性で設定を変更できます。以下が設定例です。

admin.py
from django.contrib import admin

from .models import Book

class BookAdmin(admin.ModelAdmin):
    list_display = ("title", "price", "author",)
    list_filter = ("author",)
    show_facets = admin.ShowFacets.ALWAYS  # これを設定

admin.site.register(Book, BookAdmin)

実際のadminの画面は以下のように表示されます。

Select_book_to_change___Django_site_admin.jpg

ファセット数を表示させると別途クエリが発行されるので、パフォーマンスに影響を及ぼす可能性があることに注意してください。

ModelAdmin.show_facets属性に設定できる値は以下の3種類です1

  • admin.ShowFacets.ALWAYS: 常にファセット数を表示
  • admin.ShowFacets.ALLOW: ファセット数の表示・非表示を切り替えられる(後述)
  • admin.ShowFacets.NEVER: ファセット数を表示させない

admin.ShowFacets.ALLOWは以下のリンクが表示されて、ファセット数の表示・非表示を切り替えられます。
Select_book_to_change___Django_site_admin.jpg

Simplified templates for form field rendering

独自のフォームフィールドテンプレートを作成する際の書き方がよりシンプルになりました。
まず、Django 4.2での書き方を見てください。色々なメソッドを組み合わせる必要があるため、かなり複雑な書き方にする必要があります。

<form>
...
<div>
  {{ form.name.label_tag }}
  {% if form.name.help_text %}
    <div class="helptext" id="{{ form.name.id_for_label }}_helptext">
      {{ form.name.help_text|safe }}
    </div>
  {% endif %}
  {{ form.name.errors }}
  {{ form.name }}
  <div class="row">
    <div class="col">
      {{ form.email.label_tag }}
      {% if form.email.help_text %}
        <div class="helptext" id="{{ form.email.id_for_label }}_helptext">
          {{ form.email.help_text|safe }}
        </div>
      {% endif %}
      {{ form.email.errors }}
      {{ form.email }}
    </div>
    <div class="col">
      {{ form.password.label_tag }}
      {% if form.password.help_text %}
        <div class="helptext" id="{{ form.password.id_for_label }}_helptext">
          {{ form.password.help_text|safe }}
        </div>
      {% endif %}
      {{ form.password.errors }}
      {{ form.password }}
    </div>
  </div>
</div>
...
</form>

Django 5.0では as_field_group()メソッドを使ってようにシンプルに書けます。以下の書き方は前述のテンプレートと同じ意味です。

<form>
...
<div>
  {{ form.name.as_field_group }}
  <div class="row">
    <div class="col">{{ form.email.as_field_group }}</div>
    <div class="col">{{ form.password.as_field_group }}</div>
  </div>
</div>
...
</form>

Database-computed default values

デフォルト値を設定するField.db_default属性が追加されました。従来のField.default属性と違って、データベース側で計算した値を設定できます。

以下がコード例です。

from django.db import models
from django.db.models.functions import Now, Pi

class Example(models.Model):
    name = models.CharField(max_length=100)
    created_at = models.DateTimeField(db_default=Now())
    circumference = models.FloatField(db_default=2 * Pi())

上記モデルを使ってデータを登録すると以下のような結果になります。

>>> from example.models import Example
>>> example = Example.objects.create(name="example")
>>> example.created_at
datetime.datetime(2023, 11, 14, 14, 52, 50, 579000, tzinfo=datetime.timezone.utc)
>>> example.circumference
6.283185307179586

ただし、Field.db_default属性は以下のように他のフィールドを参照する値を指定できません。

from django.db import models
from django.db.models import F
from django.db.models import Value as V
from django.db.models.functions import Concat

class Example(models.Model):
    first_name = models.CharField(max_length=100)
    last_name = models.CharField(max_length=100)
    full_name = models.TextField(
        # NG
        db_default=Concat(F("first_name"), V(" "), F("last_name")),
    )

上記のモデル定義を書いた状態でmigrateコマンドを実行すると、以下のエラーが発生します。

$ python manage.py migrate
SystemCheckError: System check identified some issues:

ERRORS:
example.Example.full_name: (fields.E012) Concat(ConcatPair(F(first_name), ConcatPair(Value(' '), F(last_name)))) cannot be used in db_default.

Database generated model field

データベースで生成した値を扱うGeneratedFieldが追加されました。
以下の例ではfirst_namelast_nameを参照した値を生成するfull_nameというフィールドにGeneratedFieldを使っています。

from django.db import models
from django.db.models import F
from django.db.models import Value as V
from django.db.models.functions import Concat

class Example(models.Model):
    first_name = models.CharField(max_length=100)
    last_name = models.CharField(max_length=100)
    full_name = models.GeneratedField(
        expression=Concat(F("first_name"), V(" "), F("last_name")),
        db_persist=True,  # TrueかFalseを必ず指定する
    )

db_persistTrueのとき値をストレージに保存する設定です。必ずTrueFalseを指定する必要があります。どちらを指定できるかは使用するデータベースによって異なります。

上記のモデルを使ってデータを登録してみましょう。登録後にfull_nameが生成されているのがわかります。また、登録後にfirst_namelast_nameを更新すると、full_nameも更新されます。

>>> from example.models import Example
>>> example = Example.objects.create(first_name="Taro", last_name="Yamada")
>>> example.full_name  # first_nameとlast_nameの登録内容が反映される
'Taro Yamada'
>>> Example.objects.filter(pk=example.pk).update(first_name="Hanako", last_name="Suzuki")
1
>>> example.refresh_from_db()
>>> example.full_name  # first_nameとlast_nameの変更内容が反映される
'Hanako Suzuki'

テーブル定義も見てみましょう(本記事ではSQLite 3.39.5を使っています)。

$ python manage.py sqlmigrate example 0001
BEGIN;
--
-- Create model Example
--
CREATE TABLE "example_example" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "first_name" varchar(100) NOT NULL, "last_name" varchar(100) NOT NULL, "full_name" varchar(100) GENERATED ALWAYS AS (COALESCE("first_name", '') || COALESCE(COALESCE(' ', '') || COALESCE("last_name", ''), '')) STORED);
COMMIT;

full_nameの定義が"full_name" varchar(100) GENERATED ALWAYS AS (COALESCE("first_name", '') || COALESCE(COALESCE(' ', '') || COALESCE("last_name", ''), '')) STORED)になっています。データベースの機能を使って値を生成していることがわかります。

More options for declaring field choices

Field.choices属性、ChoiceField.choices属性に指定できる値の種類が2つ増えました。

1つ目は、列挙型を指定する際、列挙型の.choices属性を使わず列挙型を直接指定できるようになりました。

まず、Django 4.2までの列挙型の使い方を見てください。

from django.db import models

class Post(models.Model):
    class Status(models.TextChoices):
        PUBLISHED = "published", "公開"
        DRAFT = "draft", "下書き"

    status = models.CharField(
        max_length=20,
        choices=Status.choices,  # choices属性を指定する
        default=Status.DRAFT,
    )

Django 5.0からは以下のように書けます。

from django.db import models

class Post(models.Model):
    class Status(models.TextChoices):
        PUBLISHED = "published", "公開"
        DRAFT = "draft", "下書き"

    status = models.CharField(
        max_length=20,
        choices=Status,  # choices属性を省略して直接列挙型を指定できる
        default=Status.DRAFT,
    )

2つ目は、以下のようにcallableなオブジェクトも渡せます。

from django.db import models

def get_statuses():
    return {
        "published": "公開",
        "draft": "下書き",
    }

class Post(models.Model):
    status = models.CharField(
        max_length=20,
        choices=get_statuses,
        default="draft",
    )
  1. 参考URL: https://docs.djangoproject.com/en/5.0/ref/contrib/admin/#django.contrib.admin.ModelAdmin.ShowFacets

9
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
5