Django 3.1将于2020年8月发布!从3.1版本开始,Django将逐步原生支持异步,比如异步视图和中间件。
Django 3.1只支持Python 3.6、3.7、3.8以及更高版本。
下面重点介绍Django3.1的新特性:
1. 异步视图
在Django3.1中,要定义一个异步视图很简单,只需使用Python的async def
语法,Django会自动探测到它们,并在异步上下文中运行它们。异步视图有很多优点,比如能够在不使用Python线程的情况下为数百个连接提供服务,允许使用慢速流、长轮询和其他的响应类型等等。
async def my_view(request):
await asyncio.sleep(0.5)
return HttpResponse('Hello, async world!')
无论你是运行在WSGI还是ASGI模式,都支持所有异步特性。
但是,在WSGI模式下使用异步代码可能会有性能损失,因为此时异步视图将在它们自己的一次性事件循环中运行,这意味着你虽然可以使用异步特性,如并行的异步HTTP请求,但却无法获得异步堆栈的好处。
我们可以随心所欲地混合异步和同步的视图、中间件和测试,Django会确保我们始终获得正确的执行上下文。但是,Django官方建议大多数时候依然使用同步视图,只有在真正有需求时使用异步视图,不过这完全取决于你的选择,你可以任性的都使用异步视图。
Django3.1版本中的ORM、缓存层和其他执行长时间网络调用的代码还暂时不支持异步访问,会在后期发布的版本中增加对它们的支持。(我的官网https://www.liujiangblog.com中也会持续关注并更新最新的内容和变化。)Django对异步的支持完全向后兼容,对现有的同步代码没有速度限制,它不会对任何现有的Django项目产生明显的影响。
下面是另外一个例子:
import datetime
from django.http import HttpResponse
async def current_datetime(request):
now = datetime.datetime.now()
html = '<html><body><h1>欢迎访问刘江Django教程:https://www.liujiangblog.com</h1>It is now %s.</body></html>' % now
return HttpResponse(html)
因为目前,Django还没有完成异步ORM的功能开发,为了在异步视图中使用ORM,需要将同步的代码转换为异步的代码,这就需要使用asgiref
库,这个库已经作为安装依赖随Django一起被安装。
核心是使用asgiref.sync
中的sync_to_async
方法。
使用方法有两种,第一种以函数调用的方式,注意括号的位置:
from asgiref.sync import sync_to_async
results = sync_to_async(Blog.objects.get)(pk=123)
#注意圆括号,千万不要写成results = sync_to_async(Blog.objects.get(pk=123))
第二种以装饰器的方式:
from asgiref.sync import sync_to_async
@sync_to_async
def get_blog(pk):
return Blog.objects.select_related('author').get(pk=pk)
对等的,其实也有一个异步变同步的函数,用于在同步视图中包装异步调用:
from asgiref.sync import async_to_sync
async def get_data(...):
...
sync_get_data = async_to_sync(get_data)
@async_to_sync
async def get_other_data(...):
...
2. 异步中间件
从Django3.1开始,中间件可以支持同步和异步请求的任何组合。如果Django不能同时支持这两者,它将调整请求以满足中间件的需求,但会降低性能。
默认情况下,Django假设你的中间件只能处理同步请求。要更改这个假设,请在中间件工厂函数或类上设置以下属性:
-
sync_capable
: 一个布尔值,指示中间件是否可以处理同步请求。默认为True。 -
async_capable
: 一个布尔值,指示中间件是否可以处理异步请求。默认为False。
如果中间件同时具有sync_capable=True
和async_capable=True
,那么Django将在不转换请求的情况下传递它。在这种情况下,可以使用asyncio.iscoroutine function()
检查传递给您的get_response
对象是否是一个协程函数,从而确定您的中间件是否会接收异步请求。
记住一个概念:同步和异步能力不是非左即右,互相矛盾的存在,可以共存!
那么怎么将中间件设置为同步的,或者异步的,或者同步加异步的呢?
在django.utils.decorators
模块中包含sync_only_middleware
、async_only_middleware
和sync_and_async_middleware
三个装饰器,用于帮我们实现上面的功能。
中间件返回的可调用函数必须与get_response
方法的sync或async性质匹配。如果有异步get_response
响应,则必须返回一个协程函数(async def)。
如果中间件提供了process_view
、process_template_response
和process_exception
方法,则还应进行相应的调整以匹配同步/异步模式。如果你不这样做,Django会根据需要对它们进行单独的调整,并产生额外的性能惩罚。
下面是一个如何创建同时支持同步和异步功能的中间件的示例:
import asyncio
from django.utils.decorators import sync_and_async_middleware
@sync_and_async_middleware
def simple_middleware(get_response):
# One-time configuration and initialization goes here.
if asyncio.iscoroutinefunction(get_response):
async def middleware(request):
# Do something here!
response = await get_response(request)
return response
else:
def middleware(request):
# Do something here!
response = get_response(request)
return response
return middleware
总的来说,Django对同步/异步视图和同步/异步中间件之间的搭配组合有很好的适配能力,不会让我们的项目运行不起来。只不过如果搭配不当,会导致性能损失。
3. 异步测试
从Django3.1开始,具备异步测试能力。
如果您只想测试异步视图的输出,标准测试客户端将在自己的异步循环中运行它们,你不需要做任何额外的工作。
但是,如果你想为Django项目编写完全异步的测试,则需要考虑一些事情。
首先,测试方法必须是测试类上的async def
方法(以便为它们提供异步上下文)。Django将自动检测任何异步测试并包装它们,以便它们在自己的事件循环中运行。
其次,如果要在异步函数中进行测试,还必须使用异步测试客户端。也就是django.test.AsyncClient
或self.async_client
。
除了不支持follow
参数外,AsyncClient
的使用方法和同步的测试客户端基本相同,但所有发出请求的方法都必须使用await
语法:
async def test_my_thing(self):
response = await self.async_client.get('/some-url/')
self.assertEqual(response.status_code, 200)
4. 新增JSONField
Django3.1新增了 models.JSONField
和forms.JSONField
两种新的模型字段类型,用于保存JSON编码的数据。
MariaDB 10.2.7+、MySQL 5.7.8+、Oracle、PostgreSQL和SQLite 3.9.0+都支持JSONField。
JSONField可以自定义编码器和解码器,这从它的定义上就可以看出来:
class JSONField(encoder=None, decoder=None, **options)
-
JSONField.encoder
可选参数。用于对诸如
datetime.datetime
或者UUID
之类的标准JSON序列化不了的数据指定自定义的编码器。它必须是json.JSONEncoder
的子类,比如DjangoJSONEncoder
-
JSONField.decoder
可选参数。用于解码我们自定义的编码数据。必须是
json.JSONDecoder
的子类。
如果你为JSONField字段提供了一个default默认值,它的值必须是一个不可变的类型。
对于forms.JSONField
,除了同样可以自定义编码器和解码器,还有一些表单特有的性质:
- 默认渲染的HTML元素:
Textarea
- 空值:
''
(空字符串) - 错误信息的键:
required
,invalid
5. 小功能
下面例举一些相对较小的新功能。其中最主要的是,Django3.1开始使用pathlib.Path
来替代传统的os.path了,建议大家还是尽快更新知识,迁移到pathlib.Path
上来
django.contrib.admin
-
新的空值过滤功能
django.contrib.admin.EmptyFieldListFilter
-
可以清除所有的过滤操作
-
新增可折叠的左侧边导航栏,方便我们进行菜单跳转
-
XRegExp
升级到3.2.0 -
jQuery升级到3.5.1
-
Select2 升级到4.0.13.
django.contrib.auth
- PBKDF2密码哈希的迭代次数从180,000 提高到216,000
- 新增
PASSWORD_RESET_TIMEOUT
配置项,用于替代4.0中将废弃的PASSWORD_RESET_TIMEOUT_DAYS
- 密码重置将使用 SHA-256哈希算法
AbstractBaseUser.get_session_auth_hash()
将使用SHA-256哈希算法
django.contrib.humanize
intword
将支持负整数
django.contrib.sessions
SESSION_COOKIE_SAMESITE
现在可以接收'None'
(字符串)
django.contrib.staticfiles
STATICFILES_DIRS
设置项开始支持pathlib.Path
File Storage
FileSystemStorage.save()
方法支持pathlib.Path
FileField
和ImageField
现在支持可调用的参数用于保存数据。这让你能在运行时动态选择不同的存储位置。
Migrations
- Migrations现在也可以从没有
__init__.py
文件的目录中加载了
Models
- 新增
PositiveBigIntegerField
字段类型,类似PositiveIntegerField
,从0
到9223372036854775807
都是安全的。 - 对于外键和一对一字段的
on_delete
参数,现在可以接收一个RESTRICT
值,用于模拟SQL语言中的ON DELETE RESTRICT
约束行为。