資料綁定

Channel 的資料綁定架構會自動處理 Django 的 model 寫入前端 view 中,例如使用 javascript 強化的網站。它提供了一個快速且彈性的方式來產生 Group 的 model 改變 message,以及接收 model 發生變化時的 message。

當前主要的目標是 WebSocket,但此架構有相當的彈性可以支援任何通訊協定。

資料綁定可以接受什麼?

Channel 的資料綁定以兩種方式運作:

  • 發送,當 model 透過 Django 發生變化時,訊息會發送到監聽的客戶端。這包含了事例的建立、更新與刪除。

  • 接收,標準化的訊息格式,允許客戶端發送訊息來建立、更新與刪除事例。

收發,允許 UI 可以設計成自動更新反映客戶端更新的數值。例如,網誌的即時更新可以藉由 PO 文物件的資料綁定來達成,而編輯介面也可以藉此同步顯示其他使用者的修改。

It has some limitations:

  • 發送的資料綁定是藉由 signal 來達成的,所以假使 model 的資料更新不是透過 Django (或是使用 QuerySet 的 .update() 函式),就沒有觸發的 signal,改變的訊息就不會被送出。你可以自己觸發改變,但是你會需要從系統中正確的來源來送出這個 signal。

  • 內建的序列化是來自 Django 的內建功能,它只能處理特定的資料型態。如果需要有更大的彈性,你可以透過像是 Django REST 架構的序列化函式庫來達成。

入門

單一的綁定子類別用來處理 model 發送與接收的綁定,你也可以在每個 model 使用多個綁定 (例如如果你想使用不同的格式或權限檢查)。

你可以自底層的 Binding 實作所有需要的函式,但我們這裡把重點放在 WebSocket JSON 變形上,因著這是最簡單的入手點而且最接近你可能需要的部分。

從這裡開始:

from django.db import models
from channels.binding.websockets import WebsocketBinding

class IntegerValue(models.Model):

    name = models.CharField(max_length=100, unique=True)
    value = models.IntegerField(default=0)

class IntegerValueBinding(WebsocketBinding):

    model = IntegerValue
    stream = "intval"
    fields = ["name", "value"]

    @classmethod
    def group_names(cls, instance):
        return ["intval-updates"]

    def has_permission(self, user, action, pk):
        return True

這裡定義一個 WebSocket 的綁定 - 如此就知道如何送出 JSON WebSocket 格式的頁框 - 並且提供三件你必須提供的部分:

  • fields 是一個序列化請求可傳回欄位的白名單。Channel 預設不開啟所有的欄位,主要是基於安全性的考量。如果你想全部開啟的話,把該列表設為 ["__all__"] 即可。另一方便,也可以使用 exclude 來建立黑名單。

  • group_names 傳回一個基於該事例的外送更新群組列表。例如,你可以發送PO文到名稱包含父網誌 ID 的不同即時網誌中。這裡我們只用一個固定的群組名稱。基於 group_names 如何隨著事例的改變,Channels 將會處理客戶端需要的 create, updatedelete 等訊息 (或是改變是對客戶端隱藏的)。

  • has_permission 則傳回一個接收綁定更新,是否會被 model 執行的許可與否。我們採取了一個非常不安全的作法,總是回傳 True。但是這裡就是你可以讓 Django 做檢查或是自行撰寫權限系統的地方。

做為參考, action 總是以下 "create", "update""delete" 之一的萬國碼字串。你也可以提供 WebSocket Multiplexing 串流名稱給客戶端,如果使用 WebSocket 資料綁定,你必須使用多工化。

只要如此新增一個綁定在匯入的地方,發送綁定訊息就會被送出,但你仍需要提供一個 Consumer 來接受進來的綁定更新,並且在連線時將人加到正確的群組。WebSocket 綁定類別使用標準的 WebSocket Multiplexing ,因此你只需要使用它。

from channels.generic.websockets import WebsocketDemultiplexer
from .binding import IntegerValueBinding

class Demultiplexer(WebsocketDemultiplexer):

    consumers = {
        "intval": IntegerValueBinding.consumer,
    }

    def connection_groups(self):
        return ["intval-updates"]

如同標準的串流對消費者映射,你也需要指定 connection_groups,一個將上線使用者加入群組的列表。這也符合 group_names 在你的綁定上的邏輯,這裡我們使用一個固定的群組名稱。請注意,綁定有一個 .consumer 屬性,這是一個標準 WebSocket-JSON consumer,解多工器可以發送解開的 websocket.receive 訊息給這個 consumer。

綁到你的路由,這樣子就完成了:

from channels import route_class, route
from .consumers import Demultiplexer
from .models import IntegerValueBinding

channel_routing = [
    route_class(Demultiplexer, path="^/binding/"),
]

前端的考量點

You can use the standard Channels WebSocket wrapper to automatically run demultiplexing, and then tie the events you receive into your frontend framework of choice based on action, pk and data.

備註

我們需要熱門 JavaScript 架構的資料綁定插件,如果你有興趣提供,請和我們聯絡。

客製序列化/通訊協定

不同於繼承自 WebsocketBinding,你可以直接繼承自底層的 Binding 類別,然後自己實作序列化與反序列化。在這部分的參考文件完成之前,我們建議參考 channels/bindings/base.py 原始碼,程式中有相當完整的註解。

斷線的處理

由於 Channel 的資料綁定沒有包含事件的歷史,也就是說當網路連線斷開,你會遺失這段時間發生的事例訊息。因此,建議當連線恢復之後,直接透過 API 來重新載入資料,而不要依賴即時更新在關鍵的功能,或是設計 UI 來處理資料遺失的問題。(例如只有更新沒有新建時,下個更新會修正全部的遺失資料)