Django 5.0主要变更总结
这是Qiita Django Advent Calendar 2023的第11篇文章。
2023年12月4日,Django 5.0发布了。我将解释其中主要的变更。

请参考官方网站上的发布信息。
Django 5.0 不是长期支持(LTS)版。其支持截止日期为2025年4月。
鉴于支持截止日期早于长期支持版的4.2(截止日期为2026年4月),请慎重考虑是否值得升级至Django 5.0。
有关每个版本的支持截止日期的详细信息,请参阅官方文档中的“Supported Versions”部分。
管理员中的面板筛选器
管理员现在可以在筛选器中显示相应的项目数(面板数)。您可以通过ModelAdmin.show_facets属性来进行设置。以下是设置示例。
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)
实际的管理员界面会像下面这样显示。

请注意,当显示面数量时,会发出额外的查询,可能会对性能产生影响。
ModelAdmin.show_facets属性可以设置为以下3种类型。
admin.ShowFacets.ALWAYS: 常にファセット数を表示
admin.ShowFacets.ALLOW: ファセット数の表示・非表示を切り替えられる(後述)
admin.ShowFacets.NEVER: ファセット数を表示させない

表单字段渲染的简化模板
创建独自的表单字段模板的写法变得更简单了。
首先,请看一下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>
数据库计算的默认值
新增了一个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")),
)
当在上述的模型定义的状态下执行迁移命令时,会出现以下错误。
$ 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.
数据库生成的模型字段
在数据库中生成的值被处理时,增加了GeneratedField。
在以下示例中,我们使用GeneratedField将first_name和last_name引用的值生成为full_name字段。
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_persist为True时,将值存储到存储设备的设置。必须指定为True或False。可以指定哪个取决于使用的数据库不同。
让我们使用上述模型来注册数据。注册后,您可以看到full_name已经被生成。而且,如果在注册后更新了first_name和last_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),使用数据库功能生成值,生成规则为(COALESCE(“first_name”, ”) || COALESCE(COALESCE(‘ ‘, ”) || COALESCE(“last_name”, ”), ”)) STORED。
提供更多领域选择的选项
Field.choices属性和ChoiceField.choices属性可以指定的值种类增加了两个。
现在,您可以直接指定枚举类型而不使用枚举类型的.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,
)
第二个选项是,您可以传递以下可以调用的对象。
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",
)