このチュートリアルでは、Django Channelsを使用して、ユーザーがログインおよびログアウトするときにユーザーのリストを更新するリアルタイムアプリケーションを作成します。
WebSocket(Djangoチャネル経由)でクライアントとサーバー間の通信を管理すると、ユーザーが認証されるたびに、接続されている他のすべてのユーザーにイベントがブロードキャストされます。各ユーザーの画面は、ブラウザをリロードしなくても自動的に変更されます。
注: このチュートリアルを開始する前に、Djangoの使用経験があることをお勧めします。また、WebSocketの概念に精通している必要があります。
無料ボーナス: ここをクリックして、無料のDjangoラーニングリソースガイド(PDF)にアクセスします。このガイドには、Python +DjangoWebアプリケーションを構築する際に避けるべきヒントとコツおよび一般的な落とし穴が示されています。
私たちのアプリケーションは以下を使用します:
- Python(v3.6.0)
- Django(v1.10.5)
- Djangoチャネル(v1.0.3)
- Redis(v3.2.8)
目的
このチュートリアルを終了するまでに、次のことができるようになります…
- Djangoチャネルを介してDjangoプロジェクトにWebソケットサポートを追加する
- DjangoとRedisサーバー間の簡単な接続を設定します
- 基本的なユーザー認証を実装する
- Django Signalsを利用して、ユーザーがログインまたはログアウトしたときにアクションを実行します
はじめに
まず、新しい仮想環境を作成して、プロジェクトの依存関係を分離します。
$ mkdir django-example-channels
$ cd django-example-channels
$ python3.6 -m venv env
$ source env/bin/activate
(env)$
Django、Django Channels、ASGI Redisをインストールしてから、新しいDjangoプロジェクトとアプリを作成します。
(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
注: このチュートリアルの過程で、さまざまな異なるファイルとフォルダーを作成します。行き詰まった場合は、プロジェクトのリポジトリのフォルダ構造を参照してください。
次に、Redisをダウンロードしてインストールします。 Macを使用している場合は、Homebrewの使用をお勧めします:
$ brew install redis
新しいターミナルウィンドウでRedisサーバーを起動し、デフォルトのポート6379で実行されていることを確認します。DjangoにRedisとの通信方法を指示するときは、ポート番号が重要になります。
INSTALLED_APPS
を更新して、セットアップを完了します プロジェクトのsettings.py ファイル:
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'channels',
'example',
]
次に、CHANNEL_LAYERS
を構成します デフォルトのバックエンドとルーティングを設定する:
CHANNEL_LAYERS = {
'default': {
'BACKEND': 'asgi_redis.RedisChannelLayer',
'CONFIG': {
'hosts': [('localhost', 6379)],
},
'ROUTING': 'example_channels.routing.channel_routing',
}
}
これは、本番環境でも必要なRedisバックエンドを使用します。
WebSockets 101
通常、DjangoはHTTPを使用してクライアントとサーバー間で通信します。
- クライアントはサーバーにHTTPリクエストを送信します。
- Djangoはリクエストを解析し、URLを抽出してから、ビューと照合します。
- ビューはリクエストを処理し、HTTPレスポンスをクライアントに返します。
HTTPとは異なり、WebSocketプロトコルでは双方向通信が可能です。つまり、サーバーはユーザーからのプロンプトなしにデータをクライアントにプッシュできます。 HTTPでは、要求を行ったクライアントのみが応答を受け取ります。 WebSocketを使用すると、サーバーは複数のクライアントと同時に通信できます。このチュートリアルの後半で説明するように、ws://
を使用してWebSocketメッセージを送信します http://
ではなくプレフィックス 。
注: 飛び込む前に、ChannelsConceptsのドキュメントをすばやく確認してください。
消費者とグループ
クライアントとサーバー間の基本的な接続を処理する最初のコンシューマーを作成しましょう。 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)
消費者はDjangoの見解に対応しています。アプリに接続しているユーザーはすべて「users」グループに追加され、サーバーから送信されたメッセージを受信します。クライアントがアプリから切断すると、チャネルはグループから削除され、ユーザーはメッセージの受信を停止します。
次に、 example_channels / routing.py という新しいファイルに次のコードを追加して、DjangoURL構成とほぼ同じように機能するルートを設定しましょう。 :
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),
]
そこで、channel_routing
を定義しました urlpatterns
の代わりに およびroute()
url()
の代わりに 。コンシューマー関数をWebSocketにリンクしていることに注意してください。
テンプレート
WebSocketを介してサーバーと通信できるHTMLを作成しましょう。 「example」内に「templates」フォルダを作成してから、「templates」内に「example」フォルダを追加します-「example_channels / example / templates/example」。
_base.htmlを追加します ファイル:
<!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>
そしてuser_list.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 %}
これで、クライアントがWebSocketを使用してサーバーとの接続を正常に開くと、確認メッセージがコンソールに出力されます。
ビュー
example_channels / example / views.py 内にテンプレートをレンダリングするために、サポートするDjangoビューを設定します :
from django.shortcuts import render
def user_list(request):
return render(request, 'example/user_list.html')
URLを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'),
]
example_channels / example_channels / urls.pyのプロジェクトURLも更新します :
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')),
]
テスト
テストする準備はできましたか?
(env)$ python manage.py runserver
注: または、
python manage.py runserver --noworker
を実行することもできます およびpython manage.py runworker
2つの異なる端末で、インターフェイスサーバーとワーカーサーバーを2つの別々のプロセスとしてテストします。どちらの方法でも機能します!
http:// localhost:8000 /にアクセスすると、接続メッセージが端末に出力されます。
[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]
ユーザー認証
接続を開くことができることが証明されたので、次のステップはユーザー認証を処理することです。注意:ユーザーがアプリにログインして、そのユーザーのグループに登録されている他のすべてのユーザーのリストを表示できるようにする必要があります。まず、ユーザーがアカウントを作成してログインする方法が必要です。まず、ユーザーがユーザー名とパスワードで認証できるようにする簡単なログインページを作成します。
log_in.htmlという名前の新しいファイルを作成します 「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 %}
次に、 example_channels / example / views.pyを更新します そのように:
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には、一般的な認証機能をサポートするフォームが付属しています。 AuthenticationForm
を使用できます ユーザーログインを処理します。このフォームは、指定されたユーザー名とパスワードを確認してから、User
を返します。 検証済みのユーザーが見つかった場合はオブジェクト。検証済みのユーザーにログインして、ホームページにリダイレクトします。また、ユーザーはアプリケーションからログアウトできる必要があるため、その機能を提供するログアウトビューを作成してから、ユーザーをログイン画面に戻します。
次に、 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')
]
また、新しいユーザーを作成する方法も必要です。 sign_up.html という新しいファイルを追加して、ログインと同じ方法でサインアップページを作成します。 「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 %}
ログインページにはサインアップページへのリンクがあり、サインアップページにはログインに戻るリンクがあることに注意してください。
ビューに次の関数を追加します:
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})
ユーザーの作成には、別の組み込みフォームを使用します。フォームの検証が成功すると、ログインページにリダイレクトされます。
必ずフォームをインポートしてください:
from django.contrib.auth.forms import AuthenticationForm, UserCreationForm
example_channels / example / urls.pyを更新します もう一度:
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')
]
この時点で、ユーザーを作成する必要があります。サーバーを実行し、http://localhost:8000/sign_up/
にアクセスします ブラウザで。フォームに有効なユーザー名とパスワードを入力し、送信して最初のユーザーを作成します。
注:
michael
を使用してみてください ユーザー名およびjohnson123
として パスワードとして。
sign_up
ビューはlog_in
にリダイレクトします ビューを表示し、そこから新しく作成したユーザーを認証できます。
ログイン後、新しい認証ビューをテストできます。
サインアップフォームを使用して、次のセクションの準備として複数の新しいユーザーを作成します。
ログインアラート
基本的なユーザー認証は機能していますが、ユーザーのリストを表示する必要があり、ユーザーがログインおよびログアウトしたときにグループに通知するサーバーが必要です。コンシューマー機能を編集して、ユーザーがメッセージを送信した直後に送信するようにする必要があります。クライアントは接続し、クライアントが切断する直前。メッセージデータには、ユーザーのユーザー名と接続ステータスが含まれます。
example_channels / example / Consumers.pyを更新します そのように:
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)
Djangoセッションからユーザーを取得するために、関数にデコレーターを追加したことに注意してください。また、すべてのメッセージはJSONでシリアル化可能である必要があるため、データをJSON文字列にダンプします。
次に、 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 %}
当社のホームページでは、ユーザーリストを展開してユーザーのリストを表示しています。各ユーザーのユーザー名をデータ属性として保存し、DOMでユーザーアイテムを簡単に見つけられるようにします。また、サーバーからのメッセージを処理できるイベントリスナーをWebSocketに追加します。メッセージを受信したら、JSONデータを解析し、<li>
を見つけます。 特定のユーザーの要素を指定し、そのユーザーのステータスを更新します。
Djangoはユーザーがログインしているかどうかを追跡しないため、それを行うための単純なモデルを作成する必要があります。 LoggedInUser
を作成します User
に1対1で接続するモデル 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')
私たちのアプリはLoggedInUser
を作成します ユーザーがログインするとインスタンスになり、ユーザーがログアウトするとアプリはインスタンスを削除します。
スキーマを移行してから、データベースを移行して変更を適用します。
(env)$ python manage.py makemigrations
(env)$ python manage.py migrate
次に、ユーザーリストビューを更新します。 example_channels / example / views.py 、レンダリングするユーザーのリストを取得するには:
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})
ユーザーにLoggedInUser
が関連付けられている場合 、次にユーザーのステータスを「オンライン」として記録し、そうでない場合、ユーザーは「オフライン」です。 @login_required
も追加します ユーザーリストとログアウトビューの両方へのデコレータで、登録済みユーザーのみにアクセスを制限します。
インポートも追加します:
from django.contrib.auth import get_user_model, login, logout
from django.contrib.auth.decorators import login_required
この時点で、ユーザーはログインおよびログアウトできます。これにより、サーバーはクライアントにメッセージを送信しますが、ユーザーが最初にログインしたときにどのユーザーがログインしているかを知る方法はありません。ユーザーには、別のユーザーがログインしたときにのみ更新が表示されます。ステータスが変わります。ここで、LoggedInUser
登場しますが、LoggedInUser
を作成する方法が必要です たとえば、ユーザーがログインしたときにインスタンスを作成し、そのユーザーがログアウトしたときに削除します。
Djangoライブラリには、特定のアクションが発生したときに通知をブロードキャストするシグナルと呼ばれる機能が含まれています。アプリケーションはこれらの通知をリッスンし、それに基づいて動作できます。 2つの便利な組み込みシグナル(user_logged_in
)を活用できます およびuser_logged_out
)LoggedInUser
を処理する 行動。
「example_channels/example」内に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()
アプリ構成example_channels/ example / apps.pyでシグナルを利用できるようにする必要があります :
from django.apps import AppConfig
class ExampleConfig(AppConfig):
name = 'example'
def ready(self):
import example.signals
example_channels / example /__init__。pyを更新します 同様に:
default_app_config = 'example.apps.ExampleConfig'
健全性チェック
これでコーディングが完了し、複数のユーザーがいるサーバーに接続してアプリをテストする準備が整いました。
Djangoサーバーを実行し、ユーザーとしてログインして、ホームページにアクセスします。アプリ内のすべてのユーザーのリストが表示されます。各ユーザーのステータスは「オフライン」です。次に、新しいシークレットウィンドウを開き、別のユーザーとしてログインして、両方の画面を確認します。ログインすると、通常のブラウザがユーザーステータスを「オンライン」に更新します。シークレットウィンドウから、ログインしているユーザーのステータスも「オンライン」であることがわかります。さまざまなユーザーがいるさまざまなデバイスでログインおよびログアウトすることで、WebSocketをテストできます。
クライアントの開発者コンソールと端末のサーバーアクティビティを観察すると、ユーザーがログインするとWebSocket接続が形成され、ユーザーがログアウトするとWebSocket接続が破棄されていることを確認できます。
[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]
注 :ngrokを使用して、ローカルサーバーをインターネットに安全に公開することもできます。これを行うと、携帯電話やタブレットなどのさまざまなデバイスからローカルサーバーにアクセスできるようになります。
まとめ
このチュートリアルでは、Djangoチャネル、WebSocket、ユーザー認証、シグナル、およびいくつかのフロントエンド開発について多くのことを取り上げました。主なポイントは次のとおりです。Channelsは、WebSocketを介してサーバーからユーザーのグループにメッセージをプッシュできるようにすることで、従来のDjangoアプリの機能を拡張します。
これは強力なものです!
いくつかのアプリケーションについて考えてみてください。ユーザーがリアルタイムでコミュニケーションできるチャットルーム、マルチプレイヤーゲーム、コラボレーションアプリを作成できます。ありふれたタスクでさえ、WebSocketで改善されます。たとえば、サーバーを定期的にポーリングして、実行時間の長いタスクが完了したかどうかを確認する代わりに、サーバーは、完了時にステータスの更新をクライアントにプッシュできます。
このチュートリアルは、DjangoChannelsでできることのほんの一部でもあります。 Django Channelsのドキュメントを調べて、他に何を作成できるかを確認してください。
無料ボーナス: ここをクリックして、無料のDjangoラーニングリソースガイド(PDF)にアクセスします。このガイドには、Python +DjangoWebアプリケーションを構築する際に避けるべきヒントとコツおよび一般的な落とし穴が示されています。
django-example-channelsリポジトリから最終的なコードを取得します。乾杯!