Dalam tutorial ini, kita akan menggunakan Saluran Django untuk membuat aplikasi waktu nyata yang memperbarui daftar pengguna saat mereka masuk dan keluar.
Dengan WebSockets (melalui Saluran Django) mengelola komunikasi antara klien dan server, kapan pun pengguna diautentikasi, sebuah peristiwa akan disiarkan ke setiap pengguna lain yang terhubung. Layar setiap pengguna akan berubah secara otomatis, tanpa mereka harus memuat ulang browser mereka.
CATATAN: Kami menyarankan Anda memiliki beberapa pengalaman dengan Django sebelum memulai tutorial ini. Selain itu, Anda harus terbiasa dengan konsep WebSockets.
Bonus Gratis: Klik di sini untuk mendapatkan akses ke Panduan Sumber Daya Pembelajaran Django (PDF) gratis yang menunjukkan kepada Anda tip dan trik serta perangkap umum yang harus dihindari saat membangun aplikasi web Python + Django.
Aplikasi kami menggunakan:
- Python (v3.6.0)
- Django (v1.10.5)
- Saluran Django (v1.0.3)
- Redis (v3.2.8)
Tujuan
Pada akhir tutorial ini, Anda akan dapat…
- Tambahkan dukungan soket Web ke proyek Django melalui Saluran Django
- Mengatur koneksi sederhana antara Django dan server Redis
- Menerapkan otentikasi pengguna dasar
- Manfaatkan Sinyal Django untuk mengambil tindakan saat pengguna masuk atau keluar
Memulai
Pertama, buat lingkungan virtual baru untuk mengisolasi dependensi proyek kami:
$ mkdir django-example-channels
$ cd django-example-channels
$ python3.6 -m venv env
$ source env/bin/activate
(env)$
Instal Django, Django Channels, dan ASGI Redis, lalu buat proyek dan aplikasi Django baru:
(env)$ pip install django==1.10.5 channels==1.0.2 asgi_redis==1.0.0
(env)$ django-admin.py startproject example_channels
(env)$ cd example_channels
(env)$ python manage.py startapp example
(env)$ python manage.py migrate
CATATAN: Selama tutorial ini, kita akan membuat berbagai file dan folder yang berbeda. Silakan merujuk ke struktur folder dari repositori proyek jika Anda buntu.
Selanjutnya, unduh dan instal Redis. Jika Anda menggunakan Mac, sebaiknya gunakan Homebrew:
$ brew install redis
Mulai server Redis di jendela terminal baru dan pastikan itu berjalan pada port defaultnya, 6379. Nomor port akan menjadi penting ketika kita memberitahu Django bagaimana berkomunikasi dengan Redis.
Selesaikan penyiapan dengan memperbarui INSTALLED_APPS
di settings.py project proyek berkas:
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'channels',
'example',
]
Kemudian Konfigurasikan CHANNEL_LAYERS
dengan menyetel backend dan perutean default:
CHANNEL_LAYERS = {
'default': {
'BACKEND': 'asgi_redis.RedisChannelLayer',
'CONFIG': {
'hosts': [('localhost', 6379)],
},
'ROUTING': 'example_channels.routing.channel_routing',
}
}
Ini menggunakan backend Redis yang juga diperlukan dalam produksi.
WebSocket 101
Biasanya, Django menggunakan HTTP untuk berkomunikasi antara klien dan server:
- Klien mengirimkan permintaan HTTP ke server.
- Django mengurai permintaan, mengekstrak URL, dan kemudian mencocokkannya dengan tampilan.
- Tampilan memproses permintaan dan mengembalikan respons HTTP ke klien.
Tidak seperti HTTP, protokol WebSockets memungkinkan komunikasi dua arah, artinya server dapat mendorong data ke klien tanpa diminta oleh pengguna. Dengan HTTP, hanya klien yang membuat permintaan yang menerima respons. Dengan WebSockets, server dapat berkomunikasi dengan beberapa klien secara bersamaan. Seperti yang akan kita lihat nanti dalam tutorial ini, kita mengirim pesan WebSockets menggunakan ws://
awalan, sebagai lawan dari http://
.
CATATAN: Sebelum mendalami, tinjau dokumentasi Konsep Saluran dengan cepat.
Konsumen dan Grup
Mari kita buat konsumen pertama kita, yang menangani koneksi dasar antara klien dan server. Buat file baru bernama example_channels/example/consumers.py :
from channels import Group
def ws_connect(message):
Group('users').add(message.reply_channel)
def ws_disconnect(message):
Group('users').discard(message.reply_channel)
Konsumen adalah pasangan dari pandangan Django. Setiap pengguna yang terhubung ke aplikasi kami akan ditambahkan ke grup "pengguna" dan akan menerima pesan yang dikirim oleh server. Saat klien memutuskan sambungan dari aplikasi kami, saluran akan dihapus dari grup, dan pengguna akan berhenti menerima pesan.
Selanjutnya, mari kita mengatur rute, yang bekerja dengan cara yang hampir sama seperti konfigurasi URL Django, dengan menambahkan kode berikut ke file baru bernama example_channels/routing.py :
from channels.routing import route
from example.consumers import ws_connect, ws_disconnect
channel_routing = [
route('websocket.connect', ws_connect),
route('websocket.disconnect', ws_disconnect),
]
Jadi, kami mendefinisikan channel_routing
bukannya urlpatterns
dan route()
bukannya url()
. Perhatikan bahwa kami menautkan fungsi konsumen kami ke WebSockets.
Templat
Mari kita menulis beberapa HTML yang dapat berkomunikasi dengan server kita melalui WebSocket. Buat folder “templat” di dalam “contoh”, lalu tambahkan folder “contoh” di dalam “templat” - “contoh_saluran/contoh/templat/contoh”.
Tambahkan _base.html berkas:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
<title>Example Channels</title>
</head>
<body>
<div class="container">
<br>
{% block content %}{% endblock content %}
</div>
<script src="//code.jquery.com/jquery-3.1.1.min.js"></script>
{% block script %}{% endblock script %}
</body>
</html>
Dan daftar_pengguna.html :
{% extends 'example/_base.html' %}
{% block content %}{% endblock content %}
{% block script %}
<script>
var socket = new WebSocket('ws://' + window.location.host + '/users/');
socket.onopen = function open() {
console.log('WebSockets connection created.');
};
if (socket.readyState == WebSocket.OPEN) {
socket.onopen();
}
</script>
{% endblock script %}
Sekarang, ketika klien berhasil membuka koneksi dengan server menggunakan WebSocket, kita akan melihat pesan konfirmasi dicetak ke konsol.
Tampilan
Siapkan tampilan Django yang mendukung untuk merender template kita di dalam example_channels/example/views.py :
from django.shortcuts import render
def user_list(request):
return render(request, 'example/user_list.html')
Tambahkan URL ke example_channels/example/urls.py :
from django.conf.urls import url
from example.views import user_list
urlpatterns = [
url(r'^$', user_list, name='user_list'),
]
Perbarui juga URL proyek di example_channels/example_channels/urls.py :
from django.conf.urls import include, url
from django.contrib import admin
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^', include('example.urls', namespace='example')),
]
Uji
Siap untuk menguji?
(env)$ python manage.py runserver
CATATAN: Sebagai alternatif, Anda dapat menjalankan
python manage.py runserver --noworker
danpython manage.py runworker
di dua terminal yang berbeda untuk menguji antarmuka dan server pekerja sebagai dua proses yang terpisah. Kedua metode bekerja!
Saat Anda mengunjungi http://localhost:8000/, Anda akan melihat pesan koneksi tercetak ke terminal:
[2017/02/19 23:24:57] HTTP GET / 200 [0.02, 127.0.0.1:52757]
[2017/02/19 23:24:58] WebSocket HANDSHAKING /users/ [127.0.0.1:52789]
[2017/02/19 23:25:03] WebSocket DISCONNECT /users/ [127.0.0.1:52789]
Otentikasi Pengguna
Sekarang kami telah membuktikan bahwa kami dapat membuka koneksi, langkah kami selanjutnya adalah menangani otentikasi pengguna. Ingat:Kami ingin pengguna dapat masuk ke aplikasi kami dan melihat daftar semua pengguna lain yang berlangganan grup pengguna tersebut. Pertama, kita membutuhkan cara bagi pengguna untuk membuat akun dan masuk. Mulailah dengan membuat halaman masuk sederhana yang memungkinkan pengguna untuk mengautentikasi dengan nama pengguna dan kata sandi.
Buat file baru bernama log_in.html dalam “example_channels/example/templates/example”:
{% extends 'example/_base.html' %}
{% block content %}
<form action="{% url 'example:log_in' %}" method="post">
{% csrf_token %}
{% for field in form %}
<div>
{{ field.label_tag }}
{{ field }}
</div>
{% endfor %}
<button type="submit">Log in</button>
</form>
<p>Don't have an account? <a href="{% url 'example:sign_up' %}">Sign up!</a></p>
{% endblock content %}
Selanjutnya, perbarui example_channels/example/views.py seperti ini:
from django.contrib.auth import login, logout
from django.contrib.auth.forms import AuthenticationForm
from django.core.urlresolvers import reverse
from django.shortcuts import render, redirect
def user_list(request):
return render(request, 'example/user_list.html')
def log_in(request):
form = AuthenticationForm()
if request.method == 'POST':
form = AuthenticationForm(data=request.POST)
if form.is_valid():
login(request, form.get_user())
return redirect(reverse('example:user_list'))
else:
print(form.errors)
return render(request, 'example/log_in.html', {'form': form})
def log_out(request):
logout(request)
return redirect(reverse('example:log_in'))
Django hadir dengan formulir yang mendukung fungsionalitas otentikasi umum. Kita dapat menggunakan AuthenticationForm
untuk menangani login pengguna. Formulir ini memeriksa nama pengguna dan kata sandi yang diberikan, lalu mengembalikan User
objek jika pengguna yang divalidasi ditemukan. Kami memasukkan pengguna yang divalidasi dan mengarahkan mereka ke beranda kami. Seorang pengguna juga harus memiliki kemampuan untuk logout dari aplikasi, jadi kami membuat tampilan logout yang menyediakan fungsionalitas tersebut dan kemudian membawa pengguna kembali ke layar login.
Kemudian perbarui example_channels/example/urls.py :
from django.conf.urls import url
from example.views import log_in, log_out, user_list
urlpatterns = [
url(r'^log_in/$', log_in, name='log_in'),
url(r'^log_out/$', log_out, name='log_out'),
url(r'^$', user_list, name='user_list')
]
Kami juga membutuhkan cara untuk membuat pengguna baru. Buat halaman pendaftaran dengan cara yang sama seperti login dengan menambahkan file baru bernama sign_up.html ke “example_channels/example/templates/example”:
{% extends 'example/_base.html' %}
{% block content %}
<form action="{% url 'example:sign_up' %}" method="post">
{% csrf_token %}
{% for field in form %}
<div>
{{ field.label_tag }}
{{ field }}
</div>
{% endfor %}
<button type="submit">Sign up</button>
<p>Already have an account? <a href="{% url 'example:log_in' %}">Log in!</a></p>
</form>
{% endblock content %}
Perhatikan bahwa halaman login memiliki link ke halaman sign-up, dan halaman sign-up memiliki link kembali ke login.
Tambahkan fungsi berikut ke tampilan:
def sign_up(request):
form = UserCreationForm()
if request.method == 'POST':
form = UserCreationForm(data=request.POST)
if form.is_valid():
form.save()
return redirect(reverse('example:log_in'))
else:
print(form.errors)
return render(request, 'example/sign_up.html', {'form': form})
Kami menggunakan formulir bawaan lain untuk pembuatan pengguna. Setelah validasi formulir berhasil, kami mengarahkan ulang ke halaman login.
Pastikan untuk mengimpor formulir:
from django.contrib.auth.forms import AuthenticationForm, UserCreationForm
Perbarui example_channels/example/urls.py lagi:
from django.conf.urls import url
from example.views import log_in, log_out, sign_up, user_list
urlpatterns = [
url(r'^log_in/$', log_in, name='log_in'),
url(r'^log_out/$', log_out, name='log_out'),
url(r'^sign_up/$', sign_up, name='sign_up'),
url(r'^$', user_list, name='user_list')
]
Pada titik ini, kita perlu membuat pengguna. Jalankan server dan kunjungi http://localhost:8000/sign_up/
di peramban Anda. Isi formulir dengan nama pengguna dan kata sandi yang valid dan kirimkan untuk membuat pengguna pertama kami.
CATATAN: Coba gunakan
michael
sebagai nama pengguna danjohnson123
sebagai kata sandi.
sign_up
view mengarahkan kita ke log_in
tampilan, dan dari sana kita dapat mengautentikasi pengguna yang baru kita buat.
Setelah kami masuk, kami dapat menguji tampilan otentikasi baru kami.
Gunakan formulir pendaftaran untuk membuat beberapa pengguna baru sebagai persiapan untuk bagian berikutnya.
Peringatan Masuk
Kami memiliki otentikasi pengguna dasar yang berfungsi, tetapi kami masih perlu menampilkan daftar pengguna dan kami memerlukan server untuk memberi tahu grup ketika pengguna masuk dan keluar. Kami perlu mengedit fungsi konsumen kami sehingga mereka mengirim pesan tepat setelah klien terhubung dan tepat sebelum klien terputus. Data pesan akan menyertakan nama pengguna dan status koneksi pengguna.
Perbarui example_channels/example/consumers.py seperti ini:
import json
from channels import Group
from channels.auth import channel_session_user, channel_session_user_from_http
@channel_session_user_from_http
def ws_connect(message):
Group('users').add(message.reply_channel)
Group('users').send({
'text': json.dumps({
'username': message.user.username,
'is_logged_in': True
})
})
@channel_session_user
def ws_disconnect(message):
Group('users').send({
'text': json.dumps({
'username': message.user.username,
'is_logged_in': False
})
})
Group('users').discard(message.reply_channel)
Perhatikan bahwa kami telah menambahkan dekorator ke fungsi untuk mendapatkan pengguna dari sesi Django. Selain itu, semua pesan harus dapat dibuat serial JSON, jadi kami membuang data kami ke dalam string JSON.
Selanjutnya, perbarui example_channels/example/templates/example/user_list.html :
{% extends 'example/_base.html' %}
{% block content %}
<a href="{% url 'example:log_out' %}">Log out</a>
<br>
<ul>
{% for user in users %}
<!-- NOTE: We escape HTML to prevent XSS attacks. -->
<li data-username="{{ user.username|escape }}">
{{ user.username|escape }}: {{ user.status|default:'Offline' }}
</li>
{% endfor %}
</ul>
{% endblock content %}
{% block script %}
<script>
var socket = new WebSocket('ws://' + window.location.host + '/users/');
socket.onopen = function open() {
console.log('WebSockets connection created.');
};
socket.onmessage = function message(event) {
var data = JSON.parse(event.data);
// NOTE: We escape JavaScript to prevent XSS attacks.
var username = encodeURI(data['username']);
var user = $('li').filter(function () {
return $(this).data('username') == username;
});
if (data['is_logged_in']) {
user.html(username + ': Online');
}
else {
user.html(username + ': Offline');
}
};
if (socket.readyState == WebSocket.OPEN) {
socket.onopen();
}
</script>
{% endblock script %}
Di beranda kami, kami memperluas daftar pengguna kami untuk menampilkan daftar pengguna. Kami menyimpan nama pengguna setiap pengguna sebagai atribut data untuk memudahkan menemukan item pengguna di DOM. Kami juga menambahkan pendengar acara ke WebSocket kami yang dapat menangani pesan dari server. Ketika kami menerima pesan, kami mengurai data JSON, menemukan <li>
elemen untuk pengguna tertentu, dan perbarui status pengguna tersebut.
Django tidak melacak apakah pengguna masuk, jadi kita perlu membuat model sederhana untuk melakukannya untuk kita. Buat LoggedInUser
model dengan koneksi satu-ke-satu ke User
kami model di example_channels/example/models.py :
from django.conf import settings
from django.db import models
class LoggedInUser(models.Model):
user = models.OneToOneField(
settings.AUTH_USER_MODEL, related_name='logged_in_user')
Aplikasi kami akan membuat LoggedInUser
instance saat pengguna login, dan aplikasi akan menghapus instance saat pengguna logout.
Lakukan migrasi skema, lalu migrasikan database kami untuk menerapkan perubahan.
(env)$ python manage.py makemigrations
(env)$ python manage.py migrate
Selanjutnya, perbarui tampilan daftar pengguna kami, diexample_channels/example/views.py , untuk mengambil daftar pengguna yang akan dirender:
from django.contrib.auth import get_user_model, login, logout
from django.contrib.auth.decorators import login_required
from django.contrib.auth.forms import AuthenticationForm, UserCreationForm
from django.core.urlresolvers import reverse
from django.shortcuts import render, redirect
User = get_user_model()
@login_required(login_url='/log_in/')
def user_list(request):
"""
NOTE: This is fine for demonstration purposes, but this should be
refactored before we deploy this app to production.
Imagine how 100,000 users logging in and out of our app would affect
the performance of this code!
"""
users = User.objects.select_related('logged_in_user')
for user in users:
user.status = 'Online' if hasattr(user, 'logged_in_user') else 'Offline'
return render(request, 'example/user_list.html', {'users': users})
def log_in(request):
form = AuthenticationForm()
if request.method == 'POST':
form = AuthenticationForm(data=request.POST)
if form.is_valid():
login(request, form.get_user())
return redirect(reverse('example:user_list'))
else:
print(form.errors)
return render(request, 'example/log_in.html', {'form': form})
@login_required(login_url='/log_in/')
def log_out(request):
logout(request)
return redirect(reverse('example:log_in'))
def sign_up(request):
form = UserCreationForm()
if request.method == 'POST':
form = UserCreationForm(data=request.POST)
if form.is_valid():
form.save()
return redirect(reverse('example:log_in'))
else:
print(form.errors)
return render(request, 'example/sign_up.html', {'form': form})
Jika pengguna memiliki LoggedInUser
terkait , maka kita mencatat status pengguna sebagai “Online”, dan jika tidak, pengguna adalah “Offline”. Kami juga menambahkan @login_required
dekorator untuk tampilan daftar pengguna dan logout kami untuk membatasi akses hanya untuk pengguna terdaftar.
Tambahkan juga impor:
from django.contrib.auth import get_user_model, login, logout
from django.contrib.auth.decorators import login_required
Pada titik ini, pengguna dapat masuk dan keluar, yang akan memicu server untuk mengirim pesan ke klien, tetapi kami tidak memiliki cara untuk mengetahui pengguna mana yang masuk saat pengguna pertama kali masuk. Pengguna hanya melihat pembaruan saat pengguna lain perubahan status. Di sinilah LoggedInUser
ikut bermain, tetapi kita membutuhkan cara untuk membuat LoggedInUser
contoh saat pengguna masuk, lalu menghapusnya saat pengguna itu keluar.
Pustaka Django menyertakan fitur yang dikenal sebagai sinyal yang menyiarkan pemberitahuan saat tindakan tertentu terjadi. Aplikasi dapat mendengarkan notifikasi tersebut dan kemudian menindaklanjutinya. Kita dapat memanfaatkan dua sinyal bawaan yang membantu (user_logged_in
dan user_logged_out
) untuk menangani LoggedInUser
kami perilaku.
Dalam “example_channels/example”, tambahkan file baru bernama signals.py :
from django.contrib.auth import user_logged_in, user_logged_out
from django.dispatch import receiver
from example.models import LoggedInUser
@receiver(user_logged_in)
def on_user_login(sender, **kwargs):
LoggedInUser.objects.get_or_create(user=kwargs.get('user'))
@receiver(user_logged_out)
def on_user_logout(sender, **kwargs):
LoggedInUser.objects.filter(user=kwargs.get('user')).delete()
Kami harus membuat sinyal tersedia di konfigurasi aplikasi kami, example_channels/example/apps.py :
from django.apps import AppConfig
class ExampleConfig(AppConfig):
name = 'example'
def ready(self):
import example.signals
Perbarui example_channels/example/__init__.py juga:
default_app_config = 'example.apps.ExampleConfig'
Pemeriksaan Kewarasan
Sekarang kita sudah selesai mengkode dan siap untuk terhubung ke server kita dengan banyak pengguna untuk menguji aplikasi kita.
Jalankan server Django, masuk sebagai pengguna, dan kunjungi beranda. Kita akan melihat daftar semua pengguna di aplikasi kita, masing-masing dengan status "Offline". Selanjutnya, buka jendela Penyamaran baru dan masuk sebagai pengguna yang berbeda dan tonton kedua layar. Tepat saat kita masuk, browser biasa memperbarui status pengguna menjadi "Online". Dari jendela Penyamaran kami, kami melihat bahwa pengguna yang masuk juga memiliki status "Online". Kami dapat menguji WebSockets dengan masuk dan keluar di perangkat kami yang berbeda dengan berbagai pengguna.
Mengamati konsol pengembang pada klien dan aktivitas server di terminal kami, kami dapat mengonfirmasi bahwa koneksi WebSocket terbentuk saat pengguna masuk dan dihancurkan saat pengguna keluar.
[2017/02/20 00:15:23] HTTP POST /log_in/ 302 [0.07, 127.0.0.1:55393]
[2017/02/20 00:15:23] HTTP GET / 200 [0.04, 127.0.0.1:55393]
[2017/02/20 00:15:23] WebSocket HANDSHAKING /users/ [127.0.0.1:55414]
[2017/02/20 00:15:23] WebSocket CONNECT /users/ [127.0.0.1:55414]
[2017/02/20 00:15:25] HTTP GET /log_out/ 302 [0.01, 127.0.0.1:55393]
[2017/02/20 00:15:26] HTTP GET /log_in/ 200 [0.02, 127.0.0.1:55393]
[2017/02/20 00:15:26] WebSocket DISCONNECT /users/ [127.0.0.1:55414]
CATATAN :Anda juga dapat menggunakan ngrok untuk mengekspos server lokal ke internet dengan aman. Dengan melakukan ini, Anda dapat mengakses server lokal dari berbagai perangkat seperti ponsel atau tablet Anda.
Pemikiran Penutup
Kami membahas banyak dalam tutorial ini - Saluran Django, WebSockets, otentikasi pengguna, sinyal, dan beberapa pengembangan front-end. Pengambilan utama adalah ini:Saluran memperluas fungsionalitas aplikasi Django tradisional dengan membiarkan kita mendorong pesan dari server ke grup pengguna melalui WebSockets.
Ini adalah hal yang kuat!
Pikirkan beberapa aplikasi. Kami dapat membuat ruang obrolan, game multipemain, dan aplikasi kolaboratif yang memungkinkan pengguna berkomunikasi secara real time. Bahkan tugas biasa ditingkatkan dengan WebSockets. Misalnya, alih-alih melakukan polling secara berkala ke server untuk melihat apakah tugas yang berjalan lama telah selesai, server dapat mendorong pembaruan status ke klien saat selesai.
Tutorial ini hanya menggores permukaan dari apa yang dapat kita lakukan dengan Saluran Django juga. Jelajahi dokumentasi Saluran Django dan lihat apa lagi yang dapat Anda buat.
Bonus Gratis: Klik di sini untuk mendapatkan akses ke Panduan Sumber Daya Pembelajaran Django (PDF) gratis yang menunjukkan kepada Anda tip dan trik serta perangkap umum yang harus dihindari saat membangun aplikasi web Python + Django.
Ambil kode terakhir dari repo Django-example-channels. Semangat!