Database
 sql >> Database >  >> RDS >> Database

Guida introduttiva ai canali Django

In questo tutorial, utilizzeremo i canali Django per creare un'applicazione in tempo reale che aggiorna un elenco di utenti durante l'accesso e la disconnessione.

Con WebSockets (tramite Django Channels) che gestiscono la comunicazione tra client e server, ogni volta che un utente viene autenticato, un evento verrà trasmesso a ogni altro utente connesso. La schermata di ogni utente cambierà automaticamente, senza dover ricaricare il browser.

NOTA: Ti consigliamo di avere una certa esperienza con Django prima di iniziare questo tutorial. Inoltre, dovresti avere familiarità con il concetto di WebSocket.

Bonus gratuito: Fai clic qui per accedere a una guida gratuita alle risorse di apprendimento di Django (PDF) che mostra suggerimenti e trucchi, nonché le insidie ​​​​comuni da evitare durante la creazione di applicazioni Web Python + Django.

La nostra applicazione utilizza:

  • Python (v3.6.0)
  • Django (v1.10.5)
  • Canali Django (v1.0.3)
  • Redis (v3.2.8)

Obiettivi

Alla fine di questo tutorial, sarai in grado di...

  1. Aggiungi il supporto dei socket Web a un progetto Django tramite i canali Django
  2. Imposta una semplice connessione tra Django e un server Redis
  3. Implementare l'autenticazione utente di base
  4. Sfrutta i segnali Django per agire quando un utente effettua l'accesso o la disconnessione


Per iniziare

Innanzitutto, crea un nuovo ambiente virtuale per isolare le dipendenze del nostro progetto:

$ mkdir django-example-channels
$ cd django-example-channels
$ python3.6 -m venv env
$ source env/bin/activate
(env)$

Installa Django, Django Channels e ASGI Redis, quindi crea un nuovo progetto e un'app 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

NOTA: Nel corso di questo tutorial, creeremo una varietà di diversi file e cartelle. Se rimani bloccato, fai riferimento alla struttura delle cartelle dal repository del progetto.

Quindi, scarica e installa Redis. Se sei su un Mac, ti consigliamo di utilizzare Homebrew:

$ brew install redis

Avvia il server Redis in una nuova finestra del terminale e assicurati che sia in esecuzione sulla porta predefinita, 6379. Il numero di porta sarà importante quando diremo a Django come comunicare con Redis.

Completa la configurazione aggiornando INSTALLED_APPS nel settings.py del progetto file:

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'channels',
    'example',
]

Quindi configura il CHANNEL_LAYERS impostando un backend e un routing predefiniti:

CHANNEL_LAYERS = {
    'default': {
        'BACKEND': 'asgi_redis.RedisChannelLayer',
        'CONFIG': {
            'hosts': [('localhost', 6379)],
        },
        'ROUTING': 'example_channels.routing.channel_routing',
    }
}

Questo utilizza un backend Redis che è necessario anche in produzione.



WebSocket 101

Normalmente, Django utilizza HTTP per comunicare tra client e server:

  1. Il client invia una richiesta HTTP al server.
  2. Django analizza la richiesta, estrae un URL e quindi lo abbina a una vista.
  3. La vista elabora la richiesta e restituisce una risposta HTTP al client.

A differenza di HTTP, il protocollo WebSockets consente la comunicazione bidirezionale, il che significa che il server può inviare dati al client senza che l'utente lo richieda. Con HTTP, solo il client che ha effettuato una richiesta riceve una risposta. Con WebSocket, il server può comunicare con più client contemporaneamente. Come vedremo più avanti in questo tutorial, inviamo messaggi WebSocket usando il ws:// prefisso, al contrario di http:// .

NOTA: Prima di immergerti, consulta rapidamente la documentazione sui concetti dei canali.



Consumatori e Gruppi

Creiamo il nostro primo consumatore, che gestisce le connessioni di base tra il client e il server. Crea un nuovo file chiamato 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)   

I consumatori sono la controparte delle opinioni di Django. Qualsiasi utente che si connette alla nostra app verrà aggiunto al gruppo "utenti" e riceverà i messaggi inviati dal server. Quando il client si disconnette dalla nostra app, il canale viene rimosso dal gruppo e l'utente smetterà di ricevere messaggi.

Quindi, impostiamo i percorsi, che funzionano quasi allo stesso modo della configurazione dell'URL di Django, aggiungendo il codice seguente a un nuovo file chiamato 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),
]

Quindi, abbiamo definito channel_routing invece di urlpatterns e route() invece di url() . Si noti che abbiamo collegato le nostre funzioni consumer a WebSocket.


Modelli

Scriviamo del codice HTML che può comunicare con il nostro server tramite un WebSocket. Crea una cartella "modelli" all'interno di "esempio" e quindi aggiungi una cartella "esempio" all'interno di "modelli" - "canali_esempio/esempio/modelli/esempio".

Aggiungi un _base.html file:

<!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>

E elenco_utente.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 %}

Ora, quando il client apre correttamente una connessione con il server utilizzando un WebSocket, vedremo un messaggio di conferma stampato sulla console.



Viste

Configura una vista Django di supporto per il rendering del nostro modello all'interno di example_channels/example/views.py :

from django.shortcuts import render


def user_list(request):
    return render(request, 'example/user_list.html')

Aggiungi l'URL a 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'),
]

Aggiorna anche l'URL del progetto in 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')),
]


Test

Pronto per il test?

(env)$ python manage.py runserver

NOTA: In alternativa puoi eseguire python manage.py runserver --noworker e python manage.py runworker in due terminali diversi per testare l'interfaccia e i server di lavoro come due processi separati. Entrambi i metodi funzionano!

Quando visiti http://localhost:8000/, dovresti vedere il messaggio di connessione stampato sul terminale:

[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]



Autenticazione utente

Ora che abbiamo dimostrato di poter aprire una connessione, il prossimo passo è gestire l'autenticazione dell'utente. Ricorda:vogliamo che un utente possa accedere alla nostra app e vedere un elenco di tutti gli altri utenti iscritti al gruppo di quell'utente. Innanzitutto, abbiamo bisogno di un modo per consentire agli utenti di creare account e accedere. Inizia creando una semplice pagina di accesso che consentirà a un utente di autenticarsi con un nome utente e una password.

Crea un nuovo file chiamato log_in.html all'interno di "canali_esempio/esempio/modelli/esempio":

{% 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 %}

Quindi, aggiorna example_channels/example/views.py così:

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 viene fornito con moduli che supportano la funzionalità di autenticazione comune. Possiamo usare il AuthenticationForm per gestire l'accesso dell'utente. Questo modulo controlla il nome utente e la password forniti, quindi restituisce un User oggetto se viene trovato un utente convalidato. Accediamo all'utente convalidato e lo reindirizziamo alla nostra homepage. Un utente dovrebbe anche avere la possibilità di disconnettersi dall'applicazione, quindi creiamo una vista di disconnessione che fornisce tale funzionalità e quindi riporta l'utente alla schermata di accesso.

Quindi aggiorna canali_esempio/esempio/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')
]

Abbiamo anche bisogno di un modo per creare nuovi utenti. Crea una pagina di registrazione allo stesso modo del login aggiungendo un nuovo file chiamato sign_up.html a "canali_esempio/esempio/modelli/esempio":

{% 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 %}

Si noti che la pagina di accesso ha un collegamento alla pagina di registrazione e la pagina di registrazione ha un collegamento al collegamento.

Aggiungi la seguente funzione alle viste:

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})

Usiamo un altro modulo integrato per la creazione dell'utente. Dopo la validazione del modulo, ci reindirizziamo alla pagina di accesso.

Assicurati di importare il modulo:

from django.contrib.auth.forms import AuthenticationForm, UserCreationForm

Aggiorna canali_esempio/esempio/urls.py ancora:

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')
]

A questo punto, dobbiamo creare un utente. Esegui il server e visita http://localhost:8000/sign_up/ nel tuo browser. Compila il modulo con un nome utente e una password validi e invialo per creare il nostro primo utente.

NOTA: Prova a utilizzare michael come nome utente e johnson123 come password.

La sign_up view ci reindirizza al log_in visualizzare e da lì possiamo autenticare il nostro utente appena creato.

Dopo aver effettuato l'accesso, possiamo testare le nostre nuove viste di autenticazione.

Usa il modulo di registrazione per creare diversi nuovi utenti in preparazione per la prossima sezione.



Avvisi di accesso

Abbiamo l'autenticazione utente di base funzionante, ma abbiamo ancora bisogno di visualizzare un elenco di utenti e abbiamo bisogno che il server comunichi al gruppo quando un utente si collega e si disconnette. Dobbiamo modificare le nostre funzioni consumer in modo che inviino un messaggio subito dopo un client si connette e subito prima che un client si disconnette. I dati del messaggio includeranno il nome utente dell'utente e lo stato della connessione.

Aggiorna canali_esempio/esempio/consumers.py così:

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)

Si noti che abbiamo aggiunto decoratori alle funzioni per ottenere l'utente dalla sessione di Django. Inoltre, tutti i messaggi devono essere serializzabili in JSON, quindi eseguiamo il dump dei nostri dati in una stringa JSON.

Quindi, aggiorna 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 %}

Sulla nostra homepage, espandiamo il nostro elenco di utenti per visualizzare un elenco di utenti. Memorizziamo il nome utente di ogni utente come attributo di dati per facilitare la ricerca dell'elemento utente nel DOM. Aggiungiamo anche un listener di eventi al nostro WebSocket in grado di gestire i messaggi dal server. Quando riceviamo un messaggio, analizziamo i dati JSON, troviamo il <li> elemento per l'utente specificato e aggiorna lo stato di quell'utente.

Django non tiene traccia se un utente ha effettuato l'accesso, quindi dobbiamo creare un modello semplice per farlo per noi. Crea un LoggedInUser modello con una connessione uno a uno al nostro User modello in 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')

La nostra app creerà un LoggedInUser istanza quando un utente effettua l'accesso e l'app eliminerà l'istanza quando l'utente si disconnette.

Effettua la migrazione dello schema e poi migra il nostro database per applicare le modifiche.

(env)$ python manage.py makemigrations
(env)$ python manage.py migrate

Quindi, aggiorna la nostra visualizzazione elenco utenti, inexample_channels/example/views.py , per recuperare un elenco di utenti di cui eseguire il rendering:

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})

Se un utente ha un LoggedInUser associato , quindi registriamo lo stato dell'utente come "Online" e, in caso contrario, l'utente è "Offline". Aggiungiamo anche un @login_required decorator sia al nostro elenco utenti che alle viste di disconnessione per limitare l'accesso solo agli utenti registrati.

Aggiungi anche le importazioni:

from django.contrib.auth import get_user_model, login, logout
from django.contrib.auth.decorators import login_required

A questo punto, gli utenti possono effettuare l'accesso e la disconnessione, il che attiverà il server per inviare messaggi al client, ma non abbiamo modo di sapere quali utenti hanno effettuato l'accesso al primo accesso dell'utente. L'utente vede gli aggiornamenti solo quando un altro utente cambiamenti di stato. Qui è dove il LoggedInUser entra in gioco, ma abbiamo bisogno di un modo per creare un LoggedInUser esempio quando un utente effettua l'accesso, quindi eliminalo quando quell'utente si disconnette.

La libreria Django include una funzionalità nota come segnali che trasmette notifiche quando si verificano determinate azioni. Le applicazioni possono ascoltare tali notifiche e quindi agire su di esse. Possiamo sfruttare due utili segnali integrati (user_logged_in e user_logged_out ) per gestire il nostro LoggedInUser comportamento.

All'interno di "example_channels/example", aggiungi un nuovo file chiamato 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()

Dobbiamo rendere disponibili i segnali nella nostra configurazione dell'app, example_channels/example/apps.py :

from django.apps import AppConfig


class ExampleConfig(AppConfig):
    name = 'example'

    def ready(self):
        import example.signals

Aggiorna canali_esempio/esempio/__init__.py anche:

default_app_config = 'example.apps.ExampleConfig'


Controllo della sanità mentale

Ora abbiamo terminato la codifica e siamo pronti per connetterci al nostro server con più utenti per testare la nostra app.

Esegui il server Django, accedi come utente e visita la home page. Dovremmo vedere un elenco di tutti gli utenti nella nostra app, ciascuno con lo stato "Offline". Quindi, apri una nuova finestra di navigazione in incognito, accedi come un altro utente e guarda entrambe le schermate. Subito dopo l'accesso, il normale browser aggiorna lo stato dell'utente su "Online". Dalla nostra finestra di navigazione in incognito, vediamo che l'utente che ha effettuato l'accesso ha anche lo stato "Online". Possiamo testare i WebSocket effettuando l'accesso e la disconnessione sui nostri diversi dispositivi con vari utenti.

Osservando la console dello sviluppatore sul client e l'attività del server nel nostro terminale, possiamo confermare che le connessioni WebSocket vengono formate quando un utente effettua l'accesso e distrutte quando un utente si disconnette.

[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]

NOTA :Puoi anche usare ngrok per esporre il server locale a Internet in modo sicuro. In questo modo potrai accedere al server locale da vari dispositivi come il tuo telefono o tablet.



Pensieri conclusivi

Abbiamo trattato molto in questo tutorial:canali Django, WebSocket, autenticazione utente, segnali e alcuni sviluppi front-end. La cosa principale è questa:Channels estende la funzionalità di un'app Django tradizionale consentendoci di inviare messaggi dal server a gruppi di utenti tramite WebSocket.

Questa è roba potente!

Pensa ad alcune delle applicazioni. Possiamo creare chat room, giochi multiplayer e app collaborative che consentono agli utenti di comunicare in tempo reale. Anche le attività banali vengono migliorate con WebSocket. Ad esempio, invece di eseguire periodicamente il polling del server per verificare se un'attività di lunga durata è stata completata, il server può inviare un aggiornamento di stato al client al termine.

Questo tutorial graffia la superficie di ciò che possiamo fare anche con i canali Django. Esplora la documentazione dei canali Django e scopri cos'altro puoi creare.

Bonus gratuito: Fai clic qui per accedere a una guida gratuita alle risorse di apprendimento di Django (PDF) che mostra suggerimenti e trucchi, nonché le insidie ​​​​comuni da evitare durante la creazione di applicazioni Web Python + Django.

Prendi il codice finale dal repository django-example-channels. Saluti!