V tomto tutoriálu použijeme kanály Django k vytvoření aplikace v reálném čase, která aktualizuje seznam uživatelů, když se přihlašují a odhlašují.
Díky WebSockets (prostřednictvím Django Channels) spravujícím komunikaci mezi klientem a serverem bude vždy, když je uživatel ověřen, událost vyslána všem dalším připojeným uživatelům. Obrazovka každého uživatele se automaticky změní, aniž by musel znovu načítat své prohlížeče.
POZNÁMKA: Doporučujeme, abyste měli nějaké zkušenosti s Django, než začnete tento tutoriál. Také byste měli být obeznámeni s konceptem WebSockets.
Box zdarma: Kliknutím sem získáte přístup k bezplatné příručce Django Learning Resources Guide (PDF), která vám ukáže tipy a triky a také běžné nástrahy, kterým je třeba se vyhnout při vytváření webových aplikací Python + Django.
Naše aplikace používá:
- Python (v3.6.0)
- Django (v1.10.5)
- Kanály Django (v1.0.3)
- Redis (v3.2.8)
Cíle
Na konci tohoto tutoriálu budete schopni…
- Přidat podporu webových soketů do projektu Django prostřednictvím kanálů Django
- Nastavte jednoduché spojení mezi Django a serverem Redis
- Implementujte základní ověření uživatele
- Využijte signály Django k provedení akce, když se uživatel přihlásí nebo odhlásí
Začínáme
Nejprve vytvořte nové virtuální prostředí, abyste izolovali závislosti našeho projektu:
$ mkdir django-example-channels
$ cd django-example-channels
$ python3.6 -m venv env
$ source env/bin/activate
(env)$
Nainstalujte Django, Django Channels a ASGI Redis a poté vytvořte nový projekt a aplikaci 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
POZNÁMKA: V průběhu tohoto tutoriálu vytvoříme řadu různých souborů a složek. Pokud se zaseknete, podívejte se na strukturu složek z úložiště projektu.
Dále si stáhněte a nainstalujte Redis. Pokud používáte Mac, doporučujeme používat Homebrew:
$ brew install redis
Spusťte server Redis v novém okně terminálu a ujistěte se, že běží na svém výchozím portu, 6379. Číslo portu bude důležité, až budeme Djangovi říkat, jak komunikovat s Redis.
Dokončete nastavení aktualizací INSTALLED_APPS
v settings.py projektu soubor:
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'channels',
'example',
]
Poté nakonfigurujte CHANNEL_LAYERS
nastavením výchozího backendu a směrování:
CHANNEL_LAYERS = {
'default': {
'BACKEND': 'asgi_redis.RedisChannelLayer',
'CONFIG': {
'hosts': [('localhost', 6379)],
},
'ROUTING': 'example_channels.routing.channel_routing',
}
}
To používá backend Redis, který je také potřebný v produkci.
WebSockets 101
Normálně používá Django ke komunikaci mezi klientem a serverem HTTP:
- Klient odešle na server požadavek HTTP.
- Django analyzuje požadavek, extrahuje adresu URL a poté ji přiřadí k pohledu.
- Zobrazení zpracuje požadavek a vrátí klientovi odpověď HTTP.
Na rozdíl od HTTP umožňuje protokol WebSockets obousměrnou komunikaci, což znamená, že server může odeslat data klientovi, aniž by k tomu byl uživatelem vyzván. S HTTP obdrží odpověď pouze klient, který provedl požadavek. S WebSockets může server komunikovat s více klienty současně. Jak uvidíme později v tomto tutoriálu, posíláme zprávy WebSockets pomocí ws://
prefix, na rozdíl od http://
.
POZNÁMKA: Než se ponoříte, rychle si projděte dokumentaci Channels Concepts.
Spotřebitelé a skupiny
Vytvořme našeho prvního spotřebitele, který zvládne základní spojení mezi klientem a serverem. Vytvořte nový soubor s názvem 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)
Spotřebitelé jsou protikladem k názorům Django. Každý uživatel, který se připojí k naší aplikaci, bude přidán do skupiny „users“ a bude dostávat zprávy odeslané serverem. Když se klient odpojí od naší aplikace, kanál je odstraněn ze skupiny a uživatel přestane dostávat zprávy.
Dále nastavíme trasy, které fungují téměř stejným způsobem jako konfigurace adresy URL Django, přidáním následujícího kódu do nového souboru s názvem 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),
]
Definovali jsme tedy channel_routing
namísto urlpatterns
a route()
místo url()
. Všimněte si, že jsme propojili naše spotřebitelské funkce s WebSockets.
Šablony
Pojďme napsat nějaké HTML, které může komunikovat s naším serverem přes WebSocket. Vytvořte složku „templates“ v „example“ a poté přidejte složku „example“ do „templates“ - „example_channels/example/templates/example“.
Přidejte _base.html soubor:
<!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>
A seznam_uživatelů.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 %}
Nyní, když klient úspěšně otevře spojení se serverem pomocí WebSocket, uvidíme potvrzovací zprávu vytištěnou na konzoli.
Zobrazení
Nastavte podpůrné zobrazení Django pro vykreslení naší šablony v example_channels/example/views.py :
from django.shortcuts import render
def user_list(request):
return render(request, 'example/user_list.html')
Přidejte adresu URL do 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'),
]
Aktualizujte také adresu URL projektu v 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
Jste připraveni testovat?
(env)$ python manage.py runserver
POZNÁMKA: Alternativně můžete spustit
python manage.py runserver --noworker
apython manage.py runworker
ve dvou různých terminálech k testování rozhraní a pracovních serverů jako dvou samostatných procesů. Obě metody fungují!
Když navštívíte http://localhost:8000/, měli byste vidět zprávu o připojení vytištěnou k terminálu:
[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]
Ověření uživatele
Nyní, když jsme dokázali, že můžeme otevřít připojení, je naším dalším krokem zpracování autentizace uživatele. Pamatujte:Chceme, aby se uživatel mohl přihlásit do naší aplikace a viděl seznam všech ostatních uživatelů, kteří jsou přihlášeni k odběru skupiny daného uživatele. Nejprve potřebujeme způsob, jak si uživatelé mohou vytvářet účty a přihlašovat se. Začněte vytvořením jednoduché přihlašovací stránky, která uživateli umožní autentizaci pomocí uživatelského jména a hesla.
Vytvořte nový soubor s názvem log_in.html v "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 %}
Dále aktualizujte example_channels/example/views.py takhle:
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 přichází s formuláři, které podporují běžné funkce ověřování. Můžeme použít AuthenticationForm
pro zpracování přihlášení uživatele. Tento formulář zkontroluje zadané uživatelské jméno a heslo a poté vrátí User
objekt, pokud je nalezen ověřený uživatel. Přihlásíme ověřeného uživatele a přesměrujeme ho na naši domovskou stránku. Uživatel by měl mít také možnost se z aplikace odhlásit, takže vytvoříme pohled na odhlášení, který tuto funkci poskytuje, a poté uživatele vrátí zpět na přihlašovací obrazovku.
Poté aktualizujte 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')
]
Potřebujeme také způsob, jak vytvořit nové uživatele. Vytvořte registrační stránku stejným způsobem jako přihlašovací stránku přidáním nového souboru s názvem sign_up.html na „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 %}
Všimněte si, že přihlašovací stránka obsahuje odkaz na stránku registrace a stránka registrace má odkaz zpět na přihlašovací stránku.
Přidejte do zobrazení následující funkci:
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})
Pro tvorbu uživatelů používáme jiný vestavěný formulář. Po úspěšném ověření formuláře se přesměrujeme na přihlašovací stránku.
Ujistěte se, že importujete formulář:
from django.contrib.auth.forms import AuthenticationForm, UserCreationForm
Aktualizujte example_channels/example/urls.py znovu:
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')
]
V tomto okamžiku musíme vytvořit uživatele. Spusťte server a navštivte http://localhost:8000/sign_up/
ve vašem prohlížeči. Vyplňte formulář platným uživatelským jménem a heslem a odešlete jej pro vytvoření našeho prvního uživatele.
POZNÁMKA: Zkuste použít
michael
jako uživatelské jméno ajohnson123
jako heslo.
sign_up
view nás přesměruje na log_in
a odtud můžeme ověřit našeho nově vytvořeného uživatele.
Poté, co se přihlásíme, můžeme otestovat naše nové pohledy na ověřování.
Pomocí registračního formuláře vytvořte několik nových uživatelů v přípravě na další sekci.
Upozornění na přihlášení
Funguje nám základní ověřování uživatelů, ale stále potřebujeme zobrazit seznam uživatelů a potřebujeme, aby server oznámil skupině, když se uživatel přihlásí a odhlásí. Musíme upravit naše spotřebitelské funkce tak, aby odeslaly zprávu hned po klient se připojí a těsně předtím, než se klient odpojí. Data zprávy budou zahrnovat uživatelské jméno uživatele a stav připojení.
Aktualizujte example_channels/example/consumers.py takhle:
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)
Všimněte si, že jsme do funkcí přidali dekorátory, abychom uživatele dostali z relace Django. Všechny zprávy také musí být možné serializovat JSON, takže naše data ukládáme do řetězce JSON.
Dále aktualizujte 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 %}
Na naší domovské stránce rozšiřujeme seznam uživatelů a zobrazujeme seznam uživatelů. Uživatelské jméno každého uživatele ukládáme jako datový atribut, aby bylo snadné najít položku uživatele v DOM. Do našeho WebSocket také přidáváme posluchač událostí, který dokáže zpracovávat zprávy ze serveru. Když obdržíme zprávu, analyzujeme data JSON a najdeme <li>
prvek pro daného uživatele a aktualizujte stav tohoto uživatele.
Django nesleduje, zda je uživatel přihlášen, takže musíme vytvořit jednoduchý model, který to udělá za nás. Vytvořte LoggedInUser
model s individuálním připojením k našemu User
model v 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')
Naše aplikace vytvoří LoggedInUser
instanci, když se uživatel přihlásí, a aplikace instanci smaže, když se uživatel odhlásí.
Proveďte migraci schématu a poté migrujte naši databázi, abyste použili změny.
(env)$ python manage.py makemigrations
(env)$ python manage.py migrate
Dále aktualizujte naše zobrazení seznamu uživatelů vexample_channels/example/views.py , chcete-li získat seznam uživatelů k vykreslení:
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})
Pokud má uživatel přidruženého LoggedInUser
, pak zaznamenáme stav uživatele jako „Online“, a pokud ne, uživatel je „Offline“. Také přidáváme @login_required
dekoratér do našeho seznamu uživatelů a pohledů na odhlášení, aby byl přístup omezen pouze na registrované uživatele.
Přidejte také importy:
from django.contrib.auth import get_user_model, login, logout
from django.contrib.auth.decorators import login_required
V tomto okamžiku se uživatelé mohou přihlásit a odhlásit, což spustí server k odesílání zpráv klientovi, ale nemáme způsob, jak zjistit, kteří uživatelé jsou přihlášeni, když se uživatel poprvé přihlásí. Uživatel uvidí aktualizace pouze tehdy, když změny stavu. Zde se nachází LoggedInUser
přichází do hry, ale potřebujeme způsob, jak vytvořit LoggedInUser
například, když se uživatel přihlásí, a poté jej smažte, když se tento uživatel odhlásí.
Knihovna Django obsahuje funkci známou jako signály, která vysílá upozornění, když nastanou určité akce. Aplikace mohou tato oznámení naslouchat a poté na ně reagovat. Můžeme využít dva užitečné, vestavěné signály (user_logged_in
a user_logged_out
), aby zpracovával naše LoggedInUser
chování.
Do „example_channels/example“ přidejte nový soubor s názvem 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()
Signály musíme zpřístupnit v konfiguraci naší aplikace example_channels/example/apps.py :
from django.apps import AppConfig
class ExampleConfig(AppConfig):
name = 'example'
def ready(self):
import example.signals
Aktualizujte example_channels/example/__init__.py také:
default_app_config = 'example.apps.ExampleConfig'
Kontrola zdravého rozumu
Nyní jsme dokončili kódování a jsme připraveni se připojit k našemu serveru s více uživateli za účelem testování naší aplikace.
Spusťte server Django, přihlaste se jako uživatel a navštivte domovskou stránku. Měli bychom vidět seznam všech uživatelů v naší aplikaci, každý se stavem „Offline“. Dále otevřete nové anonymní okno a přihlaste se jako jiný uživatel a sledujte obě obrazovky. Hned, když se přihlásíme, běžný prohlížeč aktualizuje stav uživatele na „Online“. Z našeho anonymního okna vidíme, že přihlášený uživatel má také stav „Online“. WebSockets můžeme testovat přihlášením a odhlášením na našich různých zařízeních s různými uživateli.
Pozorováním vývojářské konzole na klientovi a aktivity serveru v našem terminálu můžeme potvrdit, že připojení WebSocket se vytváří, když se uživatel přihlásí, a že se zničí, když se uživatel odhlásí.
[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]
POZNÁMKA :Můžete také použít ngrok k bezpečnému vystavení místního serveru internetu. To vám umožní zasáhnout místní server z různých zařízení, jako je váš telefon nebo tablet.
Úvahy na závěr
V tomto tutoriálu jsme toho probrali hodně – kanály Django, WebSockets, ověřování uživatelů, signály a vývoj front-endu. Hlavním přínosem je toto:Channels rozšiřuje funkčnost tradiční aplikace Django tím, že nám umožňuje posílat zprávy ze serveru skupinám uživatelů prostřednictvím WebSockets.
To je mocná věc!
Vzpomeňte si na některé aplikace. Můžeme vytvářet chatovací místnosti, hry pro více hráčů a aplikace pro spolupráci, které uživatelům umožňují komunikovat v reálném čase. Dokonce i všední úkoly jsou vylepšeny pomocí WebSockets. Například namísto pravidelného dotazování serveru, zda byla dokončena dlouhotrvající úloha, může server po jejím dokončení odeslat aktualizaci stavu klientovi.
Tento tutoriál jen poškrábe povrch toho, co můžeme dělat také s kanály Django. Prozkoumejte dokumentaci Django Channels a zjistěte, co dalšího můžete vytvořit.
Box zdarma: Kliknutím sem získáte přístup k bezplatné příručce Django Learning Resources Guide (PDF), která vám ukáže tipy a triky a také běžné nástrahy, kterým je třeba se vyhnout při vytváření webových aplikací Python + Django.
Získejte konečný kód z úložiště django-example-channels. Na zdraví!