Django Channels – Ví dụ cập nhật real-time trạng thái online-offline của người dùng

Tram Ho

Trong bài viết này, mình sẽ làm một ví dụ đơn giản về việc sử dụng Channels trong Django để tạo cập nhật real-time trạng thái online-offline của người dùng.

1. Cài đặt và cấu hình

Download thư mục dự án

Trong bài viết này mình sẽ sử dụng thư mục dự án myproject/ mà các bạn có thể tải về bằng cách:

Kích hoạt môi trường ảo và chạy migrate

Tiến hành cài đặt Channels tại môi trường ảo

settings

Sửa lại file settings.py để thêm Channels và dự án

routing

2. Tạo ứng dụng Accounts

Chúng ta sẽ tạo một ứng dụng tên là accounts.

Cấu trúc dự án của chúng ta sẽ như sau:

views

Tại file views.py của thư mục accounts/ xác định như sau:

URL

Tạo một file urls.py tại thư mục accounts/ để xác đinh liên kết url tới views.

Tại file urls.py của thư mục dự án mypoject/ chúng ta chỉnh sửa để liên kết tới url trên như sau:

Template

Tạo file new_user.html để xác đinh template.’

Bây giờ mở trình duyệt lên http://127.0.0.1:8000 sẽ trông như thế này:

.u61b2e40826065bb062aaea7ab247039e { padding:0px; margin: 0; padding-top:1em!important; padding-bottom:1em!important; width:100%; display: block; font-weight:bold; background-color:#eaeaea; border:0!important; border-left:4px solid #34495E!important; text-decoration:none; } .u61b2e40826065bb062aaea7ab247039e:active, .u61b2e40826065bb062aaea7ab247039e:hover { opacity: 1; transition: opacity 250ms; webkit-transition: opacity 250ms; text-decoration:none; } .u61b2e40826065bb062aaea7ab247039e { transition: background-color 250ms; webkit-transition: background-color 250ms; opacity: 1; transition: opacity 250ms; webkit-transition: opacity 250ms; } .u61b2e40826065bb062aaea7ab247039e .ctaText { font-weight:bold; color:inherit; text-decoration:none; font-size: 16px; } .u61b2e40826065bb062aaea7ab247039e .postTitle { color:#000000; text-decoration: underline!important; font-size: 16px; } .u61b2e40826065bb062aaea7ab247039e:hover .postTitle { text-decoration: underline!important; }

  Học React/Redux qua ví dụ thực tế: Kết nối với SoundCloud

.u237e8415e1baecf0c770506d8bf5a5df { padding:0px; margin: 0; padding-top:1em!important; padding-bottom:1em!important; width:100%; display: block; font-weight:bold; background-color:#eaeaea; border:0!important; border-left:4px solid #34495E!important; text-decoration:none; } .u237e8415e1baecf0c770506d8bf5a5df:active, .u237e8415e1baecf0c770506d8bf5a5df:hover { opacity: 1; transition: opacity 250ms; webkit-transition: opacity 250ms; text-decoration:none; } .u237e8415e1baecf0c770506d8bf5a5df { transition: background-color 250ms; webkit-transition: background-color 250ms; opacity: 1; transition: opacity 250ms; webkit-transition: opacity 250ms; } .u237e8415e1baecf0c770506d8bf5a5df .ctaText { font-weight:bold; color:inherit; text-decoration:none; font-size: 16px; } .u237e8415e1baecf0c770506d8bf5a5df .postTitle { color:#000000; text-decoration: underline!important; font-size: 16px; } .u237e8415e1baecf0c770506d8bf5a5df:hover .postTitle { text-decoration: underline!important; }

  So sánh các mô hình dự đoán trong bài toán nhận dạng khuôn mặt và ví dụ thực tế

3. Consumers

Bây giờ chúng ta xác định consumers của chúng ta.
Tạo một file consumers.py tại thư mục accounts/ như sau:

Consumer giúp giao tiếp xử lý các sự kiện, và gửi message lại trình duyệt.

Có một số lớp con của consumer bạn có thể sử dụng ngoài AsyncConsumer và SyncConsumer như:

  • WebsocketConsumer và AsyncWebsocketConsumer
  • JsonWebsocketConsumer và AsyncJsonWebsocketConsumer
  • AsyncHttpConsumer

Và ở đây mình sử dụng AsyncJsonWebsocketConsumer để thực truyền nhận thông qua mã hóa JSON. Nó có các thuộc tính như:

  • connect() : Thực thi khi Consumer được kết nối.
  • receive_json() : Thực thi khi nhận được message tới thông qua phương thức send của WebSocket
  • disconnect() : Thực thi khi consumer ngắt kết nối
  • accept() Sử dụng accept() để chấp nhận kết nối WebSocket, nếu không gọi phương thức này trong connect() thì nó sẽ từ chối WebSocket và sẽ bị đóng lại.
  • channel_layer(), channel_name() : Mỗi consumer khi kết nối sẽ được tự động thêm vào thuộc tính channel_layer() và channel_name(). Khi một lượt request đến chúng ta sẽ có channel_name().
  • Group : Chúng ta sẽ dùng group_add để thêm channel_name() này vào trong group với tên là “users”, và group_discard để xóa channel_name() này khỏi group khi người dùng này thoát hay consumer này đóng.

4. WebSocket

Bây giờ chúng ta sẽ tạo WebSocket để giao tiếp với Consumers . Chỉnh sửa lại file new_user.html như sau

Ở file đầu tiên chúng ta dùng reconnecting-websocket.min.js tải về tại reconnecting-websocket. Mình sẽ giải thích vì sao dùng nó sau. Tạo một file new_user.js trong thư mục site_static/js/channels/ như sau: site_static/channels/new_user.js

  1. Đầu tiên sẽ kết nối WebSocket tới đường dẫn là ws://127.0.0.1:8000/new-user/ nơi mà trước đó chúng ta đã xác định router là path(“new-user/”, NewUserConsumer).

Hai cái này phải khớp với nhau ví dụ nếu router là path(“new-user-update/”, NewUserConsumer) thì đường dẫn phải là ws://127.0.0.1:8000/new-user-update/

  1. Như đã đã cập trên, ở đây bạn có thể sử dụng như var socket = new WebSocket(endpoint). Tuy nhiên, việc sử dụng ReconnectingWebSocket sẽ giúp cho WebSocket tự động kết nối lại khi WebSocket bị đóng. Tức khi khi bị onclose thì nó sẽ tự động onopen lại.
  2. Các phương thức onopen, onclose, onmessage để xác định trạng thái WebSocket kết nối, bị đóng và nhận message. Sử dụng console.log để giúp cho việc kiểm tra lỗi đơn giản hơn.

5. Templates

Bây giờ mọi thứ đã sẵn sàng, tuy nhiên chúng ta vẫn chưa thực hiện gì cả. Nhiệm vụ ở đây là sẽ tự động cập nhật trạng thái online-offline của người dùng. Thêm vào file new_user.html như sau: templates/new_user.html

Tạo một file users.html ở thư mục templates/includes/ như sau:

Mở trình duyệt tại địa chỉ http://127.0.0.1:8000/ sẽ trông như thế này:

Không có thông tin gì vì chúng ta chưa tạo người dùng nào cả. Các bạn tự thêm một số người dùng tại trang admin. Sau khi thêm 3 người dùng nó sẽ như thế này:

6. Cập nhật trạng thái

models

Để biết trạng thái online hay offline chúng ta cần thêm trường status cho model User. Bằng cách tọa model Profile và kết nối OneToOne tới User như sau: accocunts/models.py

Mặc định sẽ để False tức là trạng thái Offline

Chạy migration:

Consumers

Bây giờ tới phần quan trọng nhất, chỉnh sửa consumers để check trạng thái online-offline.

  • Khi người dùng online: Consumer ở trạng thái connect -> cập nhật status=True -> update lại trang.
  • Khi người dùng offline: Cusumer ở trạng thái disconnect – > cập nhật status=False -> update lại trang. Tùy chỉnh lại consumer như sau:

accounts/consumers.py

  1. Vì không thể dùng mã đồng bộ với không đồng bộ cùng nhau được. Nên ở đây chúng ta sử dụng database_sync_to_async được cung cập bởi Channels để cho phép ta có thể truy cập database và chỉnh sửa Profile.
  2. Phương thức update_user_status() sẽ có nhiệm vụ cập nhật trạng thái status của người dùng.
  3. Phương thức send_status() sử dụng send_json có nhiệm vụ gửi thông tin tới Websocket dưới dạng mã JSON. Bên Websocket sẽ nhận được thông tin tại phương thức onmessage với tên data.
  4. Sử dụng phương thức group_send bao gồm 2 đối số. Đối số thứ nhất là tên group là “users”, đối số thứ 2 chứa thông tin cần gửi đi.
  • type: Sẽ xác định tên phương thức được thực thi trong consumer. Nghĩa là mỗi lần gọi group_send() nó sẽ thực thi phương thức mà type xác định.
  • html_users : Đây là mã HTML chứa thông tin người dùng đã được cập nhật status. Nó sẽ được dùng để thay thế đoạn mã html cũ bằng Jquery để cập nhật status.

Websocket

Bây giờ WebSocket có thể nhận được tin nhắn ở onmessage. Giờ chúng ta cần xử lý với thông tin đó. Chỉnh lại file new_user.js tại thư mục site_static/js/channels/ như sau

Như đã nói thông tin nhận được từ consumer gửi tới Websocket là mã JSON với tên là data. Vì thế tại đây chúng ta cần dùng phương thức JSON trong JS để đọc nó. Với thông tin nhận được, chúng ta sẽ thay thế mã HTML tại thẻ có id là new_user của file new_user.html bằng Jquery.

Thành quả

Như vậy chúng ta thực hiện xong nhiệm vụ cập nhật real-time trạng thái online – offline của người dùng. Kết quả có thể trông như thế này:

Chỉnh thêm một chút nữa

Cứ mỗi lần đăng xuất đăng nhập chúng ta cứ phải vào admin để thực hiện. Như thế hơi phiền phức trong quá trình test. Chúng ta sẽ thêm nút đăng nhập đăng xuất như sau

URL

Sửa lại file urls.py của thư mục accounts/ như sau:

Ở đây mình sử dụng ứng dụng có sẵn của Django tại django.contrib.auth để thực hiện quá trình đăng nhập, đăng xuất.

templates

Tạo thư mục registration/ tại thư mục templates/. Trong thư mục này, tạo ra 2 file là login.html và logged_out.html. Hai file này là file mà ứng dụng auth trên sẽ trỏ tới.

đăng nhập

templates/registration/login.html

Đăng xuất

templates/registration/logged_out.html

navbar

Thêm thanh navbar chứa nút đăng nhập và đăng xuất như sau: templates/base.html

Tại thư mục includes/ tạo file navbar.html như sau:

setting

Thêm vào file setting như sau:

Ở đây sẽ xác định URL cho trang đăng nhập, đăng xuất và trang chuyển hướng sau khi đăng nhập thành công.

Kết quả

.uf5eec490ff02d247de1e9780f4e16a55 { padding:0px; margin: 0; padding-top:1em!important; padding-bottom:1em!important; width:100%; display: block; font-weight:bold; background-color:#eaeaea; border:0!important; border-left:4px solid #34495E!important; text-decoration:none; } .uf5eec490ff02d247de1e9780f4e16a55:active, .uf5eec490ff02d247de1e9780f4e16a55:hover { opacity: 1; transition: opacity 250ms; webkit-transition: opacity 250ms; text-decoration:none; } .uf5eec490ff02d247de1e9780f4e16a55 { transition: background-color 250ms; webkit-transition: background-color 250ms; opacity: 1; transition: opacity 250ms; webkit-transition: opacity 250ms; } .uf5eec490ff02d247de1e9780f4e16a55 .ctaText { font-weight:bold; color:inherit; text-decoration:none; font-size: 16px; } .uf5eec490ff02d247de1e9780f4e16a55 .postTitle { color:#000000; text-decoration: underline!important; font-size: 16px; } .uf5eec490ff02d247de1e9780f4e16a55:hover .postTitle { text-decoration: underline!important; }

  Một số khác biệt cơ bản giữa Vue và React qua một ví dụ đơn giản

.uc84116fa603aacff6e847b3ed1e4e03a { padding:0px; margin: 0; padding-top:1em!important; padding-bottom:1em!important; width:100%; display: block; font-weight:bold; background-color:#eaeaea; border:0!important; border-left:4px solid #34495E!important; text-decoration:none; } .uc84116fa603aacff6e847b3ed1e4e03a:active, .uc84116fa603aacff6e847b3ed1e4e03a:hover { opacity: 1; transition: opacity 250ms; webkit-transition: opacity 250ms; text-decoration:none; } .uc84116fa603aacff6e847b3ed1e4e03a { transition: background-color 250ms; webkit-transition: background-color 250ms; opacity: 1; transition: opacity 250ms; webkit-transition: opacity 250ms; } .uc84116fa603aacff6e847b3ed1e4e03a .ctaText { font-weight:bold; color:inherit; text-decoration:none; font-size: 16px; } .uc84116fa603aacff6e847b3ed1e4e03a .postTitle { color:#000000; text-decoration: underline!important; font-size: 16px; } .uc84116fa603aacff6e847b3ed1e4e03a:hover .postTitle { text-decoration: underline!important; }

  Học UX qua ví dụ- thiết kế màn hình đăng ký ( Sign up- form)

Chia sẻ bài viết ngay

Nguồn bài viết : viblo