【翻譯】Django Channels 官方文檔 -- Tutorial

Django Channels 官方文檔

https://channels.readthedocs.io/en/latest/index.htmlcss

前言:

最近課程設計須要用到 WebSocket,而原生的 Django 又不支持 WebSocket,僅有 Django Channels 庫支持 WebSocket,可是  Django Channels 的資料,特別是中文資料異常稀缺,所以我在本身理解的基礎上,整理翻譯了這一篇 官方入門教程,僅做參考,若有疑問能夠在下方留言。感謝你們的查看!html

Tutorial
教程

Channels allows you to use WebSockets and other non-HTTP protocols in your Django site. For example you might want to use WebSockets to allow a page on your site to immediately receive updates from your Django server without using HTTP long-polling or other expensive techniques.
Channels 容許您在 Django 站點中使用 Websockets 和其餘非 HTTP 協議。例如, 您可能但願 Websockets 容許網站上的頁面當即從 Django 服務器接收更新, 而無需使用 HTTP 長輪詢或其餘昂貴的技術。python

In this tutorial we will build a simple chat server, where you can join an online room, post messages to the room, and have others in the same room see those messages immediately.
在本教程中, 咱們將創建一個簡單的聊天服務器, 在那裏你能夠加入一個在線房間, 張貼消息到房間, 並讓其餘人在同一房間看到這些消息當即。nginx

  • Tutorial Part 1: Basic Setup
  • 教程1部分: 基本設置
  • Tutorial Part 2: Implement a Chat Server
  • 教程2部分: 實現聊天服務器
  • Tutorial Part 3: Rewrite Chat Server as Asynchronous
  • 教程3部分: 將聊天服務器重寫爲異步
  • Tutorial Part 4: Automated Testing
  • 教程4部分: 自動化測試

Tutorial Part 1: Basic Setup

教程1部分: 基本設置

In this tutorial we will build a simple chat server. It will have two pages:
在本教程中, 咱們將構建一個簡單的聊天服務器。它將有兩個頁面:git

  • An index view that lets you type the name of a chat room to join.
  • 一個 index 視圖, 用於輸入要加入的聊天室的名稱。
  • A room view that lets you see messages posted in a particular chat room.
  • 一個能夠查看在特定聊天室中發送的消息的房間視圖。

The room view will use a WebSocket to communicate with the Django server and listen for any messages that are posted.
房間視圖將使用 WebSocket 與 Django 服務器進行通訊, 並監放任何發送出來的消息。web

We assume that you are familar with basic concepts for building a Django site. If not we recommend you complete the Django tutorial first and then come back to this tutorial.
咱們假設您熟悉構建 Django 站點的基本概念。若是不是, 咱們建議您先完成 Django 教程, 而後再回到本教程。redis

We assume that you have Django installed already. You can tell Django is installed and which version by running the following command in a shell prompt (indicated by the $ prefix):
咱們假設你已經安裝了 Django。您能夠經過在 shell 提示符下運行如下命令 (用 $ 前綴表示) 來查看 您安裝的 Django 版本:chrome

$ python3 -m django --version

We also assume that you have Channels installed already. You can tell Channels is installed by running the following command:
咱們還假設您已經安裝了 Channels。您能夠經過運行如下命令來查看 Channels 安裝與否:docker

$ python3 -c 'import channels; print(channels.__version__)'

This tutorial is written for Channels 2.0, which supports Python 3.5+ and Django 1.11+. If the Channels version does not match, you can refer to the tutorial for your version of Channels by using the version switcher at the bottom left corner of this page, or update Channels to the newest version.
本教程是爲 Channels 2.0 編寫的, 它支持 Python 3.5 + 和 Django 1.11 +。若是 Channels 版本不匹配, 你可使用本頁左下角的版本切換器, 或將 Channels 更新到最新版本, 以參考您的 Channels 版本的教程。shell

This tutorial also uses Docker to install and run Redis. We use Redis as the backing store for the channel layer, which is an optional component of the Channels library that we use in the tutorial. Install Docker from its official website - there are official runtimes for Mac OS and Windows that make it easy to use, and packages for many Linux distributions where it can run natively.
本教程還使用 Docker 安裝和運行 Redis。咱們使用 Redis 做爲 Channels 層的後備存儲, 它是咱們在教程中使用的 Channels 庫的可選組件。從其官方網站安裝 Docker --有用於 Mac OS 和 Windows 的易於使用的正式運行版, 併爲許多 Linux 發行版提供了可本地運行的軟件包。

Note
提醒
While you can run the standard Django runserver without the need for Docker, the channels features we’ll be using in later parts of the tutorial will need Redis to run, and we recommend Docker as the easiest way to do this.
雖然您能夠運行標準的 Django runserver 不須要 Docker , 咱們將使用的 channels 功能在後面的教程將須要 Redis 運行, 咱們建議使用 Docker 這一最簡單的方式來作到這一點。

Creating a project
新建一個項目

If you don’t already have a Django project, you will need to create one.
若是您尚未 Django 項目, 您將須要建立一個。

From the command line, cd into a directory where you’d like to store your code, then run the following command:
從命令行, 將 cd 放入要存儲代碼的目錄中, 而後運行如下命令:

$ django-admin startproject mysite

This will create a mysite directory in your current directory with the following contents:
這將在當前目錄中建立一個 mysite 目錄, 其中有如下內容:

mysite/
    manage.py
    mysite/
        __init__.py
        settings.py
        urls.py
        wsgi.py

Creating the Chat app
建立聊天應用程序

We will put the code for the chat server in its own app.
咱們會將聊天服務器的代碼放在它本身的應用程序中。

Make sure you’re in the same directory as manage.py and type this command:
請確保您位於與 manage.py 相同的目錄中. 而後輸入如下命令:

$ python3 manage.py startapp chat

That’ll create a directory chat, which is laid out like this:
這將建立一個 chat 文件夾, 它是像這樣的:

chat/
    __init__.py
    admin.py
    apps.py
    migrations/
        __init__.py
    models.py
    tests.py
    views.py

For the purposes of this tutorial, we will only be working with chat/views.py and chat/__init__.py. So remove all other files from the chat directory.
爲了達到本教程的目的, 咱們將只使用 chat/views.py 和 chat/__init__.py。所以, 從 chat 目錄中刪除全部其餘文件。

After removing unnecessary files, the chat directory should look like:
刪除沒必要要的文件後, chat 目錄應以下所示:

chat/
    __init__.py
    views.py

We need to tell our project that the chat app is installed. Edit the mysite/settings.py file and add 'chat' to the INSTALLED_APPS setting. It’ll look like this:
咱們須要告訴咱們的項目 chat app 已經安裝。編輯 mysite/settings.py 文件並將 'chat' 添加到 INSTALLED_APPS 設置中。它看起來像這樣:

# mysite/settings.py
INSTALLED_APPS = [
    'chat',
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]

Add the index view
添加 index 視圖

We will now create the first view, an index view that lets you type the name of a chat room to join.
如今, 咱們將建立第一個視圖, 這個 index 視圖容許您輸入要加入的聊天室的名稱。

Create a templates directory in your chat directory. Within the templates directory you have just created, create another directory called chat, and within that create a file called index.html to hold the template for the index view.
在 chat 目錄中建立 templates 目錄。在剛剛建立的 templates 目錄中, 建立另外一個名爲 chat 的目錄, 並在其中建立一個名爲 index.html 的文件。

Your chat directory should now look like:
您的 chat 目錄如今應該看起來像:

chat/
    __init__.py
    templates/
        chat/
            index.html
    views.py

Put the following code in chat/templates/chat/index.html:
將下面的代碼寫進 chat/templates/chat/index.html 文件中:

<!-- chat/templates/chat/index.html -->
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8"/>
    <title>Chat Rooms</title>
</head>
<body>
    What chat room would you like to enter?<br/>
    <input id="room-name-input" type="text" size="100"/><br/>
    <input id="room-name-submit" type="button" value="Enter"/>
</body>
<script>
    document.querySelector('#room-name-input').focus();
    document.querySelector('#room-name-input').onkeyup = function(e) {
        if (e.keyCode === 13) {  // enter, return
            document.querySelector('#room-name-submit').click();
        }
    };

    document.querySelector('#room-name-submit').onclick = function(e) {
        var roomName = document.querySelector('#room-name-input').value;
        window.location.pathname = '/chat/' + roomName + '/';
    };
</script>
</html>

Create the view function for the room view. Put the following code in chat/views.py:
爲 room 視圖建立視圖函數。將下面的代碼寫進 chat/views.py 文件中:

# chat/views.py
from django.shortcuts import render

def index(request):
    return render(request, 'chat/index.html', {})

To call the view, we need to map it to a URL - and for this we need a URLconf.
爲了調用這個視圖,咱們須要把它映射到一個 URL -- 所以咱們須要一個 URL 配置文件。

To create a URLconf in the chat directory, create a file called urls.py. Your app directory should now look like:
爲了在 chat 目錄下建立一個 URL 配置文件,咱們須要新建一個名爲 urls.py 的文件。你的 app 目錄應該像如今這樣子:

chat/
    __init__.py
    templates/
        chat/
            index.html
    urls.py
    views.py

In the chat/urls.py file include the following code:
在 chat/urls.py 文件中包含如下代碼:

# chat/urls.py
from django.conf.urls import url

from . import views

urlpatterns = [
    url(r'^$', views.index, name='index'),
]

The next step is to point the root URLconf at the chat.urls module. In mysite/urls.py, add an import for django.conf.urls.include and insert an include() in the urlpatterns list, so you have:
下一步是將根目錄下的 URLconf 文件指向 chat.urls 模塊。在 mysite/urls.py 中, 導入 django.conf.urls.include模塊,並在 urlpatterns 列表中插入一個 include() 函數, 所以您須要寫入如下代碼:

# mysite/urls.py
from django.conf.urls import include, url
from django.contrib import admin

urlpatterns = [
    url(r'^chat/', include('chat.urls')),
    url(r'^admin/', admin.site.urls),
]

Let’s verify that the index view works. Run the following command:
讓咱們驗證 index 視圖是否有效。運行如下命令:

$ python3 manage.py runserver

You’ll see the following output on the command line:
您將在命令行中看到如下輸出:

Performing system checks...

System check identified no issues (0 silenced).

You have 13 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
Run 'python manage.py migrate' to apply them.

February 18, 2018 - 22:08:39
Django version 1.11.10, using settings 'mysite.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

Note
提醒
Ignore the warning about unapplied database migrations. We won’t be using a database in this tutorial.
忽略有關未應用數據庫遷移的警告。咱們將不會在本教程中使用數據庫。

Go to http://127.0.0.1:8000/chat/ in your browser and you should see the text 「What chat room would you like to enter?」 along with a text input to provide a room name.
在瀏覽器中轉到 http://127.0.0.1:8000/chat/, 您應該看到文本 "What chat room would you like to enter?" 以及一個用於輸入房間名字的文本輸入框。

Type in 「lobby」 as the room name and press enter. You should be redirected to the room view at http://127.0.0.1:8000/chat/lobby/ but we haven’t written the room view yet, so you’ll get a 「Page not found」 error page.
輸入 "lobby" 做爲房間名稱, 而後按 enter 鍵。你應該被重定向到 http://127.0.0.1:8000/chat/lobby/的房間視圖, 但咱們尚未寫的房間視圖, 因此你會獲得一個 "頁面找不到" 錯誤頁面。

Go to the terminal where you ran the runserver command and press Control-C to stop the server.
轉到運行 runserver 命令的終端, 而後按下 Control+C 以中止服務器。

Integrate the Channels library
集成 Channels 庫

So far we’ve just created a regular Django app; we haven’t used the Channels library at all. Now it’s time to integrate Channels.
到目前爲止, 咱們剛剛建立了一個常規的 Django 應用程序;咱們根本就沒有使用 Channels 庫。如今是時候集成 Channels 庫了。

Let’s start by creating a root routing configuration for Channels. A Channels routing configuration is similar to a Django URLconf in that it tells Channels what code to run when an HTTP request is received by the Channels server.
讓咱們從建立 Channels 的根路由配置文件開始。Channels 路由配置相似於 Django URLconf,它會告訴 Channels 當收到由 Channels 服務器發過來的 HTTP 請求時,應該執行什麼代碼。

We’ll start with an empty routing configuration. Create a file mysite/routing.py and include the following code:
咱們將從一個空的路由配置文件開始。建立文件 mysite/routing.py, 並寫入如下代碼:

# mysite/routing.py
from channels.routing import ProtocolTypeRouter

application = ProtocolTypeRouter({
    # (http->django views is added by default)
})

Now add the Channels library to the list of installed apps. Edit the mysite/settings.py file and add 'channels' to the INSTALLED_APPS setting. It’ll look like this:
如今, 將 Channels 庫添加到已安裝的應用程序列表中。編輯 mysite/settings.py 文件並將 'channels' 添加到 INSTALLED_APPS 設置。它看起來像這樣:

# mysite/settings.py
INSTALLED_APPS = [
    'channels',
    'chat',
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]

You’ll also need to point Channels at the root routing configuration. Edit the mysite/settings.py file again and add the following to the bottom of it:
您一樣須要在根路由配置中指向 Channels。再次編輯 mysite/settings.py 文件, 並將如下內容添加到底部:

# mysite/settings.py
# Channels
ASGI_APPLICATION = 'mysite.routing.application'

With Channels now in the installed apps, it will take control of the runserver command, replacing the standard Django development server with the Channels development server.
如今已安裝的應用程序中有 Channels, 它將控制 runserver 命令, 用 Channels 開發服務器替換標準的 Django 開發服務器。

Note
提醒

The Channels development server will conflict with any other third-party apps that require an overloaded or replacement runserver command. An example of such a conflict is with whitenoise.runserver_nostatic from whitenoise. In order to solve such issues, try moving channels to the top of your INSTALLED_APPS or remove the offending app altogether.
Channels 開發服務器將與須要重載或替換 runserver 命令的任何其餘第三方應用程序衝突。whitenoise 中的 whitenoise.runserver_nostatic是一個衝突的例子。爲了解決這些問題, 請嘗試將 Channels 移動到您的 INSTALLED_APPS 的頂部, 或者徹底刪除與其發生衝突的應用程序。

Let’s ensure that the Channels development server is working correctly. Run the following command:
讓咱們確保 Channels 開發服務器工做正常。運行如下命令:

$ python3 manage.py runserver

You’ll see the following output on the command line:
您將在命令行中看到如下輸出:

Performing system checks...

System check identified no issues (0 silenced).

You have 13 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
Run 'python manage.py migrate' to apply them.

February 18, 2018 - 22:16:23
Django version 1.11.10, using settings 'mysite.settings'
Starting ASGI/Channels development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
2018-02-18 22:16:23,729 - INFO - server - HTTP/2 support not enabled (install the http2 and tls Twisted extras)
2018-02-18 22:16:23,730 - INFO - server - Configuring endpoint tcp:port=8000:interface=127.0.0.1
2018-02-18 22:16:23,731 - INFO - server - Listening on TCP address 127.0.0.1:8000

Note
提醒

Ignore the warning about unapplied database migrations. We won’t be using a database in this tutorial.
忽略有關未應用數據庫遷移的警告。咱們將不會在本教程中使用數據庫。

Notice the line beginning with Starting ASGI/Channels development server at http://127.0.0.1:8000/. This indicates that the Channels development server has taken over from the Django development server.
留意從 Starting ASGI/Channels development server at http://127.0.0.1:8000/ 開始的內容。這代表 Channels 開發服務器已接管了 Django 開發服務器。

Go to http://127.0.0.1:8000/chat/ in your browser and you should still see the index page that we created before.
在瀏覽器中轉到 http://127.0.0.1:8000/chat/, 您仍然應該看到咱們之前建立的 index 頁面。

Go to the terminal where you ran the runserver command and press Control-C to stop the server.
轉到運行 runserver 命令的終端, 而後按下 Control+C 以中止服務器。

 

Tutorial Part 2: Implement a Chat Server
教程2部分: 實現聊天服務器

This tutorial begins where Tutorial 1 left off. We’ll get the room page working so that you can chat with yourself and others in the same room.
本教程在教程1的基礎上開始。咱們會讓房間頁面工做, 這樣你能夠和你本身或者其餘人在同一個房間裏聊天。

Add the room view
添加房間視圖

We will now create the second view, a room view that lets you see messages posted in a particular chat room.
如今, 咱們將建立第二個視圖, 即一個容許您查看在特定聊天室中發佈消息的房間視圖。

Create a new file chat/templates/chat/room.html. Your app directory should now look like:
建立新的文件 chat/templates/chat/room.html。您的應用程序目錄如今應該看起來像:

chat/
    __init__.py
    templates/
        chat/
            index.html
            room.html
    urls.py
    views.py

Create the view template for the room view in chat/templates/chat/room.html:
在 chat/templates/chat/room.html 中填入一下代碼:

<!-- chat/templates/chat/room.html -->
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8"/>
    <title>Chat Room</title>
</head>
<body>
    <textarea id="chat-log" cols="100" rows="20"></textarea><br/>
    <input id="chat-message-input" type="text" size="100"/><br/>
    <input id="chat-message-submit" type="button" value="Send"/>
</body>
<script>
    var roomName = {{ room_name_json }};

    var chatSocket = new WebSocket(
        'ws://' + window.location.host +
        '/ws/chat/' + roomName + '/');

    chatSocket.onmessage = function(e) {
        var data = JSON.parse(e.data);
        var message = data['message'];
        document.querySelector('#chat-log').value += (message + '\n');
    };

    chatSocket.onclose = function(e) {
        console.error('Chat socket closed unexpectedly');
    };

    document.querySelector('#chat-message-input').focus();
    document.querySelector('#chat-message-input').onkeyup = function(e) {
        if (e.keyCode === 13) {  // enter, return
            document.querySelector('#chat-message-submit').click();
        }
    };

    document.querySelector('#chat-message-submit').onclick = function(e) {
        var messageInputDom = document.querySelector('#chat-message-input');
        var message = messageInputDom.value;
        chatSocket.send(JSON.stringify({
            'message': message
        }));

        messageInputDom.value = '';
    };
</script>
</html>

Create the view function for the room view in chat/views.py. Add the imports of mark_safe and json and add the room view function:
在 chat/views.py 中爲房間視圖建立視圖函數。添加導入 mark_safe 和 json 模塊, 並添加 房間視圖的視圖函數:

# chat/views.py
from django.shortcuts import render
from django.utils.safestring import mark_safe
import json

def index(request):
    return render(request, 'chat/index.html', {})

def room(request, room_name):
    return render(request, 'chat/room.html', {
        'room_name_json': mark_safe(json.dumps(room_name))
    })

Create the route for the room view in chat/urls.py:
在 chat/urls.py 中建立房間視圖的路由:

# chat/urls.py
from django.conf.urls import url

from . import views

urlpatterns = [
    url(r'^$', views.index, name='index'),
    url(r'^(?P<room_name>[^/]+)/$', views.room, name='room'),
]

Start the Channels development server:
啓動 Channels 開發服務器:

$ python3 manage.py runserver

Go to http://127.0.0.1:8000/chat/ in your browser and to see the index page.
在瀏覽器中轉到 http://127.0.0.1:8000/chat/並查看 index 頁面。

Type in 「lobby」 as the room name and press enter. You should be redirected to the room page at http://127.0.0.1:8000/chat/lobby/ which now displays an empty chat log.
輸入 「lobby」 做爲房間名稱, 而後按 enter 鍵。您將會重定向到 http://127.0.0.1:8000/chat/lobby/, 該頁面如今顯示一個空的聊天日誌。

Type the message 「hello」 and press enter. Nothing happens. In particular the message does not appear in the chat log. Why?
鍵入消息 "hello", 而後按 enter 鍵。什麼也沒有發生。尤爲是,消息並不會出如今聊天日誌中。爲何?

The room view is trying to open a WebSocket to the URL ws://127.0.0.1:8000/ws/chat/lobby/ but we haven’t created a consumer that accepts WebSocket connections yet. If you open your browser’s JavaScript console, you should see an error that looks like:
房間視圖試圖打開一個 WebSocket 鏈接到 URL ws://127.0.0.1:8000/ws/chat/lobby/,但咱們尚未建立一個接受 WebSocket 鏈接的 consumer。若是打開瀏覽器的 JavaScript 控制檯, 您應該會看到以下所示的錯誤:

WebSocket connection to 'ws://127.0.0.1:8000/ws/chat/lobby/' failed: Unexpected response code: 500

Write your first consumer
編寫您的第一個用戶

When Django accepts an HTTP request, it consults the root URLconf to lookup a view function, and then calls the view function to handle the request. Similarly, when Channels accepts a WebSocket connection, it consults the root routing configuration to lookup a consumer, and then calls various functions on the consumer to handle events from the connection.
當 Django 接受 HTTP 請求時, 它會根據根 URLconf 以查找視圖函數, 而後調用視圖函數來處理請求。一樣, 當 Channels 接受 WebSocket 鏈接時, 它會根據根路由配置以查找對應的 consumer, 而後調用 consumer 上的各類函數來處理來自這個鏈接的事件。

We will write a basic consumer that accepts WebSocket connections on the path /ws/chat/ROOM_NAME/ that takes any message it receives on the WebSocket and echos it back to the same WebSocket.
咱們將編寫一個簡單的 consumer, 它會在路徑 /ws/chat/ROOM_NAME/ 接收 WebSocket 鏈接,而後把接收任意的消息, 回送給同一個 WebSocket 鏈接。

Note
提醒

It is good practice to use a common path prefix like /ws/ to distinguish WebSocket connections from ordinary HTTP connections because it will make deploying Channels to a production environment in certain configurations easier.
使用常見的路徑前綴 (如/ws) 來區分 WebSocket 鏈接與普通 HTTP 鏈接是很好的作法, 由於它將使在某些配置中部署 Channels 更容易。

In particular for large sites it will be possible to configure a production-grade HTTP server like nginx to route requests based on path to either a production-grade WSGI server like Gunicorn+Django for ordinary HTTP requests or a production-grade ASGI server like Daphne+Channels for WebSocket requests.
特別是大型網站, 它們頗有可能配置像 nginx 這樣的生產級別 HTTP 服務器,根據路徑將請求發送到生產級別的 WSGI 服務器,例如用於處理普通 HTTP 請求的 Gunicorn + Django, 或生產級別的 ASGI 服務器,例如用於處理 WebSocket 請求的 Daphne + Channels。

Note that for smaller sites you can use a simpler deployment strategy where Daphne serves all requests - HTTP and WebSocket - rather than having a separate WSGI server. In this deployment configuration no common path prefix like is /ws/ is necessary.
請注意, 對於較小的站點, 您可使用更簡單的部署策略, 其中 Daphne 服務器處理全部的請求--HTTP 和 WebSocket--而不是單獨的 WSGI 服務器。在這種部署配置策略中, 不須要使用 /ws/ 這樣的通用路徑前綴。

Create a new file chat/consumers.py. Your app directory should now look like:
建立新文件 chat/consumers.py。您的應用程序目錄如今應該看起來像:

chat/
    __init__.py
    consumers.py
    templates/
        chat/
            index.html
            room.html
    urls.py
    views.py

Put the following code in chat/consumers.py:
在 chat/consumers.py 中寫入如下代碼:

# chat/consumers.py
from channels.generic.websocket import WebsocketConsumer
import json

class ChatConsumer(WebsocketConsumer):
    def connect(self):
        self.accept()

    def disconnect(self, close_code):
        pass

    def receive(self, text_data):
        text_data_json = json.loads(text_data)
        message = text_data_json['message']

        self.send(text_data=json.dumps({
            'message': message
        }))

This is a synchronous WebSocket consumer that accepts all connections, receives messages from its client, and echos those messages back to the same client. For now it does not broadcast messages to other clients in the same room.
這是一個同步 WebSocket consumer, 它接受全部鏈接, 接收來自其客戶端的消息, 並將這些消息回送到同一客戶端。如今, 它不向同一個房間的其餘客戶端廣播消息。

Note
提醒

Channels also supports writing asynchronous consumers for greater performance. However any asynchronous consumer must be careful to avoid directly performing blocking operations, such as accessing a Django model. See the Consumers reference for more information about writing asynchronous consumers.
Channels 還支持編寫異步 consumers 以提升性能。可是, 任何異步 consumers 都必須當心, 避免直接執行阻塞操做, 例如訪問 Django 的 model。有關編寫異步 consumers 的詳細信息, 請參閱 Consumers。

We need to create a routing configuration for the chat app that has a route to the consumer. Create a new file chat/routing.py. Your app directory should now look like:
咱們須要爲 chat app 建立一個路由配置, 它有一個通往 consumer 的路由。建立新文件 chat/routing.py。您的應用程序目錄如今應該看起來像:

chat/
    __init__.py
    consumers.py
    routing.py
    templates/
        chat/
            index.html
            room.html
    urls.py
    views.py

Put the following code in chat/routing.py:
在 chat/routing.py 中輸入如下代碼:

# chat/routing.py
from django.conf.urls import url

from . import consumers

websocket_urlpatterns = [
    url(r'^ws/chat/(?P<room_name>[^/]+)/$', consumers.ChatConsumer),
]

The next step is to point the root routing configuration at the chat.routing module. In mysite/routing.py, import AuthMiddlewareStack, URLRouter, and chat.routing; and insert a 'websocket' key in the ProtocolTypeRouter list in the following format:
下一步是將根路由指向 chat.routing 模塊。在 mysite/routing.py 中, 導入 AuthMiddlewareStack、URLRouter 和 chat.routing ;並在 ProtocolTypeRouter 列表中插入一個 "websocket" 鍵, 格式以下:

# mysite/routing.py
from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
import chat.routing

application = ProtocolTypeRouter({
    # (http->django views is added by default)
    'websocket': AuthMiddlewareStack(
        URLRouter(
            chat.routing.websocket_urlpatterns
        )
    ),
})

This root routing configuration specifies that when a connection is made to the Channels development server, the ProtocolTypeRouter will first inspect the type of connection. If it is a WebSocket connection (ws:// or wss://), the connection will be given to the AuthMiddlewareStack.
這個根路由配置指定,當與 Channels 開發服務器創建鏈接的時候, ProtocolTypeRouter 將首先檢查鏈接的類型。若是是 WebSocket 鏈接 (ws://或 wss://), 則鏈接會交給 AuthMiddlewareStack。

The AuthMiddlewareStack will populate the connection’s scope with a reference to the currently authenticated user, similar to how Django’s AuthenticationMiddleware populates the request object of a view function with the currently authenticated user. (Scopes will be discussed later in this tutorial.) Then the connection will be given to the URLRouter.
AuthMiddlewareStack 將使用對當前通過身份驗證的用戶的引用來填充鏈接的 scope, 相似於 Django 的 AuthenticationMiddleware 用當前通過身份驗證的用戶填充視圖函數的請求對象。(Scopes 將在本教程後面討論。)而後鏈接將被給到 URLRouter。

The URLRouter will examine the HTTP path of the connection to route it to a particular consumer, based on the provided url patterns.
根據提供的 url 模式, URLRouter 將檢查鏈接的 HTTP 路徑, 以將其路由指定到到特定的 consumer。

Let’s verify that the consumer for the /ws/chat/ROOM_NAME/ path works. Start the Channels development server:
讓咱們驗證 consumer 的 /ws/chat/ROOM_NAME/ 路徑是否工做。啓動 Channels 開發服務器:

$ python3 manage.py runserver

Go to the room page at http://127.0.0.1:8000/chat/lobby/ which now displays an empty chat log.
轉到 http://127.0.0.1:8000/chat/lobby/ 中的 房間頁面, 該頁如今顯示一個空的聊天日誌。

Type the message 「hello」 and press enter. You should now see 「hello」 echoed in the chat log.
輸入消息 "hello", 而後按 enter 鍵。您如今應該看到 "hello" 在聊天日誌中顯示。

However if you open a second browser tab to the same room page at http://127.0.0.1:8000/chat/lobby/ and type in a message, the message will not appear in the first tab. For that to work, we need to have multiple instances of the same ChatConsumer be able to talk to each other. Channels provides a channel layer abstraction that enables this kind of communication between consumers.
可是, 若是您打開第二個瀏覽器選項卡輸入 http://127.0.0.1:8000/chat/lobby/進入同一房間頁面上並輸入消息, 則消息並不會出如今第一個選項卡中。爲了作到這一點, 咱們須要有多個相同 ChatConsumer 實例才能互相交談。Channels 提供了一種 channel layer 抽象, 使 consumers 之間可以進行這種通訊。

Go to the terminal where you ran the runserver command and press Control-C to stop the server.
轉到運行 runserver 命令的終端, 而後按下 Control+C 以中止服務器。

Enable a channel layer
啓用 channel layer

A channel layer is a kind of communication system. It allows multiple consumer instances to talk with each other, and with other parts of Django.
channel layer 是一種通訊系統。它容許多個 consumer 實例互相交談, 以及與 Django 的其餘部分進行通訊。

A channel layer provides the following abstractions:
channel layer 提供如下抽象:

  • A channel is a mailbox where messages can be sent to. Each channel has a name. Anyone who has the name of a channel can send a message to the channel.
  • channel 是能夠發送消息的郵箱。每一個 channel 都有一個名稱。任何有名稱的 channel 均可以向 channel 發送消息。
  • A group is a group of related channels. A group has a name. Anyone who has the name of a group can add/remove a channel to the group by name and send a message to all channels in the group. It is not possible to enumerate what channels are in a particular group.
  • group 是一組相關的 channels。group 具備名稱。任何具備名字的 group 均可以按名稱向 group 中添加/刪除 channel, 也能夠向 group 中的全部 channel 發送消息。沒法列舉特定 group 中的 channel。

Every consumer instance has an automatically generated unique channel name, and so can be communicated with via a channel layer.
每一個 consumer 實例都有一個自動生成的惟一的 channel 名稱, 所以能夠經過 channel layer 進行通訊。

In our chat application we want to have multiple instances of ChatConsumer in the same room communicate with each other. To do that we will have each ChatConsumer add its channel to a group whose name is based on the room name. That will allow ChatConsumers to transmit messages to all other ChatConsumers in the same room.
在咱們的聊天應用程序中, 咱們但願在同一房間中有多個 ChatConsumer 的實例相互通訊。要作到這一點, 咱們將有每一個 ChatConsumer 添加它的 channel 到一個 group, 其名稱是基於房間的名稱。這將容許 ChatConsumers 將消息傳輸到同一個房間中的全部其餘 ChatConsumers。

We will use a channel layer that uses Redis as its backing store. To start a Redis server on port 6379, run the following command:
咱們將使用一個 channel layer, 使用 Redis 做爲其後備存儲。要在端口6379上啓動 Redis 服務器, 請運行如下命令:

$ docker run -p 6379:6379 -d redis:2.8

We need to install channels_redis so that Channels knows how to interface with Redis. Run the following command:
咱們須要安裝 channels_redis, 以便 Channels 知道如何調用 redis。運行如下命令:

$ pip3 install channels_redis

Before we can use a channel layer, we must configure it. Edit the mysite/settings.py file and add a CHANNEL_LAYERS setting to the bottom. It should look like:
在使用 channel layer 以前, 必須對其進行配置。編輯 mysite/settings.py 文件並將 CHANNEL_LAYERS 設置添加到底部。它應該看起來像:

# mysite/settings.py
# Channels
ASGI_APPLICATION = 'mysite.routing.application'
CHANNEL_LAYERS = {
    'default': {
        'BACKEND': 'channels_redis.core.RedisChannelLayer',
        'CONFIG': {
            "hosts": [('127.0.0.1', 6379)],
        },
    },
}

Note
提醒

It is possible to have multiple channel layers configured. However most projects will just use a single 'default' channel layer.
能夠配置多個 channel layer。然而, 大多數項目只使用一個 "默認" 的 channel layer。

Let’s make sure that the channel layer can communicate with Redis. Open a Django shell and run the following commands:
讓咱們確保 channel layer 能夠與 Redis 通訊。打開 Django shell 並運行如下命令:

$ python3 manage.py shell
>>> import channels.layers
>>> channel_layer = channels.layers.get_channel_layer()
>>> from asgiref.sync import async_to_sync
>>> async_to_sync(channel_layer.send)('test_channel', {'type': 'hello'})
>>> async_to_sync(channel_layer.receive)('test_channel')
{'type': 'hello'}

Type Control-D to exit the Django shell.
輸入 Control+D 退出 Django shell。

Now that we have a channel layer, let’s use it in ChatConsumer. Put the following code in chat/consumers.py, replacing the old code:
如今咱們有了一個 channel layer, 讓咱們在 ChatConsumer 中使用它。將如下代碼放在 chat/consumers.py 中, 替換舊代碼:

# chat/consumers.py
from asgiref.sync import async_to_sync
from channels.generic.websocket import WebsocketConsumer
import json

class ChatConsumer(WebsocketConsumer):
    def connect(self):
        self.room_name = self.scope['url_route']['kwargs']['room_name']
        self.room_group_name = 'chat_%s' % self.room_name

        # Join room group
        async_to_sync(self.channel_layer.group_add)(
            self.room_group_name,
            self.channel_name
        )

        self.accept()

    def disconnect(self, close_code):
        # Leave room group
        async_to_sync(self.channel_layer.group_discard)(
            self.room_group_name,
            self.channel_name
        )

    # Receive message from WebSocket
    def receive(self, text_data):
        text_data_json = json.loads(text_data)
        message = text_data_json['message']

        # Send message to room group
        async_to_sync(self.channel_layer.group_send)(
            self.room_group_name,
            {
                'type': 'chat_message',
                'message': message
            }
        )

    # Receive message from room group
    def chat_message(self, event):
        message = event['message']

        # Send message to WebSocket
        self.send(text_data=json.dumps({
            'message': message
        }))

When a user posts a message, a JavaScript function will transmit the message over WebSocket to a ChatConsumer. The ChatConsumer will receive that message and forward it to the group corresponding to the room name. Every ChatConsumer in the same group (and thus in the same room) will then receive the message from the group and forward it over WebSocket back to JavaScript, where it will be appended to the chat log.
當用戶發佈消息時, JavaScript 函數將經過 WebSocket 將消息傳輸到 ChatConsumer。ChatConsumer 將接收該消息並將其轉發到與房間名稱對應的 group。在同一 group 中的每一個 ChatConsumer (並所以在同一個房間中) 將接收來自該 group 的消息, 經過 WebSocket 將其轉發並返回到 JavaScript, 它將會追加到聊天日誌中。

Several parts of the new ChatConsumer code deserve further explanation:
新的 ChatConsumer 代碼中有幾個部分須要進一步解釋:

  • self.scope[‘url_route’][‘kwargs’][‘room_name’]
    • Obtains the 'room_name' parameter from the URL route in chat/routes.py that opened the WebSocket connection to the consumer.
    • 從給 consumer 打開 WebSocket 鏈接的 chat/routes.py 中的 URL 路由中獲取 "room_name" 參數。
    • Every consumer has a scope that contains information about its connection, including in particular any positional or keyword arguments from the URL route and the currently authenticated user if any.
    • 每一個 consumer 都有一個 scope, 其中包含有關其鏈接的信息, 特別是來自 URL 路由和當前通過身份驗證的用戶 (若是有的話) 中的任何位置或關鍵字參數。
  • self.room_group_name = ‘chat_%s’ % self.room_name
    • Constructs a Channels group name directly from the user-specified room name, without any quoting or escaping.
    • 直接從用戶指定的房間名稱構造一個 Channels group 名稱, 無需任何引用或轉義。
    • Group names may only contain letters, digits, hyphens, and periods. Therefore this example code will fail on room names that have other characters.
    • 組名可能只包含字母、數字、連字符和句點。所以, 此示例代碼將在具備其餘字符的房間名稱上發生失敗。
  • async_to_sync(self.channel_layer.group_add)(…)
    • Joins a group.
    • 加入一個 group。
    • The async_to_sync(…) wrapper is required because ChatConsumer is a synchronous WebsocketConsumer but it is calling an asynchronous channel layer method. (All channel layer methods are asynchronous.)
    • async_to_sync(…) wrapper 是必需的, 由於 ChatConsumer 是同步 WebsocketConsumer, 但它調用的是異步 channel layer 方法。(全部 channel layer 方法都是異步的)
    • Group names are restricted to ASCII alphanumerics, hyphens, and periods only. Since this code constructs a group name directly from the room name, it will fail if the room name contains any characters that aren’t valid in a group name.
    • group 名稱僅限於 ASCII 字母、連字符和句點。因爲此代碼直接從房間名稱構造 group 名稱, 所以若是房間名稱中包含的其餘無效的字符, 代碼運行則會失敗。
  • self.accept()
    • Accepts the WebSocket connection.
    • 接收 WebSocket 鏈接。
    • If you do not call accept() within the connect() method then the connection will be rejected and closed. You might want to reject a connection for example because the requesting user is not authorized to perform the requested action.
    • 若是你在 connect() 方法中不調用 accept(), 則鏈接將被拒絕並關閉。例如,您可能但願拒絕鏈接, 由於請求的用戶未被受權執行請求的操做。
    • It is recommended that accept() be called as the last action in connect() if you choose to accept the connection.
    • 若是你選擇接收鏈接, 建議 accept() 做爲在 connect() 方法中的最後一個操做。
  • async_to_sync(self.channel_layer.group_discard)(…)
    • Leaves a group.
    • 離開一個 group。
  • async_to_sync(self.channel_layer.group_send)
    • Sends an event to a group.
    • 將 event 發送到一個 group。
    • An event has a special 'type' key corresponding to the name of the method that should be invoked on consumers that receive the event.
    • event 具備一個特殊的鍵 'type' 對應接收 event 的 consumers 調用的方法的名稱。

Let’s verify that the new consumer for the /ws/chat/ROOM_NAME/ path works. To start the Channels development server, run the following command:
讓咱們驗證新 consumer 的 /ws/chat/ROOM_NAME/ 路徑是否工做。要啓動 Channels 開發服務器, 請運行如下命令:

$ python3 manage.py runserver

Open a browser tab to the room page at http://127.0.0.1:8000/chat/lobby/. Open a second browser tab to the same room page.
打開瀏覽器選項卡到 http://127.0.0.1:8000/chat/lobby/的房間頁面。打開另外一個瀏覽器選項卡到同一個房間頁面。

In the second browser tab, type the message 「hello」 and press enter. You should now see 「hello」 echoed in the chat log in both the second browser tab and in the first browser tab.
在第二個瀏覽器選項卡中, 輸入消息 "hello", 而後按 enter 鍵。在第二個瀏覽器選項卡和第一個瀏覽器選項卡中, 您如今應該看到 "hello" 在聊天日誌中顯示。

You now have a basic fully-functional chat server!
您如今有一個基本的功能齊全的聊天服務器!

Tutorial Part 3: Rewrite Chat Server as Asynchronous
教程3部分: 將聊天服務器重寫爲異步方式

This tutorial begins where Tutorial 2 left off. We’ll rewrite the consumer code to be asynchronous rather than synchronous to improve its performance.
本教程在教程2的基礎上開始。咱們將重寫 consumer 代碼使其變成是異步的而不是同步的, 以提升其性能。

Rewrite the consumer to be asynchronous
將 consumer 改寫爲異步

The ChatConsumer that we have written is currently synchronous. Synchronous consumers are convenient because they can call regular synchronous I/O functions such as those that access Django models without writing special code. However asynchronous consumers can provide a higher level of performance since they don’t need create additional threads when handling requests.
咱們編寫的 ChatConsumer 當前是同步的。同步的 consumers 很方便, 由於它們能夠調用常規的同步 I/O 函數, 例如訪問 Django models 而不用編寫特殊的代碼。可是, 異步的 consumers 能夠提供更高級別的性能, 由於它們在處理請求時不須要建立其餘線程。

ChatConsumer only uses async-native libraries (Channels and the channel layer) and in particular it does not access synchronous Django models. Therefore it can be rewritten to be asynchronous without complications.
ChatConsumer 只使用 async-native 庫 (Channels 和 channel layer), 特別是它不訪問同步的 Django models。所以, 它能夠被改寫爲異步的而不會變得複雜化。

Note
提醒

Even if ChatConsumer did access Django models or other synchronous code it would still be possible to rewrite it as asynchronous. Utilities like asgiref.sync.sync_to_async and channels.db.database_sync_to_async can be used to call synchronous code from an asynchronous consumer. The performance gains however would be less than if it only used async-native libraries.
即便 ChatConsumer 訪問 Django models 或其餘同步的代碼, 它仍然能夠將其重寫爲異步的。像 asgiref.sync.sync_to_async 和 channels.db.database_sync_to_async 這樣的實用工具能夠用來從異步 consumer 那裏調用同步的代碼。可是, 性能增益將小於僅使用 async-native 庫的方式。

Let’s rewrite ChatConsumer to be asynchronous. Put the following code in chat/consumers.py:
讓咱們重寫 ChatConsumer 使其變爲異步的。在 chat/consumers.py 中輸入如下代碼:

# chat/consumers.py
from channels.generic.websocket import AsyncWebsocketConsumer
import json

class ChatConsumer(AsyncWebsocketConsumer):
    async def connect(self):
        self.room_name = self.scope['url_route']['kwargs']['room_name']
        self.room_group_name = 'chat_%s' % self.room_name

        # Join room group
        await self.channel_layer.group_add(
            self.room_group_name,
            self.channel_name
        )

        await self.accept()

    async def disconnect(self, close_code):
        # Leave room group
        await self.channel_layer.group_discard(
            self.room_group_name,
            self.channel_name
        )

    # Receive message from WebSocket
    async def receive(self, text_data):
        text_data_json = json.loads(text_data)
        message = text_data_json['message']

        # Send message to room group
        await self.channel_layer.group_send(
            self.room_group_name,
            {
                'type': 'chat_message',
                'message': message
            }
        )

    # Receive message from room group
    async def chat_message(self, event):
        message = event['message']

        # Send message to WebSocket
        await self.send(text_data=json.dumps({
            'message': message
        }))

This new code is for ChatConsumer is very similar to the original code, with the following differences:
這些用於 ChatConsumer 的新代碼與原始代碼很是類似, 它們具備如下差別:

  • ChatConsumer now inherits from AsyncWebsocketConsumer rather than WebsocketConsumer.
  • 如今 ChatConsumer 繼承自 AsyncWebsocketConsumer 而不是 WebsocketConsumer。
  • All methods are async def rather than just def.
  • 全部方法都是 async def, 而不只僅是 def。
  • await is used to call asynchronous functions that perform I/O.
  • await 被用於調用執行 I/O 的異步函數。
  • async_to_sync is no longer needed when calling methods on the channel layer.
  • 在 channel layer 上調用方法時, 再也不須要 async_to_sync。

Let’s verify that the consumer for the /ws/chat/ROOM_NAME/ path still works. To start the Channels development server, run the following command:
讓咱們驗證 consumer 的 /ws/chat/ROOM_NAME/ 路徑是否仍然有效。啓動 Channels 開發服務器, 運行如下命令:

$ python3 manage.py runserver

Open a browser tab to the room page at http://127.0.0.1:8000/chat/lobby/. Open a second browser tab to the same room page.
打開瀏覽器選項卡到 http://127.0.0.1:8000/chat/lobby/的房間頁面。打開另外一個瀏覽器選項卡到同一個房間頁面。

In the second browser tab, type the message 「hello」 and press enter. You should now see 「hello」 echoed in the chat log in both the second browser tab and in the first browser tab.
在第二個瀏覽器選項卡中, 輸入消息 "hello", 而後按 enter 鍵。在第二個瀏覽器選項卡和第一個瀏覽器選項卡中, 您如今應該看到 "hello" 在聊天日誌中顯示。

Now your chat server is fully asynchronous!
如今, 您的聊天服務器是徹底異步的了!

 

Tutorial Part 4: Automated Testing
Tutorial 4 部分: 自動化測試

This tutorial begins where Tutorial 3 left off. We’ve built a simple chat server and now we’ll create some automated tests for it.
本教程在教程3的基礎上開始。咱們已經創建了一個簡單的聊天服務器, 如今咱們將爲它建立一些自動化測試。

Testing the views
測試視圖

To ensure that the chat server keeps working, we will write some tests.
爲了確保聊天服務器可以繼續工做, 咱們將編寫一些測試。

We will write a suite of end-to-end tests using Selenium to control a Chrome web browser. These tests will ensure that:
咱們將編寫一套端到端的測試, 使用 Selenium 來控制 Chrome web 瀏覽器。這些測試將確保:

  • when a chat message is posted then it is seen by everyone in the same room
  • 當一個聊天信息被髮布, 而後它能被你們在同一房間看到
  • when a chat message is posted then it is not seen by anyone in a different room
  • 當一個聊天信息被髮布, 那麼它在不一樣的房間是不會被別人看到的

Install the Chrome web browser, if you do not already have it.
若是您還沒有擁有 Chrome web 瀏覽器, 請安裝它。

Install chromedriver.
安裝 chromedriver。

Install Selenium. Run the following command:
安裝 Selenium。運行如下命令:

$ pip3 install selenium

Create a new file chat/tests.py. Your app directory should now look like:
建立新的文件 chat/tests.py。您的應用程序目錄如今應該看起來像:

chat/
    __init__.py
    consumers.py
    routing.py
    templates/
        chat/
            index.html
            room.html
    tests.py
    urls.py
    views.py

Put the following code in chat/tests.py:
在 chat/tests.py 中輸入如下代碼:

# chat/tests.py
from channels.testing import ChannelsLiveServerTestCase
from selenium import webdriver
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.support.wait import WebDriverWait

class ChatTests(ChannelsLiveServerTestCase):
    serve_static = True  # emulate StaticLiveServerTestCase

    @classmethod
    def setUpClass(cls):
        super().setUpClass()
        try:
            # NOTE: Requires "chromedriver" binary to be installed in $PATH
            cls.driver = webdriver.Chrome()
        except:
            super().tearDownClass()
            raise

    @classmethod
    def tearDownClass(cls):
        cls.driver.quit()
        super().tearDownClass()

    def test_when_chat_message_posted_then_seen_by_everyone_in_same_room(self):
        try:
            self._enter_chat_room('room_1')

            self._open_new_window()
            self._enter_chat_room('room_1')

            self._switch_to_window(0)
            self._post_message('hello')
            WebDriverWait(self.driver, 2).until(lambda _:
                'hello' in self._chat_log_value,
                'Message was not received by window 1 from window 1')
            self._switch_to_window(1)
            WebDriverWait(self.driver, 2).until(lambda _:
                'hello' in self._chat_log_value,
                'Message was not received by window 2 from window 1')
        finally:
            self._close_all_new_windows()

    def test_when_chat_message_posted_then_not_seen_by_anyone_in_different_room(self):
        try:
            self._enter_chat_room('room_1')

            self._open_new_window()
            self._enter_chat_room('room_2')

            self._switch_to_window(0)
            self._post_message('hello')
            WebDriverWait(self.driver, 2).until(lambda _:
                'hello' in self._chat_log_value,
                'Message was not received by window 1 from window 1')

            self._switch_to_window(1)
            self._post_message('world')
            WebDriverWait(self.driver, 2).until(lambda _:
                'world' in self._chat_log_value,
                'Message was not received by window 2 from window 2')
            self.assertTrue('hello' not in self._chat_log_value,
                'Message was improperly received by window 2 from window 1')
        finally:
            self._close_all_new_windows()

    # === Utility ===

    def _enter_chat_room(self, room_name):
        self.driver.get(self.live_server_url + '/chat/')
        ActionChains(self.driver).send_keys(room_name + '\n').perform()
        WebDriverWait(self.driver, 2).until(lambda _:
            room_name in self.driver.current_url)

    def _open_new_window(self):
        self.driver.execute_script('window.open("about:blank", "_blank");')
        self.driver.switch_to_window(self.driver.window_handles[-1])

    def _close_all_new_windows(self):
        while len(self.driver.window_handles) > 1:
            self.driver.switch_to_window(self.driver.window_handles[-1])
            self.driver.execute_script('window.close();')
        if len(self.driver.window_handles) == 1:
            self.driver.switch_to_window(self.driver.window_handles[0])

    def _switch_to_window(self, window_index):
        self.driver.switch_to_window(self.driver.window_handles[window_index])

    def _post_message(self, message):
        ActionChains(self.driver).send_keys(message + '\n').perform()

    @property
    def _chat_log_value(self):
        return self.driver.find_element_by_css_selector('#chat-log').get_property('value')

Our test suite extends ChannelsLiveServerTestCase rather than Django’s usual suites for end-to-end tests (StaticLiveServerTestCase or LiveServerTestCase) so that URLs inside the Channels routing configuration like /ws/room/ROOM_NAME/ will work inside the suite.
咱們的測試套件擴展了 ChannelsLiveServerTestCase, 而不是 Django 經常使用來進行端到端測試的套件 (StaticLiveServerTestCase 或 LiveServerTestCase), 這樣, Channels 路由配置裏面的 URLs(如 /ws/room/ROOM_NAME/ ) 將會在套件裏面工做。

To run the tests, run the following command:
要運行測試, 請運行如下命令:

$ python3 manage.py test chat.tests

You should see output that looks like:
您應該看到以下所示的輸出:

Creating test database for alias 'default'...
System check identified no issues (0 silenced).
..
----------------------------------------------------------------------
Ran 2 tests in 5.014s

OK
Destroying test database for alias 'default'...

You now have a tested chat server!
你如今有一個通過測試的聊天服務器了!

What’s next?
接下來應該作什麼呢?

Congratulations! You’ve fully implemented a chat server, made it performant by writing it in asynchronous style, and written automated tests to ensure it won’t break.
祝賀!您已經徹底實現了一個聊天服務器, 經過在異步樣式中編寫它來高性能, 並編寫了自動測試以確保它不會中斷。

This is the end of the tutorial. At this point you should know enough to start an app of your own that uses Channels and start fooling around. As you need to learn new tricks, come back to rest of the documentation.
這是教程的結尾。如今,你應該清楚地知道如何啓動一個使用了 Channels 的你本身的應用程序和作其餘的操做。當您須要學習新的技巧時, 請回到文檔的其他部分。

 

做者: 守護窗明守護愛

出處: http://www.javashuo.com/article/p-geaitztk-g.html

本文版權歸做者和博客園共有,歡迎轉載,但未經做者贊成必須保留此段聲明,且在文章頁面明顯位置給出。若有問題,可郵件(1269619593@qq.com)諮詢.

相關文章
相關標籤/搜索