sql >> データベース >  >> RDS >> Database

Djangoチャンネル入門

    このチュートリアルでは、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)

    目的

    このチュートリアルを終了するまでに、次のことができるようになります…

    1. Djangoチャネルを介してDjangoプロジェクトにWebソケットサポートを追加する
    2. DjangoとRedisサーバー間の簡単な接続を設定します
    3. 基本的なユーザー認証を実装する
    4. 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を使用してクライアントとサーバー間で通信します。

    1. クライアントはサーバーにHTTPリクエストを送信します。
    2. Djangoはリクエストを解析し、URLを抽出してから、ビューと照合します。
    3. ビューはリクエストを処理し、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_outLoggedInUserを処理する 行動。

    「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リポジトリから最終的なコードを取得します。乾杯!



    1. SQL select max(date)および対応する値

    2. リストエントリのあるSQLテーブルと各エントリの行のあるSQLテーブル

    3. MySQLで使用可能な文字セットのリストを返す方法

    4. MySQLで指定された位置にあるリストアイテムを見つける方法